Metadata-Version: 2.4
Name: kurmann-kinofilm-manager
Version: 2.3.0
Summary: CLI-Tool zur Konvertierung von DTS/DTS-HD Audiospuren in MKV-Dateien zu E-AC3 (Dolby Digital Plus)
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/kinofilm-manager
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: typer>=0.12
Requires-Dist: rich>=13
Provides-Extra: dev
Requires-Dist: pytest>=8; extra == "dev"
Dynamic: license-file

# kinofilm-manager

Python-CLI-Tool zur Konvertierung von DTS/DTS-HD Audiospuren in MKV-Dateien zu **E-AC3 (Dolby Digital Plus)**. Das Ergebnis ist eine neue MKV-Datei, die direkt in **Subler** importiert und zu M4V/MP4 für Apple TV und Infuse weiterverarbeitet werden kann. 

Für Artwork und Fanart wird die **TMDB**-API (The Movie Database) verwendet.

---

## Hintergrund und Motivation

### Das Container-Problem

Der **MP4/M4V-Container** – der native Container für Apple TV, iTunes und Infuse – unterstützt **DTS und DTS-HD nicht**. Dies ist eine Einschränkung des Container-Standards: DTS ist im MP4-Standard nicht als zulässiger Audio-Codec spezifiziert.

Wer eine Archivierungs-Pipeline aufbaut, die auf folgenden Komponenten basiert:

```
MKV (Quellformat) → Subler (Muxing/Metadaten) → M4V (Apple TV / Infuse)
```

steht vor dem Problem, dass Subler DTS-Spuren aus einem MKV **nicht direkt in einen M4V-Container übernehmen** kann. Der Workaround wäre ein manuelles Transcodieren in Subler – was aber keine Batch-Fähigkeit bietet und keinen kontrollierten Workflow erlaubt.

### Die Lösung: Zweistufiger Workflow

Der Workflow wird sauber in zwei unabhängige Schritte getrennt:

```
Schritt 1: kinofilm-manager convert
MKV (mit DTS) → MKV (mit E-AC3)

Schritt 2: Subler (manuell oder via SublerCLI)
MKV (mit E-AC3) → M4V (für Apple TV / Infuse)
```

Dadurch bleibt Subler ein reines **Muxing- und Metadaten-Tool** und kinofilm-manager übernimmt die Audiokonvertierung deterministisch und reproduzierbar.

---

## Unterstützte Audio-Codecs im M4V-Container

Zur Übersicht welche Audioformate im MP4/M4V-Container zulässig sind:

| Codec | M4V-kompatibel | Bemerkung |
|---|---|---|
| AAC | ✅ | Nativ |
| AC3 (Dolby Digital) | ✅ | Von Apple für M4V zugelassen |
| E-AC3 (Dolby Digital Plus) | ✅ | Inkl. Atmos-Metadata |
| DTS | ❌ | Nicht im MP4-Standard |
| DTS-HD HRA / MA | ❌ | Nicht im MP4-Standard |
| TrueHD | ❌ | Nicht im MP4-Standard |

---

## Voraussetzungen

- Python 3.11 oder neuer
- `ffmpeg` und `ffprobe` im PATH

**Installation ffmpeg auf macOS:**

```bash
brew install ffmpeg
```

> **Hinweis:** HandBrake nicht via Homebrew installieren – es überschreibt shared Libraries (z.B. `libvpx`) und kann zu `dyld: Library not loaded`-Fehlern bei ffmpeg führen. HandBrake stattdessen als `.app` via DMG installieren.

---

## Installation

```bash
# Via pip
pip install kurmann-kinofilm-manager

# Via uv
uv pip install kurmann-kinofilm-manager

# Für Entwicklung (lokal)
git clone https://github.com/kurmann/kinofilm-manager.git
cd kinofilm-manager
pip install -e ".[dev]"
```

---

## Verwendung

```bash
kinofilm-manager convert <input.mkv> [Optionen]
kinofilm-manager artwork fetch <video-file> [Optionen]
kinofilm-manager movie identify <video-file> [Optionen]
```

### Parameter

| Argument | Kurz | Standard | Beschreibung |
|---|---|---|---|
| `INPUT_FILE` | – | – | Pfad zur Eingabe-MKV (Pflichtfeld) |
| `--output` | `-o` | `<input>_eac3.mkv` | Pfad zur Ausgabe-MKV |
| `--bitrate` | `-b` | automatisch oder `config`-Wert | E-AC3-Bitrate für alle konvertierten Spuren |
| `--dry-run` / `--no-dry-run` | – | `false` oder `config`-Wert | Zeigt Analyse und FFmpeg-Befehl, erstellt keine Datei |
| `--verbose` / `--no-verbose` | `-v` | `false` oder `config`-Wert | Zusätzliche Diagnoseinformationen auf stderr |

### Beispiele

```bash
# Einfachster Aufruf – Ausgabe landet neben der Quelldatei
kinofilm-manager convert "Film (2024).mkv"

# Ausgabepfad selbst bestimmen
kinofilm-manager convert "Film (2024).mkv" -o "Film (2024)_subler.mkv"

# Erst prüfen was passieren würde (empfohlen beim ersten Test)
kinofilm-manager convert "Film (2024).mkv" --dry-run

# Eigene Bitrate (kleinere Datei, geringfügig weniger Qualität)
kinofilm-manager convert "Film (2024).mkv" --bitrate 448k

# Persistente Defaults für künftige Aufrufe setzen
kinofilm-manager config set bitrate 448k
kinofilm-manager config set verbose true

# Pipeline: Ergebnis-Pfad auf stdout, Diagnose auf stderr
output=$(kinofilm-manager convert "Film (2024).mkv")
echo "Konvertierte Datei: $output"
```

## Artwork / Fanart für Infuse

Für Infuse-kompatibles Fanart kann pro Videodatei ein Backdrop im Format
`<Titel (Jahr)-fanart.jpg>` geladen werden. Die Datei wird immer im gleichen
Verzeichnis wie die Videodatei gespeichert.

```bash
kinofilm-manager artwork fetch <video-file> [Optionen]
```

### Unterstützte Eingaben

- Primär: Dateiname im Schema `<Titel (Jahr)>[ - Zusatz].m4v`, `.mkv` oder `.mp4`; Zusätze nach ` - ` werden ignoriert
- Alternativ: `--title` und `--year`, falls der Dateiname nicht parsebar ist
- Direkter Lookup: `--tmdb-id`, falls ein Film über Titel und Jahr nicht zuverlässig gefunden werden kann

### Optionen

| Option | Standard | Beschreibung |
|---|---|---|
| `--title TEXT` | aus Dateiname | Alternativer Filmtitel |
| `--year INTEGER` | aus Dateiname | Erscheinungsjahr; nur zusammen mit `--title` |
| `--tmdb-id INTEGER` | – | Direkter TMDB-ID-Lookup; umgeht die Titelsuche. Kann nicht zusammen mit `--title`/`--year` verwendet werden |
| `--dry-run` / `--no-dry-run` | `false` oder `config`-Wert | Prüft den Workflow ohne Dateiablage |
| `--language TEXT` | – | Suchsprache für TMDB, z.B. `de-DE` oder `en-US` |
| `--provider TEXT` | `tmdb` oder `config`-Wert | Artwork-Provider; aktuell wird nur `tmdb` unterstützt |
| `--tmdb-api-key TEXT` | `config`-Wert | TMDB API Key |
| `--interactive` / `--no-interactive` | `false` | Erlaubt eine manuelle Auswahl bei mehrdeutigen Treffern |

### Beispiele

```bash
# Fanart direkt neben der Videodatei speichern
kinofilm-manager artwork fetch "Frozen (2013).m4v" --tmdb-api-key "$TMDB_API_KEY"

# Parsebaren Dateinamen überschreiben
kinofilm-manager artwork fetch "film-final.mp4" --title "Frozen" --year 2013 --tmdb-api-key "$TMDB_API_KEY"

# Direkter TMDB-ID-Lookup für hartnäckige Suchfälle
kinofilm-manager artwork fetch "Neues von uns Kindern aus Bullerbü (1987).m4v" --tmdb-id 26111 --tmdb-api-key "$TMDB_API_KEY"

# Mehrdeutige Treffer bewusst interaktiv auswählen
kinofilm-manager artwork fetch "Frozen (2013).m4v" --interactive --tmdb-api-key "$TMDB_API_KEY"

# Workflow testen ohne Dateiablage
kinofilm-manager artwork fetch "Frozen (2013).m4v" --dry-run --tmdb-api-key "$TMDB_API_KEY"
```

### Ausgabe-Konvention für `artwork fetch`

- **stdout**: Zielpfad der Fanart-Datei
- **stderr**: Parsing-/Such-Events, Auswahlhinweise, Fehlerdiagnosen
- Ohne `--interactive` wird bei mehrdeutigen Treffern mit Exit-Code `1` abgebrochen
- Mit `--dry-run` wird der Zielpfad ausgegeben, aber keine Datei geschrieben

### Verwendete Dienste

| Dienst | Verwendung | Nutzungsform |
|---|---|---|
| [TMDB](https://www.themoviedb.org) | Filmsuche und Backdrop-Download | Kostenlos, API-Key erforderlich |

## Film-Identifikation: `movie identify`

Mit `movie identify` wird eine Videodatei so umbenannt, dass die TMDB-ID direkt im Dateinamen eingebettet ist. Das Format `[tmdb-<id>]` wird von **Infuse** direkt gelesen und ermöglicht eine zuverlässige Metadaten- und Artwork-Erkennung, ohne dass eine manuelle Zuordnung in Infuse erforderlich ist.

```bash
kinofilm-manager movie identify <video-file> [Optionen]
```

### Filmidentifikation via Videometadaten

Vorhandene Metadaten im Videocontainer werden standardmässig **bevorzugt** für die TMDB-Suche verwendet. Sind in der Datei ein Filmtitel oder ein Erscheinungsjahr hinterlegt (z. B. durch Subler oder andere Werkzeuge), werden diese Informationen gegenüber dem Dateinamen priorisiert. Liefert die Metadaten-basierte Suche keinen Treffer, wird automatisch auf den Dateinamen zurückgefallen.

Das Verhalten lässt sich mit `--lookup-source` steuern:
- `auto` (Standard): Metadaten bevorzugt, Fallback auf Dateiname
- `metadata`: nur Metadaten, kein Dateiname-Fallback
- `filename`: nur Dateiname (bisheriges Verhalten)

### Beispiele

```bash
# Datei automatisch identifizieren und TMDB-ID anhängen
kinofilm-manager movie identify 'Wir Kinder aus Bullerbü (1986).m4v' --tmdb-api-key "$TMDB_API_KEY"
# → Wir Kinder aus Bullerbü (1986) [tmdb-21394].m4v

# Direkter Lookup per TMDB-ID (umgeht die Titelsuche)
kinofilm-manager movie identify 'Film (1987).m4v' --tmdb-id 26111 --tmdb-api-key "$TMDB_API_KEY"

# Mehrdeutige Treffer interaktiv auswählen
kinofilm-manager movie identify 'Pippi Langstrumpf (1969).m4v' --interactive --tmdb-api-key "$TMDB_API_KEY"

# Nur Zielpfad anzeigen, nicht umbenennen
kinofilm-manager movie identify 'Film (2000).m4v' --dry-run --tmdb-api-key "$TMDB_API_KEY"

# Nur Dateinamen für die Suche verwenden (Metadaten ignorieren)
kinofilm-manager movie identify 'Film (2000).m4v' --lookup-source filename --tmdb-api-key "$TMDB_API_KEY"
```

Wenn bereits ein `[tmdb-xxxx]`-Suffix im Dateinamen vorhanden ist, wird er **ersetzt**, nicht doppelt angehängt.

### Optionen

| Option | Standard | Beschreibung |
|---|---|---|
| `--title TEXT` | aus Dateiname | Alternativer Filmtitel |
| `--year INTEGER` | aus Dateiname | Erscheinungsjahr; nur zusammen mit `--title` |
| `--tmdb-id INTEGER` | – | Direkter TMDB-ID-Lookup; umgeht die Titelsuche. Kann nicht zusammen mit `--title`/`--year` verwendet werden |
| `--lookup-source TEXT` | `auto` | Quelle für die Filmidentifikation: `auto` (Metadaten bevorzugt, dann Dateiname), `metadata` (nur Metadaten) oder `filename` (nur Dateiname) |
| `--dry-run` / `--no-dry-run` | `false` oder `config`-Wert | Zeigt Zielpfad, benennt aber nicht um |
| `--language TEXT` | – | Suchsprache für TMDB, z.B. `de-DE` oder `en-US` |
| `--provider TEXT` | `tmdb` oder `config`-Wert | Provider; aktuell wird nur `tmdb` unterstützt |
| `--tmdb-api-key TEXT` | `config`-Wert | TMDB API Key |
| `--interactive` / `--no-interactive` | `false` | Erlaubt eine manuelle Auswahl bei mehrdeutigen Treffern |

### Ausgabe-Konvention für `movie identify`

- **stdout**: Neuer Dateipfad nach der Umbenennung
- **stderr**: Such-Events, Auswahlhinweise, Fehlerdiagnosen
- Ohne `--interactive` wird bei mehrdeutigen Treffern mit Exit-Code `1` abgebrochen
- Mit `--dry-run` wird der Zielpfad ausgegeben, aber die Datei nicht umbenannt

## Persistente Konfiguration

Persistente CLI-Defaults werden XDG-konform in `~/.config/kinofilm-manager/config.toml`
gespeichert. Falls `XDG_CONFIG_HOME` gesetzt ist, wird stattdessen
`$XDG_CONFIG_HOME/kinofilm-manager/config.toml` verwendet.

### Unterstützte Konfigurationsschlüssel

| Schlüssel | Typ | Beschreibung |
|---|---|---|
| `bitrate` | String | Default für `--bitrate`, z.B. `448k` oder `640k` |
| `dry_run` | Boolean | Default für `--dry-run` / `--no-dry-run` |
| `verbose` | Boolean | Default für `--verbose` / `--no-verbose` |
| `artwork_provider` | String | Default für `artwork fetch --provider` (`tmdb`) |
| `tmdb_api_key` | String | Default für `artwork fetch --tmdb-api-key` |

### CLI-Befehle

```bash
# Alle unterstützten Schlüssel und aktuelle Werte anzeigen
kinofilm-manager config list

# Einzelnen Wert setzen
kinofilm-manager config set bitrate 448k
kinofilm-manager config set dry_run false
kinofilm-manager config set artwork_provider tmdb
kinofilm-manager config set tmdb_api_key "<API_KEY>"

# Einzelnen Wert lesen
kinofilm-manager config get bitrate
```

### Beispiel-Konfiguration

```toml
bitrate = "448k"
dry_run = false
verbose = true
artwork_provider = "tmdb"
tmdb_api_key = "your-key"
```

CLI-Optionen überschreiben persistierte Defaults immer explizit pro Aufruf. Die
Bibliotheks-API liest **keine** globale Konfiguration; technische Laufzeitoptionen werden
explizit als `RuntimeOptions` übergeben.

### Ausgabekonvention

Gemäss den Architekturprinzipien gilt:
- **stdout**: Ergebnis-Pfad der konvertierten Datei (pipeline-freundlich)
- **stderr**: Stream-Analyse, Fortschritt, Warnungen (Rich-formatiert)
- `--verbose`: Zusätzliche Diagnoseinformationen (FFmpeg-Befehl) auf stderr

---

## API-Nutzung

kinofilm-manager kann auch als Python-Bibliothek verwendet werden:

```python
from pathlib import Path
from kinofilm_manager.api import ConvertRequest, RuntimeOptions, convert

request = ConvertRequest(
    input_path=Path("Film.mkv"),
)
runtime_options = RuntimeOptions(
    dry_run=True,
    bitrate="640k",
)
result = convert(request, runtime_options=runtime_options)

if result.success:
    print(f"Konvertiert: {result.converted_count} Spuren")
    for stream in result.streams:
        print(f"  #{stream.index}: {stream.codec_name} → {stream.action}")
```

### Event-Callbacks

Für lang laufende Konvertierungen können strukturierte Fortschrittsereignisse und
FFmpeg-Textausgabe abonniert werden:

```python
from kinofilm_manager.api import ConvertEvent, ConvertRequest, RuntimeOptions, convert

def on_event(event: ConvertEvent) -> None:
    print(f"[{event.stage_id}] {event.message}")

def on_output(line: str) -> None:
    print(f"  ffmpeg: {line}")

result = convert(
    ConvertRequest(input_path=Path("Film.mkv")),
    on_event=on_event,
    on_output=on_output,
)
```

Stabile `stage_id`-Werte: `analyse_start`, `analyse_done`, `konvertierung_start`, `konvertierung_done`.

### Laufzeitoptionen und Konfiguration

`ConvertRequest` beschreibt nur den fachlichen Auftrag (`input_path`, optional `output_path`).
Technische Laufzeitoptionen wie Bitrate, Dry-Run oder Pfade zu externen Werkzeugen werden getrennt über
`RuntimeOptions` übergeben. Die CLI liest dafür auf Wunsch persistente Defaults aus
der XDG-Konfiguration und reicht sie explizit an die API weiter.

```python
runtime_options = RuntimeOptions(
    bitrate="1536k",
    dry_run=False,
    ffmpeg_path="/opt/homebrew/bin/ffmpeg",   # Standard: Auflösung via PATH
    ffprobe_path="/opt/homebrew/bin/ffprobe",
)
```

---

## Fachlogik

### 1. DTS-Erkennung

DTS-Spuren werden über `codec_name` und `profile` aus den ffprobe-Metadaten erkannt:

- `dts` (DTS Core)
- `dts-hd` / `dtshd`
- Profile wie `DTS-HD HRA`, `DTS-HD MA`

### 2. Duplikat-Erkennung: DTS überspringen wenn E-AC3/AC3 mit gleicher Kanalanzahl vorhanden

Viele MKV-Dateien enthalten bereits mehrere Audiospuren pro Sprache. Eine DTS-Spur wird nur dann **übersprungen**, wenn bereits eine **E-AC3- oder AC3-Spur gleicher Sprache und mindestens gleicher Kanalanzahl** vorhanden ist.

Eine vorhandene E-AC3 **Stereo** (2.0) Spur ist **kein** Ersatz für eine DTS **5.1** Spur – der Mehrkanal-Surround-Ton würde verloren gehen. Ebenso ist eine E-AC3 **5.1**-Spur kein Ersatz für eine DTS **7.1**-Spur – da E-AC3 nativ 7.1 unterstützt, wird DTS 7.1 erst übersprungen, wenn bereits E-AC3 7.1 gleicher Sprache vorhanden ist.

### 3. Zielformat: E-AC3 (Dolby Digital Plus)

E-AC3 (Dolby Digital Plus) unterstützt nativ bis zu **7.1 (8 Kanäle)** – ein 7.1→5.1 Downmix ist nicht mehr nötig. Die Kanalanzahl der Quelle wird direkt übernommen:

| Quell-Kanalanzahl | Zielformat | Automatische Bitrate |
|---|---|---|
| Mono (1ch) | E-AC3 Mono | 192k |
| Stereo (2ch) | E-AC3 Stereo | 192k |
| 5.1 (6ch) | E-AC3 5.1 | 640k |
| 7.1 (8ch) | E-AC3 7.1 | 1536k |

Für andere Kanalanzahlen wird die Default-Bitrate von 640k verwendet.

### 4. Tonversatz (Audio Delay)

MKV-Dateien können pro Spur einen `start_time`-Wert enthalten. FFmpeg erhält `-avoid_negative_ts make_zero` für korrekte Übertragung.

### 5. Stream-Metadaten

Alle Stream-Metadaten (Sprache, Titel) werden explizit pro Stream übertragen. Für konvertierte DTS-Spuren wird der Titel automatisch neu generiert (z.B. `E-AC3 7.1 (von DTS-HD MA 7.1)`).

### 6. Untertitel-Kompatibilität

Inkompatible Untertitel-Streams (z.B. `dvd_subtitle`) werden erkannt und weggelassen. MKV-kompatible Formate (SRT, ASS, PGS, etc.) werden unverändert kopiert.

**PGS-Untertitel und OCR:** Blu-ray PGS-Untertitel werden als Bitmap ins MKV kopiert. Eine integrierte OCR-Lösung wurde evaluiert, wird aber bewusst nicht umgesetzt. Das Tool bleibt im Scope der **Audio-Konvertierung**. Subler übernimmt im nachgelagerten Schritt die OCR-Konvertierung der PGS-Untertitel beim Muxing zu M4V – mit guter Qualität.

---

## Bekannte Einschränkungen

- **DVD-Untertitel** werden weggelassen. Workaround: In Subler als Bitmap importieren.
- **Qualitätsverlust** – DTS→E-AC3 ist verlustbehaftet. Für audiophile Ansprüche bleibt MKV mit DTS-HD das bessere Archivformat.

---

## Weiterführender Workflow

Nach der Konvertierung empfiehlt sich folgender Workflow in **Subler**:

1. Konvertierte MKV in Subler öffnen
2. Gewünschte Spuren auswählen (typisch: eine Audiospur pro Sprache)
3. PGS-Untertitel via Sublers integrierte OCR zu Text konvertieren (falls vorhanden)
4. Metadaten ergänzen (Titel, Jahr, Beschreibung, Artwork)
5. Als M4V exportieren

---

## Änderungsverlauf

### [2.3.0] – 2026-03-19
- Ziel-Audiocodec: DTS/DTS-HD → **E-AC3 (Dolby Digital Plus)** statt AC3
- DTS 7.1 → E-AC3 7.1 direkt (kein Downmix mehr); DTS 5.1 → E-AC3 5.1
- Neue Bitrate für 7.1: 1536k; Duplikat-Erkennung für 7.1 angepasst
- Event-Callbacks (`on_event`, `on_output`) in `convert()`
- `ffmpeg_path`/`ffprobe_path` in `RuntimeOptions` für explizite Tool-Pfade
- Standardmässiger Ausgabedateiname: `<input>_eac3.mkv`

### [2.2.0] – 2026-03-19
- Neuer Befehl `movie identify`: identifiziert einen Film via TMDB und ergänzt `[tmdb-<id>]` im Dateinamen
- Neues Modul `movie_search` mit wiederverwendbarer Film-Such- und Auswahllogik
- `artwork fetch --tmdb-id`: direkter TMDB-ID-Lookup ohne Titelsuche

### [2.1.1] – 2026-03-18
- Fallback-Suche ohne Jahr, wenn Suche mit Jahr keinen Treffer liefert
- Fallback-Suche ohne Untertitel bei fehlendem Treffer
- Ausgabedateiname basiert auf originalem Input-Stem

### [2.1.0] – 2026-03-18
- Neues Subcommand `kinofilm-manager config` mit `set`, `get` und `list`
- Persistente XDG-Konfiguration unter `~/.config/kinofilm-manager/config.toml`
- `RuntimeOptions` trennt CLI-Laufzeitoptionen von `ConvertRequest`

Vollständiger Änderungsverlauf: [CHANGELOG.md](CHANGELOG.md)

---

## Lizenz

MIT
