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 in ISO-Dateien (organisiert nach echtem Aufnahmedatum) und synchronisiert sie auf Cloud-Speicher.
2.0.0 ist ein Major-Release mit zwei strukturellen Verbesserungen:
- Manifest-System pro ISO mit reichhaltigen Metadaten (Aufnahmedatum mit Provenance, Kamera, GPS, Audio/Video-Codec, Production-Felder), vereinheitlicht über Blackmagic Camera, iPhone Camera und Final Cut Camera
- Build-Strategie auf
recorded_at: Files werden nach echtem EXIF-Aufnahmedatum gruppiert, nicht mehr nach Filesystem-mtime. Damit entstehen keine "Misch-Tag-ISOs" mehr.Breaking-Changes und Migrations-Pfad: siehe CHANGELOG.md.
✨ Features
- 🗜 ZIP-Archivierung (ab 2.2.0): Erstellt
ZIP_STORED-Archive (ohne Kompression). Streaming-Bau ohne Staging-Zwischenkopie spart pro Tag eine komplette Source-Grösse an Disk-Footprint. - 📦 Append in bestehende ZIPs (ab 2.2.0): Kommen für einen Tag innerhalb der Cache-Window neue Files dazu, wird das bestehende ZIP in-place erweitert (Stream-Copy alter Members + neue Files dazu). Kein
-Teil-Nmehr — ein ZIP pro Tag. - 🛡️ Lazy-Check vor Append: Vor jedem Server-überschreibenden Upload wird das Server-Manifest gepullt (sub-Sekunde, KBs) und mit dem geplanten neuen Inhalt verglichen. Würde ein Datei verloren gehen, blockt der Lauf hart.
- 📅 Tag-Gruppierung nach echtem Aufnahmedatum: Files werden anhand des EXIF-
recorded_atzugeordnet (ab 2.0.0). Ein Misch-Tag-Bug aus 1.x ist damit strukturell gelöst. - 🔄 Idempotente Archivierung: Prüft vor Erstellung, ob Dateien bereits im Archiv vorhanden sind. Funktioniert format-agnostisch für
.iso(Legacy) und.zip. - ☁️ rclone-Integration: Synchronisiert Archive zu beliebig vielen Cloud-Zielen (NAS, Mega.nz, Google Drive, etc.) mit dynamischem
--immutable-Schutz. - ♻️ Cache-Variante-C (ab 2.2.0): ZIPs der letzten N Tage (Default 7) bleiben lokal für schnellen Append, ältere werden auto-prunt — sofern repliziert (
*.uploaded.json-Marker bestätigt). - 🔗 SMB-Mount: Mounte Archive direkt via SMB ohne Download.
- 📥 Archiv-Wiederherstellung: Lädt Archiv vom NAS herunter und mountet es automatisch.
- 🔍 Intelligente Suche: Findet Archive nach Namen mit Pagination und Filterung.
- 🤖 Headless-Modus: Automatische Wiederherstellung/Mount per Kommandozeile.
- ⚙️ Interaktive Konfiguration: Benutzerfreundliche Einstellungsverwaltung.
- 📂 Mehrere Quellen: Sequenzielle Verarbeitung mehrerer Quellverzeichnisse.
- 🗑️ Optionale Löschung: Fragt am Ende nach dem Löschen der Originaldateien.
- 📑 Reichhaltige Manifests pro Archiv: jedes Archiv trägt automatisch ein JSONL-Sidecar mit pro-Datei-Metadaten — Aufnahmedatum mit Provenance, Kamera-Modell, GPS, Codec, Apple-ProApps-Production-Felder. Die Metadaten-Logik lebt im separaten Paket
kurmann-medien-leser(ab 2.1.0). - 🔍 Audit-Befehl:
kamera-einleser audit iso-groupingzeigt Misch-Tag-Archive (alte ISOs aus 1.x mit mtime-Gruppierung). - ♻️ Recovery ohne erneuten Pull:
kamera-einleser cache resyncschickt unvollständig replizierte Cache-Archive an die fehlenden Targets nach.
🎬 Unterstützte Aufnahme-Quellen (ab 2.0.0)
Eines der zentralen Features: kamera-einleser vereinheitlicht die Metadaten aus verschiedenen iPhone-Kamera-Apps in ein einziges, konsistentes Manifest-Schema. Jeder App-Hersteller schreibt EXIF-Tags in seine eigene Stelle und mit eigenen Konventionen — der Indexer kennt die Eigenheiten und produziert für alle drei Quellen dasselbe Schema, mit klarer Provenance über die Quelle und das Aufnahmedatum.
| App | Aufnahmedatum | Eigenheiten | Reiche Metadaten |
|---|---|---|---|
| Blackmagic Camera | Keys:CreationDate mit TZ ✓ |
viele proprietäre Felder (Blackmagic-design:*) |
Apple ProApps Production-Felder (Reel/Scene/Shot/Project/Director), Kamera-Settings (ISO, Shutter, WB, Aperture) |
| iPhone Camera (Apple Foto-App) | Keys:CreationDate mit TZ ✓ |
Standard Apple QuickTime-Tags | Lens-Modell (VideoKeys:LensModel), GPS mit Höhe |
| Final Cut Camera (Apple, iOS 18+) | kein CreationDate — nur CreateDate (UTC ohne TZ) ⚠️ |
identifizierbar via AppleProappsAppBundleID = com.apple.FinalCutApp.companion |
Lens-Modell, GPS mit Höhe, App-Version |
Wichtigste Vereinheitlichungs-Tricks:
-
Tag-Pfad-Aggregation: Tag-Werte können je nach App in unterschiedlichen EXIF-Atomen liegen (z.B.
Keys:Makebei iPhone vs.QuickTime:Makebei BMD). Der Indexer schaut alle bekannten Pfade in priorisierter Reihenfolge an. -
Format-Normalisierung: exiftool liefert Datums oft als
2026:04:20 12:52:15(mit:als Datums-Trenner). Der Indexer normalisiert auf saubere ISO-8601 (2026-04-20T12:52:15+02:00), damit Konsumenten konsistent parsen können. -
Zeitzonen-Inferenz für Final Cut Camera: FCC schreibt
CreateDatein UTC ohne TZ-Marker. Wenn das als-ist gelesen wird, ist die Aufnahmezeit 1-2 Stunden falsch. Der Indexer leitet die Zeitzone ausfile_mtime(lokal mit TZ) ab und setzt sie auf den UTC-Wert auf.recorded_at_sourcezeigt dannexif:CreateDate+tz_from_mtime, damit transparent ist, dass die TZ inferiert wurde. -
Datums-Provenance: jedes Manifest-Entry hat ein
recorded_at(Best-Guess) und einrecorded_at_source(welche Quelle wurde gewählt). Bei Misstrauen kann der Konsument imdates-Subblock alle Roh-Quellen einsehen und selbst entscheiden. -
Source-App-Detection: jede Datei kriegt im Manifest ein
source_app-Feld mit "Blackmagic Camera", "iPhone Camera" oder "Final Cut Camera" — falls die Quelle eindeutig erkannt werden konnte. Andere Apps funktionieren oft auch, kriegen abersource_app=nullund können einzelne Felder vermissen.
Beispiel-Output aus einem Final Cut Camera-File:
{
"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",
"software": "26.4.2",
"lens": "iPhone 15 Pro Max back camera 6.765mm f/1.78"
},
"gps": {"lat": 47.07054, "lon": 7.58165, "altitude_m": 516.0},
"video": {"codec": "HEVC", "resolution": "3840x2160", "fps": 30}
}
📋 Voraussetzungen
- macOS (für ISO-Erstellung via hdiutil)
- Python 3.10 oder höher
- rclone (für Cloud-Synchronisation)
- exiftool (für Manifest-Metadaten ab 2.0.0):
brew install exiftool
🚀 Installation
Empfohlen: Mit uv
# uv installieren (falls noch nicht vorhanden)
curl -LsSf https://astral.sh/uv/install.sh | sh
# oder mit brew
brew install uv
# kamera-einleser installieren
uv tool install git+https://github.com/kurmann/kamera-einleser.git
Alternative: Mit pip / pipx / uv
Von PyPI (empfohlen ab 1.4.0):
uv tool install kurmann-kamera-einleser
# oder
pipx install kurmann-kamera-einleser
# oder
pip install kurmann-kamera-einleser
Direkt vom Git-Repository (für Entwicklung):
pip install git+https://github.com/kurmann/kamera-einleser.git
rclone installieren
# macOS
brew install rclone
🎯 Schnellstart
1. Einstellungen konfigurieren
kamera-einleser settings
Hier konfigurierst du:
- Quellverzeichnisse: Wo liegen deine Kamera-Aufnahmen?
- rclone-Ziele: Wohin sollen die Backups synchronisiert werden?
- Archiv-Verzeichnisse: Wo sind deine Archive gespeichert? (für Wiederherstellung)
- Arbeitsverzeichnis: Wo werden ISO-Dateien lokal erstellt/gespeichert?
- Ausschluss-Muster: Dateien/Ordner, die nicht archiviert werden sollen
2. Archivierung starten
# Archiviert alle konfigurierten Quellverzeichnisse
kamera-einleser archive
# Oder direkt ein bestimmtes Verzeichnis angeben
kamera-einleser archive /Volumes/SSD/Videos
# Mit optionaler Löschung der Originale am Ende
kamera-einleser archive --delete-source
2a. Arbeitsverzeichnisse (primär + Fallback)
Ab 1.4.0 kann man zwei Arbeitsverzeichnisse konfigurieren. Das Tool nimmt automatisch das passende:
# Primär: interne SSD — schnell, ideal für kleine/mittlere Volumina
kamera-einleser config set working_directory ~/Movies/Kamera-Einleser
# Fallback: externe SSD — gross, ideal für 100 GB+ Pulls vom NAS
kamera-einleser config set working_directory_fallback /Volumes/Samsung2TB/Kamera-Einleser
Split-Pipeline (ab 1.6.0) — Pull und ISO auf verschiedene Volumes
Sind beide Arbeitsverzeichnisse verfügbar und haben je genug Platz, wird die Pipeline automatisch gesplittet:
- Pull → Primary (interne SSD, schneller Lesepfad für den rclone-Download)
- ISO → Fallback (externe SSD, sequentieller Schreibpfad fürs Image)
Vorteile:
- Jedes Volume braucht nur ~1.5× Quell-Grösse frei statt zusammen 3.5×.
- Lese- und Schreibpfad auf verschiedenen Disks → entkoppelte I/O, potenziell schneller.
- Staging landet wie bisher im System-Temp (
/var/foldersauf macOS).
Ist nur ein Volume verfügbar oder hat nur eins genug Platz, fällt der Resolver auf Single-Volume-Modus zurück (alles auf einer Disk mit 3.5× Footprint).
Resolver-Logik pro Quelle
Der Resolver entscheidet pro Quelle und wählt in dieser Reihenfolge:
- Split-Modus: beide Volumes mounted, je ≥
Quelle × 1.5frei → Pull→Primary, ISO→Fallback. - Single-Volume-Modus: ein Volume mit voller Pipeline-Kapazität (Quelle × 3.5 bei rclone, × 2.5 bei lokal).
- Fallback bei lokaler Quelle: konfiguriertes Working-Dir oder System-Temp.
- Fehler bei rclone-Quelle: harter Abbruch mit klarer Meldung — Temp kann auf macOS im RAM liegen und einen 200-GB-Pull sprengen.
2a-bis. Lokaler Archiv-Cache (Variante C, ab 2.2.0)
Die erstellten Archive bleiben nach erfolgreichem Upload im Arbeitsverzeichnis liegen. Das ist kein echtes Backup mit Garantien — die echten Sicherungen sind deine rclone-Targets (NAS, Cloud). Der lokale Cache dient zwei Zwecken:
- Schnelle Subset-Checks (Fall A): das Tool öffnet das lokale ZIP/ISO
direkt, statt den Mount-Roundtrip via
hdiutilzu fahren oder das Archiv vom Server zu pullen. - Append-Window: solange das ZIP eines Tages im Cache ist, können neue
Files für denselben Tag in dieses ZIP hineinwachsen — kein
-Teil-Nmehr.
Variante C — zwei orthogonale Limits:
iso_cache_max_days = 7(Default): nach 7 Tagen rückwärts (gemessen amday_key) fliegen Archive aus dem Cache. Die Server-Kopie bleibt natürlich unangetastet. Tag-Limit0deaktiviert dieses Verhalten.iso_cache_max_bytes = 500 GB(Default): Belt-and-Suspenders gegen Disk-Überlauf. Falls nach dem Tag-basierten Prune die Cache-Summe noch zu hoch ist, fallen die ältesten replizierten Archive zusätzlich.0= unlimitiert.
Safety: nur Archive mit vollständigem *.uploaded.json-Marker (= auf
allen aktuell konfigurierten Targets repliziert) werden gelöscht. Archive
ohne Marker bleiben in jedem Fall liegen.
# Cache-Window anpassen
kamera-einleser config set iso_cache_max_days 14 # zwei Wochen lokal
kamera-einleser config set iso_cache_max_bytes 1000000000000 # 1 TB Backstop
# Manuell aufräumen (Tag-basiert)
kamera-einleser cache list
kamera-einleser cache prune --dry-run # Defaults aus Config
kamera-einleser cache prune --max-days 7
kamera-einleser cache prune --max-size-gb 500
# Einen einzelnen Archive-Lauf overriden
kamera-einleser archive --no-prune-cache # Cache diesmal in Ruhe lassen
kamera-einleser archive --prune-cache # Pruning erzwingen
2b. Netzlaufwerke als Quellen (rclone, z.B. Synology NAS)
Ab 1.4.0 dürfen Quellen auch rclone-Remotes sein — zum Beispiel ein Synology NAS. Die Dateien werden beim Archive-Lauf zunächst temporär in das Arbeitsverzeichnis gezogen (Staging) und von dort ganz normal in ISO-Dateien pro Tag verpackt.
# Einmalig rclone für das NAS einrichten (interaktiv)
rclone config
# Quellen können ab jetzt lokale Pfade *oder* rclone-Specs sein:
kamera-einleser config get source_directories
# → ["/Volumes/Card", "synology:/volume1/Fotos/Eingang"]
Das Parsing erfolgt über das Muster remote:pfad. Lokale Pfade beginnen mit
/ oder ~. Auf NAS-Quellen verhält sich die CLI identisch zu lokalen
Quellen, mit Ausnahme von --delete-source: das wirkt nur auf lokale Quellen.
Manifest-Pre-Filter: wir pullen nur, was noch nicht archiviert ist
Beim rclone-Pull zieht der Tool nicht jedes Mal alle Dateien vom Netz- laufwerk. Stattdessen:
- Die lokalen
*.manifest.jsonl-Sidecars im Cache werden gelesen — sie enthalten alle Dateinamen, die bereits in einem Archiv (ISO oder ZIP) stecken. - Vor dem Pull wird eine temporäre rclone-Filter-Datei erzeugt, die genau diese Dateinamen als Exclude-Patterns enthält.
- rclone bekommt die Filter-Datei via
--filter-fromund überspringt die bereits archivierten Dateien remote-seitig — sie werden nie über das Netz übertragen.
Das macht wiederholte Läufe um Grössenordnungen schneller. Beispiel: bei einer 265-GB-NAS-Quelle, von der 95 % bereits archiviert sind, werden nur die restlichen ~13 GB gezogen statt die vollen 265 GB.
Warum filename-basiert und nicht checksum-basiert? Nicht alle rclone-
Backends liefern server-seitige Hashes (SMB z.B. nicht). Filename-basiert
funktioniert dagegen auf jedem Backend und ist für typische
Kamera-Dateinamenskonventionen (z.B. BMPCC: A001_04051010_C123.mov
= Karte+Timecode+Clip-Nummer) effektiv eindeutig. Die zusätzliche
Fall-A-Subset-Check-Prüfung im Build-Schritt fängt Rest-Kollisionen ab,
falls doch zwei Dateien mit identischem Namen unterschiedlichen Inhalt
hätten.
Pre-Filter umgehen (Debugging):
kamera-einleser archive --full-pull
Zieht alle Dateien der Remote-Quelle erneut, unabhängig vom Manifest-Cache.
Startup-Guard: Schutz vor "Manifest-Cache leer, Remote voll"
Wenn beim archive-Lauf der lokale Manifest-Cache leer ist, aber auf
den konfigurierten rclone-Targets Archive existieren, bricht der Tool
mit klarer Fehlermeldung ab. Hintergrund: in diesem Zustand würde archive
alle Tage neu aufbauen und am rclone --immutable-Upload scheitern.
❌ Abbruch: kein lokales Manifest, aber auf den konfigurierten Targets
existieren 113 Archiv(e).
Empfohlen — Manifests vom Server holen:
rclone copy <target>:Originalmedien /pfad/zum/cache --include '*.manifest.jsonl'
Übersteuern (nur bewusst, z.B. nach kompletter Remote-Löschung):
kamera-einleser archive --force
Mit --no-upload ist der Guard ohnehin deaktiviert (kein Netz-Zugriff).
Cross-Machine: Manifests sind selbst die Wahrheit
Bis 2.1.0 lebte parallel zu den Archiven eine zentrale checksum_index.csv,
die via kamera-einleser index push/pull zwischen Rechnern synchronisiert
wurde. Ab 2.2.0 ist diese Infrastruktur entfernt:
- Jedes Archiv hat sein
*.manifest.jsonl-Sidecar direkt neben sich auf dem Server (wandert beim Upload mit). Das Manifest enthält pro File SHA-256 + medien-leser-Metadaten — alles was der CSV-Index früher trug. - Auf einem Zweit-Rechner reicht ein einmaliges
rclone copyder Sidecars vom NAS in den lokalen Cache, und Pre-Filter + Fall-A-Subset-Check funktionieren ohne Wissen über fremde Maschinen. - Der
*.uploaded.json-Marker pro Archiv hält maschinenlokal fest, dass DIESE Maschine erfolgreich zu allen aktuell konfigurierten Targets repliziert hat — Voraussetzung fürcache prune-Safety.
3. Archive mounten (ohne Download)
Mounte Archive direkt via SMB für schnellen, schreibgeschützten Zugriff:
# Interaktiver Modus: Wähle Archiv aus Liste
kamera-einleser mount
# Headless-Modus: Mounte direkt
kamera-einleser mount Originalmedien-2026-01-15.iso
# Oder mit Alias:
kamera-einleser load Originalmedien-2026-01-15.iso
# Zeige gemountete Archive
kamera-einleser mount --list-mounted
kamera-einleser status
# Unmounten
kamera-einleser unmount Originalmedien-2026-01-15.iso
# Oder alle:
kamera-einleser unmount --all
Vorteile:
- ✅ Kein lokaler Speicherplatz nötig
- ✅ Sofortiger Zugriff (kein Download)
- ✅ Ideal für Durchsuchen, Ansehen, Videoschnitt mit Referenzen
Einrichtung:
Füge smb_url zu deinen rclone-Zielen in der Config hinzu:
kamera-einleser settings # → rclone-Ziele bearbeiten
In der Config-Datei (~/.config/kamera-einleser/config.yaml):
rclone_targets:
- name: "NAS"
path: "nas:/Archiv"
smb_url: "smb://user@nas.local/Archiv" # Diese Zeile hinzufügen
Beim ersten Mount fragt macOS nach Zugangsdaten und bietet an, diese im Schlüsselbund zu speichern. Danach erfolgen Mounts automatisch.
4. Archiv wiederherstellen
# Interaktiver Modus: Durchsuche und wähle Archive aus
kamera-einleser restore
# Headless-Modus: Automatische Wiederherstellung nach Dateiname
kamera-einleser restore Originalmedien-2026-01-15.iso
# Mit eigenem Zielverzeichnis
kamera-einleser restore --restore-dir /custom/path Originalmedien-2026-01-15
5. Optionale Parameter für Archivierung
# Nur Archiv erstellen, ohne Upload
kamera-einleser archive --no-upload
# Originaldateien nach erfolgreichem Backup löschen (fragt nach)
kamera-einleser archive --delete-source
📖 Befehle
settings
Öffnet das interaktive Einstellungsmenü.
kamera-einleser settings
config
Zeigt die aktuelle Konfiguration an.
kamera-einleser config
archive
Erstellt tägliche Archive (ZIP ab 2.2.0, ISO als Legacy) und synchronisiert sie zu Cloud-Zielen. Append in bestehende ZIPs des Cache-Windows.
kamera-einleser archive [QUELLVERZEICHNIS] [OPTIONEN]
Optionen:
--no-upload: Nur Archiv erstellen, nicht synchronisieren--delete-source: Originaldateien nach erfolgreichem Backup löschen--full-pull: Manifest-Pre-Filter umgehen, alle Files erneut pullen--force: Startup-Guard (leerer Manifest-Cache + volle Remotes) übersteuern--prune-cache/--no-prune-cache: Auto-Prune des lokalen Caches am Ende des Laufs erzwingen bzw. überspringen
cache
Operator-Befehle rund um den lokalen Archiv-Cache.
kamera-einleser cache list # alle lokalen Archive + Replikations-Status
kamera-einleser cache prune # Auto-Prune nach Config-Defaults
kamera-einleser cache prune --max-days 7 # Tag-basiert
kamera-einleser cache prune --max-size-gb 500 # Bytes-basiert
kamera-einleser cache prune --dry-run # nur Plan, nichts löschen
kamera-einleser cache resync # nicht voll replizierte ZIPs erneut hochladen
manifest / audit
Diagnose-Werkzeuge rund um die *.manifest.jsonl-Sidecars.
kamera-einleser manifest build <archiv> # Manifest neben einer ISO/ZIP nachholen
kamera-einleser manifest show <manifest> # Manifest-Inhalt anzeigen
kamera-einleser audit iso-grouping <pfad> # Misch-Tag-Archive aus 1.x erkennen
restore
Stellt ein ISO-Archiv aus Cloud-Speicher wieder her (mit Download).
# Interaktiver Modus
kamera-einleser restore
# Headless-Modus mit Dateiname
kamera-einleser restore [DATEINAME] [OPTIONEN]
Optionen:
--restore-dir, -r PATH: Zielverzeichnis für Wiederherstellung (optional)
mount / load
Mountet ein Archiv direkt via SMB ohne Download (schneller, schreibgeschützter Zugriff).
# Interaktiver Modus
kamera-einleser mount
kamera-einleser load # Alias
# Headless-Modus mit Dateiname
kamera-einleser mount [ARCHIVNAME]
# Zeige gemountete Archive
kamera-einleser mount --list-mounted
# Zeige Mount-Pfad (für Scripting)
kamera-einleser mount --print-path [ARCHIVNAME]
Voraussetzungen:
- SMB-URL in
config.yamlkonfiguriert (siehe "Archive mounten" oben) - Netzwerkzugriff auf das NAS
unmount
Unmountet gemountete Archive.
# Interaktiver Modus (Auswahl aus Liste)
kamera-einleser unmount
# Headless-Modus mit Archivname
kamera-einleser unmount [ARCHIVNAME]
# Alle Archive unmounten
kamera-einleser unmount --all
status
Zeigt alle aktuell gemounteten Archive an.
kamera-einleser status
version
Zeigt die installierte Version an.
kamera-einleser --version
⚙️ Konfiguration
Die Konfigurationsdatei liegt unter:
~/.config/kamera-einleser/config.yaml
Beispiel-Konfiguration:
source_directories:
- /Volumes/SSD/BlackmagicCamera
- /Volumes/iPhone/DCIM
rclone_targets:
- name: NAS
path: nas:/Backups/Videos/2026
- name: Mega.nz
path: mega:Backups/Videos
archive_directories:
- nas:/Backups/Videos/2026
- nas:/Backups/Videos/2025
- mega:Backups/Videos
current_archive_directory: nas:/Backups/Videos/2026
working_directory: /Users/username/Movies/Kamera-Einleser
restore_directory: /Users/username/Movies/Kamera-Restore
excludes:
- ._*
- .DS_Store
Konfigurationsoptionen:
source_directories: Liste von Quellverzeichnissen zum Archivierenrclone_targets: Liste von rclone-Zielen für Synchronisationarchive_directories: Liste von rclone-Pfaden, die nach Archiven durchsucht werdencurrent_archive_directory: Das aktuell verwendete Verzeichnis für neue Archiveworking_directory: Lokales Verzeichnis für ISO-Dateien (nicht temporär!)restore_directory: Standard-Verzeichnis für wiederhergestellte ISOsexcludes: Muster für auszuschließende Dateien (z.B..DS_Store,._*)
💡 Tipp: Das Arbeitsverzeichnis ist der lokale Speicher für ISO-Dateien. Diese werden mit rclone zu den Zielen synchronisiert und lokal behalten für Videoschnitt etc.
🔧 rclone einrichten
Bevor du Cloud-Ziele nutzen kannst, musst du rclone konfigurieren:
# Interaktive rclone-Konfiguration
rclone config
# Teste die Verbindung
rclone lsd nas:
Weitere Infos: rclone.org/docs
🎬 Workflow-Beispiele
Archivierung
- Kamera-SSD am Mac anschließen
kamera-einleser archiveausführen- Programm analysiert Dateien und gruppiert sie nach Tag (Änderungsdatum)
- Prüft bestehende ISOs im Arbeitsverzeichnis und auf allen rclone-Zielen
- Prüft Idempotenz: Überspringt Tage, deren Dateien bereits archiviert sind
- Erstellt ditto-Staging für neue/geänderte Tage (Metadaten-erhaltend)
- Erstellt ISO-Dateien mit UDF-Format (z.B. "Originalmedien-2026-01-15.iso")
- Verifiziert die Integrität mit hdiutil
- Synchronisiert zu allen konfigurierten Cloud-Zielen (organisiert in Jahresordner)
- Optional: Löscht Originaldateien nach Bestätigung am Ende
Wiederherstellung
Interaktiv:
kamera-einleser restoreausführen- Optional: Nach Dateinamen suchen
- Durch Archive blättern (10 pro Seite)
- Archiv auswählen
- Bestätigen
- Programm lädt ISO herunter und mountet es
Headless (Automatisiert):
# In Skript oder Cronjob
kamera-einleser restore Originalmedien-2026-01-15 --restore-dir /restore/path
🎯 Archivierungs-Konzept
Das Programm verwendet tägliche ISO-Archive:
- Datei-Analyse: Scannt alle Dateien und liest deren Änderungsdatum
- Tägliche Gruppierung: Gruppiert Dateien nach Tag (YYYY-MM-DD)
- ISO pro Tag:
- Dateiname:
Originalmedien-YYYY-MM-DD.iso - Volume-Name:
Originalmedien-YYYY-MM-DD - Format: UDF (Universal Disk Format)
- Dateiname:
- Idempotenz:
- Prüft vor Erstellung, ob alle Dateien bereits im bestehenden ISO vorhanden sind
- Prüft bestehende ISOs auf allen konfigurierten rclone-Zielen (Namenskollision)
- Überspringt Tag wenn bereits vollständig archiviert
- Bei neuen Dateien wird neue ISO mit Suffix erstellt (
-Teil-2,-Teil-3, etc.)
- ditto-Staging:
- Kopiert Dateien via macOS
dittoins temporäre Staging-Verzeichnis - Erhält Resource Forks, Extended Attributes und HFS+/APFS-Metadaten
- Erzeugt ein eigenständiges, vollständiges Archiv
- Kopiert Dateien via macOS
- Verzeichnisstruktur: Unterverzeichnisse werden im ISO übernommen
- rclone-Organisation: ISOs werden in Jahresordner auf Remote-Zielen organisiert
- Idempotente Uploads: rclone läuft auch ohne neue ISOs, um fehlgeschlagene Uploads zu wiederholen
Beispiel (ab 2.2.0):
Quellverzeichnis/
├── 2026-01-15_Video1.mp4 → Originalmedien-2026-01-15.zip
├── 2026-01-15_Video2.mp4 → Originalmedien-2026-01-15.zip
├── 2026-02-05_Video3.mp4 → Originalmedien-2026-02-05.zip
└── Projekt/
├── 2026-01-15_Clip.mp4 → Originalmedien-2026-01-15.zip (Projekt/...)
└── 2026-02-10_Raw.mov → Originalmedien-2026-02-10.zip (Projekt/...)
Remote-Struktur:
nas:/Backups/Videos/
└── 2026/
├── Originalmedien-2026-01-15.zip
├── Originalmedien-2026-01-15.zip.manifest.jsonl ← Sidecar
├── Originalmedien-2026-02-05.zip
└── Originalmedien-2026-02-10.zip
Vorteile:
- ✅ Tägliche Organisation für präzise Übersicht
- ✅ Idempotenz vermeidet Duplikate und unnötige I/O
- ✅ Append innerhalb des Cache-Windows: ein ZIP pro Tag, das organisch wächst
- ✅ ZIP_STORED universell lesbar (unzip, 7z, macOS Finder, …)
- ✅ Verzeichnisstruktur bleibt erhalten
- ✅ Sub-Sekunden-Subset-Check via
ZipFile.namelist()ohne Mount - ✅ Manifest-Sidecar pro Archiv mit reichhaltigen EXIF/Video-Metadaten
📝 Archiv-Format & Technische Details
Format-Switch ISO → ZIP (ab 2.2.0)
Neue Archive werden als .zip-Dateien (ZIP_STORED, ohne Kompression)
geschrieben. Vorteile gegenüber dem bisherigen ISO-Format:
- Streaming-Bau ohne Staging:
zipfile.ZipFile.writeliest direkt aus den Source-Pfaden in das ZIP — keinditto-Staging mehr. Spart pro Tag etwa eine Source-Grösse an Disk-Footprint. - Sub-Sekunden-Subset-Check:
ZipFile.namelist()liefert die komplette File-Liste aus dem Central Directory am Ende des ZIPs in Millisekunden — keinhdiutil attach/detach-Roundtrip mehr. - Append-Möglichkeit: ZIP-Members können streaming-fähig aus einem
bestehenden ZIP in ein neues kopiert werden (
ZipFile.open('w', force_zip64=True)shutil.copyfileobj). Damit wird "noch ein Clip für denselben Tag" zu einem in-place Rebuild des ZIPs, kein-Teil-Nmehr.
- Plattform-unabhängig: kein macOS-
hdiutilmehr nötig;zipfileist Python-Stdlib.
Determinismus: Member werden alphabetisch nach arcname sortiert geschrieben.
Gleicher Input → byte-identisches ZIP → stabiler SHA-256-Wert im Manifest.
Bestand bleibt: ISO-Archive aus 2.0.x bleiben gültig und werden vom
Subset-Check und dem cache list/prune weiter erkannt. Eine optionale
Bestand-Migration ISO → ZIP folgt in 2.3.0.
Append-Modell (ab 2.2.0)
Kommen für einen Tag innerhalb des Cache-Windows neue Files dazu, wird das bestehende ZIP in-place erweitert:
- Manifest-Pre-Filter sorgt dafür, dass nur die neuen Files vom NAS gezogen werden.
- Lazy-Check (Stufe 2) pullt das
*.zip.manifest.jsonl-Sidecar vom Server (KBs, sub-Sekunde) und prüft per Subset-Vergleich:- Jeder (Filename, SHA-256) aus dem Server-Manifest muss im geplanten neuen Inhalt vorkommen.
- Server-Archiv-Grösse muss zum Manifest-Header passen.
- Bei Konflikt: Hard-Abort mit Klartext-Diagnose und Recovery-Hinweis.
- In-place Rebuild des ZIPs (atomar via tempfile +
os.replace): alte Members per Stream-Copy aus alter ZIP, neue Files von Disk dazu. - Manifest neu schreiben: alte
FileEntry-Objekte 1:1 übernehmen (ZIP_STORED-Bytes sind identisch, SHAs unverändert), neue Files frisch via medien-leser-Pipeline erfassen. - Upload mit dynamischem
--immutable: für die zu ersetzende ZIP wird--immutablefür diesen einen Upload abgeschaltet; alle anderen Archive bleiben beim sicheren Default--immutable=True.
ISO-und-ZIP-Koexistenz
Für Tage mit Bestand-ISO und neuen Files entsteht eine separate .zip
neben der ISO (kein Append in eine ISO, die ist eingefroren). Beide
sind gültige Archive für denselben Tag; das Manifest-Cache-System ist
format-agnostisch und kennt beide.
🔒 Unveränderlichkeit (Immutability)
Drei Schutz-Ebenen:
Lokal (Arbeitsverzeichnis):
- Bestehende Archive (
.iso,.zip) werden nie unbeabsichtigt überschrieben. - Im Append-Fall ist die Modifikation des lokalen ZIPs atomar (tempfile +
os.replace) — eine halbfertige ZIP existiert nie am Zielpfad.
Replikations-Marker (.uploaded.json):
- Pro Archiv liegt nach erfolgreichem Upload-zu-allen-Targets ein winziger Sidecar mit der Target-Liste + Zeitstempel.
- Voraussetzung für
cache prune-Safety: nur Archive mit vollständigem Marker werden lokal gelöscht.
Remote (rclone-Ziele):
rclone copyto --immutableverhindert standardmässig jedes Überschreiben.- Dynamische Ausnahme beim Append: für die ein-bestimmte ZIP, die gerade
replace-uploadet wird, ist
--immutableaus. Alle anderen Uploads im selben Lauf bleiben weiterhin--immutable=True. - Lazy-Check (Subset-Garantie) vor dem Replace verhindert, dass der Server-Inhalt fragmentiert oder partiell überschrieben wird.
🔄 Idempotenz: Keine Duplikate
Archivierungs-Idempotenz:
Vor dem Bau für einen Tag prüft das Programm:
- Lokales Archiv (ISO oder ZIP) für diesen Tag vorhanden?
- Falls ja: format-agnostischer Subset-Check (ZIP-
namelist()für ZIPs,hdiutil-Mount für ISOs). - Alle Files bereits vorhanden → Tag überspringen.
- ZIP da, neue Files dabei → Append-Pfad: ein einziges ZIP wächst.
- Nur ISO da, neue Files dabei → eine zusätzliche ZIP entsteht daneben.
- Kein lokales Archiv, Manifest-Cache deckt den Tag aber ab → Skip (Fall C — verhindert Doppel-Upload nach Cache-Prune).
Upload-Idempotenz:
Selbst wenn keine neuen Archive erstellt wurden:
- Existierende Archive aus Arbeitsverzeichnis sammeln.
- Integritätsprüfung (
ZipFile.testzip()für ZIP,hdiutil imageinfofür ISO). - rclone-Synchronisation — identische Files werden als No-Op übersprungen.
🤝 Beitragen
Issues und Pull Requests sind willkommen! GitHub Repository
📄 Lizenz
MIT License - siehe LICENSE
👨💻 Autor
Patrick Kurmann
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file kurmann_kamera_einleser-2.3.1.tar.gz.
File metadata
- Download URL: kurmann_kamera_einleser-2.3.1.tar.gz
- Upload date:
- Size: 112.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f20d110d90d81c93f10671c8a203348b10943fffc6411fb4e1dbc2614ec239b9
|
|
| MD5 |
de8ae0ccdbd0752885bbd17b356616b4
|
|
| BLAKE2b-256 |
e79276366385ee06ec89e208947086410f7eb893c61e628d8b50e18aca91027b
|
Provenance
The following attestation bundles were made for kurmann_kamera_einleser-2.3.1.tar.gz:
Publisher:
publish.yml on kurmann/kamera-einleser
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
kurmann_kamera_einleser-2.3.1.tar.gz -
Subject digest:
f20d110d90d81c93f10671c8a203348b10943fffc6411fb4e1dbc2614ec239b9 - Sigstore transparency entry: 1533176239
- Sigstore integration time:
-
Permalink:
kurmann/kamera-einleser@6b72cb7a4424c78c38b582411cf5aa304a50a546 -
Branch / Tag:
refs/tags/v2.3.1 - Owner: https://github.com/kurmann
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@6b72cb7a4424c78c38b582411cf5aa304a50a546 -
Trigger Event:
release
-
Statement type:
File details
Details for the file kurmann_kamera_einleser-2.3.1-py3-none-any.whl.
File metadata
- Download URL: kurmann_kamera_einleser-2.3.1-py3-none-any.whl
- Upload date:
- Size: 122.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
54c189a1a58ffe8a32a6b6659df2fd7fc876c30a0209c7b8b622fb28ea4e4800
|
|
| MD5 |
a458c3f61111f3af23f5731e2c12e7fe
|
|
| BLAKE2b-256 |
466412336a75dc7b6e5a6a28a0907f629d74731a10acb31079539bda45ea1197
|
Provenance
The following attestation bundles were made for kurmann_kamera_einleser-2.3.1-py3-none-any.whl:
Publisher:
publish.yml on kurmann/kamera-einleser
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
kurmann_kamera_einleser-2.3.1-py3-none-any.whl -
Subject digest:
54c189a1a58ffe8a32a6b6659df2fd7fc876c30a0209c7b8b622fb28ea4e4800 - Sigstore transparency entry: 1533176394
- Sigstore integration time:
-
Permalink:
kurmann/kamera-einleser@6b72cb7a4424c78c38b582411cf5aa304a50a546 -
Branch / Tag:
refs/tags/v2.3.1 - Owner: https://github.com/kurmann
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@6b72cb7a4424c78c38b582411cf5aa304a50a546 -
Trigger Event:
release
-
Statement type: