Compare commits

...

81 Commits

Author SHA1 Message Date
Melbar fa40821319 Update cutter report 2026-05-18 08:48:26 +02:00
Melbar 68ec775916 Auto-update cutter report 2026-05-09 19:06
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-09 19:06:02 +02:00
Melbar 3b42c5d018 Mark trailer title cards as graphics 2026-05-09 18:48:24 +02:00
Melbar f3c3a9cfd4 Auto-update cutter report 2026-05-09 18:46
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-09 18:46:52 +02:00
Melbar e966a4c321 Filter cached vision action windows 2026-05-09 18:30:13 +02:00
Melbar 45b5376cef Auto-update cutter report 2026-05-09 18:28
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-09 18:28:33 +02:00
Melbar 4b3894a812 Auto-update cutter report 2026-05-09 18:22
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-09 18:22:35 +02:00
Melbar 3ad2b51e56 Auto-update cutter report 2026-05-09 18:04
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-09 18:04:24 +02:00
Melbar c16e46fb9d Auto-update cutter report 2026-05-09 18:03
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-09 18:03:14 +02:00
Melbar 8ca6d4b696 Auto-update cutter report 2026-05-09 18:02
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-09 18:02:11 +02:00
Melbar b771c6792b Auto-update cutter report 2026-05-09 18:01
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-09 18:01:01 +02:00
Melbar 6bf3ab6626 Auto-update cutter report 2026-05-09 17:59
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-09 17:59:15 +02:00
Melbar 9a5abd5312 Auto-update cutter report 2026-05-09 17:55
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-09 17:55:55 +02:00
Melbar b2abdafc7a Auto-update cutter report 2026-05-09 17:53
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-09 17:53:39 +02:00
Melbar 02e9fee982 Auto-update cutter report 2026-05-09 17:36
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-09 17:36:04 +02:00
Melbar 5425939a84 Auto-update cutter report 2026-05-09 17:29
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-09 17:29:01 +02:00
Melbar ed7b083dca Recover weak low-light matches via vision 2026-05-09 17:26:10 +02:00
Melbar ae3c2b1b13 Improve local phase retuning 2026-05-09 12:35:33 +02:00
Melbar 71117a8a3b Auto-update cutter report 2026-05-09 12:30
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-09 12:30:17 +02:00
Melbar c1425003c1 Normalize visible island segments 2026-05-09 11:29:07 +02:00
Melbar bcaf0417b3 Recover short low-light vibe matches 2026-05-09 10:38:57 +02:00
Melbar f63d65fcd2 Handle fade-led segment phase ties 2026-05-09 10:11:36 +02:00
Melbar c08ba97d37 Improve multi-shot phase retune 2026-05-09 09:36:11 +02:00
Melbar a275b2efb6 Retune weak multi-shot segment phases 2026-05-09 05:10:38 +02:00
Melbar fab6c53698 Remove legacy match report 2026-05-09 04:33:53 +02:00
Melbar c5b7d61451 Restore visible beat 14 cutter candidate 2026-05-09 04:31:14 +02:00
Melbar acafe538b2 Tighten cutter phase span validation 2026-05-08 14:56:44 +02:00
Melbar 10e27afc8d Make cutter report the only generated review report 2026-05-08 14:29:49 +02:00
Melbar e335fffe92 Mask timecode in phase refine and guard cutter scene starts 2026-05-08 14:18:27 +02:00
Melbar bdc9e4ab31 Clamp cutter clips to source scene start 2026-05-08 14:11:02 +02:00
Melbar 430a81a988 Constrain hi-res phase refine and update beat 14 2026-05-08 13:45:09 +02:00
Melbar 5611902eb5 Update cutter report for beat 14 compare clip 2026-05-08 13:21:35 +02:00
Melbar 4eeecca80d Fix cutter compare fallback for single-shot matches 2026-05-08 13:18:56 +02:00
Melbar 5407f08fbc Auto-update cutter report 2026-05-08 12:46
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 12:46:57 +02:00
Melbar 0baedb3a17 Auto-update cutter report 2026-05-08 12:22
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 12:22:35 +02:00
Melbar d83fced8d2 Fix multi-shot matching: increase cut correlation threshold to properly segment multi-island beats 2026-05-08 12:16:09 +02:00
Melbar 4fe1d35f1a Fix multi-shot matching: Always use continuity seed for first island to prevent wrong scene jumps 2026-05-08 11:50:13 +02:00
Melbar 730b5ef3c0 Auto-update cutter report 2026-05-08 11:31
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 11:31:15 +02:00
Melbar f20f89b06b Add hi-res phase refinement for intra-scene phase matching (Beat 03 investigation) 2026-05-08 10:52:11 +02:00
Melbar 18c8c89ee6 Auto-update cutter report 2026-05-08 10:31
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 10:31:59 +02:00
Melbar 9b524c9329 Auto-update cutter report 2026-05-08 10:18
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 10:18:12 +02:00
Melbar 1e5ffffd91 Auto-update cutter report 2026-05-08 10:04
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 10:04:18 +02:00
Melbar 8fd0442724 Auto-update cutter report 2026-05-08 09:40
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 09:40:51 +02:00
Melbar 18a67387f6 Auto-update cutter report 2026-05-08 09:25
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 09:25:43 +02:00
Melbar 7ffe4adc3b Auto-update cutter report 2026-05-08 09:10
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 09:10:33 +02:00
Melbar 92a12276ee Auto-update cutter report 2026-05-06 20:53
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 20:53:01 +02:00
Melbar 64b53c0e82 Auto-update cutter report 2026-05-06 20:28
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 20:28:39 +02:00
Melbar 8096f9b4d8 Auto-update cutter report 2026-05-06 20:10
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 20:10:55 +02:00
Melbar e960b1c080 Auto-update cutter report 2026-05-06 19:40
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 19:40:19 +02:00
Melbar c972894972 Fix: prevent tail-trimming of valid matches at hard scene boundaries in global_scan.py 2026-05-06 19:06:33 +02:00
Melbar 72e22969b4 Auto-update cutter report 2026-05-06 19:00
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 19:00:08 +02:00
Melbar 9d3c5d5afd Auto-update cutter report 2026-05-06 18:47
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 18:47:39 +02:00
Melbar 0375580373 Auto-update cutter report 2026-05-06 18:33
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 18:33:17 +02:00
Melbar e3a4c22b71 Auto-update cutter report 2026-05-06 17:34
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 17:34:37 +02:00
Melbar c71ed2b701 Auto-update cutter report 2026-05-06 14:07
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 14:07:48 +02:00
Melbar 49412c54a6 Auto-update cutter report 2026-05-06 13:58
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 13:58:00 +02:00
Melbar 533ab49d62 Auto-update cutter report 2026-05-06 13:25
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 13:25:40 +02:00
Melbar f1e9636a83 Auto-update cutter report 2026-05-06 13:05
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 13:05:24 +02:00
Melbar cd10e2bc03 Auto-update cutter report 2026-05-06 12:48
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 12:48:55 +02:00
Melbar c118428167 gitignore: exclude .code-workspace and .claude/ session data
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 12:45:36 +02:00
Melbar 45769aa366 Refactor report pipeline: redesign HTML, add motion alignment, remove legacy reporter
- scripts/generate_cutter_report.py: complete HTML redesign with glassmorphism
  dark-mode style, compare video links in markdown output
- cli.py: cmd_report now calls _regenerate_cutter_report directly; also writes
  legacy match_report.html; removes dependency on src/pipeline/reporter.py
- src/cv/global_scan.py: add motion-phase alignment refinement step after
  initial in-point search (align_in_point_by_motion, threshold +0.015)
- Remove HANDOVER.md and src/pipeline/reporter.py (superseded)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 12:44:10 +02:00
Melbar 3b90905d07 Auto-update cutter report 2026-05-06 12:30
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 12:30:03 +02:00
Melbar 07f47ebe2b Auto-update cutter report 2026-05-06 10:52
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 10:52:14 +02:00
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
Melbar d287952572 Auto-update cutter report 2026-05-06 10:29
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 10:29:06 +02:00
Melbar e2c30d0062 Auto-update cutter report 2026-05-06 09:21
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 09:21:11 +02:00
Melbar fab6aa9388 Remove legacy _seg*.mp4 files replaced by new compare clip pipeline
Old per-segment source clips (beat_10, beat_12, beat_15) are obsolete —
generate_cutter_report.py now produces beat_NN_compare.mp4 side-by-side clips.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 07:53:20 +02:00
Melbar 64e0132cc7 Upgrade CUTTER_REPORT: Frame-Locked Compare, full metadata, auto-commit+push
generate_cutter_report.py:
- Frame-Locked Compare video (trailer left / source right, single MP4) per
  beat replaces two separate side-by-side clips; rendered via accurate
  double-seek + black-fill segmented source reconstruction
- Generation timestamp now includes HH:MM:SS (Uhrzeit-Angabe)
- Per-beat segment list for multi-shot beats (TC, duration, offset, scene,
  score per segment)
- Score warning badge (yellow) if score < 0.65
- python cli.py rematch --beat N command hint in every card
- Overview table links to each beat card via #anchor
- Cleaner dark/light CSS using design tokens (--fg/--bg/--card/--bd)
- --no-clips flag (replaces --with-clips; default is now with clips)

cli.py:
- _auto_commit_push_reports(): after every report regeneration, stages the
  report output files (CUTTER_REPORT.*, output/cutter_clips/, output/report/)
  and auto-commits + pushes to origin/main so remote is always current
- Removed the legacy match_report.html call from _regenerate_cutter_report
  (CUTTER_REPORT now supersedes it)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 07:39:02 +02:00
Melbar 4afb438a4d Beat 15: phase-correct coffee in-point to 2618.80s (scene 309, 00:45:27)
Actor-focused multi-frame temporal scan showed the correct action phase
(left woman back to camera, right woman in profile) at t=2618.8s vs
the previously used 2615.5s where both women were in different positions.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 07:26:25 +02:00
Melbar a7b103b4fe Beat 15: correct knife match to scene 17 (t=130.32s) + coffee to scene 309 (t=2615.52s)
Template scan of entire source found the knife/letter-opener close-up at
t=130-133s in the film (scene 17, 127.76-133.04s).  Previous wrong match was
pointing at scene 309 (the coffee/window scene) for both shots because the
strong continuity seed from beat 14 overwhelmed the global search.

Two-segment provisional match written to cache manually:
  seg[0] knife: scene 17  in=130.32s  dur=2.80s  score=0.72 (confirmed)
  seg[1] coffee: scene 309 in=2615.52s dur=1.28s  score=0.38 (provisional)

Regenerated CUTTER_REPORT and match_report with correct beat 15 clips/stills.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 00:05:59 +02:00
Melbar 54d3f04616 Fix matching regressions, cache guard, and multi-shot algorithm for beat 15
- config.toml: revert scoreable_luma/contrast thresholds to 24/58/24 (lowering
  them let cross-fade blend frames contaminate content-validation templates,
  dropping scores below provisional_content_threshold)
- src/cv/global_scan.py: _is_dark_reference_frame now requires contrast<30 so
  genuine dark silhouette frames are not rejected as scoreable; two-path
  _is_scoreable_reference_frame separates standard vs fade-content scoring
- cli.py: _keeps_cached_match() guard prevents a weaker single-span rematch
  from overwriting a better multi-segment provisional cache entry
- cli.py: _fade_content_shots() restricted to between-island gaps only—
  pre-island black leaders were incorrectly emitted as matchable shots
- cli.py: island[0] of _match_unmatched_visual_segments() now uses no
  continuity seed so an insert cut at the start of a multi-shot beat is not
  forced toward the previous beat's scene
- scripts/generate_cutter_report.py: fix ffmpeg concat demuxer on Windows—
  use part.absolute().as_posix() so paths in the concat txt are absolute and
  not double-resolved relative to the concat file's directory

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 00:05:37 +02:00
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
Melbar 8aa6fe8323 Fix forehead_touch action group + always-fresh cutter assets
1. Action-group classifier conflated object-touches and person-touches.
   "man touches the red door with a small object" was being tagged as
   forehead_touch because "touch" was in the forehead_touch needles set.
   That made the realign pass yank Beat 16 from scene 451 (correct: man
   painting red door, IV stand) over to scene 623 (woman/man in bed) —
   a totally wrong shot at score 0.344.

   Fix: removed generic "touch*" verbs from forehead_touch's needle set.
   forehead_touch is now added in _semantic_action_groups() only when a
   touch verb is paired with an explicit body-part target (forehead,
   face, cheek, head, hand, ...) and not paired with an object target
   (door, handle, brush, tool, lock, ...).

   Effect on Beat 16 after `match --beat 16 --vision`:
   scene 623 in=5476.28 score=0.344 -> scene 451 in=3912.48 score=0.626.

2. Cutter-report stills/clips were keyed by source-video mtime, so a
   match-position change without a video change served stale frames from
   the previous match. Dropped the mtime cache; both extractors now
   render fresh every time. Slower (~minute per full regen) but correct.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-05 05:23:24 +02:00
Melbar dbadc3fc26 Track output/report/ so remote repo always has the live match_report
The legacy HTML report under output/report/ (match_report.html plus per-beat
ref/src/compare MP4s) is now part of the repository so the remote always
shows the latest preview state. Added gitignore exception. Total ~26 MB on
disk; comparable to the cutter_clips assets we already track.

Auto-regenerated on every match command via _regenerate_cutter_report, so
this stays in sync without manual intervention.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-05 04:42:27 +02:00
Melbar b70d7e11be Concat multi-shot source clips and auto-regen match_report.html
1. Cutter-report source clip for multi-segment beats was using only the
   primary in/out, which equals the FIRST segment's range. Beat 10 with
   3 shots therefore showed only ~0.88 s of source instead of all 3.32 s.
   Added extract_concat_clip(): renders each segment as its own MP4 and
   concatenates them via ffmpeg's concat demuxer into one continuous
   source clip the same length as the trailer beat.

   Per-segment intermediate clips (beat_NN_source_seg00.mp4 etc.) are
   kept too so individual shots stay inspectable.

2. _regenerate_cutter_report now also regenerates the legacy
   output/report/match_report.html via src.pipeline.reporter.generate_report.
   Both reports stay in sync after every match command.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-05 04:40:01 +02:00
Melbar cc27208d2a Per-shot match for beats with internal cuts; protect cache on --beat runs
Two issues fixed:

1. Beats with internal hard cuts (e.g. man-shot then back to woman) were
   being approximated by a single source clip because the multi-segment
   path only triggered for fade-bounded multi-island beats. Added
   _reference_shot_segments(), which returns the shot ranges by splitting
   each visible island at detected internal cuts. The multi-island gate in
   cmd_match and the per-island loop in _match_unmatched_visual_segments
   now use shots, so any beat with cuts > 0 produces one MatchSegment per
   shot. Each shot is matched independently against the source movie.

   Effect on Beat 10: 1 segment (3.32 s in scene 558) -> 3 segments
   covering shots 0-0.88 s, 0.88-2.64 s, 2.64-3.32 s in scenes 554, 559,
   556 respectively, with the previously missing "back to woman" cut now
   correctly placed in scene 556.

2. Targeted --beat N runs were silently dropping cache entries for other
   beats whose old scores no longer pass current quality gates
   (_normalize_cached_results runs at load time and removes them). The
   save path now re-loads the raw cache from disk and writes back every
   non-targeted beat verbatim, so a per-beat run can never regress
   another beat's stored match.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-05 00:06:39 +02:00
Melbar 2a3840e528 Fix cutter-report clip duration: render full beat / match length
Bug: the report rendered every preview clip at most 3 s, so any beat longer
than 3 s (e.g. beat 02 at 8.56 s) only got a fragment in the cutter report
and looked wrong. The hard 3 s cap was an early prototype constant that
silently truncated normal-length beats.

Trailer clip is now the full beat duration so the cutter sees the entire
reference beat. Source clip is the full matched duration (may be shorter
than the beat when the match drops before the beat ends — that's correct,
the cutter needs to see exactly the matched span).

A 30 s safety cap stays as a guard against runaway durations but it should
never trip on a normal trailer beat.

All existing clips were dropped and re-rendered so the report on disk
matches the new logic.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-04 22:31:40 +02:00
Melbar 17db238ea3 Update cutter report after full match run (23/25, 6 confirmed)
Refreshed CUTTER_REPORT.{md,html} plus stills and clips from the latest
match cache. Notable changes vs prior cache:

- 6 confirmed (was 5): newly confirmed beats 2, 9, 12, 17 (beat 8 lost
  confirmed status, beat 18 lost match entirely; both will be addressed
  per-beat).
- Beat 2: previously unmatched -> scene 3 in=35.190s score 0.761 (OK).
- Beat 20: scene 613 in=5284.706s score 0.663 (OK), correct phase via
  recovery.
- Beats 21, 23: previously unmatched -> now provisional via recovery.
- Beat 18: regression, currently MAN. (was confirmed before).
- Beat 24: still MAN. (end credits, expected).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-04 22:25:42 +02:00
Melbar f57bd6a669 Track cutter stills and clips in repo, always render with clips
- cli.py auto-regen now produces video clips on every match (no opt-in
  flag). Best presentation comes first; speed cost (~minutes per match)
  is accepted.
- output/cutter_stills/ and output/cutter_clips/ are no longer gitignored.
  All 45 stills and 45 short MP4 previews are committed alongside the
  CUTTER_REPORT.{md,html} so the remote repo always shows the current
  state — even when the report files are inspected without running the
  generator.
- Other output/ contents (FCPXML, EDL, debug folders, HTML report) stay
  ignored.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-04 13:36:20 +02:00
Melbar a405df0ddb Embed cutter-report stills inline + add HTML report with video previews
Two issues fixed:

1. Source frame rate was wrong. The script trusted ffprobe, which on this
   re-wrapped proxy reports 25 fps. The real number for the EDL/FCPXML and
   for what the cutter sees in the NLE comes from config.toml's
   edl_frame_rate (here 23.976). Source fps now reads that value first;
   ffprobe is only a fallback. Trailer fps still probes ffprobe (correct
   for the trailer file) with optional config override.

2. Stills in CUTTER_REPORT.md showed as broken links because output/ is
   gitignored, so the git server can't serve them. Stills are now embedded
   as base64 data URIs directly in the markdown. The file is therefore
   self-contained and renders in any markdown viewer including the git
   server's web preview.

3. New CUTTER_REPORT.html alongside the markdown: same data, proper card
   layout, side-by-side trailer/source columns per beat, base64-embedded
   stills, and (with --with-clips) base64-embedded 3 s MP4 video previews
   so the cutter can sight-check phase agreement directly in a browser.
   The auto-regen on each match writes both files; --with-clips is opt-in
   from the CLI for slower full renders.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-04 13:33:07 +02:00
Melbar 5a6ae2175c 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>
2026-05-04 13:24:19 +02:00
314 changed files with 3691 additions and 1767 deletions
+6
View File
@@ -0,0 +1,6 @@
* text=auto
.gitattributes text eol=lf
*.py text eol=lf
*.md text eol=lf
*.html text eol=lf
*.ps1 text eol=crlf
+17 -1
View File
@@ -30,10 +30,14 @@ proxy/
*.jpeg
*.png
# IDE
# IDE / editor
.vscode/
.idea/
*.swp
*.code-workspace
# Claude Code session data
.claude/
# OS
.DS_Store
@@ -42,3 +46,15 @@ Thumbs.db
# Secrets / local overrides
.env
config.local.toml
# Cutter-Report assets — these MUST be tracked so the remote repo can serve
# the inline previews even without re-running the generator. Negated rules
# must come after the broader ignores above.
!output/
output/*
!output/cutter_stills/
!output/cutter_stills/**
!output/cutter_clips/
!output/cutter_clips/**
!output/report/
!output/report/**
+136
View File
File diff suppressed because one or more lines are too long
+403 -81
View File
File diff suppressed because one or more lines are too long
-125
View File
@@ -1,125 +0,0 @@
# Handover Notes
Stand: 2026-05-03 (Beat-20-Reparatur abgeschlossen).
## Zustand
- `pytest tests/ -q` → 52/52 grün.
- `python cli.py match --beat 20 --vision` läuft erfolgreich durch und schreibt
einen confirmed Match (Score 0.6632, scene 613, in=5284.706s, dur=0.88s).
- Vorheriger Cache wurde nach `.cache/match_results.json.bak` gesichert.
- Kein offener PR; lokale Änderungen sind committed (siehe letzter Commit).
## Was zuletzt geändert wurde und warum
### 1. `cli.py` — `realign_window` wählt das Action-Window pro Segment
In `_filter_semantically_invalid_vision_matches.realign_window`:
- **Vorher:** `find_action_window_in_scene(action_beat or check_beat, …)` — bei
segmentierten Beats wurde immer der ganze Beat als semantischer Kontext
benutzt. Das hat für Beat 20 die Source-Position auf die Kuss-Phase
(5270 s) gelegt, obwohl das *sichtbare* Segment nur "approaching and pulling
apart" zeigt — diese Phase liegt im Source erst um 5284 s.
- **Jetzt:** Es werden zwei Fenster gesucht (Segment-Beschreibung *und* Beat-
Beschreibung). Der Beat-Kontext gewinnt nur bei deutlichem (>0.06) Score-
Vorsprung. Der Trailer-Offset-Shift (`visible_content_offset`) wird nur
angewendet, wenn tatsächlich der Beat-Kontext benutzt wurde — sonst zeigt
das Segment-Fenster bereits auf die richtige Phase.
Effekt für Beat 20: 5270.118 → 5284.706, Score 0.6449 (provisional) → 0.6632
(confirmed).
### 2. `cli.py` — Filter-/Repair-Stufe ist crash-tolerant
`_filter_semantically_invalid_vision_matches` hat den Per-Result-Body in eine
lokale Funktion `_filter_repair_one` herausgezogen und in einen try/except
verpackt. Wenn die Reparatur abbricht (z. B. weil Vision-API mitten in der
Antwort wegfällt), wird der bisher gecachte Treffer behalten statt komplett
verworfen.
### 3. `src/llm/vision_cache.py` — Vision-Retry für Lesefehler
`_call_vision_model` fängt jetzt zusätzlich `TimeoutError`,
`socket.timeout`, `ConnectionError` und `OSError` während des Antwort-Lesens
und retryt mit demselben Backoff wie HTTP-/URL-Fehler. Die Auslöse-Bedingung
war ein 24-h-DSL-Disconnect mitten im Lauf; davor wurde der Match-Lauf hart
abgebrochen und der Cache stand auf "kein Match".
### 4. `README.md`
Zwei kurze Absätze ergänzt, die (1) die Segment-vs-Beat-Window-Auswahl und
(2) das neue Crash-/Netzfehler-Verhalten beschreiben.
## Nicht angefasst, aber relevant für die Übergabe
- Der **vollständige FFmpeg-Vollscan** liefert für Beat 20 weiterhin keinen
bestätigten Treffer (final score 0.419 < provisional 0.430). Den
Confirmed-Match liefert die Action-Window-Reparatur. Das ist erwartet:
das sichtbare Segment ist visuell sehr generisch (Two-Shot Profil mit
unscharfem Hintergrund), die korrekte Phase fällt erst durch die
semantische Aktionsbeschreibung auf.
- Die `candidate_points`-Schleife in `realign_window` (lines ~700765) sucht
nur ±~2 s um `start_s` herum. Solange `start_s` jetzt aus dem Segment-
Fenster kommt, liegt der korrekte Source-Punkt in diesem Bereich. Wenn
künftig Beats mit längeren visiblen Inseln auftauchen, kann diese Range
zu eng werden — dann den Suchradius erweitern statt das Window-Picking
rückgängig machen.
- Es gibt **keine Tests** für `_filter_semantically_invalid_vision_matches`
oder `realign_window`. Wer das anfasst, sollte Beat 20 als Live-Smoke-Test
benutzen (siehe unten).
## Reproduktion / Smoke-Test
```powershell
.\.venv\Scripts\Activate.ps1
python cli.py match --beat 20 --vision
```
Erwartet: `Beat 20: realigned semantically valid long scene by motion/action
windows`, danach `is_confirmed: true` für Beat 20 in
`.cache/match_results.json` mit `in_point_s ≈ 5284.7` und `match_score ≥ 0.65`.
Wenn das fehlschlägt:
1. `python -m pytest tests/ -q` — falls rot, ist die Codebasis selbst kaputt.
2. `.cache/vision_descriptions.json` prüfen — die Schlüssel
`beat:20:73.560:74.680:…` und `action_window:613:5282.390:5285.430:…` müssen
existieren, sonst ruft Vision live ab (kostet Credits; braucht Netz).
3. `match_results.json.bak` zurückspielen, falls der Cache zerschossen ist.
## Aktuelle Coverage (vor neuestem Lauf)
```
total beats: 25
matched: 20 (5 confirmed, 15 provisional)
unmatched: beats 0, 2, 21, 23, 24
```
Beat 0 ist das SHO-Logo (kein Source-Match möglich, korrekt).
Beats 22/23/24 haben keine sichtbaren Inseln (Endcredits/Title) — auch
korrekt unmatched.
Beat 2 und Beat 21 sind die echten Recovery-Kandidaten; die neue
Recovery-Stufe versucht sie beim nächsten `match`-Lauf nachzuziehen.
## Offene Risiken / Bekannte Schwächen
- Die Schwelle `0.06` für "Beat-Kontext gewinnt" in `realign_window` ist
kalibriert an Beat 20. Andere Beats sollten auch durchlaufen werden, bevor
weitere Beats angefasst werden — am besten ein voller `python cli.py match`
ohne `--beat` und Diff der `match_results.json` gegen `.bak`.
- Die Filter-/Repair-Stufe kann durch Vision-Calls minutenlang laufen. Das
ist nicht neu, aber bei Netzproblemen sehr sichtbar.
- Die `_filter_repair_one`-Funktion bekommt viele Argumente durchgereicht
(closure-Variablen aus dem Parent). Bei einer nächsten Iteration könnte das
in eine kleine Klasse umgebaut werden.
## Useful greps
- `find_action_window_in_scene` — semantische Action-Window-Suche (Vision).
- `_reference_scoreable_segments` — bestimmt die sichtbaren Inseln eines
Beats.
- `estimate_usable_source_duration` — kürzt Match-Clips, wenn die Source
vor Beat-Ende in eine andere Phase wechselt.
- `_filter_semantically_invalid_vision_matches` — Eintrittspunkt der
Repair-Stufe in `cli.py`.
+35 -269
View File
@@ -30,12 +30,16 @@ Was du bekommst sind zwei Dateien, mit denen du arbeitest:
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 den Vorschauclip aus dem Report-HTML (siehe unten) prüfen
und im NLE den Source-In um wenige Frames vor/zurück verschieben, bis die
Bewegungsphase exakt zum Trailer passt.
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.
---
@@ -48,7 +52,7 @@ Alles andere unten ist Hintergrund für den Tool-Verantwortlichen.
| **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. |
| **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`. |
@@ -56,6 +60,10 @@ Alles andere unten ist Hintergrund für den Tool-Verantwortlichen.
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.
@@ -139,275 +147,33 @@ python cli.py rematch --beat 5 --threshold 0.50 # Schwelle anpassen (Globaler S
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.
### Cutter-Report neu erzeugen
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.
`CUTTER_REPORT.md` wird bei jedem `match`-Lauf automatisch geschrieben.
Manuell neu erzeugen (z. B. nach Edit eines einzelnen Beats):
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.
```powershell
python scripts/generate_cutter_report.py # mit Vorschau-Stills
python scripts/generate_cutter_report.py --no-stills # nur die Tabelle
```
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.
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`.
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).
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.
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 deshalb die grobe Szenenähnlichkeit ohne harte
Aktionsstrafe gerankt; die harte Aktionsprüfung greift erst auf den lokalen
Fenstern und dem finalen Source-Zeitbereich.
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. Dadurch kann ein grob passender Dialogmoment nicht bestehen
bleiben, wenn ein anderes lokales Fenster derselben Szene die gesuchte
Aktionsphase und Bewegung klarer trifft.
Die Kandidatenbewertung dieser Reparatur vergleicht dabei zwei Kontexte:
den ganzen Beat als semantischen Handlungsrahmen und das konkret sichtbare
Beat-Segment als Phasenprüfung. Ein Source-Zeitpunkt muss also 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.
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 dabei 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 dabei 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.
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.
Für Vision-Action-Fenster nutzt die finale Retiming-Prüfung eine gemeinsame
Content-und-Motion-Suche pro Frame. Content und Bewegungsphase werden dabei
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. 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.
Auch Netzfehler beim Lesen der Antwort (Timeouts, Verbindungsabbrüche während
einer DSL-Trennung) werden als retrybar behandelt, nicht nur Verbindungsfehler
beim Verbindungsaufbau. 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.
Die Phasen-Reparatur an gefundenen Treffern läuft nicht mehr 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.
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
+901 -43
View File
File diff suppressed because it is too large Load Diff
+9 -6
View File
@@ -8,7 +8,7 @@
[project]
name = "AI Trailer Generator v2"
version = "2.0.0"
log_level = "INFO" # DEBUG | INFO | WARNING | ERROR
log_level = "DEBUG" # DEBUG | INFO | WARNING | ERROR
# -----------------------------------------------------------------------------
# [paths] — External video sources (read-only access)
@@ -72,12 +72,12 @@ match_threshold = 0.65
# Store/report lower-confidence automatic candidates for visual review instead
# of dropping them as "NO MATCH". Confirmed exports can still use match_threshold.
provisional_match_threshold = 0.43
provisional_match_threshold = 0.35
# Lower gate for entering temporal multi-frame refinement. The final decision
# still uses sequence/span scoring; this only avoids rejecting real matches
# because one midpoint frame is weak.
coarse_candidate_threshold = 0.50
coarse_candidate_threshold = 0.40
# Candidate ranking weights. Duration coverage matters when the same visual
# shot appears multiple times: prefer the occurrence that can cover the beat.
@@ -86,7 +86,10 @@ span_score_weight = 0.15
coarse_score_weight = 0.10
duration_score_weight = 0.20
duration_tie_break_score_delta = 0.03
min_duration_coverage = 0.65
min_duration_coverage = 0.55
# Every visible sub-shot in a multi-shot beat must pass this stricter gate.
# A weak segment is left unmatched instead of being hidden by a strong neighbor.
multi_shot_segment_threshold = 0.50
continuity_seed_offsets_s = [-1.0, 0.0, 0.5, 1.0, 1.5, 2.0, 3.0]
scene_seed_top_k = 30
scene_seed_points_per_scene = 6
@@ -103,7 +106,7 @@ refine_step_seconds = 0.04 # ≈ 1 frame at 25 fps
content_align_window_seconds = 0.48
content_align_sample_step_s = 0.28
content_validation_weight = 0.35
provisional_content_threshold = 0.42
provisional_content_threshold = 0.30
# When several adjacent frame offsets score almost the same, prefer the earlier
# one. This avoids matches that are visually correct but start a few frames late.
@@ -183,7 +186,7 @@ local_scan_step_s = 0.12
local_scan_max_points_per_scene = 180
local_scan_top_candidates = 36
local_scan_tie_break_score_delta = 0.08
multi_shot_cut_corr_threshold = 0.20
multi_shot_cut_corr_threshold = 0.55
multi_shot_boundary_tolerance_s = 0.20
fullscan_fallback = false
content_threshold = 0.22
+381
View File
@@ -0,0 +1,381 @@
# Algorithmus-Notizen
Detaillierte Verhaltens­beschreibung 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 Treffer im Cutter-Report klar als phasenkritisch
markiert. Schwarz/Placeholder wird nur für wirklich ungematchte Trailer-
Bereiche oder Fades verwendet, nicht um sichtbare Kandidatenbewegung im Review
zu verstecken.
Diese Span-Schätzung ist strenger als der grobe Suchscore: Ein fast stehender
Anfang darf einen Match nicht retten, wenn spätere Frames sichtbar in eine
andere Gestik, Körperposition oder eintretende Figur driften. Stabile
Score-Plateaus dürfen nur verlängern, wenn sie noch nah genug am Anfangsniveau
liegen; sonst bleibt der Treffer vorläufig und muss neu gesucht oder visuell
geprüft werden. Der Review-Clip zeigt den Kandidaten weiterhin sichtbar, damit
Phasenfehler nicht durch Schwarz verdeckt werden.
Für Multi-Shot-Beats gilt zusätzlich eine Segment-Schwelle pro sichtbarer
Einstellung. Ein gutes erstes Segment darf kein zweites Segment mit schwachem
Score mitziehen. Segmente unter `multi_shot_segment_threshold` werden nicht als
stabile Wahrheit behandelt, sondern innerhalb derselben plausiblen Source-Scene
nachjustiert. Die Nachjustierung nutzt eine saliency-gewichtete Mehrframe-Prüfung:
Timecodes und statische Randbereiche werden entwertet, kontrastreiche und über
mehrere Trailerframes unterscheidbare Bildbereiche zählen stärker. Dadurch kann
eine schwache zweite Einstellung phasengenauer repariert werden, ohne den Fehler
durch Schwarzbild zu verdecken oder einen Beat manuell zu kuratieren.
Der Cutter-Report verwendet Clip-Caching. Bereits vorhandene Compare-Clips werden
wiederverwendet; bei gezielten Rematches wird nur der betroffene Beat neu gerendert
(`CUTTER_REPORT_FORCE_BEATS`). So bleibt der Report aktuell, ohne alle Beats jedes
Mal neu zu kodieren.
## 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.
Die Vision-Recovery läuft nicht nur für komplett fehlende Beats, sondern auch
für schwache unbestätigte Treffer. Gerade Low-Light-Beats dürfen nicht an einem
falschen dunklen CV-Treffer hängen bleiben, wenn der Cache semantisch eine
bessere Handlungsphase kennt.
Bei langen Source-Szenen prüft die Action-Window-Suche immer den Szenenanfang
und mehrere frühe Fenster, bevor sie gleichmäßig über die ganze Szene sampelt.
Damit gehen kurze Trailer-Aktionen am Anfang einer langen Szene nicht unter,
wenn der Rest der Szene aus Credits, Schwarzbild oder ruhigen Folgeframes
besteht.
Wenn ein Action-Window die starke Beat-Aktion explizit enthält, darf es eine
etwas niedrigere Textähnlichkeit haben; die Handlung zählt dann stärker als
Nebenwörter zu Licht, Bildausschnitt oder Stimmung.
Bereits gecachte Action-Windows einer Szene bleiben gültige Kandidaten, auch
wenn sich das aktuelle Sampling-Raster ändert. So verliert der Matcher keine
teuren Vision-Hinweise und muss dieselben Fenster nicht erneut beschreiben.
Wenn neue Vision-Calls deaktiviert sind, darf die Recovery vorhandene Cache-
Beschreibungen trotzdem lesen; das erzeugt keine API-Kosten und verhindert,
dass alte schwache CV-Treffer stehen bleiben.
Schlägt die CV-Feinjustierung bei einem semantisch klaren Low-Light-Fenster
fehl, bleibt das Action-Window als provisorischer Treffer erhalten. CV darf
einen dunklen Treffer verfeinern, aber nicht einen eindeutigen Cache-Hinweis
komplett verwerfen.
Zusätzlich kann Recovery vorhandene gecachte Action-Windows direkt über alle
Szenen ranken. Dieser schnelle Pfad vermeidet einen teuren Vollscan, wenn der
Cache bereits eine starke Aktion wie Hand-am-Mund, Kuss oder Blickwechsel
enthält.
Eindeutige Begriffe aus der Beat-Beschreibung wirken als harte Filter für
Vision-Fenster: `mouth` muss im Kandidaten wiederkehren, `dark interior` darf
nicht auf Outdoor-Material fallen, und markante Personenmerkmale wie `blonde`
bleiben bindend.
Der zusätzliche Hi-Res-Phasenrefine bleibt lokal um den bereits validierten
Inpoint und übernimmt nur klare Verbesserungen. Er darf keine ganze lange
Dialogszene nach ähnlichen Layouts durchsuchen, weil sonst dieselbe Location
mit anderer Gestik als falsche Phase gewinnen kann und die Laufzeit explodiert.
Die lokale Retune-Wertung nutzt deshalb nicht nur den mittleren Frame-Score,
sondern auch den schlechtesten Einzelvergleich, die ersten sichtbaren Frames
und die Frame-zu-Frame-Bewegung. Dadurch gewinnt nicht mehr ein späteres
Standbild derselben Einstellung, nur weil Fenster, Gesichter und Licht fast
identisch aussehen.
Unsichere Einzeltreffer ohne Segmentliste laufen ebenfalls durch diesen lokalen
Phasen-Probe. Das repariert alte Cache-Einträge, deren Szene korrekt ist, deren
Inpoint aber einige Frames in der Bewegung daneben liegt. Der Probe bleibt auf
kleine lokale Shifts begrenzt und wird nicht für jeden bestätigten Treffer
erzwungen, damit Report-Refreshes nicht zum Vollscan werden.
Report-Clips werden zusätzlich an den bekannten Source-Szenenstart plus eine
sehr kurze Ein-Frame-Guard-Zone geklemmt, damit ein knapp vor oder direkt auf
der Schnittkante liegender Inpoint nicht mit Frames der vorherigen Einstellung
beginnt. Die Guard-Zone bleibt bewusst klein, weil eine längere Korrektur die
sichtbare Bewegungsphase innerhalb derselben Einstellung verschieben würde.
## 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.
## Titel- und Grafikbeats
Dunkle Trailerkarten mit deutlich isoliertem Text werden im Cutter-Report als
`GFX` markiert, wenn es keinen Source-Treffer gibt. Diese Beats sind keine
fehlgeschlagenen Matches: Der Cutter soll die Trailer-Grafik beziehungsweise
eine NLE-Titelkarte übernehmen und nicht im Spielfilm nach einem Bild suchen.
## 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.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Some files were not shown because too many files have changed in this diff Show More