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:
@@ -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
|
||||
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 + compare clips)")
|
||||
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)")
|
||||
except Exception as exc:
|
||||
logging.getLogger(__name__).warning("Cutter report regen failed: %s", 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]
|
||||
|
||||
Reference in New Issue
Block a user