Files
aitrailer/README.md
T
2026-05-09 18:48:24 +02:00

317 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# AI Trailer Generator v2
**Frame-genaues Nachbauen eines Trailers aus dem Quellfilm.**
Du gibst zwei Videos rein — einen Referenz-Trailer und den dazugehörigen
Spielfilm — und bekommst eine fertige FCPXML/EDL für deinen Schnittplatz, die
den Trailer Beat für Beat aus dem Quellfilm nachbaut.
---
## Für den Cutter — was du wirklich brauchst
Du musst dieses Tool **nicht selbst bedienen** und musst **kein Python können**.
Was du bekommst sind zwei Dateien, mit denen du arbeitest:
1. **`CUTTER_REPORT.md`** — die Tabelle für die manuelle Kontrolle und das
Nachschneiden. Pro Beat steht drin:
- der Trailer-Zeitcode (h:mm:ss:ff),
- der vorgeschlagene Source-Zeitcode aus dem Spielfilm,
- ein Status: `OK` (kann übernommen werden), `?` (bitte sichten) oder
`MAN.` (kein Treffer, manuell setzen),
- eine kurze Beschreibung, was im Trailer-Beat zu sehen ist (damit du
die richtige Stelle im Source schneller findest).
2. **`output/*.fcpxml`** und **`output/*.edl`** — die fertige Timeline für
FCP / Premiere / Avid / Resolve. Beats mit Status `OK` sind dort schon
richtig gesetzt; `?` und `MAN.` musst du im NLE prüfen bzw. selbst setzen.
**Workflow-Empfehlung:**
1. Öffne `CUTTER_REPORT.md` und arbeite die Tabelle von oben nach unten ab.
2. Importiere die FCPXML/EDL ins NLE, lade Trailer und Spielfilm dazu.
3. Bei `OK`-Beats nur stichprobenartig sichten.
4. Bei `?`-Beats die beiden Vorschau-Stills im `CUTTER_REPORT.md` direkt
vergleichen (Trailer-Frame neben Source-Frame). Wenn die Bewegungsphase
nicht passt, im NLE den Source-In um wenige Frames vor/zurück verschieben.
5. Bei `MAN.`-Beats selbst die passende Stelle im Spielfilm suchen — die
Beschreibung im Report sagt dir was du suchst.
Für die visuelle Kontrolle ist zusätzlich **`CUTTER_REPORT.html`** relevant:
er enthält die frame-locked Compare-Clips. Der alte `match_report.html` ist
nicht mehr Teil des Workflows.
Alles andere unten ist Hintergrund für den Tool-Verantwortlichen.
---
## Wie das Tool die Treffer findet (Kurzfassung)
| Phase | Was passiert |
|-------|--------------|
| **0** | Trailer in Beats zerlegen (PySceneDetect). |
| **1** | Schneller Vibe-Check: für jeden Beat die Top-K ähnlichsten Szenen aus dem Spielfilm vorauswählen (Histogramm + pHash). |
| **2** | Optional: Vision-LLM beschreibt unsichere Szenen mit 3-Frame-Samples; die Beschreibungen liegen gecached vor. |
| **3** | Frame-genaue Verfeinerung pro Beat (OpenCV-Templatematching, Bewegungsphasen-Vergleich). |
| **4** | Phasen-Reparatur: bei segmentierten Beats wird die Bewegungsphase lokal um den gefundenen Inpoint saliency- und motion-gewichtet mit der sichtbaren Trailerphase abgeglichen. |
| **5** | Recovery: Beats ohne Treffer werden via Vision-Phasensuche in den Top-K Szenen nochmal probiert. |
| **6** | Export als FCPXML 1.10 oder CMX-3600-EDL plus `CUTTER_REPORT.md`. |
**Text-Safe Crop:** Obere 15 % und untere 30 % jedes Frames werden vor dem
Vergleich ausgeblendet, damit Title-Cards, Logos und Letterbox die Treffer
nicht verfälschen.
**Cutter-Report-Caching:** Vorhandene Compare-Clips werden wiederverwendet.
Bei gezielten Rematches wird nur der betroffene Beat neu gerendert, damit der
Report schnell aktuell bleibt und keine unnötigen Videoartefakte neu entstehen.
**Wichtig:** Auch wenn Vision aktiviert ist — der finale Match bleibt
CV-verifiziert. Das LLM liefert nur zusätzliche Suchanker.
---
## Voraussetzungen
- Python **3.11+**
- [ffmpeg](https://ffmpeg.org/download.html) im PATH (für Whisper Audio-Extraktion)
- CUDA-fähige GPU empfohlen (für faster-whisper; CPU funktioniert auch)
---
## Setup
### 1. Virtual Environment erstellen & aktivieren
```powershell
# Im Projektordner
python -m venv .venv
.\.venv\Scripts\Activate.ps1
# Falls ExecutionPolicy blockiert:
# Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
```
### 2. Abhängigkeiten installieren
```powershell
pip install -r requirements.txt
```
### 3. API-Key konfigurieren
```powershell
# .env aus dem Template kopieren
Copy-Item .env.example .env
# Dann .env öffnen und den echten Key eintragen:
# OPENROUTER_API_KEY=sk-or-v1-...
```
### 4. Videodateien eintragen
`config.toml` öffnen und die Pfade anpassen:
```toml
[paths]
source_movie = "B:/Proxy/DeinFilm_FTR.mp4"
reference_trailer = "F:/Encodings/DeinFilm_Trailer.mp4"
```
---
## Verwendung
```powershell
# Vollständige Pipeline (analyze → match → report → export)
python cli.py run
# Ohne Whisper-Transkription (schneller)
python cli.py run --no-audio
# Ohne LLM-Klassifikation
python cli.py run --no-audio --no-llm
# Schrittweise
python cli.py analyze # Reference Trailer → Beats erkennen
python cli.py match # Globaler FFmpeg Scan (Szenen-unabhängig)
python cli.py report # HTML Report mit Video-Vergleich bauen
python cli.py export --format both # FCPXML + EDL ausgeben
# Gezielt nur einen Beat bearbeiten (empfohlen für erste Iterationen)
python cli.py match --beat 5
python cli.py match --beat 5 --vision # optionale gecachte Vision-Seeds
python cli.py report --beat 5
python cli.py export --beat 5 --format both
# Fehlerhafte Matches korrigieren
python cli.py rematch --beat 5 --threshold 0.50 # Schwelle anpassen (Globaler Scan wird für diesen Beat wiederholt)
python cli.py rematch --beat 5 --refine # Cached Match per lokalem Bildinhalt-Offset nachschärfen
```
### Cutter-Report neu erzeugen
`CUTTER_REPORT.md` wird bei jedem `match`-Lauf automatisch geschrieben.
Manuell neu erzeugen (z. B. nach Edit eines einzelnen Beats):
```powershell
python scripts/generate_cutter_report.py # mit Vorschau-Stills
python scripts/generate_cutter_report.py --no-stills # nur die Tabelle
```
Stills landen unter `output/cutter_stills/` und werden nur neu gerendert,
wenn sich das zugrundeliegende Match geändert hat.
### Wenn ein Match falsch wirkt
| Symptom | Was tun |
|---------|---------|
| Source-Clip zeigt richtige Szene, aber falsche Bewegungsphase | `python cli.py rematch --beat N --refine` — schiebt den Inpoint frame-genau aus dem Bildinhalt. |
| Score zu niedrig, andere Szene wäre richtig | `python cli.py match --beat N --vision` — vollständiger Re-Match nur für diesen Beat mit Vision-Phasenprüfung. |
| Match offensichtlich falsche Szene | `python cli.py rematch --beat N --threshold 0.50` — Schwelle absenken, neuer globaler Scan nur für diesen Beat. |
| Beat ist Schwarzbild / Logo / Titel und sollte gar nicht matchen | nichts tun, der Status `GFX` im `CUTTER_REPORT.md` ist korrekt. |
### Algorithmische Details
Tiefer in die Matcher-Logik (Phasen-Reparatur, segmentierte Beats,
Vision-Seeds, Recovery-Stufe usw.) — siehe [`docs/ALGORITHM.md`](docs/ALGORITHM.md).
### Log-Level
```powershell
python cli.py run --log-level DEBUG
```
---
## Projektstruktur
```
ai_trailer_2026/
├── config.toml ← Alle Parameter (kein Hardcoding im Code)
├── .env ← API-Keys (NICHT commiten)
├── cli.py ← Einstiegspunkt
├── src/
│ ├── core/
│ │ ├── config.py load_config() → AppConfig (frozen dataclasses)
│ │ └── models.py Scene, TrailerBeat, VibeHit, MatchResult, EditTimeline
│ ├── cv/
│ │ ├── fingerprinting.py Text-Safe Crop · HS-Histogramme · pHash
│ │ ├── vibe_check.py Phase 1: Histogram+pHash Filter
│ │ ├── scene_indexer.py PySceneDetect → Fingerprint → JSON-Cache
│ │ ├── frame_extractor.py VideoCapture-Wrapper
│ │ └── deep_scan.py Phase 2: Coarse+Refine Template-Matching
│ ├── audio/
│ │ └── transcriber.py faster-whisper Transkription
│ ├── llm/
│ │ ├── dramaturg.py OpenRouter → BeatType (Dialog/Dramaturgie)
│ │ └── vision_cache.py optionale gecachte 3-Frame Vision-Seeds
│ ├── pipeline/
│ │ ├── trailer_analyzer.py Reference-Trailer → TrailerBeat[]
│ │ └── matcher.py Orchestrierung + EditTimeline-Builder
│ └── export/
│ ├── timecode.py Sekunden ↔ FCPXML-Rational ↔ SMPTE
│ ├── fcpxml_writer.py FCPXML 1.10
│ └── edl_writer.py CMX 3600 EDL
├── output/ ← FCPXML/EDL Output (gitignored)
├── .cache/ ← Szenen-Index + Match-Ergebnisse (gitignored)
└── tests/ 52 Unit-Tests (pytest)
```
---
## Cache-Verhalten
Damit nicht bei jedem Lauf der gesamte Quellfilm neu analysiert werden muss:
| Datei | Inhalt | Neu bauen mit |
|-------|--------|---------------|
| `.cache/scene_index.json` | Alle Quellfilm-Szenen + Fingerprints | `--force-reindex` |
| `.cache/trailer_beats.json` | Erkannte Trailer-Beats | `python cli.py analyze` erneut |
| `.cache/match_results.json` | CV-Matching-Ergebnisse | `python cli.py match` erneut |
| `.cache/vision_descriptions.json` | Optionale 3-Frame Vision-Beschreibungen für Beats/Szenen | löschen oder anderes Vision-Modell konfigurieren |
---
## Tests
```powershell
pytest tests/ -v
```
Alle Tests laufen ohne echte Videodateien (synthetische Frames via numpy/OpenCV).
---
## Konfiguration (Auszug)
Alle Werte in `config.toml` — keine hardgecodeten Konstanten im Code.
```toml
[cv.vibe_check]
top_k_candidates = 10 # Top-K Kandidaten für Deep Scan
phash_max_distance = 12 # Hamming-Distanz Schwelle (064)
crop_top_fraction = 0.15 # Obere 15% ausblenden (Logos)
crop_bottom_fraction = 0.30 # Untere 30% ausblenden (Letterbox/Subs)
[cv.deep_scan]
coarse_step_seconds = 0.5 # Scan-Schrittgröße (Coarse Pass)
match_threshold = 0.65 # Mindestscore für bestätigte automatische Matches
provisional_match_threshold = 0.43 # Niedrigere automatische Kandidaten im Report zeigen
coarse_candidate_threshold = 0.50 # Niedrigeres Gate vor Multi-Frame-Refine
refine_window_seconds = 0.6 # Suchfenster für framegenaue Inpoint-Feinjustage
refine_step_seconds = 0.04 # ~1 Frame bei 25fps (Refine Pass)
content_align_window_seconds = 0.48 # Lokales Suchfenster um einen groben Treffer
content_align_sample_step_s = 0.28 # Referenzframes für direkten Bildinhalt-Offset
content_validation_weight = 0.35 # Gewicht der festen Whole-Frame-/Spatial-Endprüfung
provisional_content_threshold = 0.42 # Untergrenze für Report-/Cache-Kandidaten
start_tie_break_score_delta = 0.015 # Bei fast gleichen Scores früheren Inpoint wählen
start_preroll_frames = 0 # Kein pauschaler Start-Ausgleich; Offset kommt aus Bildinhalt
sequence_candidate_count = 240 # Breiter Kandidatenpool vor Inhalts-Reranking
max_refine_candidates = 6 # Teurer Frame-Refine läuft nur auf den besten Inhaltskandidaten
scene_seed_top_k = 30 # Scene-Level-Kandidaten als zusätzliche Suchanker
scene_seed_points_per_scene = 6 # Inpoint-Samples pro Scene-Level-Kandidat
content_rerank_candidate_count = 100 # Grobe Kandidaten vor Inhalts-Reranking
skip_coarse_scan_with_weighted_seeds = false # Vision-Seeds nur als Hinweise; Vollscan bleibt robust
sequence_score_weight = 0.55 # Gewicht für mehrere zeitliche Vergleichsframes
span_score_weight = 0.15 # Gewicht für Stabilität bis zum Beat-Ende
coarse_score_weight = 0.10 # Gewicht des groben Midpoint-Treffers
duration_score_weight = 0.20 # Gewicht für nutzbare Länge des Source-Treffers
duration_tie_break_score_delta = 0.03 # Bei ähnlichem Score längeren Treffer bevorzugen
min_duration_coverage = 0.65 # Treffer muss mindestens 65% des matchbaren Referenzanteils tragen
continuity_seed_offsets_s = [-1.0, 0.0, 0.5, 1.0, 1.5, 2.0, 3.0] # Suchanker um gematchte Nachbarbeats
span_sample_step_s = 0.08 # Schrittweite für End-/Drift-Erkennung
trim_tail_frames = 4 # Sicherheitsabstand gegen kurze Blitzer am Ende
scene_boundary_epsilon_s = 0.12 # Szenengrenzen-Toleranz gegen 1-2 Frame Cut-Drift
scoreable_luma_mean_min = 24.0 # Zu dunkle/Fade-Frames nicht scoren
scoreable_luma_p90_min = 58.0 # Helle Bildanteile müssen sichtbar genug sein
scoreable_contrast_min = 24.0 # Kontrastarme Blenden/Titelinseln ignorieren
[vision]
enabled = false # Kostenkontrolle: per CLI mit --vision aktivierbar
model = "google/gemma-4-31b-it" # Muss ein visionfähiges OpenAI-kompatibles Modell sein
scene_candidate_top_k = 48 # Breiter Vision-Kandidatenpool für schwierige Beats
max_new_descriptions_per_run = 24 # Gecachte Beschreibungen pro Lauf; Rate-Limits bekommen Backoff
max_seed_scenes = 8 # Mehr Vision-Szenen als Suchanker, kein manueller Override
seed_points_per_scene = 12 # Inpoint-Samples pro Vision-Szene
seed_score = 0.88 # Vision-Seeds bekommen mehr Priorität als normale Scene-Seeds
max_refine_candidates = 12 # Vision-Pfad prüft mehrere Bewegungsphasen derselben Szene
local_scan_step_s = 0.12 # Dichte lokale Bildsuche in Vision-Szenen
local_scan_max_points_per_scene = 180 # Laufzeitgrenze pro Source-Szene
local_scan_top_candidates = 36 # Beste lokale Kandidaten gehen ins Refinement
local_scan_tie_break_score_delta = 0.08 # Ähnliche Vision-Treffer: frühere Phase bevorzugen
multi_shot_cut_corr_threshold = 0.20 # Interne Trailer-Umschnitte erkennen
multi_shot_boundary_tolerance_s = 0.20 # Source-Grenze muss zum Trailer-Cut passen
fullscan_fallback = false # Nur relevant, wenn skip_coarse_scan_with_weighted_seeds=true ist
content_threshold = 0.22 # Lockeres Content-Gate nur für gewichtete Vision-Seeds
similarity_threshold = 0.18 # Mindest-Textähnlichkeit für Vision-Seeds
```
---
## Lizenz
Internes Tool — nicht für den öffentlichen Vertrieb.