Slim README, move algorithm prose to docs, add stills + per-fps TC to cutter report
README: 550 -> 308 lines. The dense algorithm prose was moved verbatim to docs/ALGORITHM.md and replaced in the README with a compact "Wenn ein Match falsch wirkt" troubleshooting table and a link. The cutter-facing intro points at the new in-report stills instead of the old HTML report. Cutter report: - Per-side frame rates: trailer timecodes use the trailer file's fps (typically 25), source timecodes use the source file's fps. ffprobe is used to detect each side; falls back to edl_frame_rate if unavailable. - Side-by-side trailer/source preview stills extracted via ffmpeg, taken ~30% into the beat / match window. Stored under output/cutter_stills/ (gitignored). Re-rendered only when the underlying video is newer than the cached jpg. - Compact table at the top, detailed per-beat sections below with the stills inline so the cutter can sight-check phase agreement directly. - New --no-stills flag for fast text-only regeneration. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,298 @@
|
||||
# Algorithmus-Notizen
|
||||
|
||||
Detaillierte Verhaltensbeschreibung und Designentscheidungen des Matchers.
|
||||
Für die normale Bedienung reicht die [README](../README.md) — dieses Dokument
|
||||
ist die Referenz für den Tool-Verantwortlichen, wenn etwas im Verhalten
|
||||
unklar wird oder ein Match systematisch falsch landet.
|
||||
|
||||
## HTML-Report und Vorschauclips
|
||||
|
||||
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.
|
||||
|
||||
## Trailer-Tails ohne Source-Pendant
|
||||
|
||||
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.
|
||||
|
||||
## Targeted single-beat re-matches
|
||||
|
||||
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.
|
||||
|
||||
## Bildvergleich auf Luma + Kanten
|
||||
|
||||
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.
|
||||
|
||||
## Bewegungsphasen-Vergleich
|
||||
|
||||
Zusätzlich wird die Bewegungsphase über Frame-zu-Frame-Differenzen verglichen.
|
||||
Dadurch kann der Matcher innerhalb derselben Source-Szene unterscheiden, ob
|
||||
zwei Figuren noch sprechen, sich annähern, bereits im Kontakt sind oder sich
|
||||
wieder voneinander lösen. Ein optisch ähnlicher Standbild-Treffer reicht damit
|
||||
nicht mehr aus, wenn der Bewegungsverlauf nicht zur Referenz passt.
|
||||
|
||||
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`.
|
||||
|
||||
## Vision-Layer (optional)
|
||||
|
||||
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. Dieser lokale
|
||||
Fenster-Probe ist bewusst breiter als die finale Seed-Auswahl: eine lange
|
||||
Dialogszene kann in der Gesamtbeschreibung nur als Gespräch erscheinen, aber
|
||||
an einer späteren Stelle trotzdem genau die gesuchte Aktionsphase enthalten.
|
||||
|
||||
Für diese Probe wird die grobe Szenenähnlichkeit ohne harte Aktionsstrafe
|
||||
gerankt; die harte Aktionsprüfung greift erst auf den lokalen Fenstern und
|
||||
dem finalen Source-Zeitbereich.
|
||||
|
||||
## Aktionsphase-Verifikation nach dem CV-Match
|
||||
|
||||
Nach dem CV-Match kann derselbe Vision-Layer den konkreten finalen Source-
|
||||
Zeitbereich nochmals gegen den Trailer-Beat prüfen. Starke Aktionsphasen wie
|
||||
Annäherung, Kuss/Stirnkontakt, Handbewegungen oder Schneiden müssen dann auch
|
||||
im Source-Fenster beschrieben sein; fehlt diese Aktionsphase, wird der
|
||||
Treffer nicht gespeichert, selbst wenn der Low-Level-CV-Score hoch ist.
|
||||
|
||||
Wenn die Szene selbst plausibel ist, aber der konkrete Source-Zeitpunkt
|
||||
diese Aktionsphase verfehlt, sucht der Matcher automatisch dichter innerhalb
|
||||
derselben Source-Szene nach lokalen Vision-Fenstern mit der passenden Aktion
|
||||
und richtet den Inpoint mit der Motion-Phase-Prüfung darauf neu aus. Erst
|
||||
wenn auch diese In-Scene-Reparatur scheitert, wird der Treffer verworfen.
|
||||
Diese In-Scene-Reparatur läuft auch für semantisch gültige Treffer aus langen
|
||||
Source-Szenen.
|
||||
|
||||
Die Kandidatenbewertung dieser Reparatur vergleicht zwei Kontexte: den ganzen
|
||||
Beat als semantischen Handlungsrahmen und das konkret sichtbare Beat-Segment
|
||||
als Phasenprüfung. Ein Source-Zeitpunkt muss nicht nur „die Szene mit dem
|
||||
Kuss" enthalten, sondern auch zur aktuellen Bewegungsphase des sichtbaren
|
||||
Trailerabschnitts passen. Pro Kandidat fließt zusätzlich ein lokaler
|
||||
Content-/Motion-Frame-Score ein, damit cached Vision-Beschreibungen keinen
|
||||
sichtbar versetzten Bewegungsmoment überstimmen.
|
||||
|
||||
## Segmentierte Beats (Beats mit Blenden / Inseln)
|
||||
|
||||
Bei blendigen oder segmentierten Beats nutzt die semantische Action-Suche den
|
||||
ganzen Trailerbeat als Kontext. Die eigentliche Frame-Ausrichtung bleibt auf
|
||||
das sichtbare Segment begrenzt; der gefundene Source-Inpoint wird um den
|
||||
Trailer-Offset des Segments verschoben. So geht die globale Aktionsbeschreibung
|
||||
eines Beats nicht verloren, nur weil der scorebare Teil erst nach einer Blende
|
||||
beginnt.
|
||||
|
||||
Die Suche nach diesem Action-Window prüft pro Segment zwei Beschreibungen:
|
||||
zuerst die des konkret sichtbaren Segments (so trifft die Phasensuche genau
|
||||
die gerade gezeigte Bewegung), als Rückfall die des gesamten Beats. Der
|
||||
Beat-Kontext gewinnt nur, wenn er deutlich (>0.06) besser scort; sonst bleibt
|
||||
das Segment-Fenster die Wahl, weil die Beat-Beschreibung Aktionen aus
|
||||
Fade-Bildern mit aufnehmen kann, die im sichtbaren Segment nicht stattfinden.
|
||||
Der Trailer-Offset-Shift wird nur angewendet, wenn tatsächlich der Beat-
|
||||
Kontext benutzt wurde; bei segmentbasierter Wahl ist das gefundene Fenster
|
||||
bereits auf die sichtbare Aktionsphase ausgerichtet.
|
||||
|
||||
Der Segment-Offset zählt nur über vorherige scorebare Bildinseln, nicht über
|
||||
schwarze oder blendige Lücken. Nach dem Retiming wird die nutzbare Source-
|
||||
Dauer erneut geschätzt; läuft die Source am Ende in eine sichtbar andere
|
||||
Aktionsphase, wird der Clip gekürzt und der Rest bleibt Placeholder/Fade
|
||||
statt einen falschen Bewegungsmoment zu zeigen.
|
||||
|
||||
## Vision-Seeds vs. Vollscan
|
||||
|
||||
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 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.
|
||||
|
||||
Für Vision-Action-Fenster nutzt die finale Retiming-Prüfung eine gemeinsame
|
||||
Content-und-Motion-Suche pro Frame. Content und Bewegungsphase werden nicht
|
||||
mehr als zwei getrennte Korrekturschritte angewendet; das verhindert, dass
|
||||
eine kurze Geste erst korrekt erkannt und anschließend in eine spätere
|
||||
ähnliche Körperhaltung verschoben wird. Wenn mehrere Vision-Kandidaten in
|
||||
derselben Source-Szene ähnlich gut scoren und die Beat-Dauer abdecken,
|
||||
bevorzugt der Matcher die frühere Phase.
|
||||
|
||||
## Multi-Shot-Beats
|
||||
|
||||
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.
|
||||
|
||||
## Reranking-Pipeline
|
||||
|
||||
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, nutzt bewusst nur wenige repräsentative Referenzframes
|
||||
und eine begrenzte Kandidatenzahl. Confirmed Matches werden zusätzlich durch
|
||||
eine feste nahezu-Whole-Frame-Prüfung aus Luma, Kanten, Farbhistogramm und
|
||||
räumlichen 4×4-Farbhistogrammen gedeckelt. Auch der lokale Content-Aligner
|
||||
darf einen Inpoint nur noch übernehmen, wenn die feste Whole-Frame-/Spatial-
|
||||
Validation dadurch besser wird.
|
||||
|
||||
Für gewichtete Vision-Kandidaten gibt es zusätzlich eine eigene Provisional-
|
||||
Bewertung aus Content-Score, Restdauer und Seed-Stärke. Die Cache-
|
||||
Normalisierung für Report/Export verwendet dieselbe niedrigere
|
||||
Content-Untergrenze für nicht bestätigte Vision-Provisional-Treffer und
|
||||
übernimmt 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.
|
||||
|
||||
## Continuity-Seeds
|
||||
|
||||
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.
|
||||
|
||||
## Vision-Prepass für gezielte Match-Läufe
|
||||
|
||||
Bei aktivierter Vision wird für gezielte Match-Läufe 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.
|
||||
|
||||
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.
|
||||
|
||||
## Fehlertoleranz bei Vision-API
|
||||
|
||||
OpenRouter-/Vision-Rate-Limits werden mit progressiv längeren Pausen erneut
|
||||
versucht. Auch Netzfehler beim Lesen der Antwort (Timeouts,
|
||||
Verbindungsabbrüche während einer DSL-Trennung) werden als retrybar
|
||||
behandelt, nicht nur Verbindungsfehler beim Verbindungsaufbau.
|
||||
Billing-, Credit- oder Token-Guthaben-Fehler werden dagegen sofort als
|
||||
echter Blocker gemeldet, weil Warten dort nicht hilft.
|
||||
|
||||
Schlägt die Vision-Verifikation während der finalen Filter-/Repair-Stufe
|
||||
trotzdem dauerhaft fehl, wird der bisherige gecachte Treffer für diesen
|
||||
Beat behalten statt verworfen — ein Netzproblem darf keinen schon korrekt
|
||||
gefundenen Match aus dem Cache löschen.
|
||||
|
||||
## Phasen-Reparatur und Recovery
|
||||
|
||||
Die Phasen-Reparatur an gefundenen Treffern läuft nicht nur in „langen"
|
||||
Source-Szenen, sondern überall dort, wo die Szene mehr als nur das
|
||||
Segment-Fenster trägt. Eine korrigierte Position wird übernommen, sobald sie
|
||||
das Bildinhalt-Validate besteht UND nicht spürbar schlechter scort als das
|
||||
Original (≤ 0.02 Verlust). Bereits bestätigte Treffer in eng zugeschnittenen
|
||||
Szenen werden bewusst nicht angefasst, damit ein guter Match nicht durch
|
||||
eine nominell gleichwertige Alternative ausgetauscht wird.
|
||||
|
||||
Beats, die nach dem CV-Lauf weder als Vollmatch noch als Segmentmatch
|
||||
landen, durchlaufen anschließend eine Recovery-Stufe: Vibe-Check
|
||||
(Histogramm/pHash) liefert Top-K Kandidatenszenen, die semantische
|
||||
Action-Window-Suche prüft darin die Phase des sichtbaren Trailerbeat-
|
||||
Anteils, und der CV-Aligner setzt den Inpoint frame-genau. Übernommen wird
|
||||
nur ein Kandidat, der dieselbe Vision-Phasenvalidierung wie der Hauptpfad
|
||||
besteht. Beats ohne sichtbares Bildmaterial (Logos, Titel-Karten,
|
||||
durchgehende Fades) werden gar nicht erst gesucht — sie sind bewusst kein
|
||||
Match.
|
||||
|
||||
## Behandlung von Blenden und Schwarzfeldern
|
||||
|
||||
Lange Trailerbeats werden nicht 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 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.
|
||||
|
||||
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. Falls ein kompletter Beat keinen belastbaren
|
||||
Einzelclip ergibt, versucht der Matcher dieselbe Segmentlogik automatisch
|
||||
als Fallback. Sehr kurze Inseln dürfen zusätzlich in den Source-Szenen
|
||||
benachbarter bereits gematchter Beats lokal nach ihrer Bewegungsphase
|
||||
suchen.
|
||||
|
||||
Besteht ein Beat nach automatischer Fade-/Titel-Filterung nur aus einer
|
||||
einzigen sichtbaren Insel, wird diese Insel direkt als primäres Suchziel
|
||||
verwendet. Gecachte segmentierte Treffer werden gegen die automatisch
|
||||
sichtbare Referenzdauer normalisiert, nicht gegen Schwarz-/Blendränder
|
||||
des gesamten Beats.
|
||||
|
||||
Sehr dunkle, kontrastarme oder noch nicht sauber auf-/abgeblendete
|
||||
Referenzframes werden aus Score, Inhalts-Reranking, Phasen-Alignment und
|
||||
Motion-Templates herausgenommen. Sichtbare Fade-Rampen werden nur in eine
|
||||
matchbare Insel hinein erweitert, wenn sie strukturell stark zum ersten
|
||||
bzw. letzten scorebaren Frame derselben Einstellung passen.
|
||||
|
||||
Treffer unter `provisional_content_threshold` werden nicht mehr gespeichert
|
||||
oder aus alten Cache-Ergebnissen übernommen.
|
||||
Reference in New Issue
Block a user