Read-only-Bibliothek zur Introspektion von Videoschnitt-Projekten (FCPXML, iMovieMobile).
Project description
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.
Designprinzip: Der Leser schreibt niemals in Schnittprojekte. Er ist ausschliesslich Beobachter.
Voraussetzungen
- Python ≥ 3.11 (nutzt
StrEnum,tomllib,zoneinfoaus der Standardlib). - exiftool – 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.
- macOS:
- rclone – optional. Nur nötig, wenn
RuntimeOptions.source_search_rootsrclone-Remotes enthält (z. B.lyssach-nas:/Videoschnitt/...). Auch nur für FCPXML-Pfad relevant.
Installation
uv pip install kurmann-schnittprojekt-leser
# oder klassisch
pip install kurmann-schnittprojekt-leser
Lokale Entwicklung (Repo geklont):
uv venv
uv pip install -e ".[dev]"
uv run pytest
Verwendung
CLI
Zwei Subcommands unter inspect:
schnittprojekt-leser inspect first-clip-date <pfad> [--format value|json|iso-datetime]
schnittprojekt-leser inspect summary <pfad> [--format text|json]
Beispiel: Datum des ersten Clips als ISO-Datum (Pipeline-Default):
$ schnittprojekt-leser inspect first-clip-date "Mein Schnitt.fcpxmld" --timezone Europe/Zurich
2026-03-29
$ schnittprojekt-leser inspect first-clip-date "Mein Projekt.iMovieMobile" --timezone Europe/Zurich
2026-04-23
Beispiel: Vollzeitstempel:
$ schnittprojekt-leser inspect first-clip-date "Mein Schnitt.fcpxmld" \
--timezone Europe/Zurich --format iso-datetime
2026-03-29T08:20:02+02:00
Beispiel: Vollständiges Result als JSON (für familienfilm-manager):
$ schnittprojekt-leser inspect first-clip-date "Mein Schnitt.fcpxmld" \
--timezone Europe/Zurich --format json
{
"success": true,
"iso_date": "2026-03-29",
"iso_datetime": "2026-03-29T08:20:02+02:00",
"source_label": "exif:Keys:CreationDate",
"confidence": "high",
"project_format": "fcpxml_bundle",
"resolved_source_file": "/.../Mein Schnitt.fcpxmld/A001_03290820_D498.mov",
"error_message": null,
"warnings": []
}
Beispiel: Projekt-Übersicht:
$ schnittprojekt-leser inspect summary "Mein Schnitt.fcpxml"
Projekt: Mein Schnitt
Format: fcpxml_file (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
Beispiel: Quelldateien auf einem rclone-Remote suchen lassen:
schnittprojekt-leser inspect first-clip-date "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
from pathlib import Path
from schnittprojekt_leser.api import read_first_clip_date, read_project_summary
from schnittprojekt_leser.models import (
ReadFirstClipDateRequest,
ReadProjectSummaryRequest,
RuntimeOptions,
RcloneSource,
)
# 1. Datum des ersten Clips
result = read_first_clip_date(
ReadFirstClipDateRequest(
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.iso_date, result.iso_datetime, result.confidence)
else:
print("Fehler:", result.error_message)
# 2. Projekt-Übersicht (rein FCPXML-intern, kein Quelldatei-Zugriff)
summary = read_project_summary(
ReadProjectSummaryRequest(project_path=Path("Mein Schnitt.fcpxml")),
)
print(summary.project_name, summary.video_resolution, summary.clip_count_primary)
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:
- Projekt – Name, Schema-Version, Auflösung, Audio-Layout, Timeline-Dauer, Anzahl Clips, Projektdatei-mtime.
- Timeline – geordnete Spine-Clip-Sequenz, Lane-Sub-Spines.
- Asset (Resource) – Eintrag aus dem
<resources>-Pool:src-Verweis, Format, Audio-Charakteristik. - Quelldatei – die tatsächliche
.mov/.heic/.jpg. Hier wohnen QuickTime/EXIF-Metadaten, GPS, Filesystem-mtime/birthtime.
read_project_summary operiert rein auf Projekt + Timeline.
read_first_clip_date ist eine Komposition Timeline → Asset → Quelldatei.
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 (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 inlane-Sub-Spines), der ein Asset mitis_real_source=Truereferenziert. 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:
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-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 (SchnittprojektLeserEventmit Englisch-Stage-IDs gemäss ADR-0009, Deutsch-message).on_output– roher Subprozess-Text (exiftool/rclone). Nur ans Terminal bei--verbose.
Hinweis: Code-Spiegelung mit kamera-einleser
Die Datums-Extraktion (core/recorded_at.py, services/exiftool_runner.py,
models/dates.py) und die rclone-Integration
(services/rclone_source.py, RcloneSource-Dataclass, parse_source_spec)
sind bewusst nahe an
kamera-einleser gehalten:
identische Tag-Liste, identische Hierarchie, identisches Source-Label-Format,
identische rclone-Aufrufmuster, identische NFC-Normalisierung.
Aktuell als Redundanz akzeptiert. Bei einem dritten Konsumenten dieser Logik
ist ein Extract zu einer gemeinsamen Bibliothek kurmann-recorded-at
geplant – die API-Form ist exakt spiegelnd, damit der Extract mechanisch
möglich ist.
Änderungsverlauf
Die letzten drei Versionen:
- 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 ModelleIMovieMobileProject/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-dateundinspect summary. - 0.1.0 (2026-05-08) – Initiale Spezifikation
Specs.mdals Starthilfe (siehe ADR-0010).
Vollständige Historie: CHANGELOG.md.
Konsumenten
familienfilm-manager– Hauptmotivation. Holt Aufnahmedatum für Datei-Naming und Text-Overlay-Vorbereitung.- Plausible spätere:
fcplib-manager,mediaset-creator.
Lizenz
MIT. Siehe 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.
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
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_schnittprojekt_leser-0.3.0.tar.gz.
File metadata
- Download URL: kurmann_schnittprojekt_leser-0.3.0.tar.gz
- Upload date:
- Size: 44.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
26f01d297e6be5e3a744938dbc0104056e4424439a117ed03005f452b2bb1edf
|
|
| MD5 |
6f18088e69783c767e37d1fc5d6d4a97
|
|
| BLAKE2b-256 |
eb01b10f31a95bb91b7dc67d3530e16dfcdc397ac88d565037e65490e4553a4a
|
Provenance
The following attestation bundles were made for kurmann_schnittprojekt_leser-0.3.0.tar.gz:
Publisher:
publish.yml on kurmann/schnittprojekt-leser
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
kurmann_schnittprojekt_leser-0.3.0.tar.gz -
Subject digest:
26f01d297e6be5e3a744938dbc0104056e4424439a117ed03005f452b2bb1edf - Sigstore transparency entry: 1477210281
- Sigstore integration time:
-
Permalink:
kurmann/schnittprojekt-leser@e1232d827c999b3aaacd3dbfd5768f98ccd8bb22 -
Branch / Tag:
refs/tags/v0.3.0 - Owner: https://github.com/kurmann
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@e1232d827c999b3aaacd3dbfd5768f98ccd8bb22 -
Trigger Event:
release
-
Statement type:
File details
Details for the file kurmann_schnittprojekt_leser-0.3.0-py3-none-any.whl.
File metadata
- Download URL: kurmann_schnittprojekt_leser-0.3.0-py3-none-any.whl
- Upload date:
- Size: 48.1 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 |
3f0704b7988eba495d4e486a64253027a089de55dd1492c3c861d16636b47f38
|
|
| MD5 |
1ada0e58fe0ef3fcc58a6f12b48e13a2
|
|
| BLAKE2b-256 |
52351faeb332214f8f74bbb8491efd83379933c839ec3223f44b3c7ca06ac609
|
Provenance
The following attestation bundles were made for kurmann_schnittprojekt_leser-0.3.0-py3-none-any.whl:
Publisher:
publish.yml on kurmann/schnittprojekt-leser
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
kurmann_schnittprojekt_leser-0.3.0-py3-none-any.whl -
Subject digest:
3f0704b7988eba495d4e486a64253027a089de55dd1492c3c861d16636b47f38 - Sigstore transparency entry: 1477210460
- Sigstore integration time:
-
Permalink:
kurmann/schnittprojekt-leser@e1232d827c999b3aaacd3dbfd5768f98ccd8bb22 -
Branch / Tag:
refs/tags/v0.3.0 - Owner: https://github.com/kurmann
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@e1232d827c999b3aaacd3dbfd5768f98ccd8bb22 -
Trigger Event:
release
-
Statement type: