Files
aitrailer/README.md
T

436 lines
23 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-accurate trailer reconstruction via pure Computer Vision**
> Gibt einen Reference Trailer und den dazugehörigen Quellfilm hinein — bekommt eine fertige FCPXML/EDL heraus, die den Trailer Frame-genau aus dem Quellfilm nachbaut.
---
## Das Kernprinzip
Standardmäßig kein LLM für visuelles Matching. Optional kann ein Vision-Layer
gecachte 3-Frame-Beschreibungen als zusätzliche Suchanker liefern; der finale
Match bleibt aber CV-verifiziert.
| Phase | Was passiert | Technologie |
|-------|-------------|-------------|
| **0 — Prep** | Reference Trailer analysieren & Beats extrahieren | PySceneDetect + OpenCV |
| **1 — Global Scan**| Gesamten Quellfilm via FFmpeg-Stream (2 FPS) gegen alle Beats scannen | FFmpeg Pipe + Luma-Histogramm |
| **1b — Optional Vision Seeds** | Unsichere Top-K Szenen mit 3-Frame-Beschreibungen cachen | OpenAI-kompatibles Vision-LLM |
| **2 — Refine** | Beste Treffer auf Frame-Ebene präzisieren | OpenCV `matchTemplate` |
| **3 — Dramaturgie** | Narrative BeatType-Klassifikation aus Dialog-Text | OpenRouter LLM |
| **4 — Export** | Timeline → FCPXML 1.10 oder CMX 3600 EDL | xml.etree + eigener Timecode-Layer |
**Text-Safe Crop:** Obere 15% und untere 30% des Frames werden vor jedem Vergleich ausgeblendet, um Title Cards, Logos und Letterbox zu ignorieren.
---
## 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
```
Der HTML-Report regeneriert seine Preview-Clips bei jedem Lauf mit genauer
FFmpeg-Nachsuche und synchronisiert die beiden Video-Player pro Beat. Dadurch
ist der Report zur Frame-Prüfung geeignet und zeigt keine alten gecachten
Preview-Clips.
Source-Previews bekommen bei Trailer-only-Tails denselben schwarzen Tail wie der
Export, damit der Browser nicht einen zu kurzen Source-Clip gegen den längeren
Referenzbeat weiterspult oder loopt.
Zur Synchronprüfung rendert der Report ein einzelnes Frame-Locked-Compare-Video
mit Referenz und Source in demselben MP4-Stream. Dieses Compare-Video ist
maßgeblich, weil zwei getrennte Browser-Videoelemente nie zuverlässig
framegenau synchron bleiben.
Wenn ein Trailer-Beat am Ende eine Blende, Schwarzfläche oder Textkarte enthält,
die im Source-Film nicht als normaler Shot vorhanden ist, endet der Source-Match
am letzten stabil passenden Frame. Exportierte Timelines behalten trotzdem die
volle Beat-Länge und fügen danach automatisch einen schwarzen Trailer-Tail mit
Marker für Fade/Dissolve ein.
Gezielte Ein-Beat-Matches nutzen zusätzlich vorhandene automatische Nachbarbeats
aus dem Cache als zeitliche Suchanker. Das hilft bei aufeinanderfolgenden Shots,
ohne manuelle Szenen oder Timecodes zu kuratieren.
Bei `match --beat N` wird ein alter Cache-Treffer für genau diesen Beat entfernt
und nur ein neu gefundener automatischer Treffer wieder eingetragen. Ein
fehlgeschlagener neuer Lauf kann dadurch keinen alten falschen Report-Treffer
stehen lassen.
Der globale Bildvergleich arbeitet auf kontrast-normalisierten Luma- und
Kantenfeatures statt auf rohen Farb-Pixeln. Dadurch bleiben Schwarzweiß- oder
anders gegradete Trailerbilder mit dem Source-Material vergleichbar, während
unähnliche Farbshots schlechter ranken.
Die Inpoint-Feinjustage bestimmt den Versatz lokal aus dem Bildinhalt: Um einen
groben Treffer herum werden mehrere Referenzframes gegen mehrere Source-Offsets
verglichen, und der beste gemeinsame Offset wird übernommen. Das ist schneller
als ein erneuter globaler Scan und vermeidet pauschale Frame-Prerolls.
Schwarze Referenzframes aus Blenden oder Titel-Tails werden für diese
Offset-Messung ausgelassen, damit echte Bildbewegung und nicht die Blende selbst
den Inpoint bestimmt.
`rematch --refine` nutzt denselben lokalen FFmpeg/Pillow-Aligner und schreibt
den korrigierten Inpoint direkt zurück in `.cache/match_results.json`.
Zusätzlich werden aus den besten szenenweiten Luma/Histogramm-Kandidaten
mehrere Inpoint-Suchanker erzeugt. Diese Scene-Seeds verwenden keine harte
pHash-Sperre, weil pHash bei stark anders gegradeten Trailerbildern echte
Matches zu früh ausschließen kann.
Optional kann `python cli.py match --beat N --vision` einen Vision-Layer
zuschalten. Dann werden pro Trailer-Beat und pro wenigen Scene-Level-Kandidaten
je drei Frames (Anfang, Mitte, Ende) von einem visionfähigen OpenAI-kompatiblen
Modell beschrieben. Die Beschreibungen liegen in
`.cache/vision_descriptions.json` und werden wiederverwendet. Vision erzeugt
nur zusätzliche Suchanker; der eigentliche Match muss weiterhin durch CV,
Content-Reranking, Timing und Duration-Coverage bestätigt werden.
Gecachte Szenenbeschreibungen zählen nur, wenn sie vom aktuell konfigurierten
Vision-Modell stammen. Bei langen semantisch passenden Source-Szenen beschreibt
der Vision-Layer zusätzlich wenige lokale Zeitfenster und cached auch diese
Fenster, damit eine grob ähnliche Szene nicht automatisch mit dem falschen
Bewegungs- oder Dialogmoment gleichgesetzt wird.
Der gewichtete Vision-Seed-Pfad ersetzt standardmäßig keinen normalen
FFmpeg-Vollscan. Vision-Beschreibungen sind semantische Hinweise, aber keine
Beweise; der volle CV-Scan bleibt deshalb aktiv, damit falsch bewertete
Vision-Szenen echte Treffer nicht verdrängen. Für schnelle Experimente kann
`skip_coarse_scan_with_weighted_seeds = true` gesetzt werden.
Gewichtete Vision-Seeds werden nicht zuerst durch den alten Midpoint-Template
Refine verschoben; sie gehen direkt in die lokale Content-Alignment-Prüfung.
Das schützt wiederholte Gesprächseinstellungen, bei denen ähnliche Momente
mehrfach in derselben Szene vorkommen.
Innerhalb der automatisch von Vision vorgeschlagenen Szenen läuft zusätzlich
eine dichte lokale Bildsequenzsuche. Sie misst den Phasenversatz in kleinen
Zeitschritten direkt am Bildinhalt und bevorzugt Kandidaten mit genügend
Restdauer in derselben Source-Szene. Das ist kein manueller Override: Vision
grenzt nur Suchbereiche ein, die Auswahl bleibt Content-, Timing- und
Coverage-getrieben.
Nach einem dichten Vision-Treffer darf der spätere lokale Aligner nur noch im
Bereich dieses Scan-Schritts nachjustieren. So kann ein korrekt gefundener
Bewegungsmoment nicht wieder um viele Frames in eine ähnlich aussehende Phase
derselben Szene verschoben werden.
Wenn mehrere Vision-Kandidaten in derselben Source-Szene ähnlich gut scoren
und die Beat-Dauer abdecken, bevorzugt der Matcher die frühere Phase. Das
verhindert, dass ein späterer, minimal stärkerer Standbildtreffer die
Bewegungsphase des Trailers sichtbar überholt.
Enthält ein Trailerbeat selbst einen harten Umschnitt, werden Kandidaten an
angrenzenden Source-Szenengrenzen zusätzlich als zusammenhängender Multi-Shot-
Span geprüft. Ein Match darf dann über eine Source-Szenengrenze laufen, aber
nur wenn die relative Source-Grenze zeitlich zu einem erkannten Trailer-Umschnitt
passt. So kann ein Beat aus Frage/Antwort-Shots vollständig erfasst werden,
ohne Szenen willkürlich zusammenzukleben.
Auch der lokale Content-Aligner darf einen Inpoint nur noch übernehmen, wenn
die feste Whole-Frame-/Spatial-Validation dadurch besser wird.
Vor dem teuren Frame-Refine wird der gesamte Kandidatenpool mit einer schnellen
festen Inhaltsprüfung neu sortiert. Dadurch können korrekte Treffer aus
wiederholten Einstellungen einer Szene nach oben kommen, auch wenn ein freier
Template-Peak an anderer Stelle numerisch stärker war. Suchanker bleiben im
Pool erhalten, dürfen aber erst nach der Inhaltsprüfung nach oben rücken. Wenn
ein Kandidat visuell plausibel ist, aber wegen Trailerblende oder kurzem
Source-Span die normale Coverage knapp verfehlt, wird er als provisional Match
behalten statt als `NO MATCH` verworfen.
Dieses Reranking berücksichtigt zusätzlich die verbleibende Szenenlänge ab dem
Kandidaten-Inpoint. Dadurch werden zu späte ähnliche Gesprächsphasen innerhalb
derselben Szene nicht mehr vor frühere, tragfähigere Phasen sortiert.
Das Inhalts-Reranking nutzt bewusst nur wenige repräsentative Referenzframes und
eine begrenzte Kandidatenzahl. So bleiben wiederholte Szenen auffindbar, ohne
dass der Lauf durch tausende Random-Seeks minutenlang festhängt.
Confirmed Matches werden zusätzlich durch eine feste nahezu-Whole-Frame-Prüfung
aus Luma, Kanten, Farbhistogramm und räumlichen 4x4-Farbhistogrammen gedeckelt.
Dadurch kann ein freier Template-Hit mit ähnlicher Fenster-/Gesichtsstruktur
nicht mehr als sicherer Match gelten, wenn die Gesamtkomposition oder die
Bewegungsphase sichtbar eine andere Szene ist.
Für gewichtete Vision-Kandidaten gibt es zusätzlich eine eigene Provisional-
Bewertung aus Content-Score, Restdauer und Seed-Stärke. Dadurch können echte,
aber durch Trailer-Grading/Crop numerisch schwache Treffer im Report landen,
ohne als confirmed Match durchzugehen.
Die Cache-Normalisierung für Report/Export verwendet dieselbe niedrigere
Content-Untergrenze für nicht bestätigte Vision-Provisional-Treffer, damit ein
gerade gefundener automatischer Match nicht beim Report-Aufbau wieder
weggefiltert wird.
Sie übernimmt auch die Multi-Shot-Coverage-Regel: gecachte Treffer, die passend
zu internen Trailer-Umschnitten über angrenzende Source-Szenen laufen, werden
nicht mehr auf die erste Source-Szene zurückgekürzt.
Gezielte Einzel-Beat-Matches gewichten außerdem die automatisch aus Nachbarbeats
abgeleiteten Continuity-Seeds. Wenn ein Beat direkt an einen bereits passenden
Vorgänger anschließt, kann ein späterer ähnlich aussehender Moment derselben
Dialogszene den erwarteten Anschluss nicht mehr nur wegen eines höheren
Standbildscores verdrängen.
Diese Continuity-Seeds sind aber nur Suchanker: in derselben Szene darf ein
späterer Inpoint gewinnen, wenn die mehrframeige Content-Prüfung die
Bewegungsphase klar besser trifft. Dadurch bleiben Anschlussmatches stabil,
ohne Hand-/Kopfbewegungen auf einen falschen Zeitpunkt festzunageln.
Continuity- und Vision-Seeds allein schalten den globalen FFmpeg-Scan
standardmäßig nicht ab. Sie sind Suchanker, keine Beweise; der volle CV-Scan
bleibt aktiv, damit semantisch plausible, aber falsche Vision-Treffer echte
Bildmatches nicht verdrängen.
Bei aktivierter Vision wird für gezielte Match-Läufe trotzdem zuerst ein
schneller seed-basierter CV-Prepass ausgeführt. Er überspringt den vollen
FFmpeg-Stream nur vorläufig und akzeptiert einen Treffer erst nach derselben
Bild-/Phasenvalidierung wie der normale Matcher. Nur nicht gelöste Beats fallen
danach auf den vollständigen Scan zurück. Die Qualitätsparameter für lokale
Vision-Szenenscans und Refine-Kandidaten bleiben dabei erhalten; der Prepass ist
eine Reihenfolge-Optimierung, kein Qualitätsdeckel.
Provisional Treffer aus diesem schnellen Prepass sind nicht endgültig: wenn sie
unterhalb der Confirmed-Schwelle bleiben, läuft zusätzlich der vollständige
CV-Scan und darf den besseren oder bestätigten Treffer übernehmen.
OpenRouter-/Vision-Rate-Limits werden mit progressiv längeren Pausen erneut
versucht. Billing-, Credit- oder Token-Guthaben-Fehler werden dagegen sofort als
echter Blocker gemeldet, weil Warten dort nicht hilft.
Lange Trailerbeats werden nicht mehr automatisch über ihre gesamte Beat-Länge
gegen einen einzigen Source-Clip validiert. Sobald nach einem sichtbaren
Source-Abschnitt eine anhaltende Schwarzblende oder Titel-/Credit-Insel beginnt,
endet der matchbare Referenzbereich dort; zwei aufeinanderfolgende dunkle
Samples reichen dafür. Spätere Text-/Creditbilder im selben Beat gehen damit
nicht mehr in Reranking, Validation oder Span-Schätzung ein.
Wenn nach einer Blende wieder sichtbares, matchbares Material kommt, wird der
Beat nicht mehr als „Source + schwarzer Tail“ behandelt. Der CLI-Match speichert
zusätzliche `MatchSegment`-Einträge für jede automatisch erkannte sichtbare
Insel; der HTML-Report setzt diese Source-Segmente frame-lockend zusammen und
füllt nur echte Zwischenlücken mit Schwarz. Dadurch können per Blende verbundene
Trailer-Einstellungen innerhalb eines Beats getrennt gematcht werden, ohne die
globale Scene Detection aggressiver oder beat-spezifisch zu kuratieren.
Beats mit mehreren sichtbaren Inseln werden direkt segmentiert gesucht, statt
zuerst als ein künstlich zusammenhängender Source-Clip über den ganzen Film zu
laufen. Jede Insel nutzt dieselbe gestufte Vision-/CV-Validierung wie ein
normaler Beat; der zusammengesetzte Report bleibt beat-synchron. Wenn der
schnelle validierte Vision-Prepass für eine Insel keinen Treffer liefert, darf
diese Insel weiterhin in den vollständigen Scan fallen.
Falls ein kompletter Beat keinen belastbaren Einzelclip ergibt, versucht der
Matcher dieselbe Segmentlogik automatisch als Fallback: sichtbare Inseln werden
einzeln global gesucht und anschließend wieder zu einem Beat-Ergebnis
zusammengesetzt. Sehr kurze Inseln dürfen zusätzlich in den Source-Szenen
benachbarter bereits gematchter Beats lokal nach ihrer Bewegungsphase suchen.
Das ist weiterhin nur ein allgemeiner Continuity-Anker, kein manueller Override
für bestimmte Beat-Nummern oder Szenen.
Besteht ein Beat nach automatischer Fade-/Titel-Filterung nur aus einer
einzigen sichtbaren Insel, wird diese Insel direkt als primäres Suchziel
verwendet. Dadurch scannt der Matcher denselben Bildinhalt nicht erst als
vollen Beat und danach noch einmal als Segment; der Report behält trotzdem die
korrekte Beat-Position und füllt echte Randlücken mit Schwarz.
Gecachte segmentierte Treffer werden ebenfalls gegen die automatisch sichtbare
Referenzdauer normalisiert, nicht gegen Schwarz-/Blendränder des gesamten Beats.
Ein korrekt gematchter kurzer Bildinhalt wird dadurch beim Report-Aufbau nicht
nachträglich als zu kurz verworfen.
Zusätzlich werden sehr dunkle, kontrastarme oder noch nicht sauber
auf-/abgeblendete Referenzframes aus Score, Inhalts-Reranking,
Phasen-Alignment und Motion-Templates herausgenommen. Blenden sollen bestimmen,
wie der Clip später exportiert wird, aber nicht, ob der Bildinhalt als Match
gilt.
Sichtbare Fade-Rampen werden nur in eine matchbare Insel hinein erweitert, wenn
sie strukturell stark zum ersten bzw. letzten scorebaren Frame derselben
Einstellung passen. Doppelbelichtungen aus Cross-Dissolves bleiben dadurch
Übergangsmaterial und werden nicht als einzelner Quellclip erzwungen.
Treffer unter `provisional_content_threshold` werden gar nicht mehr gespeichert
oder aus alten Cache-Ergebnissen übernommen. Das verhindert, dass offensichtlich
falsche Szenen im Report als Match-Kandidat weiterleben.
### 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.