Melbar 2f8b0585e2 Auto-update cutter report 2026-05-06 10:44
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 10:44:20 +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%