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>
This commit is contained in:
Melbar
2026-05-06 07:39:02 +02:00
parent 4afb438a4d
commit 64e0132cc7
75 changed files with 924 additions and 515 deletions
+81 -34
View File
File diff suppressed because one or more lines are too long
+208 -163
View File
File diff suppressed because one or more lines are too long
+50 -22
View File
@@ -92,40 +92,68 @@ def _save_results(results: list, cfg: "AppConfig") -> None: # type: ignore[name
logging.getLogger(__name__).info("Match results cached → %s", p)
def _auto_commit_push_reports(project_root: "Path") -> None: # type: ignore[name-defined]
"""Stage changed report files, commit, and push to origin.
Only touches report output files — never stages source or config changes.
Failures are logged but never propagate.
"""
import subprocess as _sp
from datetime import datetime as _dt
report_globs = [
"CUTTER_REPORT.html",
"CUTTER_REPORT.md",
"output/report/match_report.html",
"output/report/beat_*_compare.mp4",
"output/report/beat_*_src.mp4",
"output/report/beat_*_ref.mp4",
"output/cutter_clips/beat_*_compare.mp4",
"output/cutter_clips/beat_*_source.mp4",
"output/cutter_clips/beat_*_source_seg*.mp4",
"output/cutter_clips/beat_*_trailer.mp4",
"output/cutter_stills/beat_*_source.jpg",
"output/cutter_stills/beat_*_trailer.jpg",
]
log = logging.getLogger(__name__)
cwd = str(project_root)
try:
for pattern in report_globs:
_sp.run(["git", "add", "--", pattern], capture_output=True, cwd=cwd)
status = _sp.run(
["git", "status", "--porcelain"], capture_output=True, text=True, cwd=cwd
)
if not status.stdout.strip():
log.info("Auto-commit: nothing changed in report files.")
return
now = _dt.now().strftime("%Y-%m-%d %H:%M")
msg = f"Auto-update cutter report {now}\n\nCo-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>"
_sp.run(["git", "commit", "-m", msg], capture_output=True, cwd=cwd, check=True)
_sp.run(["git", "push", "origin", "main"], capture_output=True, cwd=cwd, check=True)
log.info("Auto-commit+push: cutter report updated → remote.")
except Exception as exc:
log.warning("Auto-commit/push failed (non-fatal): %s", exc)
def _regenerate_cutter_report(cfg: "AppConfig") -> None: # type: ignore[name-defined]
"""Re-render CUTTER_REPORT.{md,html} and output/report/match_report.html.
"""Re-render CUTTER_REPORT.{md,html} with Frame-Locked Compare clips.
Called from every match-style command after the cache is written so all
cutter-facing artefacts stay in sync with the current `match_results.json`.
Failures are logged but never abort the run — the cache is the source of
truth, the reports can always be re-rendered manually later.
cutter-facing artefacts stay in sync with `match_results.json`.
After rendering, stages and pushes changed report files to the remote.
Failures are logged but never abort the run.
"""
project_root = cfg.paths.cache_dir.parent
try:
from scripts.generate_cutter_report import render_report
except Exception as exc:
logging.getLogger(__name__).warning("Cutter report regen skipped: %s", exc)
else:
try:
project_root = cfg.paths.cache_dir.parent
md, html = render_report(project_root, with_stills=True, with_clips=True)
(project_root / "CUTTER_REPORT.md").write_text(md, encoding="utf-8")
(project_root / "CUTTER_REPORT.html").write_text(html, encoding="utf-8")
logging.getLogger(__name__).info("Cutter report regenerated (md + html)")
logging.getLogger(__name__).info("Cutter report regenerated (md + html + compare clips)")
except Exception as exc:
logging.getLogger(__name__).warning("Cutter report regen failed: %s", exc)
# Also keep the legacy output/report/match_report.html in sync. It uses
# its own preview-clip pipeline (frame-locked compare videos) and is the
# heavier of the two reports — kept up-to-date so the cutter can choose
# whichever view they prefer.
try:
from src.pipeline.reporter import generate_report
all_beats = _load_beats(cfg)
all_results = _normalize_cached_results(all_beats, _load_results(cfg), cfg)
generate_report(all_beats, all_results, cfg)
logging.getLogger(__name__).info("Match report regenerated → output/report/match_report.html")
except Exception as exc:
logging.getLogger(__name__).warning("Match report regen failed: %s", exc)
_auto_commit_push_reports(project_root)
def _load_results(cfg: "AppConfig") -> list: # type: ignore[name-defined]
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.
File diff suppressed because it is too large Load Diff