Metadata-Version: 2.4
Name: kurmann-mediathek-manager
Version: 0.6.0
Summary: Generiert statische HTML-Bibliotheken aus Videodateien – archivierungsfähig, ohne Server, ohne Datenbank.
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/mediathek-manager
Project-URL: Repository, https://github.com/kurmann/mediathek-manager
Project-URL: Changelog, https://github.com/kurmann/mediathek-manager/blob/main/CHANGELOG.md
Project-URL: Issues, https://github.com/kurmann/mediathek-manager/issues
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: typer>=0.12
Requires-Dist: rich>=13
Requires-Dist: jinja2>=3
Requires-Dist: pyyaml>=6
Provides-Extra: dev
Requires-Dist: pytest>=8; extra == "dev"
Dynamic: license-file

# Mediathek Manager

Generiert statische HTML-Bibliotheken aus Videodateien — archivierungsfähig, ohne Server, ohne Datenbank.

Das Tool scannt Verzeichnisse mit Videodateien (M4V, MP4, MKV, MOV), extrahiert eingebettete Metadaten (Titel, Tags, Genre, Kapitel, Beschreibungen) und generiert eine navigierbare HTML-Bibliothek direkt neben den Videos. Eine einzige Bibliothek pro Aufruf — rekursiv über alle Unterverzeichnisse mit hierarchie-gespiegelter Struktur, Breadcrumb-Navigation, Tag- und Genre-Filtern sowie Detailseiten inkl. klickbaren Kapiteln.

## Scope — was der Mediathek Manager **nicht** tut

Der Mediathek Manager erzeugt **keine neuen Vorschaubilder aus dem Videoinhalt**. Poster-Generierung (Frame-Extraktion, Resize, Tone-Mapping, Komposition) ist eine eigenständige Kompetenz und gehört in spezialisierte Tools (z. B. den `kurmann-vorschaubild-manager`, aufgerufen aus dem `kurmann-mediaset-creator`). Der Mediathek Manager konsumiert nur existierende Bildquellen nach folgender Prioritätenkette:

1. **Poster-Sidecar** `{stem}-poster.jpg` (z. B. `Urlaub-poster.jpg` neben `Urlaub.m4v`) wird direkt als Thumbnail für Grid-Cards verwendet.
2. **Eingebettetes Cover-Atom** im Video-Header (iTunes-`covr` bzw. `disposition.attached_pic` in ffprobe-Sprech). Liegt kein Sidecar neben dem Video, aber trägt die Datei bereits ein Cover-Atom, wird es verlustfrei (`ffmpeg -c copy`) nach `mediathek/embedded-poster/<layout>.jpg` extrahiert und als Thumbnail genutzt. Wichtig: Das Tool liest damit nur, was **im Datei-Header bereits existiert** — kein Bildinhalt wird neu berechnet, kein Frame extrahiert.
3. **Platzhalter-Card** mit zentriertem Titel. Sie ist weiterhin voll klickbar und führt zur Detailseite.

Zusätzlich wird **Fanart** (`{stem}-fanart.jpg`, typisch landscape 16:9) — wenn vorhanden — auf der Detailseite als `<video poster="…">` eingesetzt, damit sich der Player harmonisch füllt, bevor man auf Play klickt. Fehlt das Fanart, dient das Poster (Sidecar oder extrahiertes Cover) als Fallback.

Dadurch bleibt der Mediathek Manager fokussiert auf Scannen, Metadaten und HTML — der einzige ffmpeg-Einsatzzweck ist ein narrow-scoped Atom-Copy, kein Bildverarbeitungs-Pipeline.

## Medienset-Verzeichnisse (Infuse-Konvention)

Bibliotheken nach Infuse-Konvention organisieren jeden Einzelfilm in einem eigenen Ordner, dessen Name dem Video entspricht und in dem auch die Sidecars liegen:

```
Familienfilme/
└── 2026/
    └── 2026-03-22-Geburtstag-Dennis-2026/          ← Medienset-Ordner
        ├── 2026-03-22 Geburtstag Dennis 2026.mov
        ├── 2026-03-22 Geburtstag Dennis 2026-poster.jpg
        ├── 2026-03-22 Geburtstag Dennis 2026-fanart.jpg
        └── 2026-03-22 Geburtstag Dennis 2026.nfo
```

Der Mediathek Manager erkennt solche Ordner automatisch und rendert sie nicht als Zwischen-Ordner. Das Video erscheint stattdessen direkt in der Eltern-Ebene (im Beispiel: unter `2026`) als Video-Card. Klick führt direkt zur Detailseite — ein Klick weniger als zuvor.

**Erkennungsregel**: Das Verzeichnis enthält genau ein Video, und dessen Dateiname (ohne Erweiterung) entspricht nach Normalisierung dem Ordnernamen (Leerzeichen und Bindestriche werden gleichgesetzt, Groß-/Kleinschreibung ignoriert). Enthält der Ordner zwei oder mehr Videos, bleibt er ein normales Unterverzeichnis.

## Metadaten-Quellen (Reihenfolge)

Pro Feld wird die jeweils zuverlässigste verfügbare Quelle verwendet. NFO ersetzt nicht alles — fehlt ein NFO-Feld, wird die nächste Quelle geprüft:

| Feld  | Reihenfolge |
|-------|-------------|
| Titel | NFO `<title>` → ffprobe `tags.title` → Dateiname-Stem ohne ISO-Präfix |
| Datum | NFO `<published>` → Dateiname-Präfix `YYYY-MM-DD<sep>…` → ffprobe `tags.date` / `content_create_date` |
| Genre | NFO erstes `<genre>` → ffprobe `tags.genre` |
| Quelle/Autor | NFO erstes `<author>` → ffprobe `tags.artist` ∨ `tags.album_artist` |
| Beschreibung | ffprobe `tags.description` |
| Langbeschreibung | NFO `<plot>` → ffprobe `tags.synopsis` ∨ `tags.long_description` |

NFO-Dateien folgen der Infuse-Konvention (`<media type="Other">` mit `<title>`, `<published>`, `<genres>`, `<authors>`, optional `<plot>`). NFO-Werte haben Vorrang vor YAML-Sidecars.

**Was _nicht_ als Datumsquelle verwendet wird**: Das Feld `creation_time` (QuickTime/MP4 mvhd-Header-Timestamp) ist bei den meisten Workflows der Encoder- oder Kopierzeitpunkt und nicht das echte Aufnahmedatum — vor allem bei Apple-Memories-Renderings wie `*.mov`. Verlässlich ist hingegen das eingebettete `date`-Atom (iTunes-/M4V-Atom `©day`, exiftool: "Content Create Date"), das User-Tools beim Kodieren explizit setzen.

Eine optionale **YAML-Sidecar-Datei** (`{stem}.yaml` neben dem Video) wird weiterhin unterstützt — sie liegt in der Reihenfolge _zwischen_ ffprobe und NFO und eignet sich als schneller Ad-hoc-Override für Bibliotheken ohne vollständige NFO-Pflege.

## Voraussetzungen

- Python >= 3.11
- ffprobe (für Metadaten-Extraktion aus eingebetteten Tags und Kapiteln)
- ffmpeg (optional — nur nötig, wenn eingebettete Cover-Atome als Poster-Fallback verlustfrei kopiert werden sollen; fehlt ffmpeg, bleibt es bei der Platzhalter-Card für Videos ohne `-poster.jpg`-Sidecar)
Der Mediathek Manager arbeitet ausschließlich mit lokal erreichbaren Pfaden — das schliesst SMB-/NFS-/FUSE-Mounts ein. Remote-Speicher wie Synology-NAS werden typischerweise als SMB-Share gemountet (z. B. `/Volumes/Familienfilme`). rclone-Remotes werden seit v0.4.0 nicht mehr unterstützt.

## Installation

```bash
# Via PyPI
pip install kurmann-mediathek-manager

# Lokale Entwicklung
uv sync
```

## Verwendung

```bash
# Bibliothek lokal generieren
mediathek-manager generate /pfad/zum/videoverzeichnis

# Mit eigenem Titel (sonst wird der Ordnername verwendet)
mediathek-manager generate /pfad/zum/Familienfilme --title "Familienfilme"

# Ohne Rekursion: nur Videos im Root-Verzeichnis
mediathek-manager generate /pfad/zum/videoverzeichnis --no-recursive

# Mit ausführlicher Ausgabe
mediathek-manager generate /pfad/zum/videoverzeichnis --verbose

# Nur scannen (zeigt gefundene Videos und Metadaten)
mediathek-manager scan /pfad/zum/videoverzeichnis

# HTML an einem separaten Ort generieren (Quellverzeichnis bleibt unberührt)
mediathek-manager generate /pfad/zum/videoverzeichnis --output ~/Mediathek-HTML

# Mediathek über lokalen Webserver im Browser öffnen
mediathek-manager serve ~/Mediathek-HTML

# Dateinamen auf SMB/URL/APFS-Kompatibilität prüfen (Read-only-Diagnose)
mediathek-manager check-names /pfad/zum/videoverzeichnis

# Konfiguration
mediathek-manager config list
mediathek-manager config set tools.ffprobe_path /usr/local/bin/ffprobe
```

## Erzeugte Struktur

```
videoverzeichnis/                  ← CLI-Input (Root der Bibliothek)
├── index.html                     ← Einstiegspunkt (einzige HTML im Root)
├── Film1.m4v
├── Film1-poster.jpg               ← optionales Sidecar (nicht vom Tool erzeugt)
├── Ferien/
│   ├── Urlaub.m4v
│   └── Urlaub-poster.jpg
└── mediathek/                     ← alle weiteren Artefakte
    ├── style.css
    ├── browse/                    ← Unterverzeichnis-Indexe (Hierarchie gespiegelt)
    │   └── Ferien/
    │       └── index.html
    ├── detail/                    ← Detailseiten (Hierarchie gespiegelt)
    │   ├── Film1.html
    │   └── Ferien/
    │       └── Urlaub.html
    ├── embedded-poster/           ← extrahierte Cover-Atome (nur bei Bedarf)
    │   └── Ferien/
    │       └── Urlaub.jpg
    ├── tag/                       ← Tag-Filterseiten (über gesamte Bibliothek)
    │   └── weltraum.html
    └── genre/                     ← Genre-Filterseiten
        └── dokumentation.html
```

Poster werden direkt vom `{stem}-poster.jpg`-Sidecar neben dem Video referenziert. Fehlt das Sidecar, prüft der Generator, ob das Video einen `attached_pic`-Stream im Header trägt, und kopiert ihn verlustfrei nach `mediathek/embedded-poster/<layout>.jpg`. Fehlt beides, zeigt die Card einen Platzhalter mit zentriertem Titel.

**Safari-optimiert**: Die Detailseiten nutzen native `<video>`-Tags, die in Safari via AVFoundation rendern — dieselbe Pipeline wie QuickTime. HDR10 / Dolby Vision / Rec.2020 PQ wird korrekt tone-mapped. Safari ist daher der empfohlene Browser.

**Suche**: Die Bibliothek nutzt keine JavaScript-Suche — die Browser-interne Textsuche (⌘+F bzw. Strg+F) funktioniert bestens auf der langen Videoliste.

### Card-Layout

Grid-Cards zeigen per Default das Poster, darunter **Jahr links und Laufzeit rechts** — keinen zusätzlichen Titel-Text, weil das Poster den Titel typischerweise bereits trägt (YouTube-Thumbnails, gestaltete Mediaset-Poster). Bei Videos ohne Poster-Sidecar und ohne eingebettetes Cover-Atom erscheint der Titel zentriert in der Platzhalter-Box.

Wer den Titel-Text zusätzlich unter jeder Card rendern will (z. B. bei Bibliotheken mit rein fotografischen Postern, die keinen Titel enthalten), nutzt:

```bash
mediathek-manager generate /pfad --show-card-titles
```

Die Tag- und Genre-Listen auf der Startseite sind als **Stichwort-Index nach Buchsatz-Vorbild** gesetzt — enge Zeilen, rechtsbündige Zählungen im Tabular-Numerics-Stil, ruhiger Rhythmus.

### Metadaten-Cache

Ab v0.4.0 speichert der Mediathek Manager die extrahierten Metadaten (ffprobe-Ergebnisse, Sidecar-Daten, Filename-Heuristiken) in `mediathek/.metadata-cache.json`. Beim nächsten `generate`-Lauf wird ffprobe nur für geänderte oder neue Videos aufgerufen — unveränderte Videos werden direkt aus dem Cache geladen. Das beschleunigt wiederholte Läufe auf grossen Bibliotheken erheblich, besonders über SMB-Mounts.

Der Cache-Key pro Video besteht aus `mtime + size` der Videodatei sowie den `mtimes` aller Sidecars (`.yaml`, `.nfo`, `-poster.jpg`, `-fanart.jpg`). Ändert sich eines dieser Attribute, wird das Video neu extrahiert. Der Cache ist ein Hilfsmittel — er kann jederzeit gelöscht werden, die HTML-Dateien sind das archivierungswürdige Artefakt.

Bei **Abbruch** (Ctrl+C, Verbindungsabbruch) bleibt die alte Cache-Datei intakt. Der nächste Lauf profitiert von allen bisherigen Einträgen des vorherigen Laufs und extrahiert nur die fehlenden Videos nach.

### Separater Ausgabepfad

Die HTML-Bibliothek kann optional an einem separaten Ort generiert werden (z. B. auf einem schnellen lokalen Laufwerk statt auf dem NAS):

```bash
mediathek-manager generate /Volumes/Familienfilme/Final --output ~/Mediathek-HTML --title "Familienfilme"
```

Das Quellverzeichnis bleibt vollständig unberührt — es erfolgt keinerlei Schreibzugriff darauf. Im Output-Verzeichnis wird automatisch ein Symlink `_videos → <Quellverzeichnis>` erstellt — alle Video- und Sidecar-Referenzen in den HTML-Seiten gehen über `_videos/…`.

**Wichtig**: Bei `file://` blockiert Safari den Cross-Origin-Zugriff auf Sub-Ressourcen auch über Symlinks hinweg. Videos in Detailseiten spielen daher bei unterschiedlichen Pfadbäumen (z. B. `/Volumes/NAS` vs. `/Users/…`) nicht ab. Die Lösung: `mediathek-manager serve` startet einen lokalen Webserver, der den Symlink transparent auflöst:

```bash
mediathek-manager generate /Volumes/Internetfilme-Patrick --output ~/Movies/Internetfilme --title "Internetfilme"
mediathek-manager serve ~/Movies/Internetfilme
# → http://127.0.0.1:8080/ öffnet sich — alle Videos spielen ab
```

Auch ohne `--output` ist `serve` nützlich: es umgeht sämtliche `file://`-Probleme mit NFD-Unicode, URL-reservierten Zeichen und Browser-Sicherheitsrestriktionen.

## Dateinamen: NFC vs. NFD und `file://`-Kompatibilität

Wer die generierte Bibliothek im **SMB-gemounteten Finder** per Doppelklick auf `index.html` öffnet, bewegt sich im `file://`-Schema und stößt schnell auf drei Fallen, die im Browser keine Fehlermeldung erzeugen:

### NFC vs. NFD — die macOS-Unicode-Falle

macOS speichert Dateinamen mit Umlauten historisch in **NFD** (Normalization Form Decomposed): Das `ä` in `Bäbibett` liegt nicht als ein einzelnes Codepoint `U+00E4` vor, sondern als `a` (`U+0061`) + Combining Diaeresis (`U+0308`). Dieselbe Datei, erzeugt unter Linux/Windows oder über git, liegt in **NFC** (Normalization Form Composed) als ein einzelnes `U+00E4` vor.

Der Finder und Safari zeigen beide Formen identisch an. Aber:

- Beim URL-Encoding eines NFD-Namens für einen `file://`-Link erzeugt der Browser zwei Escape-Sequenzen (`%CC%88` für das Combining-Zeichen nach dem `a`), statt einem einzigen `%C3%A4`. Manche Player und Downloader (z. B. einige Browser-Plugins, ältere Video-Tools) lösen diese Kaskade nicht auf und finden die Datei nicht.
- SMB-Shares auf Windows-Servern speichern intern NFC und können NFD-kodierte Pfade vom macOS-Client als „nicht existent" melden, obwohl das Finder-Listing die Datei zeigt.
- Sortierungen und Vergleiche über Dateisystem-Grenzen hinweg schlagen fehl, wenn eine Seite NFC und die andere NFD liefert.

**Zielformat**: NFC für alles. Das ist der Default überall außer auf macOS-HFS+/APFS, wenn der Name ursprünglich im Finder getippt wurde. Konvertierung nachträglich z. B. mit `convmv`:

```bash
brew install convmv
convmv -f utf-8 -t utf-8 --nfc --notest /pfad/zur/bibliothek -r
```

### URL-reservierte Zeichen

`file://`-Links in Browsern interpretieren folgende Zeichen besonders, **egal** wie der Name intern kodiert ist:

| Zeichen | Folge |
|---------|-------|
| `#` | Terminiert die URL als Fragment-Anker — alles danach wird abgeschnitten |
| `?` | Startet den Query-String — alles danach wird abgeschnitten |
| `&` | Trennt Query-Parameter |
| `+` | Wird zum Leerzeichen interpretiert |
| `%` | Leitet eine Escape-Sequenz ein |

Dateinamen wie `Urlaub#2024.m4v` oder `Doku & Film.m4v` führen deshalb zu stumm kaputten Links. Der Browser lädt nichts, zeigt aber auch keinen Fehler.

### Windows/SMB-verbotene Zeichen

`< > : " | ? *` sind unter Windows/SMB in Dateinamen komplett verboten. Wer die Bibliothek später auf eine Windows-Freigabe oder OneDrive legt, verliert diese Dateien still (`Invalid name` beim Kopieren).

### Der `check-names`-Befehl

Zur **Read-only-Diagnose** der eigenen Bibliothek liefert der Mediathek Manager einen dedizierten Subcommand:

```bash
mediathek-manager check-names /pfad/zum/videoverzeichnis
```

Der Befehl scannt alle Videos und Unterverzeichnisse und meldet sieben Klassen von Auffälligkeiten:

| Tag | Bedeutung |
|-----|-----------|
| `NFD` | Dekomponierte Umlaute (macOS-Default), NFC erwartet |
| `URL` | Zeichen, die `file://`-Links brechen: `# ? & + %` |
| `WIN` | Unter Windows/SMB verbotene Zeichen: `< > : " \| ? *` |
| `LEN` | Pfadlänge > 240 Zeichen (Windows-MAX_PATH-Zone) |
| `TRIM` | Führende/abschließende Leerzeichen oder trailing Punkte |
| `RSVD` | Windows-Device-Name (`CON`, `PRN`, `AUX`, `NUL`, `COM1`…`LPT9`) |
| `CASE` | Groß-/Kleinschreibungs-Kollision mit anderem Pfad |

Ausgabeformat:

```
[URL]                        Bäbibett & Co/film.m4v
[TRIM]                       ferien /urlaub.m4v
[NFD URL]                    2024/Bäbibett & Co/film.m4v
```

stdout enthält nur die maschinenlesbare `[TAG] <pfad>`-Liste (pipebar), stderr enthält Zusammenfassung und Details. Exit-Code `1` bei Fundstellen, sonst `0` — damit ist `check-names` scriptbar:

```bash
if ! mediathek-manager check-names /pfad/zur/bibliothek > /tmp/issues.txt; then
    echo "Bibliothek hat $(wc -l < /tmp/issues.txt) auffällige Pfade"
fi
```

Der Mediathek Manager **benennt niemals um**. Er ist ein reines Diagnose-Werkzeug — die Korrektur bleibt beim Nutzer (typisch `convmv --nfc` für NFD→NFC, manuell für den Rest).

## Konfiguration

Persistente Konfiguration unter `~/.config/mediathek-manager/config.toml`:

```toml
[tools]
ffprobe_path = "ffprobe"
ffmpeg_path = "ffmpeg"
```

## Upgrade von 0.1.x auf 0.2.0

Version 0.2.0 ändert die Ausgabestruktur grundlegend. Alte `.mediathek/`-Ordner sind nicht kompatibel und sollten vor der erneuten Generierung gelöscht werden:

```bash
rm -rf /pfad/zum/videoverzeichnis/.mediathek
mediathek-manager generate /pfad/zum/videoverzeichnis --title "Deine Bibliothek"
```

## Architektur und Hosting-Strategie

Für das Hosting der generierten Mediathek im Heimnetz und das übergreifende Architektur-Pattern für zukünftige Apps siehe das Fachdokument:

- [`docs/static-hosting-architecture.md`](docs/static-hosting-architecture.md) — beschreibt die Entscheidung für Synology Web Station als statischen HTTP-Backend, das Webserver-Mental-Model, Symlinks vs. URLs als Pfad-Indirection, HTTP-Range-Requests vs. SMB-Random-Access, mDNS-Hostnames, Apple TV via Infuse und AirPlay, Sicherheits-Setup und das übergreifende Pattern „NAS als Read-Only-Backend, Apps als HTTP-Clients".

## Änderungsverlauf

Siehe [CHANGELOG.md](CHANGELOG.md).

## Lizenz

MIT — siehe [LICENSE](LICENSE).
