Metadata-Version: 2.4
Name: kurmann-schnittprojekt-leser
Version: 0.5.0
Summary: Read-only-Bibliothek zur Introspektion von Videoschnitt-Projekten (FCPXML, iMovieMobile).
Author: Patrick Kurmann
License: MIT License
        
        Copyright (c) 2026 Patrick Kurmann
        
        Permission is hereby granted, free of charge, to any person obtaining a copy
        of this software and associated documentation files (the "Software"), to deal
        in the Software without restriction, including without limitation the rights
        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:
        
        The above copyright notice and this permission notice shall be included in all
        copies or substantial portions of the Software.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        SOFTWARE.
        
Project-URL: Homepage, https://github.com/kurmann/schnittprojekt-leser
Project-URL: Repository, https://github.com/kurmann/schnittprojekt-leser
Project-URL: Changelog, https://github.com/kurmann/schnittprojekt-leser/blob/main/CHANGELOG.md
Project-URL: Issues, https://github.com/kurmann/schnittprojekt-leser/issues
Keywords: video,fcpxml,luma-fusion,final-cut-pro,metadata
Classifier: Development Status :: 3 - Alpha
Classifier: Environment :: Console
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: MacOS
Classifier: Operating System :: POSIX :: Linux
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Multimedia :: Video
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: typer>=0.12
Requires-Dist: rich>=13
Requires-Dist: lxml>=5.0
Requires-Dist: kurmann-medien-leser<0.3,>=0.2
Provides-Extra: dev
Requires-Dist: pytest>=8; extra == "dev"
Requires-Dist: ruff>=0.5; extra == "dev"
Dynamic: license-file

# kurmann-schnittprojekt-leser

Read-only-Bibliothek zur Introspektion von Videoschnitt-Projekten. Liest FCPXML
(Final Cut Pro und LumaFusion) sowie **iMovieMobile-ZIP-Bundles** (iOS-iMovie)
und beantwortet strukturierte Fragen zum Projekt – primär: **Aufnahmedatum des
ersten Clips** für die automatische Datei-Naming- und Text-Overlay-Vorbereitung
im [`familienfilm-manager`](https://github.com/kurmann/familienfilm-manager).

> **Designprinzip:** Der Leser schreibt **niemals** in Schnittprojekte. Er ist
> ausschliesslich Beobachter.

## Voraussetzungen

- **Python ≥ 3.11** (nutzt `StrEnum`, `tomllib`, `zoneinfo` aus der Standardlib).
- **[exiftool](https://exiftool.org/)** – Pflicht für **FCPXML**-Projekte.
  Liest Aufnahmedaten aus den referenzierten Quelldateien (MOV/MP4/HEIC/JPG)
  via exiftool-Subprozess.
  - macOS: `brew install exiftool`
  - Linux: `sudo apt install libimage-exiftool-perl`
  - Für **iMovieMobile**-Projekte **nicht** nötig – das Datum lebt direkt
    im `iMovieMobileProject.plist`.
- **[rclone](https://rclone.org/)** – optional. Nur nötig, wenn
  `RuntimeOptions.source_search_roots` rclone-Remotes enthält (z. B.
  `lyssach-nas:/Videoschnitt/...`). Auch nur für FCPXML-Pfad relevant.

## Installation

```bash
uv pip install kurmann-schnittprojekt-leser
# oder klassisch
pip install kurmann-schnittprojekt-leser
```

Lokale Entwicklung (Repo geklont):

```bash
uv venv
uv pip install -e ".[dev]"
uv run pytest
```

## Verwendung

### CLI

Ein Subcommand unter `inspect` (seit v0.5.0):

```bash
schnittprojekt-leser inspect summary <pfad> [--format text|json] [--include-clips] [--timezone TZ]
```

**Beispiel: Projekt-Übersicht (Text):**

```bash
$ schnittprojekt-leser inspect summary "Mein Schnitt.fcpxmld" --timezone Europe/Zurich
Projekt: Mein Schnitt
Format: fcpxml_bundle (Schema 1.8)
Auflösung: 3840×2160
Farbraum: 9-16-9 (Rec. 2020 PQ)
Audio: stereo
Timeline-Dauer: 164.1s
Clips primär: 4
Clips total (inkl. sekundärer Spuren): 5
Projektdatei mtime: 2026-04-06T21:32:08+02:00

Aufnahme:
  Datum: 2026-03-29
  Zeitstempel: 2026-03-29T08:20:02+02:00
  Quelle: exif:Keys:CreationDate
  Konfidenz: high
```

**Beispiel: Vollständiges Result als JSON (für `familienfilm-manager`):**

```bash
$ schnittprojekt-leser inspect summary "Mein Schnitt.fcpxmld" \
    --timezone Europe/Zurich --format json
{
  "success": true,
  "project_name": "Mein Schnitt",
  "project_format": "fcpxml_bundle",
  "schema_version": "1.8",
  "timeline_duration_seconds": 164.1,
  "clip_count_primary": 4,
  "clip_count_total": 5,
  "video_resolution": [3840, 2160],
  "color_space": "9-16-9 (Rec. 2020 PQ)",
  "audio_layout": "stereo",
  "project_mtime_iso": "2026-04-06T21:32:08+02:00",
  "recording": {
    "first_clip_date": "2026-03-29",
    "first_clip_datetime": "2026-03-29T08:20:02+02:00",
    "first_clip_source_label": "exif:Keys:CreationDate",
    "first_clip_confidence": "high",
    "first_clip_resolved_source_file": "/.../A001_03290820_D498.mov"
  },
  "clips": null,
  "warnings": []
}
```

**Skript-Pipelines** für nur das Datum: `jq` auf den `recording`-Block.

```bash
$ schnittprojekt-leser inspect summary "Mein Schnitt.fcpxmld" --format json \
    | jq -r '.recording.first_clip_date'
2026-03-29
```

**Beispiel: Clip-Inventar einbeziehen:**

```bash
$ schnittprojekt-leser inspect summary "Mein Schnitt.fcpxmld" --include-clips --format json \
    | jq '.clips | length, .clips[0]'
4
{
  "spine_index": 0,
  "asset_id": "a1",
  "clip_name": "A001_03290820_D498",
  "relative_path": "A001_03290820_D498.mov",
  "is_video": true,
  "resolution_status": "resolved",
  "resolved_uri": "/.../A001_03290820_D498.mov",
  "media_metadata": {
    "recorded_at": "2026-03-29T08:20:02+02:00",
    "recorded_at_source": "exif:Keys:CreationDate",
    "source_app": "iPhone Camera",
    "video": { "codec": "HEVC", "resolution": "3840x2160" },
    "camera": { "make": "Apple", "model": "iPhone 15 Pro Max" }
  }
}
```

Siehe Sektion **Scope von `include_clips`** weiter unten — heute nur Video-Originalmedien.

**Beispiel: Quelldateien auf einem rclone-Remote suchen lassen:**

```bash
schnittprojekt-leser inspect summary "Mein Schnitt.fcpxml" \
    --source-root "lyssach-nas:/Videoschnitt/Luma Fusion-Export" \
    --source-depth 2 \
    --timezone Europe/Zurich
```

**stdout/stderr-Disziplin:**

- **stdout** – nur Ergebnisdaten (Datum, JSON, Übersicht). Pipeline-fähig.
- **stderr** – Events, Warnungen, Fehlermeldung, Diagnose; rohen exiftool/rclone-Output
  nur mit `--verbose`.
- `--verbose` ändert nie stdout.

**Exit-Codes:** `0` Erfolg, `1` Laufzeitfehler, `2` Argument-/Config-Fehler.

### Als Bibliothek

```python
from pathlib import Path

from schnittprojekt_leser.api import read_project_summary
from schnittprojekt_leser.models import (
    ReadProjectSummaryRequest,
    RuntimeOptions,
    RcloneSource,
)

# 1. Projekt-Übersicht inkl. Recording-Info (Default: ohne Clip-Inventar)
result = read_project_summary(
    ReadProjectSummaryRequest(
        project_path=Path("Mein Schnitt.fcpxmld"),
        timezone_assumption="Europe/Zurich",
    ),
    RuntimeOptions(
        source_search_roots=[
            RcloneSource(remote="lyssach-nas", path="/Videoschnitt/Luma Fusion-Export"),
            Path("/Volumes/Aufnahme-Archiv"),
        ],
        source_search_max_depth=2,
    ),
)
if result.success:
    print(result.project_name, result.video_resolution)
    print(result.recording.first_clip_date, result.recording.first_clip_confidence)
else:
    print("Fehler:", result.error_message)

# 2. Mit vollem Clip-Inventar (für Archivar-/Indexierungs-Use-Cases)
result = read_project_summary(
    ReadProjectSummaryRequest(
        project_path=Path("Mein Schnitt.fcpxmld"),
        include_clips=True,
    ),
    RuntimeOptions(),
)
for clip in result.clips or []:
    if clip.media_metadata:
        print(clip.relative_path, clip.media_metadata.recorded_at,
              clip.media_metadata.source_app)
```

## Konfiguration

`RuntimeOptions` enthält ausschliesslich technische Parameter (Werkzeug-Pfade,
Timeouts, Suchverzeichnisse). Fachliche Parameter wie `project_path` oder
`timezone_assumption` gehören in den jeweiligen `Request`.

| Feld | Default | Bedeutung |
|---|---|---|
| `exiftool_path` | `"exiftool"` | Pfad zum exiftool-Binary (PATH-Auflösung). |
| `exiftool_timeout_seconds` | `30.0` | Timeout pro exiftool-Aufruf. |
| `rclone_path` | `"rclone"` | Pfad zum rclone-Binary (PATH-Auflösung). Nur für rclone-Quellen. |
| `rclone_timeout_seconds` | `60.0` | Timeout pro rclone-Aufruf. |
| `source_search_roots` | `[]` | Zusätzliche Suchverzeichnisse für Quelldateien (`Path` lokal oder `RcloneSource`). |
| `source_search_max_depth` | `2` | Rekursionstiefe pro Search-Root. 0 = nur Wurzel, 1 = +eine Ebene, etc. |
| `temp_dir` | `None` | Temp-Verzeichnis für rclone-Downloads. `None` = System-Temp. |

## Datenebenen-Modell

Jede Antwort sitzt auf genau einer dieser vier Ebenen oder ist eine bewusste
Komposition daraus:

1. **Projekt** – Name, Schema-Version, Auflösung, Audio-Layout, Timeline-Dauer,
   Anzahl Clips, Projektdatei-mtime.
2. **Timeline** – geordnete Spine-Clip-Sequenz, Lane-Sub-Spines.
3. **Asset (Resource)** – Eintrag aus dem `<resources>`-Pool: `src`-Verweis,
   Format, Audio-Charakteristik.
4. **Quelldatei** – die tatsächliche `.mov`/`.heic`/`.jpg`. Hier wohnen
   QuickTime/EXIF-Metadaten, GPS, Filesystem-mtime/birthtime.

`read_project_summary` operiert standardmässig auf Projekt + Timeline + ein Asset-Lookup für die Recording-Info (Komposition Timeline → Asset → Quelldatei für den ersten Clip). Mit `include_clips=True` zusätzlich der volle Pass durch alle Top-Level-Asset-Clips (siehe **Scope von `include_clips`** unten).

## Hierarchie der Datums-Quellen

Die Datums-Quellen unterscheiden sich pro Format. Bei **iMovieMobile** lebt
das Aufnahmedatum direkt im Projekt-Plist (`editList[i].creationDate`) – ein
Quelldatei-Lesepass ist nicht nötig. Bei **FCPXML** muss die Quelldatei mit
exiftool gelesen werden, weil weder Final Cut Pro noch LumaFusion das
Aufnahmedatum im XML mitspeichern.

### iMovieMobile

| Quelle | Source-Label | Konfidenz |
|---|---|---|
| `editInfo.editList[i].creationDate` (erster Clip mit `clipType=1`) | `imovie:editList:creationDate` | HIGH |

Bei Abweichung zwischen `editList[i].creationDate` und
`editList[i].movie.creationDate` gewinnt der Clip-Wert; die Differenz steht
als Warnung im Result.

### FCPXML

Spiegelt das Vorbild aus
[`kamera-einleser`](https://github.com/kurmann/kamera-einleser) (siehe Hinweis
zur Code-Spiegelung am Ende). Erste Quelle mit nicht-leerem Wert gewinnt:

| Position | Quelle | Source-Label | Konfidenz |
|---|---|---|---|
| 1 | `Keys:CreationDate` (Apple Item-List, mit TZ) | `exif:Keys:CreationDate` | HIGH |
| 2 | `QuickTime:CreationDate` (UDTA, mit TZ) | `exif:CreationDate` | HIGH |
| 3 | `Blackmagic-design:CameraDateRecorded` | `exif:Blackmagic-design:CameraDateRecorded` | HIGH |
| 4 | `DateTimeOriginal` (klassisches EXIF) | `exif:DateTimeOriginal` | HIGH |
| 5 | `CreateDate` (QuickTime MVHD, oft UTC ohne TZ) | `exif:CreateDate` (ggf. `+tz_from_mtime`) | MEDIUM |
| 6 | Filesystem `st_birthtime` (macOS APFS/HFS+) | `file_birthtime` | MEDIUM |
| 7 | Filesystem `st_mtime` | `file_mtime` | LOW |
| Notnagel | Projektdatei-mtime, wenn Quelldatei nicht auflösbar | `project_fallback` | LOW |

**Konflikt-Strategie:** die hierarchisch höchste Quelle gewinnt. Abweichende
Sekundär-Werte werden als `warnings` mitgegeben. `confidence` und
`source_label` machen jederzeit nachvollziehbar, woher der Wert kam.

### Final-Cut-Camera-Sonderfall

Apples Final Cut Camera (iOS 18+) schreibt `CreateDate` in **UTC ohne TZ-Marker**.
Wird der Wert als-ist angezeigt, missverstehen Konsumenten ihn als lokale Zeit
(1–2 Stunden falsch). Die Bibliothek erkennt diesen Fall: wenn
`exif_create_date` ohne TZ-Marker ist und `file_mtime` eine TZ trägt, wird die
TZ inferiert und auf den UTC-Wert angewendet. Das Source-Label bekommt das
Suffix `+tz_from_mtime` – Konsumenten sehen sofort, dass die TZ abgeleitet ist.

## Timezone-Verhalten

`request.timezone_assumption` ist die *intendierte lokale Zeitzone des
Konsumenten* (IANA-Name, z. B. `"Europe/Zurich"`). Sie wirkt **konsistent
auf beide Felder** `iso_datetime` und `iso_date`: ist sie gesetzt, werden
Stempel in diese Zone konvertiert; ist sie `None`, bleiben Stempel in
ihrer Eigen-TZ (bzw. UTC bei naiven Werten).

| Stempel | Assumption | `iso_datetime` | `iso_date` |
|---|---|---|---|
| Aware (z. B. `…+02:00`) | gesetzt | konvertiert in Assumption-TZ | Tag in Assumption-TZ |
| Aware | `None` | unverändert (Eigen-TZ des Stempels) | Tag in Eigen-TZ des Stempels |
| Naive (kein Offset) | gesetzt | als Assumption-Lokalzeit interpretiert | Tag in Assumption-TZ |
| Naive | `None` | als UTC interpretiert | UTC-Tag |

Für Familienfilme zählt der Wallclock-Tag. Beispiel iMovie-Plist:
`2026-04-23T15:52:21Z` (UTC) wird mit `--timezone Europe/Zurich` zu
`2026-04-23T17:52:21+02:00` (CEST) – passt zum macOS-Finder, der die
gleiche Wallclock-Zeit anzeigt. Ohne Assumption führt UTC-`Z`-Stamps in
der Schweiz (CEST) bei späten Abend-Aufnahmen zu Datumsverschiebungen
um einen Tag. Für `familienfilm-manager` gilt: immer
`timezone_assumption="Europe/Zurich"` setzen.

## „Erster Clip" – formatkonkret

- **FCPXML:** erster `<asset-clip>` direkt unter `<spine>` (nicht in
  `lane`-Sub-Spines), der ein Asset mit `is_real_source=True` referenziert.
  Generator/Title-Refs (z. B. `*.titleData`) werden übersprungen.
- **Edge Case:** leere Spine oder nur Generators → `success=False`,
  Fehlermeldung „Schnittprojekt enthält keinen primären Video-Clip mit Quelldatei".
- **Edge Case:** erste echte Quelldatei nur auf `lane > 0` (sekundäre Spur) →
  Phase 1 sucht **nicht** dort. Konvention: Familienfilm-Schnitte folgen der
  Spine-Konvention; Sonderfälle sollen auffällig fehlschlagen statt still zu raten.

## rclone-Quellen und Umlaute

Quellverzeichnisse können lokal oder rclone-Remotes sein:

```python
from schnittprojekt_leser.models import RcloneSource, parse_source_spec
from pathlib import Path

# Direkt:
RcloneSource(remote="lyssach-nas", path="/Videoschnitt/Luma Fusion-Export")

# Aus Config-String (gleiches Format wie kamera-einleser):
parse_source_spec("lyssach-nas:/Videoschnitt/Luma Fusion-Export")  # → RcloneSource
parse_source_spec("/Volumes/Card")                                  # → Path
parse_source_spec("~/Schnittprojekte")                              # → Path (expandiert)
```

**NFC-Normalisierung:** macOS speichert Dateinamen typisch als NFD
(`ä` als `a` + Combining Diaeresis), Linux/SMB-NAS als NFC (precomposed `ä`).
Beim Filename-Match in `source_search_roots` werden Namen via
`unicodedata.normalize("NFC", name)` normalisiert, damit der Match auch bei
gemischten Setups (macOS-Schnitt + Synology-NAS) funktioniert.

## Unterstützte Formate

| Format | Status | Anmerkung |
|---|---|---|
| `*.fcpxml` (Single-File) | unterstützt | Final Cut Pro und LumaFusion, Schema 1.x |
| `*.fcpxmld/` (Bundle) | unterstützt | FCP-Konvention; Quelldateien direkt im Bundle |
| `*.iMovieMobile` (ZIP) | unterstützt | iOS-iMovie-Export; Datum, Auflösung, GPS, Titel direkt aus Plist |
| DaVinci, Premiere, Avid, native FCP-Library | nicht geplant | – |

## Architektur

Schichten gemäss [`kurmann-python-api`](https://github.com/kurmann/atelier/tree/main/skills/kurmann-python-api)-Skill.
Importrichtung strikt: `cli` → `api` → `core` ← `services`. Services werden
über Funktionsargumente injiziert, nicht direkt aus `core` importiert. Die
Core-Funktionen `read_first_clip_date` und `read_project_summary` dispatchen
nach erkanntem Format auf den jeweiligen Lese-Pfad.

```
src/schnittprojekt_leser/
├── api/                       # Public-Fassaden, fangen Exceptions zu error_message
├── cli/                       # Typer-Adapter, stdout/stderr-Disziplin
├── core/
│   ├── format_detection.py    # detect_format(path) → ProjectFormat
│   ├── recorded_at.py         # pick_recorded_at + Hierarchie + FCC-Fallback (FCPXML-Pfad)
│   ├── first_clip_date.py     # Komposition: Dispatch FCPXML / iMovieMobile
│   └── project_summary.py     # Komposition: Dispatch FCPXML / iMovieMobile
├── services/
│   ├── fcpxml_reader.py       # lxml → FcpxmlProject
│   ├── imovie_mobile_reader.py# zipfile + plistlib → IMovieMobileProject
│   ├── exiftool_runner.py     # subprocess → DateSources
│   ├── rclone_source.py       # rclone-Subprocess + NFC
│   └── source_resolver.py     # Lokale + rclone-Suche, Temp-Cleanup
└── models/                    # Datenmodelle, Enums, Errors
```

**Drei-Kanal-Events:**

- **Result** (Pflicht) – fachliches Endergebnis, Exceptions als `error_message`.
- **`on_event`** – strukturierte Stages (`SchnittprojektLeserEvent` mit
  Englisch-Stage-IDs gemäss
  [ADR-0009](https://github.com/kurmann/atelier/blob/main/decisions/0009-stage-ids-englisch.md),
  Deutsch-`message`).
- **`on_output`** – roher Subprozess-Text (exiftool/rclone). Nur ans Terminal
  bei `--verbose`.

## Scope von `include_clips` (v0.5.0+)

`read_project_summary(..., include_clips=True)` liefert das Inventar **ausschliesslich Video-Originalmedien** des Schnittprojekts. Konkret:

- **FCPXML**: Top-Level-Spine-Asset-Clips mit `is_real_source=True` (Asset hat eine echte `src`-Quelldatei, kein Title/Generator).
- **iMovieMobile**: `editList`-Einträge mit `clipType=1` (Video). Hinweis: die Quelldateien liegen im selben ZIP-Bundle wie das Plist; in v0.5.0 wird keine medien-leser-Auflösung für Bundle-interne Pfade gemacht — `resolution_status="skipped"`, `media_metadata=None`. Das Aufnahmedatum kommt für iMovieMobile aus dem Plist selbst (Recording-Info), das Clip-Inventar dient hier als Pfad-Referenz.

**Bewusst nicht enthalten** (Stand v0.5.0):

- Audio-Subspuren oder separate Audio-Synchronspuren
- Foto-Clips (`clipType=5` in iMovieMobile)
- Transitions (`clipType=3`)
- Sekundäre Tracks / Picture-in-Picture
- Generators, Titles, Effekte ohne Asset-Backing

**Hintergrund**: die Detail-Metadaten pro Clip werden vom [`kurmann-medien-leser`](https://github.com/kurmann/medien-leser) gelesen, der heute auf Video-Metadaten (Codec, Auflösung, GPS, Kamera-Modell, Production) ausgerichtet ist. Audio-spezifische Schemas (id3, Tonmeister-Tags) sind dort noch nicht implementiert. Sobald der medien-leser Audio-Support hat, wird `include_audio_tracks=True` als additives Flag ergänzt — kein Schema-Bruch nötig.

**Use-Case für Konsumenten**: das Clip-Inventar erlaubt Cross-References zwischen Schnittprojekten und Originalmedien-Volumes (z.B. ISO-Manifests aus [`kamera-einleser`](https://github.com/kurmann/kamera-einleser)). Fragen wie „welche Clips aus ISO XYZ wurden in Projekt ABC verwendet?" sind mit beiden Manifesten gemeinsam beantwortbar. Der geplante [`schnittprojekt-archivar`](https://github.com/kurmann/atelier) wird `include_clips=True` für die `clips.jsonl`-Persistierung pro archiviertem Projekt nutzen.

## Geteilte Auslese-Logik via `kurmann-medien-leser` (ab 0.4.0)

Seit 0.4.0 delegiert die Datums-Extraktion an
[`kurmann-medien-leser`](https://github.com/kurmann/medien-leser) — eine
geteilte Library für Medien-Metadaten via exiftool, mit Best-Guess-
Aufnahmedatum und Provenance. Was hier im Repo bleibt:

- **Konfidenz-Bewertung** (`ClipDateConfidence.HIGH/MEDIUM/LOW/UNKNOWN`) —
  schnittprojekt-spezifisches Mapping vom medien-leser-`source_label` zur
  Vier-Stufen-Skala. Der familienfilm-manager nutzt das, um nur
  vertrauenswürdige Lookups (HIGH/MEDIUM) zu akzeptieren.
- **Schnittprojekt-Sentinels** (`SOURCE_PROJECT_FALLBACK`,
  `SOURCE_IMOVIE_EDITLIST_CREATION_DATE`) — Datums-Quellen auf Projekt-
  Ebene, die kein exiftool brauchen.
- **Signatur-Anpassung + Exception-Mapping** in `services/exiftool_runner.py`:
  Konsumenten unserer Library übergeben `RuntimeOptions` (mit
  `exiftool_path` und `exiftool_timeout_seconds`); medien-leser-API nimmt
  Keyword-Args. Exceptions werden auf unsere `SchnittprojektLeserError`-
  Hierarchie gemappt.

Andere Konsumenten der medien-leser-Library: `kamera-einleser` (für
ISO-Manifests), geplant `schnittprojekt-archivar` (für Clip-Inventar in
archivierten Schnittprojekten).

## Änderungsverlauf

Die letzten drei Versionen:

- **0.5.0** (2026-05-21) – API-Konsolidierung: `read_first_clip_date` als
  Top-Level-Operation entfernt, Recording-Info ist Teil von `read_project_summary`
  als `ProjectRecordingInfo`. Neu: `include_clips=True` liefert das vollständige
  Inventar der referenzierten Video-Originalmedien mit `MediaFileMetadata` pro
  Clip. CLI-Subcommand `inspect first-clip-date` entfernt; `inspect summary
  --include-clips` ist der einzige Weg. Breaking Change auf API-Ebene; Konsumenten
  (z.B. familienfilm-manager) benötigen kleinen Patch.
- **0.4.0** (2026-05-14) – Migration auf
  [`kurmann-medien-leser>=0.2`](https://github.com/kurmann/medien-leser).
  Public API unverändert; `services/exiftool_runner.py` und
  `core/recorded_at.py` auf dünne Wrapper reduziert
  (Signatur-Anpassung + Exception-Mapping + Konfidenz-Bewertung).
  `DateSources` wird aus medien-leser re-exportiert.
- **0.3.0** (2026-05-08) – iMovieMobile-Support (`*.iMovieMobile`-ZIP-Bundles
  von iOS-iMovie). Aufnahmedatum direkt aus dem Plist (`editList[i].creationDate`,
  HIGH-Konfidenz), kein exiftool nötig. Format-Detection erweitert,
  Core-Dispatch FCPXML/iMovieMobile, neue Modelle `IMovieMobileProject`/
  `IMovieClip`/`IMovieMapLocation`. 22 zusätzliche Tests.
- **0.2.0** (2026-05-08) – Erste Implementierung Phase 1: FCPXML-Reader (LumaFusion + FCP),
  exiftool-basierte Datums-Auslesung mit 7-stufiger Hierarchie, FCC-TZ-Fallback,
  rclone-Quellen, NFC-Normalisierung, CLI mit `inspect first-clip-date` und
  `inspect summary`.

Vollständige Historie: [CHANGELOG.md](CHANGELOG.md).

## Konsumenten

- **[`familienfilm-manager`](https://github.com/kurmann/familienfilm-manager)** –
  Hauptmotivation. Holt Aufnahmedatum für Datei-Naming und Text-Overlay-Vorbereitung.
- Plausible spätere: [`fcplib-manager`](https://github.com/kurmann/fcplib-manager),
  [`mediaset-creator`](https://github.com/kurmann/mediaset-creator).

## Lizenz

MIT. Siehe [LICENSE](LICENSE).

---

`Specs.md` im Repo ist die ursprüngliche Spec als Starthilfe und wird
zukünftig archiviert; die kanonische Doku der Fachlogik ist diese README.
