Melbar 223789eafc Re-match Beat 15 as multi-shot in scene 309
Beat 15 (4.40 s, two women + coffee mugs + snowy window) was matched as a
single 4-second clip at scene 309 in=2626.93 s, score 0.531. The trailer
beat actually contains two shots split by an internal cut at offset 3.12 s,
and the previous match approximated both with the same continuous span,
landing at the wrong phase for the second sub-shot.

After `match --beat 15 --vision` with the per-shot pipeline:
  Shot 1 (offset 0.32, dur 2.80 s): scene 309 in=2626.929 score 0.531
  Shot 2 (offset 3.12, dur 1.20 s): scene 309 in=2607.254 score 0.608

Same scene, but the second shot is now placed earlier in the source where
the leopard-print woman enters the frame — the right phase for that sub-
shot. Other beats unchanged (0 diffs verified against bak).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-05 10:04:56 +02:00
2026-05-02 09:07:41 +02:00
2026-05-02 09:07:41 +02:00
2026-05-02 09:07:41 +02:00
2026-05-02 09:07:41 +02:00

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.

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 im Source 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.

Wichtig: Auch wenn Vision aktiviert ist — der finale Match bleibt CV-verifiziert. Das LLM liefert nur zusätzliche Suchanker.


Voraussetzungen

  • Python 3.11+
  • ffmpeg 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

# Im Projektordner
python -m venv .venv
.\.venv\Scripts\Activate.ps1

# Falls ExecutionPolicy blockiert:
# Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser

2. Abhängigkeiten installieren

pip install -r requirements.txt

3. API-Key konfigurieren

# .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:

[paths]
source_movie      = "B:/Proxy/DeinFilm_FTR.mp4"
reference_trailer = "F:/Encodings/DeinFilm_Trailer.mp4"

Verwendung

# 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):

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 MAN. 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.

Log-Level

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

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.

[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.

S
Description
Frame-accurate trailer reconstruction via pure Computer Vision
Readme 218 MiB
Languages
Python 98.7%
PowerShell 1.3%