Skip to main content

Einfacher & solider Medien-Importeur. Archiviert Videoaufnahmen von SSD/iPhone/NAS in ISO-Dateien.

Project description

🎬 Kamera-Einleser

Einfacher & solider Medien-Importeur. Archiviert Videoaufnahmen von SSD/iPhone/NAS in ZIP-Dateien pro Aufnahmetag (ZIP_STORED, ohne Kompression) und synchronisiert sie auf beliebige rclone-Ziele (Synology NAS, Mega.nz, etc.).

3.0.0 ist ein Aufräum-Major-Release. Der ISO-Code-Pfad ist komplett entfernt. Das Tool ist ab 3.0.0 ein reines ZIP-Werkzeug ohne Legacy-Ballast. Bestand-ISOs aus 1.x/2.0.x lassen sich vorher auf 2.3.x mit convert iso-to-zip migrieren. Vollständige Breaking-Changes und Migrations-Pfad: siehe CHANGELOG.md.

✨ Features

  • 🗜 ZIP-Archive (ZIP_STORED, ohne Kompression). Streaming-Bau direkt aus den Source-Pfaden, kein Staging-Zwischenspeicher.
  • 📦 Append in bestehende ZIPs im Cache-Window (Default 7 Tage). Neue Files vom selben Tag werden in das bestehende ZIP eingefügt — kein -Teil-N, ein ZIP pro Tag.
  • 🛡️ Lazy-Check vor Append: Vor jedem Server-überschreibenden Upload wird das Server-Manifest gepullt (sub-Sekunde, KBs) und gegen den geplanten Inhalt verifiziert. Würde eine Datei verloren gehen, blockt der Lauf hart.
  • 📅 Tag-Gruppierung nach echtem Aufnahmedatum aus EXIF (statt Filesystem-mtime). Misch-Tag-Archive aus 1.x sind strukturell gelöst.
  • 🔁 Idempotente Archivierung: ein Re-Run mit denselben Sources tut nichts — Pre-Filter aus den Manifest-Sidecars erkennt jede einzelne archivierte Datei in Millisekunden.
  • ☁️ rclone-Multi-Target: parallel zu NAS und Cloud mit dynamischem --immutable-Schutz (Append-Replace wird gezielt ausgenommen).
  • ♻️ Auto-Prune lokal (Cache-Variante C): ZIPs älter als 7 Tage werden lokal entfernt — aber nur, wenn der *.uploaded.json-Marker bestätigt, dass das Archiv auf allen Targets liegt.
  • 📥 Auto-Fetch: kommt ein Tag aus dem Cache-Window ins Archive-Flow, wird sein ZIP automatisch vom ersten Target gezogen, damit der Append-Pfad greift statt am --immutable zu scheitern.
  • 🚦 Startup-Guard: leerer Manifest-Cache + Server hat Archive → harter Abbruch mit cache sync-manifests-Hinweis. Schützt vor dem Zweit-Rechner-Worst-Case (stundenlanger Pull + Wall-of-immutable-Errors).
  • 📑 Reichhaltige Manifests pro Archiv (JSONL-Sidecar): pro Datei Aufnahmedatum mit Provenance, Kamera-Modell, GPS, Codec, Apple-ProApps-Production-Felder. Die Metadaten-Logik lebt im separaten Paket kurmann-medien-leser.
  • 🔍 Audit-Befehl: kamera-einleser audit archive-grouping zeigt Misch-Tag-Archive aus dem Bestand.
  • 🔧 XDG-konforme TOML-Config mit kamera-einleser config show/get/set.

🎬 Unterstützte Aufnahme-Quellen

kamera-einleser vereinheitlicht die Metadaten aus verschiedenen iPhone-Kamera-Apps in ein einziges, konsistentes Manifest-Schema. Jede App-Familie schreibt EXIF-Tags in eigene Atome — der Indexer kennt die Eigenheiten und produziert für alle dasselbe Schema, mit klarer Provenance über die Quelle und das Aufnahmedatum.

App Aufnahmedatum Reiche Metadaten
Blackmagic Camera Keys:CreationDate mit TZ ✓ Apple ProApps Production-Felder (Reel/Scene/Shot/Project/Director), Kamera-Settings (ISO, Shutter, WB, Aperture)
iPhone Camera (Apple Foto-App) Keys:CreationDate mit TZ ✓ Lens-Modell, GPS mit Höhe
Final Cut Camera (Apple, iOS 18+) CreateDate UTC ohne TZ ⚠️ — Indexer leitet TZ aus file_mtime ab Lens-Modell, GPS mit Höhe, App-Version

Beispiel-Output aus einem Final Cut Camera-Clip:

{
  "filename": "IMG_0001.mov",
  "recorded_at": "2026-05-03T07:38:55+02:00",
  "recorded_at_source": "exif:CreateDate+tz_from_mtime",
  "source_app": "Final Cut Camera",
  "camera": {"make": "Apple", "model": "iPhone 15 Pro Max", "lens": "..."},
  "gps": {"lat": 47.07054, "lon": 7.58165, "altitude_m": 516.0},
  "video": {"codec": "HEVC", "resolution": "3840x2160", "fps": 30}
}

Andere Apps funktionieren oft auch — kriegen source_app=null und können einzelne Felder vermissen. recorded_at_source zeigt immer, welche EXIF-Quelle gewählt wurde, damit Konsumenten dem Wert vertrauen oder gezielt nachprüfen können.

📋 Voraussetzungen

  • macOS (Working-Dir-Pfade gehen von APFS aus; Hot-Path ist plattformunabhängig)
  • Python 3.10 oder höher
  • rclone (für Cloud-Synchronisation): brew install rclone
  • exiftool (für Manifest-Metadaten): brew install exiftool

🚀 Installation

# Empfohlen: pipx oder uv tool
pipx install kurmann-kamera-einleser
# oder
uv tool install kurmann-kamera-einleser

# Alternativ: in eine virtualenv via pip
pip install kurmann-kamera-einleser

Direkt vom Git-Repository (für Entwicklung):

uv tool install git+https://github.com/kurmann/kamera-einleser.git

🎯 Schnellstart

1. Konfigurieren (einmalig)

kamera-einleser settings

Interaktives Menü für:

  • Quellverzeichnisse: lokal (/Volumes/Crucial-P3/Eingang) oder rclone-Pfad (synology:/volume1/Fotos/Eingang)
  • rclone-Targets: erstes ist konventionell das lokale NAS, weiteres die Cloud
  • Arbeitsverzeichnis: lokaler Cache für ZIPs (primär + optionaler Fallback auf zweiter SSD)
  • Ausschluss-Patterns: glob-Muster, die ignoriert werden (Default: ._*, .DS_Store)

Oder über die CLI:

kamera-einleser config set working_directory /Volumes/Crucial-P3/Originalmedien
kamera-einleser config set working_directory_fallback /Volumes/Samsung2TB/Originalmedien

2. Archivieren

# Archiviert alle konfigurierten Quellverzeichnisse
kamera-einleser archive

# Mit optionaler Löschung der Originale am Ende
kamera-einleser archive --delete-source

3. Zweit-Rechner einrichten

Beim ersten Lauf auf einer frischen Maschine, die das gleiche NAS nutzt:

# Konfiguration wie oben
kamera-einleser settings

# Manifest-Cache vom NAS aufbauen (KBs pro Archiv, sub-Sekunde)
kamera-einleser cache sync-manifests

# Jetzt ist der Pre-Filter scharf — Re-Archivierungen werden lautlos übersprungen
kamera-einleser archive

Ohne Schritt 2 würde der Startup-Guard mit klarem Recovery-Hinweis abbrechen.

📖 Befehle

archive

Der Hauptbefehl. Liest alle konfigurierten Quellen, gruppiert nach Aufnahmedatum, baut ZIPs, lädt zu allen Targets hoch.

kamera-einleser archive [SOURCE_PATH] [--delete-source] [--no-upload]
                        [--prune-cache/--no-prune-cache]
                        [--auto-fetch/--no-auto-fetch] [--force]
  • --delete-source: nach erfolgreichem Upload Originaldateien löschen (fragt nach)
  • --no-upload: nur lokal bauen, nicht zu Targets pushen
  • --prune-cache/--no-prune-cache: Auto-Prune am Lauf-Ende übersteuern
  • --auto-fetch/--no-auto-fetch: Auto-Fetch für out-of-window-Tage übersteuern
  • --force: Startup-Guard übergehen (nach bewusster Remote-Löschung)

config

kamera-einleser config              # aktuelle Konfiguration anzeigen
kamera-einleser config show         # idem
kamera-einleser config path         # Pfad der TOML-Datei
kamera-einleser config list         # alle Werte als TOML
kamera-einleser config get <key>    # einzelner Wert
kamera-einleser config set <key> <value>

cache

kamera-einleser cache list                                # lokale ZIPs mit Status
kamera-einleser cache fetch <day-or-archive>              # einzelnes Archiv vom NAS holen
kamera-einleser cache sync-manifests                      # alle Manifest-Sidecars vom NAS
kamera-einleser cache prune [--max-size-gb] [--max-days] [--dry-run]
kamera-einleser cache resync                              # nicht-replizierte ZIPs nachschieben

manifest / audit

kamera-einleser manifest show <manifest>             # Manifest-Inhalt lesbar anzeigen
kamera-einleser audit archive-grouping <pfad>        # Misch-Tag-Archive aus 1.x-Bestand erkennen

settings

kamera-einleser settings   # interaktives Konfigurations-Menü

⚙️ Konfigurationsdatei

Die Config liegt XDG-konform unter:

$XDG_CONFIG_HOME/kamera-einleser/config.toml
# meistens: ~/.config/kamera-einleser/config.toml

Vollständige Schlüssel:

Schlüssel Typ Default Bedeutung
source_directories list [] Quellen (lokale Pfade oder rclone-Specs remote:path)
rclone_targets list of dicts [] [{name, path}, ...]
working_directory string "" Primärer Cache für ZIPs
working_directory_fallback string "" Sekundär (z.B. externe SSD), wenn primär fehlt/voll
archive_cache_max_bytes int 500_000_000_000 Cache-Limit in Byte (500 GB), 0 = aus
archive_cache_max_days int 7 Cache-Window in Tagen (Variante C), 0 = aus
archive_auto_fetch bool true Out-of-window-Tag automatisch vom NAS holen
excludes list ["._*", ".DS_Store"] Glob-Patterns für Source-Scan

🔧 rclone einrichten

# Interaktive rclone-Konfiguration (NAS, Mega, Drive, ...)
rclone config

# Teste die Verbindung
rclone lsd nas:Originalmedien

In kamera-einleser:

kamera-einleser settings
# → rclone-Ziele verwalten → Hinzufügen
#   Name: nas
#   Pfad: nas:Originalmedien

Konvention: das erste Target ist das lokale/schnelle (NAS), weitere sind Cold-Storage. Beim Lazy-Check und beim Manifest-Pull wird immer das erste Target gefragt.

🎯 Archivierungs-Konzept

Working-Directories (primär + Fallback)

kamera-einleser config set working_directory /Volumes/Crucial-P3/Originalmedien
# Primär: schnelle interne/externe SSD

kamera-einleser config set working_directory_fallback /Volumes/Samsung2TB/Originalmedien
# Fallback: grössere SSD für 100GB+-Pulls vom NAS

Split-Pipeline: bei knappem Platz auf dem Primary werden Pull (vom NAS) und ZIP-Bau automatisch auf zwei Volumes aufgeteilt — Pull aufs Primary, ZIP aufs Fallback. Jede Disk braucht nur ~1.5× Quell-Grösse statt ~3.5× zusammen.

Lokaler Archiv-Cache (Variante C)

Die ZIPs bleiben nach erfolgreichem Upload im Working-Dir liegen, damit Folge-Läufe schnell sind (Append in das gleiche ZIP ohne erneuten Server-Roundtrip). Aufräumen passiert automatisch:

  • Tag-basiert (archive_cache_max_days, Default 7): ZIPs mit day_key älter als heute−7 werden geprunt — aber nur, wenn ihr *.uploaded.json-Marker bestätigt, dass sie auf allen Targets liegen.
  • Bytes-Backstop (archive_cache_max_bytes, Default 500 GB): zusätzliche Obergrenze. Wird sie nach dem Tag-Prune noch überschritten, fliegen weitere alte ZIPs raus.

Pro Lauf werden beide Limits geprüft. Operator-Eingriffe:

# Limits anpassen
kamera-einleser config set archive_cache_max_days 14
kamera-einleser config set archive_cache_max_bytes 1000000000000  # 1 TB

# Manuell aufräumen
kamera-einleser cache list
kamera-einleser cache prune --max-days 14 --dry-run
kamera-einleser cache prune --max-days 14

# Einen einzelnen Lauf override
kamera-einleser archive --no-prune-cache

Der lokale Cache ist kein Backup mit Garantien — die echten Sicherungen sind die rclone-Targets. Der Cache existiert nur für schnellen Append und für Wiederholung bei Teil-Uploads.

Multi-Source-Verarbeitung

Sind mehrere Quellverzeichnisse konfiguriert, werden sie sequenziell verarbeitet:

kamera-einleser archive
# → 1. /Volumes/Crucial-P3/Eingang
# → 2. synology:/volume1/Fotos/Eingang
# → 3. /Volumes/Sandisk/Eingang

Pro Quelle wird unabhängig idempotent geprüft. Eine fehlgeschlagene Quelle blockiert die anderen nicht.

📦 Archiv-Format & technische Details

ZIP-Bau (ZIP_STORED)

  • Ohne Kompression — Video-Codecs (HEVC, ProRes etc.) komprimieren bereits intern; eine zweite Schicht würde nur CPU verbrennen ohne Platz zu sparen.
  • Streaming: jede Source-Datei wird direkt aus dem Filesystem in den ZIP-Stream geschrieben, kein Staging-Zwischenspeicher (spart pro Tag ≈ 1× Source-Grösse Disk-Footprint).
  • Atomar: ZIP wird unter Temp-Name gebaut und via os.replace umbenannt. Bei Abbruch entsteht nie ein halbes Archiv.
  • Members sortiert (alphabetisch nach Pfad) — deterministische SHA-256.

Append-Modell

Für ZIPs innerhalb des Cache-Window (Default 7 Tage) wird beim Re-Lauf mit neuen Files für denselben Tag das bestehende ZIP in-place erweitert. Mechanik:

  1. Lazy-Check Stufe 2: das .zip.manifest.jsonl-Sidecar vom ersten Target wird gepullt (KBs, sub-Sekunde) und gegen den geplanten neuen Inhalt verifiziert. Subset-Garantie: jeder (filename, sha256) aus dem Server-Manifest muss im neuen Plan vorkommen.
  2. Stream-Copy: alte Members aus dem bestehenden ZIP werden bytegenau ins neue übernommen (ZIP_STORED + Stream-Copy = bit-identisch).
  3. Neue Files kommen frisch dazu.
  4. Manifest neu schreiben: alte Entries 1:1 reusen, neue Files via medien-leser frisch erfassen.
  5. Upload mit --immutable=False für genau dieses Archiv — alle anderen Uploads bleiben beim sicheren Default.

Bei Verletzung der Subset-Garantie wird hart abgebrochen mit Klartext-Diagnose (welche Files würden verloren gehen). Manueller Eingriff via cache fetch <day> + erneuter Lauf.

Manifest-Sidecars

Jedes ZIP trägt automatisch ein *.zip.manifest.jsonl daneben mit:

  • Header: ZIP-SHA-256, Grösse, File-Count, Tool-Version, Timestamp
  • Pro-File-Entries: relative_path, size, sha256, MediaFileMetadata (recorded_at + Provenance, camera, gps, video/audio-Codec, Apple-ProApps-Production-Felder)

Das Manifest wandert beim Upload mit zum NAS — kein --immutable für Manifests (Re-Indexierungen sind valide Updates).

Replikations-Marker (*.uploaded.json)

Nach erfolgreichem Upload zu allen Targets wird ein *.uploaded.json neben dem lokalen ZIP geschrieben:

{"targets": ["nas", "mega"], "uploaded_at": "2026-05-14T09:01:23+02:00", "tool_version": "3.0.0"}

Maschinenlokal (nicht auf Server). Cache-Prune respektiert diesen Marker: gelöscht wird nur, wenn die Target-Liste die aktuell konfigurierten Targets vollständig abdeckt.

🛠 Migration von 2.x

Wer von 2.0.x–2.3.x kommt:

  1. Vor dem Update: ein cache list laufen lassen. Wenn .iso-Files auftauchen, vorher auf einer 2.3.x-Installation migrieren:
    kamera-einleser convert iso-to-zip 2026-04-15 --delete-source-iso
    
  2. Update ziehen: pipx upgrade kurmann-kamera-einleser (Version 3.0.0+).
  3. Config wird beim ersten Start auto-migriert: alte iso_cache_*-Keys werden in archive_cache_* umgesetzt; ist transparent.

Wer das Update zieht, bevor alle ISOs migriert sind, hat keinen Datenverlust — ISO-Files bleiben physisch liegen, werden vom Tool aber ignoriert. Downgrade auf 2.3.1 für Konvertierung ist jederzeit möglich.

Wer von 1.x kommt, hat einen längeren Migrations-Pfad — am einfachsten zuerst auf 2.3.1 upgraden, mtime-gruppierte ISOs mit convert iso-to-zip neu nach recorded_at umpacken (Multi-Day-Split heilt sich automatisch), dann auf 3.0.0.

🤝 Beitragen

Pull-Requests willkommen. Architektur folgt dem kurmann-python-api-Skill: Schichten core/api/cli/services/models, Request/Result-Signaturen, dateibasierte Outputs.

📄 Lizenz

MIT — siehe LICENSE.

👨‍💻 Autor

Patrick Kurmann (@kurmann) — Willisau, Schweiz.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

kurmann_kamera_einleser-3.0.2.tar.gz (81.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

kurmann_kamera_einleser-3.0.2-py3-none-any.whl (97.6 kB view details)

Uploaded Python 3

File details

Details for the file kurmann_kamera_einleser-3.0.2.tar.gz.

File metadata

  • Download URL: kurmann_kamera_einleser-3.0.2.tar.gz
  • Upload date:
  • Size: 81.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for kurmann_kamera_einleser-3.0.2.tar.gz
Algorithm Hash digest
SHA256 6eab0ad1361cce3805f3bc9db99c1fb17802e8e81c49a807910b95b0c0ee1361
MD5 47e1801cb6070da562888213bba70c99
BLAKE2b-256 697d57c8f3f772cae702238e26bcd259e04c75a24015c259f8b53c23e675226b

See more details on using hashes here.

Provenance

The following attestation bundles were made for kurmann_kamera_einleser-3.0.2.tar.gz:

Publisher: publish.yml on kurmann/kamera-einleser

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file kurmann_kamera_einleser-3.0.2-py3-none-any.whl.

File metadata

File hashes

Hashes for kurmann_kamera_einleser-3.0.2-py3-none-any.whl
Algorithm Hash digest
SHA256 acb136c461680758ef9ea66a67bb9a5ba3315a5a094cf0ab273384a3df1b4fea
MD5 f80abc882fb0ac1362f924051551b11a
BLAKE2b-256 a0e97a0de14ea70513b552b28ce54ef7f4306b6d0ce0a12127a0b33c6afeecc1

See more details on using hashes here.

Provenance

The following attestation bundles were made for kurmann_kamera_einleser-3.0.2-py3-none-any.whl:

Publisher: publish.yml on kurmann/kamera-einleser

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page