Metadata-Version: 2.4
Name: kurmann-kamera-einleser
Version: 1.10.0
Summary: Einfacher & solider Medien-Importeur. Archiviert Videoaufnahmen von SSD/iPhone/NAS in ISO-Dateien.
Project-URL: Homepage, https://github.com/kurmann/kamera-einleser
Project-URL: Repository, https://github.com/kurmann/kamera-einleser
Project-URL: Changelog, https://github.com/kurmann/kamera-einleser/blob/main/CHANGELOG.md
Project-URL: Issues, https://github.com/kurmann/kamera-einleser/issues
Author: Patrick Kurmann
License-Expression: MIT
License-File: LICENSE
Keywords: archive,backup,import,iso,media,nas,rclone,synology
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: MacOS
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: System :: Archiving :: Backup
Requires-Python: >=3.10
Requires-Dist: inquirerpy>=0.3.4
Requires-Dist: pyyaml>=6.0
Requires-Dist: rich>=13.0.0
Requires-Dist: tomli-w>=1.0
Requires-Dist: tomli>=2.0; python_version < '3.11'
Requires-Dist: typer>=0.9.0
Provides-Extra: dev
Requires-Dist: pytest-cov>=4.0.0; extra == 'dev'
Requires-Dist: pytest>=7.0.0; extra == 'dev'
Requires-Dist: ruff>=0.1.0; extra == 'dev'
Description-Content-Type: text/markdown

# 🎬 Kamera-Einleser

Einfacher & solider Medien-Importeur. Archiviert Videoaufnahmen von SSD/iPhone in ISO-Dateien (organisiert nach Tag) und synchronisiert sie auf Cloud-Speicher. Stellt Archive bei Bedarf wieder her.

## ✨ Features

- **💿 ISO-Archivierung**: Erstellt zuverlässige ISO-Dateien mit UDF-Format (hdiutil makehybrid)
- **📅 Tägliche Gruppierung**: Dateien werden automatisch nach Änderungsdatum in tägliche Archive gruppiert
- **🔄 Idempotente Archivierung**: Prüft vor Erstellung, ob Dateien bereits im ISO vorhanden sind
- **📋 ditto-Staging**: Metadaten-erhaltendes Kopieren via macOS `ditto` ins Staging
- **☁️ rclone-Integration**: Synchronisiert ISOs zu beliebig vielen Cloud-Zielen (NAS, Mega.nz, Google Drive, etc.)
- **🔄 Idempotente Uploads**: rclone läuft auch ohne neue ISOs, um fehlgeschlagene Uploads zu wiederholen
- **🔗 SMB-Mount**: Mounte Archive direkt via SMB ohne Download (schneller, schreibgeschützter Zugriff)
- **📥 Archiv-Wiederherstellung**: Lädt ISO 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

## 📋 Voraussetzungen

- macOS (für ISO-Erstellung via hdiutil)
- Python 3.10 oder höher
- [rclone](https://rclone.org/) (für Cloud-Synchronisation)

## 🚀 Installation

### Empfohlen: Mit uv

```bash
# 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):

```bash
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):

```bash
pip install git+https://github.com/kurmann/kamera-einleser.git
```

### rclone installieren

```bash
# macOS
brew install rclone
```

## 🎯 Schnellstart

### 1. Einstellungen konfigurieren

```bash
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

```bash
# 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:

```bash
# 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/folders` auf 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:

1. **Split-Modus**: beide Volumes mounted, je ≥ `Quelle × 1.5` frei →
   Pull→Primary, ISO→Fallback.
2. **Single-Volume-Modus**: ein Volume mit voller Pipeline-Kapazität
   (Quelle × 3.5 bei rclone, × 2.5 bei lokal).
3. **Fallback bei lokaler Quelle**: konfiguriertes Working-Dir oder System-Temp.
4. **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 ISO-Cache (ab 1.8.0)

Die erstellten ISOs 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 nur dazu, dass
Folge-Läufe schnell bleiben (Fall A: ISO-Mount für Subset-Check) und dass
Teil-Uploads wiederholt werden können, ohne die Pipeline neu zu bauen.

**Schutz gegen Festplatten-Überlauf**: ab 1.8.0 gibt es eine obere Grenze
(Default **500 GB**). Ist die überschritten, werden am Ende jedes erfolgreichen
Archive-Laufs die ältesten ISOs entfernt, bis der Cache wieder unter dem Limit
liegt. Nur Dateien, die im Checksum-Index stehen (= verifiziert auf mindestens
einem Remote-Target), werden gelöscht — nicht-indizierte ISOs bleiben
unangetastet.

```bash
# Limit anpassen (0 = deaktiviert)
kamera-einleser config set iso_cache_max_bytes 1000000000000   # 1 TB

# Manuell aufräumen
kamera-einleser cache list
kamera-einleser cache prune --max-size-gb 500 --dry-run
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.

```bash
# 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.

#### Index-Prefilter: wir pullen nur, was noch nicht archiviert ist

Ab **1.5.0** zieht der Tool beim rclone-Pull **nicht jedes Mal alle Dateien**
vom Netzlaufwerk. Stattdessen:

1. Der lokale Checksum-Index (`checksum_index.csv`) wird gelesen — er enthält
   alle Dateinamen, die bereits in einer ISO auf einem Remote-Target liegen.
2. Vor dem Pull wird eine temporäre rclone-Filter-Datei erzeugt, die genau
   diese Dateinamen als Exclude-Patterns enthält.
3. rclone bekommt die Filter-Datei via `--filter-from` und ü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
`check_files_in_existing_iso`-Prüfung im Staging-Schritt fängt Rest-
Kollisionen ab, falls doch zwei Dateien mit identischem Namen
unterschiedlichen Inhalt hätten.

**Prefilter umgehen (Debugging/Migration):**

```bash
kamera-einleser archive --full-pull
```

Zieht alle Dateien der Remote-Quelle erneut, unabhängig vom Index. Nützlich,
wenn man vermutet dass der Index inkonsistent ist (für operator-getriggerte
Reparatur siehe auch `kamera-einleser index rebuild`).

#### Speicherort des Checksum-Index (ab 1.6.0)

Der `checksum_index.csv` liegt unter **`$XDG_DATA_HOME/kamera-einleser/checksum_index.csv`**
(Fallback: `~/.local/share/kamera-einleser/checksum_index.csv`). Bewusst getrennt
vom Arbeitsverzeichnis: der Index ist eine Datenbank, kein Scratch-File.

```bash
kamera-einleser config get checksum_index_path   # leer = XDG-Default
```

**Auto-Migration:** Wer von ≤ 1.5.0 kommt, dessen `checksum_index.csv` lag im
Working-Dir (z.B. `~/Movies/Kamera-Einleser/`). Beim ersten Aufruf eines 1.6.0-
Kommandos wird die Datei einmalig an den neuen XDG-Pfad verschoben (`shutil.move`).
Passiert automatisch, keine Aktion nötig.

**Override:** Wer einen anderen Ort bevorzugt:
```bash
kamera-einleser config set checksum_index_path /Volumes/DataSSD/index.csv
```

#### Startup-Guard: Schutz vor "Index leer, Remote voll" (ab 1.6.0)

Wenn beim `archive`-Lauf der lokale Index **leer** ist, aber auf den konfigurierten
rclone-Targets ISOs **existieren**, bricht der Tool mit klarer Fehlermeldung ab.
Hintergrund: in diesem Zustand würde `archive` alle Tage neu aufbauen, Base-Namen
wählen und am `rclone --immutable`-Upload scheitern (siehe [Safety](#safety)).
Statt Stunden ins Leere zu arbeiten → Stopp und Hinweis:

```
❌ Abbruch: lokaler Checksum-Index ist leer, aber auf den konfigurierten
Targets existieren 113 ISO(s).

Bitte zuerst Index aus den Remote-ISOs neu aufbauen:
    kamera-einleser index rebuild
```

Übersteuern (nur bewusst, z.B. nach kompletter Remote-Löschung):
```bash
kamera-einleser archive --force
```

Mit `--no-upload` ist der Guard ohnehin deaktiviert (kein Netz-Zugriff).

#### Index lebt auch auf dem NAS — `index push` / `index pull` (ab 1.9.0)

Bislang lebte der `checksum_index.csv` ausschliesslich lokal. Auf einem
**Zweit-Rechner** musste der Index aus den ISOs neu aufgebaut werden — und
dabei gingen alle SHA-256-Werte verloren, weil ein Rebuild aus den Datei-
Listen der ISOs keine Hashes enthält.

Ab **1.9.0** wird der Index zusätzlich zur lokalen Kopie auch auf dem
**ersten konfigurierten rclone-Target** gespeichert (Konvention:
erstes Target = lokales NAS). Genauer Pfad:
`<target_path>/checksum_index.csv` direkt im Root, neben den Year-Ordnern.
Beispiel:

```
lyssach-nas:/Originalmedien/checksum_index.csv
lyssach-nas:/Originalmedien/2025/Originalmedien-2025-12-31.iso
lyssach-nas:/Originalmedien/2026/Originalmedien-2026-01-01.iso
```

**Workflow:**

```bash
# Erst-Rechner — einmalig nach dem Update auf 1.9.0:
kamera-einleser index push        # Migration: lokale CSV → NAS

# Ab jetzt: jeder erfolgreiche `archive`-Lauf pusht automatisch.
kamera-einleser archive

# Zweit-Rechner (z.B. Mac mini) — frisch eingerichtet:
kamera-einleser index pull        # NAS → lokale CSV, mit allen SHAs
kamera-einleser archive           # läuft mit vollem Index
```

**Vorteile:**

- **SHA-256 bleiben erhalten**, auch wenn der lokale Index auf einem Rechner
  verloren geht. Subset-Check Fall A funktioniert nach `index pull` ohne
  Einschränkung.
- **Sekunden statt Stunden** auf Zweit-Rechnern: `index pull` lädt eine
  CSV von wenigen KB, kein ISO-Download, kein hdiutil-Mount.
- **Kein FUSE, kein zusätzliches Setup** — `rclone` ist eh konfiguriert.

**Sync-Strategie:** Replace (last-writer-wins). Konflikte sind in der Praxis
unwahrscheinlich, weil Kamera-Einleser typisch nicht gleichzeitig auf
mehreren Rechnern läuft. Sollte es doch einmal vorkommen, schützen
`--immutable` auf den ISOs selbst und der Append-Only-Charakter des Index
(Einträge werden nie modifiziert) vor echtem Datenverlust.

**Disaster-Recovery:** wenn der Remote-Index korrupt oder gelöscht ist,
bleibt `kamera-einleser index rebuild` als Fallback. Der Befehl lädt jede
ISO sequentiell vom Target, indexiert mit SHA-256, löscht und macht mit der
nächsten weiter. Stundenlang, aber funktioniert ohne weitere Voraussetzungen.

### 3. Archive mounten (ohne Download)

Mounte Archive direkt via SMB für schnellen, schreibgeschützten Zugriff:

```bash
# 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:

```bash
kamera-einleser settings  # → rclone-Ziele bearbeiten
```

In der Config-Datei (`~/.config/kamera-einleser/config.yaml`):
```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

```bash
# 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

```bash
# 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ü.

```bash
kamera-einleser settings
```

### `config`
Zeigt die aktuelle Konfiguration an.

```bash
kamera-einleser config
```

### `archive`
Erstellt tägliche ISO-Archive und synchronisiert sie zu Cloud-Zielen.

```bash
kamera-einleser archive [QUELLVERZEICHNIS] [OPTIONEN]
```

**Optionen:**
- `--no-upload`: Nur Archiv erstellen, nicht synchronisieren
- `--delete-source`: Originaldateien nach erfolgreichem Backup löschen

### `restore`
Stellt ein ISO-Archiv aus Cloud-Speicher wieder her (mit Download).

```bash
# 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).

```bash
# 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.yaml` konfiguriert (siehe "Archive mounten" oben)
- Netzwerkzugriff auf das NAS

### `unmount`
Unmountet gemountete Archive.

```bash
# 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.

```bash
kamera-einleser status
```

### `version`
Zeigt die installierte Version an.

```bash
kamera-einleser --version
```

## ⚙️ Konfiguration

Die Konfigurationsdatei liegt unter:
```
~/.config/kamera-einleser/config.yaml
```

Beispiel-Konfiguration:

```yaml
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 Archivieren
- `rclone_targets`: Liste von rclone-Zielen für Synchronisation
- `archive_directories`: Liste von rclone-Pfaden, die nach Archiven durchsucht werden
- `current_archive_directory`: Das aktuell verwendete Verzeichnis für neue Archive
- `working_directory`: Lokales Verzeichnis für ISO-Dateien (nicht temporär!)
- `restore_directory`: Standard-Verzeichnis für wiederhergestellte ISOs
- `excludes`: 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:

```bash
# Interaktive rclone-Konfiguration
rclone config

# Teste die Verbindung
rclone lsd nas:
```

Weitere Infos: [rclone.org/docs](https://rclone.org/docs/)

## 🎬 Workflow-Beispiele

### Archivierung

1. Kamera-SSD am Mac anschließen
2. `kamera-einleser archive` ausführen
3. Programm analysiert Dateien und gruppiert sie nach Tag (Änderungsdatum)
4. Prüft bestehende ISOs im Arbeitsverzeichnis und auf allen rclone-Zielen
5. Prüft Idempotenz: Überspringt Tage, deren Dateien bereits archiviert sind
6. Erstellt ditto-Staging für neue/geänderte Tage (Metadaten-erhaltend)
7. Erstellt ISO-Dateien mit UDF-Format (z.B. "Originalmedien-2026-01-15.iso")
8. Verifiziert die Integrität mit hdiutil
9. Synchronisiert zu allen konfigurierten Cloud-Zielen (organisiert in Jahresordner)
10. Optional: Löscht Originaldateien nach Bestätigung am Ende

### Wiederherstellung

**Interaktiv:**
1. `kamera-einleser restore` ausführen
2. Optional: Nach Dateinamen suchen
3. Durch Archive blättern (10 pro Seite)
4. Archiv auswählen
5. Bestätigen
6. Programm lädt ISO herunter und mountet es

**Headless (Automatisiert):**
```bash
# In Skript oder Cronjob
kamera-einleser restore Originalmedien-2026-01-15 --restore-dir /restore/path
```

## 🎯 Archivierungs-Konzept

Das Programm verwendet tägliche ISO-Archive:

1. **Datei-Analyse**: Scannt alle Dateien und liest deren Änderungsdatum
2. **Tägliche Gruppierung**: Gruppiert Dateien nach Tag (YYYY-MM-DD)
3. **ISO pro Tag**: 
   - Dateiname: `Originalmedien-YYYY-MM-DD.iso`
   - Volume-Name: `Originalmedien-YYYY-MM-DD`
   - Format: UDF (Universal Disk Format)
4. **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.)
5. **ditto-Staging**:
   - Kopiert Dateien via macOS `ditto` ins temporäre Staging-Verzeichnis
   - Erhält Resource Forks, Extended Attributes und HFS+/APFS-Metadaten
   - Erzeugt ein eigenständiges, vollständiges Archiv
6. **Verzeichnisstruktur**: Unterverzeichnisse werden im ISO übernommen
7. **rclone-Organisation**: ISOs werden in Jahresordner auf Remote-Zielen organisiert
8. **Idempotente Uploads**: rclone läuft auch ohne neue ISOs, um fehlgeschlagene Uploads zu wiederholen

**Beispiel:**
```
Quellverzeichnis/
├── 2026-01-15_Video1.mp4   → Originalmedien-2026-01-15.iso
├── 2026-01-15_Video2.mp4   → Originalmedien-2026-01-15.iso
├── 2026-02-05_Video3.mp4   → Originalmedien-2026-02-05.iso
└── Projekt/
    ├── 2026-01-15_Clip.mp4 → Originalmedien-2026-01-15.iso (Projekt/...)
    └── 2026-02-10_Raw.mov  → Originalmedien-2026-02-10.iso (Projekt/...)
```

**Remote-Struktur:**
```
nas:/Backups/Videos/
└── 2026/
    ├── Originalmedien-2026-01-15.iso
    ├── Originalmedien-2026-02-05.iso
    └── Originalmedien-2026-02-10.iso
```

**Vorteile:**
- ✅ Tägliche Organisation für präzise Übersicht
- ✅ Idempotenz vermeidet Duplikate und unnötige I/O
- ✅ Unveränderliche ISOs: Einmal erstellt, nie überschrieben
- ✅ UDF-Format (universell lesbar, nicht nur macOS)
- ✅ Verzeichnisstruktur bleibt erhalten
- ✅ Natives macOS-Format via hdiutil, mountbar im Finder
- ✅ Effiziente rclone-Synchronisation (nur neue ISOs werden hochgeladen)

## 📝 ISO-Format & Technische Details

### ISO-Erstellung mit hdiutil makehybrid

Das Programm nutzt `hdiutil makehybrid` zur ISO-Erstellung:

- **Format**: UDF (Universal Disk Format)
- **Kompatibilität**: Lesbar auf macOS, Windows, Linux
- **Kommando**: `hdiutil makehybrid -o output.iso source_dir -udf -default-volume-name NAME`
- **Mount**: Automatisch via macOS Finder oder `hdiutil attach`

### 📋 ditto-Staging

1. Erstelle temporäres Staging-Verzeichnis
2. Kopiere Dateien via macOS `ditto` (erhält Metadaten, Resource Forks, Extended Attributes)
3. Erstelle ISO aus dem Staging-Verzeichnis mit `hdiutil makehybrid`
4. Lösche Staging-Verzeichnis

### 🔒 Unveränderlichkeit (Immutability)

ISO-Dateien sind nach der Erstellung unveränderlich. Das wird auf drei Ebenen sichergestellt:

**Lokal (Arbeitsverzeichnis):**
- Bestehende ISOs werden nie überschrieben
- Wenn `Originalmedien-2026-01-15.iso` bereits existiert, wird automatisch `-Teil-2`, `-Teil-3`, etc. angehängt
- Neue Quelldateien für denselben Tag erzeugen eine zusätzliche ISO

**Remote-Abgleich (rclone-Ziele):**
- Vor der ISO-Erstellung werden alle konfigurierten rclone-Ziele auf bestehende ISO-Dateien geprüft
- Wenn ein Ziel bereits eine ISO-Datei mit demselben Namen hat, wird eine neue ISO mit Suffix erstellt (`-Teil-2`, `-Teil-3`, etc.)
- Dies gilt auch wenn die ISO im Arbeitsverzeichnis nicht vorhanden ist (z.B. bei Einlesen an verschiedenen Computern)
- In der Konsole wird ausgegeben, welche Ziele geprüft werden und ob Namenskollisionen gefunden wurden

**Remote (rclone-Ziele):**
- `rclone copyto --immutable` verhindert jedes Überschreiben auf den Zielen
- Identische Dateien werden automatisch übersprungen (kein Fehler)
- Unterschiedliche Dateien mit gleichem Namen erzeugen einen Abbruch — der Anwender muss manuell eingreifen

**Warum?**
- ✅ Schutz vor versehentlichem Datenverlust
- ✅ Dreischichtiger Schutz: Lokal (Suffix) + Remote-Abgleich (Namenskollision) + Remote (rclone --immutable)
- ✅ Inkonsistenzen werden sofort erkannt statt stillschweigend überschrieben
- ✅ Funktioniert auch bei Einlesen an mehreren Computern mit unterschiedlichen Arbeitsverzeichnissen

### 🔄 Idempotenz: Keine Duplikate

**Archivierungs-Idempotenz:**

Vor der ISO-Erstellung für einen Tag prüft das Programm:
1. Existiert bereits ISO für diesen Tag im Arbeitsverzeichnis?
2. Falls ja: Mount ISO readonly, vergleiche alle Dateien (Name + Größe)
3. Falls alle Dateien bereits vorhanden → Tag überspringen
4. Falls neue/geänderte Dateien → Erstelle neue ISO mit Suffix (`-Teil-2`, `-Teil-3`)
5. Zusätzlich: Prüfe alle konfigurierten rclone-Ziele auf bestehende ISO-Dateinamen
6. Falls ein Ziel bereits eine ISO mit demselben Namen hat → Verwende Suffix, auch wenn im Arbeitsverzeichnis keine ISO vorhanden ist

**Upload-Idempotenz:**

Selbst wenn keine neuen ISOs erstellt wurden:
1. Sammle alle existierenden ISOs aus Arbeitsverzeichnis
2. Führe Integritätsprüfung durch
3. Führe rclone-Synchronisation durch

**Warum ist das wichtig?**
- ✅ Wiederholt fehlgeschlagene Uploads automatisch
- ✅ Stellt gelöschte Remote-Dateien wieder her
- ✅ rclone selbst ist idempotent (vergleicht Größe/Checksum)
- ✅ Keine unnötige Arbeit bei bereits archivierten Tagen

## 🤝 Beitragen

Issues und Pull Requests sind willkommen! [GitHub Repository](https://github.com/kurmann/kamera-einleser)

## 📄 Lizenz

MIT License - siehe [LICENSE](LICENSE)

## 👨‍💻 Autor

Patrick Kurmann
