Metadata-Version: 2.4
Name: kurmann-familienfilm-manager
Version: 0.5.0
Summary: CLI-Tool und Python-Bibliothek zur Veröffentlichung von Familienfilmen: Medienset-Erstellung, Infuse-Deployment, Webserver-Deployment und Archivierung.
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/familienfilm-manager
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: typer>=0.12
Requires-Dist: rich>=13
Requires-Dist: kurmann-mediaset-creator>=3.0.0
Provides-Extra: dev
Requires-Dist: pytest>=8; extra == "dev"
Dynamic: license-file

# familienfilm-manager

CLI-Tool und Python-Bibliothek zur Veröffentlichung von selbst geschnittenen Familienfilmen.
Orchestriert den gesamten Workflow: Metadaten extrahieren, Medienset erstellen (via `mediaset-creator`),
Infuse-/Webserver-Deployment und Archivierung.

---

## Voraussetzungen

- Python 3.11+
- [ffmpeg](https://ffmpeg.org/) und `ffprobe` im `$PATH`
- [rclone](https://rclone.org/) im `$PATH` (für Deployments und Archivierung)
- [hdiutil](https://ss64.com/mac/hdiutil.html) (macOS, für ISO-Erstellung)
- [kurmann-mediaset-creator](https://pypi.org/project/kurmann-mediaset-creator/) (wird als Dependency installiert)

---

## Installation

```bash
uv pip install kurmann-familienfilm-manager
```

Im Entwicklungsmodus:

```bash
uv sync
```

---

## Verwendung (CLI)

### Gesamtworkflow

```bash
familienfilm-manager publish /pfad/zu/video.m4v \
  --title "Wanderung auf den Napf" \
  --category "Familie Kurmann-Glück" \
  --date "2025-03-24" \
  -v
```

Das Tool:
1. Extrahiert Metadaten (QuickTime-Tags oder Dateiname)
2. Erstellt ein Medienset via `mediaset-creator` mit separaten Profilen (Infuse/Web)
3. Deployt zu Infuse sofort bei Fertigstellung (direkt via rclone, kein ZIP-Entpacken)
4. Deployt zum Webserver nach Fertigstellung der Web-Komprimierung (rclone)
5. Archiviert Original + Sidecars als TAR (Python tarfile → rclone move)

### Einzeloperationen

```bash
# Nur Infuse-Deployment eines Verzeichnisses mit Infuse-Dateien
familienfilm-manager deploy-infuse /pfad/zum/infuse-verzeichnis/

# Nur Web-Deployment eines Web-ID-Verzeichnisses
familienfilm-manager deploy-web /pfad/zum/01JNXYZ.../

# Nur Archivierung
familienfilm-manager archive /pfad/zu/video.m4v --date 2025-03-24
```

### Optionen (publish)

| Option | Beschreibung |
|--------|-------------|
| `--title TEXT` | Video-Titel (überschreibt extrahierte Metadaten) |
| `--description TEXT` | Beschreibung des Videos |
| `--category TEXT` | Kategorie (z.B. «Familie Kurmann-Glück») |
| `--date TEXT` | Aufnahmedatum im ISO-Format (YYYY-MM-DD) |
| `--output-dir PATH` | Basisverzeichnis für die Web-Medienset-Ausgabe |
| `--poster-frame INT` | Frame-Nummer für Vorschaubild (überspringt KI-Auswahl) |
| `--poster-at FLOAT` | Zeitpunkt in Sekunden für Vorschaubild (z.B. 90 oder 1.5) |
| `--poster-crop TEXT` | Bildausschnitt (left/center-left/center/center-right/right) |
| `--web-id TEXT` | Web-ID für Web-Medienset (12 Zeichen, a-z 0-9; überschreibt Sidecar-Wert) |
| `--max-luminance INT` | Peak-Leuchtdichte in nits für HDR-Metadaten (Default: 1000) |
| `--no-sidecar` | Sidecar-Datei nicht lesen/schreiben |
| `--no-infuse` | Infuse-Deployment überspringen |
| `--no-web` | Web-Deployment überspringen |
| `--no-archive` | Archivierung überspringen |
| `--force` | Neuerstellung erzwingen |
| `--verbose`, `-v` | Zusätzliche Ablaufinformationen auf stderr |

### Ausgabe

stdout enthält den Pfad zum erstellten Verzeichnis (Web-ID-Verzeichnis oder Infuse-Verzeichnis):

```
/pfad/zum/output/01JNXYZ.../
```

---

## Verzeichnis-Publikation (Batch)

Mit `publish-dir` werden alle qualifizierten Videos in einem Verzeichnis sequenziell publiziert
(älteste zuerst). Ideal für Watch-Folder-Workflows.

```bash
# Trockenlauf: zeige welche Videos publiziert würden
familienfilm-manager publish-dir /Movies/LumaFusion-Export/ --dry-run

# Alle qualifizierten Videos publizieren
familienfilm-manager publish-dir /Movies/LumaFusion-Export/

# Auch bereits publizierte Videos neu publizieren
familienfilm-manager publish-dir /Movies/LumaFusion-Export/ --force
```

### Filter-Kriterien

Nur Dateien werden publiziert, die **alle** Bedingungen erfüllen:

- Bekannte Video-Endung: `.mov`, `.mp4`, `.m4v`, `.mkv`
- Dateiname matcht das Muster `<YYYY-MM-DD> <titel>.<ext>` (mindestens Datum-Präfix)
- Kein Hidden-File (kein `.` am Anfang)
- Keine Verzeichnisse

Alles andere wird stillschweigend ignoriert.

### Idempotenz

Nach erfolgreicher Publikation wird der Zeitpunkt als `published_at` ins Sidecar
(`<video>.settings.toml`) geschrieben. Beim nächsten `publish-dir`-Lauf werden Videos
übersprungen, sofern:

- Sidecar mit `published_at` existiert, **und**
- Quellvideo seit `published_at` nicht geändert wurde, **und**
- Sidecar selbst seit `published_at` nicht geändert wurde

Eine manuelle Sidecar-Anpassung (z.B. neuer `poster-at`-Wert) triggert also automatisch
einen Republish. Mit `--force` werden alle Videos unabhängig vom Status neu publiziert.

**Hinweis:** Beim Einzeldatei-`publish` greift der Skip-Check **nicht** — der explizite
Aufruf publiziert immer.

### Optionen (publish-dir)

| Option | Beschreibung |
|--------|-------------|
| `--no-infuse` | Infuse-Deployment für alle Videos überspringen |
| `--no-web` | Web-Deployment für alle Videos überspringen |
| `--no-archive` | Archivierung für alle Videos überspringen |
| `--force` | Auch bereits publizierte Videos neu publizieren |
| `--dry-run` | Nur anzeigen welche Videos publiziert würden, ohne zu publizieren |
| `--work-dir PATH` | Arbeitsverzeichnis für temporäre Dateien (überschreibt `paths.work_dir` Config) |
| `--verbose`, `-v` | Zusätzliche Ablaufinformationen pro Video auf stderr |

### Verhalten bei Fehlern

Ein Fehler bei einem Video stoppt den Batch-Lauf nicht — die Verarbeitung läuft mit
dem nächsten Video weiter. Am Schluss wird eine Zusammenfassung mit Erfolgen,
übersprungenen und fehlgeschlagenen Videos ausgegeben. Exit-Code `1` wenn mindestens
ein Video fehlschlug.

---

## Metadaten-Extraktion

Metadaten werden in folgender Priorität ermittelt:

### Aufnahmedatum

1. **CLI-Parameter** (`--date`) – überschreibt alles
2. **QuickTime-Tag** (`com.apple.quicktime.creationdate`) – bewusst in Final Cut Pro gesetztes Datum
3. **Dateiname** – ISO-Datum am Anfang des Dateinamens (z.B. `2019-12-22 Twannbachschlucht.mov`)
4. **Generische Tags** (`creation_time`, `date`) – oft das Datei-Änderungsdatum, nicht das Aufnahmedatum

Der Dateiname hat bewusst höhere Priorität als generische Tags (`creation_time`), weil der Videoschnitt selten am gleichen Tag wie die Aufnahme stattfindet. So kann das korrekte Aufnahmedatum über den Dateinamen bestimmt werden.

### Titel

1. **CLI-Parameter** (`--title`) – überschreibt alles
2. **QuickTime-Tags** (via ffprobe) – aus Final Cut Pro exportierte Metadaten
3. **Dateiname** – Titel nach dem Datumspräfix:
   - `2025-03-24 Wanderung auf den Napf.m4v` → «Wanderung auf den Napf»
   - `2025-03-24 - Wanderung auf den Napf.m4v` → «Wanderung auf den Napf»

### Poster-Zeitpunkt aus dem Dateinamen

Der Zeitpunkt für das Vorschaubild kann direkt im Dateinamen mitgegeben werden – ideal für
Videoschnitt-Programme wie LumaFusion, die keine Sidecar-Dateien beim Export erstellen.

**Format:** ` poster-<zeit>` am Ende des Dateinamens (Leerzeichen davor).
Minuten-Marker: `min` (vollständig) oder `m` (Kurzform, ffmpeg-Stil).

| Suffix | Bedeutung |
|--------|-----------|
| `poster-2min` / `poster-2m` | 2 Minuten = 120 Sek. |
| `poster-3min12` / `poster-3m12` | 3 Min 12 Sek = 192 Sek. |
| `poster-1min30s500` / `poster-1m30s500` | 1 Min 30.5 Sek (Sub-Sekunden) |
| `poster-30s` | 30 Sek. |
| `poster-30s500` | 30.5 Sek. |

**Beispiel:**

```
2026-04-16 HDR-Test poster-1min30.mov
```

→ Datum: `2026-04-16`, Titel: «HDR-Test», Poster-Zeitpunkt: 90 Sek.

Der Suffix wird beim Parsen vom Titel entfernt (landet nicht im Output-Dateinamen) und
automatisch in die Sidecar-Datei (`.settings.toml`) geschrieben. Bei einem späteren
`publish` ohne Suffix im Dateinamen wird der Wert aus der Sidecar-Datei gelesen.

**Priorität:** CLI (`--poster-at`) > Filename-Suffix > Sidecar.

**Wichtig:** Ein Aufnahmedatum ist zwingend erforderlich. Ohne Datum wird die Verarbeitung abgebrochen.

---

## Settings-Sidecar-Datei

Wenn Poster-Einstellungen via CLI angegeben werden (`--poster-frame`, `--poster-at`, `--poster-crop`), speichert der Familienfilm Manager diese automatisch in einer `.settings.toml`-Datei neben dem Video:

```
2025-03-24 Wanderung auf den Napf.m4v
2025-03-24 Wanderung auf den Napf.settings.toml   ← automatisch erstellt
```

Inhalt:
```toml
[poster]
timestamp_seconds = 42.5
crop_position = "center-right"

[web]
web_id = "a3xk9mn2pqrw"
```

Bei einem erneuten `publish` werden die Werte automatisch aus der Sidecar-Datei gelesen – CLI-Parameter überschreiben gespeicherte Werte. Die Web-ID wird nach erfolgreicher Erstellung automatisch gespeichert, damit bei erneutem Publish die gleiche ID wiederverwendet wird (gleiche URL bleibt stabil).

Die Sidecar-Datei wird bei der Archivierung automatisch mit ins ISO aufgenommen.

**Deaktivieren:** `--no-sidecar` (pro Aufruf) oder `familienfilm-manager config set sidecar.enabled false` (global).

---

## Konfiguration

Einstellungen werden in `~/.config/familienfilm-manager/config.toml` gespeichert.

### Befehle

```bash
# Wert speichern
familienfilm-manager config set <schlüssel> "<wert>"

# Einzelnen Wert lesen
familienfilm-manager config get <schlüssel>

# Alle gespeicherten Werte anzeigen
familienfilm-manager config list
```

### Erlaubte Schlüssel

| Schlüssel | Beschreibung | Standard |
|-----------|--------------|---------|
| `targets.infuse` | rclone-Ziel für Infuse (z.B. `nas:/media/videos/`) | *(leer)* |
| `targets.web` | rclone-Ziel für Webserver (z.B. `azure:container/shares/`) | *(leer)* |
| `targets.archive` | rclone-Ziel für Archiv (z.B. `nas:/archive/familienfilme/`) | *(leer)* |
| `infuse.group_by` | Gruppierung: `year` oder `month` | `year` |
| `defaults.category` | Standard-Kategorie für Videos | *(leer)* |
| `sidecar.enabled` | Sidecar-Dateien automatisch lesen/schreiben | `true` |
| `cleanup.web_workdir` | Temporäres Web-Arbeitsverzeichnis nach erfolgreichem Deployment löschen | `true` |
| `paths.work_dir` | Standard-Arbeitsverzeichnis für temporäre Dateien (lokaler SSD bei SMB-Quellen empfohlen) | *(leer)* |
| `mediaset.branding_base_url` | Stamm-URL für Web-Mediasets und OG-Tags | *(leer)* |
| `mediaset.branding_site_name` | Site-Name (sichtbar im Header + OG-Meta) | *(leer)* |
| `tools.ffmpeg` | Pfad zur ffmpeg-Binärdatei | `ffmpeg` |
| `tools.ffprobe` | Pfad zur ffprobe-Binärdatei | `ffprobe` |
| `tools.rclone` | Pfad zur rclone-Binärdatei | `rclone` |

### Beispiel

```bash
familienfilm-manager config set targets.infuse "nas:/media/familienfilme/"
familienfilm-manager config set targets.web "azure:kurmann-glueck/shares/"
familienfilm-manager config set targets.archive "nas:/archive/familienfilme/"
familienfilm-manager config set defaults.category "Familie Kurmann-Glück"
familienfilm-manager config set mediaset.branding_base_url "https://kurmannmedia.blob.core.windows.net/kurmann-glueck/"
```

### Arbeitsverzeichnis bei SMB-Quellen

Wenn die Quellvideos auf einem SMB-/NAS-Laufwerk liegen, sollte ein lokales
Arbeitsverzeichnis konfiguriert werden, damit Komprimierung und temporäre
Dateien nicht über das Netzwerk laufen:

```bash
familienfilm-manager config set paths.work_dir "~/Movies/.familienfilm-tmp"
```

Dann werden bei jeder Publikation:
- Quellvideo gelesen vom SMB
- Sidecar `.settings.toml` geschrieben auf SMB (klein)
- Komprimierung, Thumbnails, Web-Output: lokal im Work-Dir
- Web-Deployment: rclone push vom lokalen Work-Dir zum Webserver

Resolution: CLI `--work-dir` > Config `paths.work_dir` > Quellverzeichnis.

---

## Verwendung (Python API)

```python
from pathlib import Path
from familienfilm_manager.api import (
    PublishRequest,
    RuntimeOptions,
    publish,
)

request = PublishRequest(
    source_path=Path("/pfad/zu/video.m4v"),
    title="Wanderung auf den Napf",
    category="Familie Kurmann-Glück",
    recording_date="2025-03-24",
)

runtime = RuntimeOptions(
    infuse_target="nas:/media/familienfilme/",
    web_target="azure:kurmann-glueck/shares/",
    archive_target="nas:/archive/familienfilme/",
    branding_base_url="https://example.com/shares/",
)

result = publish(request, runtime)

if result.success:
    print(f"Infuse: {result.infuse_dir} (deployed: {result.infuse_deployed})")
    print(f"Web: {result.web_dir} (deployed: {result.web_deployed})")
    print(f"Archiv: {result.archived}")
else:
    print(f"Fehler: {result.error_message}")
```

### Öffentliche API-Exporte

```python
from familienfilm_manager.api import (
    publish,                 # Gesamtworkflow
    deploy_infuse,           # Nur Infuse-Deployment
    deploy_web,              # Nur Web-Deployment
    archive,                 # Nur Archivierung
    PublishRequest,          # Fachlicher Request
    PublishResult,           # Ergebnis
    DeployRequest,           # Deploy-Request
    DeployResult,            # Deploy-Ergebnis
    ArchiveRequest,          # Archiv-Request
    ArchiveResult,           # Archiv-Ergebnis
    RuntimeOptions,          # Technische Optionen
    FamilienfilmEvent,       # Fortschritts-Event
    FamilienfilmStage,       # Stage-IDs
)
```

---

## Lizenz

MIT
