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>
This commit is contained in:
@@ -591,6 +591,11 @@ def render_markdown(
|
||||
out.append(f"- **Bild**: {r.composition}{extra}")
|
||||
out.append("")
|
||||
|
||||
if r.compare_clip:
|
||||
rel_clip = f"output/cutter_clips/beat_{r.bid:02d}_compare.mp4"
|
||||
out.append(f"**[▶️ Frame-Locked Compare Video ansehen]({rel_clip})**")
|
||||
out.append("")
|
||||
|
||||
t_uri = data_uri(r.trailer_still, "image/jpeg")
|
||||
s_uri = data_uri(r.source_still, "image/jpeg")
|
||||
if t_uri or s_uri:
|
||||
@@ -613,89 +618,133 @@ HTML_HEAD = """\
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Cutter-Report</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Cutter-Report & Match-Report</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700&family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
:root {
|
||||
color-scheme: light dark;
|
||||
--fg: #1a1a1a; --bg: #f4f4f5; --mut: #666; --card: #fff; --bd: #d4d4d8;
|
||||
--ok: #16a34a; --q: #b45309; --man: #b91c1c;
|
||||
--ok-bg: #dcfce7; --q-bg: #fef3c7; --man-bg: #fee2e2;
|
||||
--code-bg: #18181b; --code-fg: #86efac;
|
||||
--warn: #92400e; --warn-bg: #fef9c3;
|
||||
}
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--fg: #e4e4e7; --bg: #0f0f10; --mut: #a1a1aa; --card: #18181b; --bd: #27272a;
|
||||
--ok: #4ade80; --q: #fbbf24; --man: #f87171;
|
||||
--ok-bg: #14532d; --q-bg: #451a03; --man-bg: #450a0a;
|
||||
--code-bg: #09090b; --code-fg: #86efac;
|
||||
--warn: #fef08a; --warn-bg: #422006;
|
||||
}
|
||||
--fg: #e4e4e7;
|
||||
--bg-gradient: linear-gradient(135deg, #09090b 0%, #18181b 100%);
|
||||
--mut: #a1a1aa;
|
||||
--card-bg: rgba(24, 24, 27, 0.6);
|
||||
--card-border: rgba(255, 255, 255, 0.08);
|
||||
--glass-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.37);
|
||||
--accent: #6366f1;
|
||||
--accent-hover: #818cf8;
|
||||
|
||||
--ok: #4ade80;
|
||||
--q: #fbbf24;
|
||||
--man: #f87171;
|
||||
--ok-bg: rgba(74, 222, 128, 0.15);
|
||||
--q-bg: rgba(251, 191, 36, 0.15);
|
||||
--man-bg: rgba(248, 113, 113, 0.15);
|
||||
|
||||
--code-bg: rgba(0, 0, 0, 0.3);
|
||||
--code-fg: #86efac;
|
||||
--warn: #fef08a;
|
||||
--warn-bg: rgba(161, 98, 7, 0.3);
|
||||
}
|
||||
|
||||
*, *::before, *::after { box-sizing: border-box; }
|
||||
html, body { margin: 0; padding: 0; font-family: -apple-system, BlinkMacSystemFont,
|
||||
"Segoe UI", Roboto, sans-serif; color: var(--fg); background: var(--bg);
|
||||
font-size: 14px; line-height: 1.5; }
|
||||
.wrap { max-width: 1300px; margin: 0 auto; padding: 24px 20px; }
|
||||
h1 { margin: 0 0 2px; font-size: 22px; }
|
||||
h2 { margin: 36px 0 12px; font-size: 17px; border-bottom: 1px solid var(--bd);
|
||||
padding-bottom: 6px; }
|
||||
.meta { color: var(--mut); font-size: 13px; margin-bottom: 20px; }
|
||||
.meta b { color: var(--fg); }
|
||||
.summary { margin: 0 0 28px; font-size: 14px; }
|
||||
html, body {
|
||||
margin: 0; padding: 0;
|
||||
font-family: 'Inter', sans-serif;
|
||||
color: var(--fg);
|
||||
background: var(--bg-gradient);
|
||||
background-attachment: fixed;
|
||||
font-size: 15px; line-height: 1.6;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
.wrap { max-width: 1400px; margin: 0 auto; padding: 40px 24px; }
|
||||
|
||||
h1, h2, h3 { font-family: 'Outfit', sans-serif; font-weight: 600; letter-spacing: -0.02em; }
|
||||
h1 {
|
||||
margin: 0 0 8px; font-size: 36px;
|
||||
background: linear-gradient(to right, #a855f7, #6366f1, #3b82f6);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
display: inline-block;
|
||||
}
|
||||
h2 { margin: 48px 0 20px; font-size: 24px; border-bottom: 1px solid var(--card-border); padding-bottom: 12px; }
|
||||
|
||||
.header-glass {
|
||||
background: rgba(15, 15, 16, 0.4);
|
||||
backdrop-filter: blur(16px);
|
||||
-webkit-backdrop-filter: blur(16px);
|
||||
border: 1px solid var(--card-border);
|
||||
border-radius: 16px;
|
||||
padding: 24px;
|
||||
margin-bottom: 40px;
|
||||
box-shadow: var(--glass-shadow);
|
||||
}
|
||||
|
||||
.meta { color: var(--mut); font-size: 14px; margin-bottom: 16px; display: flex; flex-wrap: wrap; gap: 16px; }
|
||||
.meta b { color: var(--fg); font-weight: 500; }
|
||||
.meta-chip { background: rgba(255, 255, 255, 0.05); padding: 6px 12px; border-radius: 20px; border: 1px solid var(--card-border); }
|
||||
|
||||
.summary { font-size: 16px; font-weight: 500; }
|
||||
.recent-changes { margin-top: 16px; padding-top: 16px; border-top: 1px dashed var(--card-border); color: #c084fc; font-size: 14px; }
|
||||
|
||||
/* Legend table */
|
||||
table.leg { border-collapse: collapse; margin: 8px 0 20px; font-size: 13px; }
|
||||
table.leg td { padding: 5px 10px; border: 1px solid var(--bd); }
|
||||
table.leg { border-collapse: separate; border-spacing: 0; margin: 12px 0 32px; font-size: 14px; width: 100%; max-width: 600px; }
|
||||
table.leg td { padding: 12px 16px; border-bottom: 1px solid var(--card-border); background: var(--card-bg); }
|
||||
table.leg tr:first-child td:first-child { border-top-left-radius: 12px; }
|
||||
table.leg tr:first-child td:last-child { border-top-right-radius: 12px; }
|
||||
table.leg tr:last-child td:first-child { border-bottom-left-radius: 12px; border-bottom: none; }
|
||||
table.leg tr:last-child td:last-child { border-bottom-right-radius: 12px; border-bottom: none; }
|
||||
|
||||
/* Overview table */
|
||||
table.ov { width: 100%; border-collapse: collapse; font-size: 13px; margin-bottom: 32px; }
|
||||
table.ov th, table.ov td { padding: 6px 8px; border-bottom: 1px solid var(--bd);
|
||||
text-align: left; white-space: nowrap; }
|
||||
table.ov th { background: var(--card); font-weight: 600; }
|
||||
table.ov td.num { text-align: right; font-variant-numeric: tabular-nums; }
|
||||
table.ov tr:hover td { background: var(--bd); }
|
||||
.table-container { overflow-x: auto; border-radius: 12px; border: 1px solid var(--card-border); margin-bottom: 48px; box-shadow: var(--glass-shadow); }
|
||||
table.ov { width: 100%; border-collapse: collapse; font-size: 14px; background: var(--card-bg); }
|
||||
table.ov th, table.ov td { padding: 14px 16px; border-bottom: 1px solid var(--card-border); text-align: left; white-space: nowrap; }
|
||||
table.ov th { background: rgba(255, 255, 255, 0.03); font-family: 'Outfit', sans-serif; font-weight: 500; font-size: 15px; color: var(--mut); text-transform: uppercase; letter-spacing: 0.05em; }
|
||||
table.ov td.num { text-align: right; font-variant-numeric: tabular-nums; font-family: 'Inter', monospace; }
|
||||
table.ov tr { transition: background-color 0.2s ease; }
|
||||
table.ov tr:hover { background: rgba(255, 255, 255, 0.05); }
|
||||
|
||||
/* Badges */
|
||||
.badge { display: inline-block; padding: 1px 7px; border-radius: 4px;
|
||||
font-weight: 700; font-size: 11px; letter-spacing: .03em; }
|
||||
.badge.ok { background: var(--ok-bg); color: var(--ok); }
|
||||
.badge.q { background: var(--q-bg); color: var(--q); }
|
||||
.badge.man { background: var(--man-bg); color: var(--man); }
|
||||
.badge { display: inline-flex; align-items: center; justify-content: center; padding: 4px 10px; border-radius: 6px; font-weight: 600; font-size: 12px; letter-spacing: 0.04em; text-transform: uppercase; }
|
||||
.badge.ok { background: var(--ok-bg); color: var(--ok); border: 1px solid rgba(74, 222, 128, 0.2); }
|
||||
.badge.q { background: var(--q-bg); color: var(--q); border: 1px solid rgba(251, 191, 36, 0.2); }
|
||||
.badge.man { background: var(--man-bg); color: var(--man); border: 1px solid rgba(248, 113, 113, 0.2); }
|
||||
|
||||
/* Beat cards */
|
||||
.beat { background: var(--card); border: 1px solid var(--bd); border-radius: 10px;
|
||||
padding: 16px; margin-bottom: 28px; }
|
||||
.beat-hdr { display: flex; align-items: center; gap: 10px; margin-bottom: 12px; }
|
||||
.beat-hdr h3 { margin: 0; font-size: 16px; }
|
||||
.beat-hdr .status-text { font-size: 13px; color: var(--mut); }
|
||||
.beats-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(600px, 1fr)); gap: 32px; }
|
||||
.beat {
|
||||
background: var(--card-bg);
|
||||
backdrop-filter: blur(12px);
|
||||
-webkit-backdrop-filter: blur(12px);
|
||||
border: 1px solid var(--card-border);
|
||||
border-radius: 16px;
|
||||
padding: 24px;
|
||||
box-shadow: var(--glass-shadow);
|
||||
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
||||
}
|
||||
.beat:hover { transform: translateY(-4px); box-shadow: 0 12px 40px 0 rgba(0, 0, 0, 0.45); border-color: rgba(255,255,255,0.15); }
|
||||
.beat-hdr { display: flex; align-items: center; gap: 12px; margin-bottom: 20px; }
|
||||
.beat-hdr h3 { margin: 0; font-size: 20px; color: #fff; }
|
||||
.beat-hdr .status-text { font-size: 14px; color: var(--mut); margin-left: auto; }
|
||||
|
||||
/* Compare video — full width */
|
||||
.compare-wrap { margin-bottom: 12px; }
|
||||
.compare-label { font-size: 11px; font-weight: 600; text-transform: uppercase;
|
||||
letter-spacing: .06em; color: var(--mut); margin-bottom: 4px; }
|
||||
.compare-wrap video, .compare-wrap img { width: 100%; height: auto;
|
||||
border-radius: 6px; background: #000; display: block; }
|
||||
.stills-pair { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
|
||||
.stills-pair img { width: 100%; height: auto; border-radius: 6px; background: #000; }
|
||||
.empty-media { display: flex; align-items: center; justify-content: center;
|
||||
aspect-ratio: 32/9; background: #000; color: #555; border-radius: 6px;
|
||||
font-size: 13px; }
|
||||
/* Compare video */
|
||||
.compare-wrap { margin-bottom: 20px; border-radius: 12px; overflow: hidden; position: relative; background: #000; box-shadow: inset 0 0 0 1px var(--card-border); }
|
||||
.compare-label { position: absolute; top: 12px; left: 12px; z-index: 10; font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.08em; background: rgba(0,0,0,0.6); backdrop-filter: blur(4px); padding: 4px 10px; border-radius: 4px; color: #fff; border: 1px solid rgba(255,255,255,0.1); }
|
||||
.compare-wrap video, .compare-wrap img { width: 100%; height: auto; display: block; transition: opacity 0.3s; }
|
||||
.compare-wrap:hover video { opacity: 0.95; }
|
||||
.stills-pair { display: grid; grid-template-columns: 1fr 1fr; gap: 2px; background: var(--card-border); }
|
||||
.stills-pair img { width: 100%; height: auto; background: #000; }
|
||||
.empty-media { display: flex; align-items: center; justify-content: center; aspect-ratio: 32/9; background: rgba(0,0,0,0.5); color: var(--mut); font-size: 14px; }
|
||||
|
||||
/* Metadata footer inside card */
|
||||
.beat-meta { display: grid; grid-template-columns: 1fr 1fr; gap: 12px 24px;
|
||||
margin-top: 12px; font-size: 13px; border-top: 1px solid var(--bd); padding-top: 10px; }
|
||||
.kv { margin: 3px 0; }
|
||||
.kv b { display: inline-block; min-width: 72px; color: var(--mut); font-weight: 500; }
|
||||
.tc { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px; }
|
||||
.seg-list { margin: 4px 0 0 0; padding-left: 16px; font-size: 12px; color: var(--mut);
|
||||
list-style: disc; }
|
||||
.seg-list li { margin: 2px 0; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
|
||||
.warn-box { background: var(--warn-bg); color: var(--warn); border-radius: 4px;
|
||||
padding: 4px 8px; font-size: 12px; margin: 4px 0; }
|
||||
.hint { display: inline-block; background: var(--code-bg); color: var(--code-fg);
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 11px;
|
||||
padding: 2px 8px; border-radius: 4px; margin-top: 6px; }
|
||||
.beat-meta { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin-top: 20px; font-size: 14px; border-top: 1px solid var(--card-border); padding-top: 20px; }
|
||||
.kv { margin: 8px 0; display: flex; flex-direction: column; gap: 2px; }
|
||||
.kv b { color: var(--mut); font-weight: 500; font-size: 12px; text-transform: uppercase; letter-spacing: 0.05em; }
|
||||
.kv-val { color: var(--fg); font-weight: 400; }
|
||||
.tc { font-family: 'Inter', monospace; font-size: 13px; background: var(--code-bg); padding: 2px 6px; border-radius: 4px; border: 1px solid rgba(255,255,255,0.05); }
|
||||
.seg-list { margin: 8px 0 0 0; padding-left: 0; font-size: 13px; list-style: none; display: flex; flex-direction: column; gap: 6px; }
|
||||
.seg-list li { background: rgba(255,255,255,0.03); padding: 8px 12px; border-radius: 6px; border: 1px solid var(--card-border); font-family: 'Inter', monospace; }
|
||||
.warn-box { background: var(--warn-bg); color: var(--warn); border-radius: 8px; padding: 10px 14px; font-size: 13px; margin: 12px 0; border: 1px solid rgba(254, 240, 138, 0.2); display: flex; align-items: center; gap: 8px; }
|
||||
.hint { display: inline-block; background: var(--code-bg); color: var(--code-fg); font-family: 'Inter', monospace; font-size: 12px; padding: 6px 12px; border-radius: 6px; margin-top: 12px; border: 1px solid rgba(134, 239, 172, 0.2); }
|
||||
</style>
|
||||
</head>
|
||||
<body><div class="wrap">
|
||||
@@ -709,6 +758,22 @@ def _he(s: str) -> str:
|
||||
.replace(">", ">").replace('"', """))
|
||||
|
||||
|
||||
def _get_recent_changes(project_root: Path) -> str:
|
||||
try:
|
||||
import subprocess
|
||||
proc = subprocess.run(
|
||||
["git", "log", "--invert-grep", "--grep=Auto-update", "-1", "--pretty=%B"],
|
||||
capture_output=True, text=True, cwd=str(project_root), timeout=5
|
||||
)
|
||||
if proc.stdout:
|
||||
lines = proc.stdout.strip().split('\n')
|
||||
cleaned = [line for line in lines if line and not line.startswith('Co-Authored-By')]
|
||||
return "<br>".join(cleaned[:3]) or "No recent changes found."
|
||||
except Exception:
|
||||
pass
|
||||
return "No recent changes available."
|
||||
|
||||
|
||||
def render_html(
|
||||
rows: list[BeatRow],
|
||||
trailer_fps: float,
|
||||
@@ -717,6 +782,7 @@ def render_html(
|
||||
source_path: Path,
|
||||
with_clips: bool,
|
||||
generated_at: datetime,
|
||||
project_root: Path = Path("."),
|
||||
) -> str:
|
||||
matched = sum(1 for r in rows if r.matched)
|
||||
confirmed = sum(1 for r in rows if r.confirmed)
|
||||
@@ -724,43 +790,49 @@ def render_html(
|
||||
parts: list[str] = [HTML_HEAD]
|
||||
|
||||
# Header
|
||||
parts.append(f'<h1>Cutter-Report</h1>')
|
||||
recent = _get_recent_changes(project_root)
|
||||
|
||||
parts.append('<div class="header-glass">')
|
||||
parts.append(f'<h1>Cutter & Match Report</h1>')
|
||||
parts.append('<div class="meta">')
|
||||
parts.append(
|
||||
f'Generiert: <b>{generated_at.strftime("%Y-%m-%d %H:%M:%S")}</b> | '
|
||||
f'Trailer: <b>{_he(trailer_path.name)}</b> @ {trailer_fps:.3f} fps | '
|
||||
f'Source: <b>{_he(source_path.name)}</b> @ {source_fps:.3f} fps'
|
||||
f'<span class="meta-chip">Generiert: <b>{generated_at.strftime("%Y-%m-%d %H:%M:%S")}</b></span>'
|
||||
f'<span class="meta-chip">Trailer: <b>{_he(trailer_path.name)}</b> @ {trailer_fps:.3f} fps</span>'
|
||||
f'<span class="meta-chip">Source: <b>{_he(source_path.name)}</b> @ {source_fps:.3f} fps</span>'
|
||||
)
|
||||
parts.append('</div>')
|
||||
parts.append(
|
||||
f'<div class="summary"><b>{len(rows)}</b> Beats — '
|
||||
f'<b>{matched}</b> automatisch (<b>{confirmed}</b> bestätigt) — '
|
||||
f'<b>{len(rows) - matched}</b> manuell.</div>'
|
||||
f'<span style="color:var(--ok)"><b>{matched}</b> automatisch (<b>{confirmed}</b> bestätigt)</span> — '
|
||||
f'<span style="color:var(--man)"><b>{len(rows) - matched}</b> manuell</span>.</div>'
|
||||
)
|
||||
parts.append(f'<div class="recent-changes"><b>Recent Changes:</b><br>{recent}</div>')
|
||||
parts.append('</div>')
|
||||
|
||||
# Legend
|
||||
parts.append('<h2>Legende</h2>')
|
||||
parts.append('<table class="leg"><tbody>')
|
||||
parts.append(
|
||||
'<tr><td><span class="badge ok">OK</span></td>'
|
||||
'<td>Bestätigt — übernehmen</td></tr>'
|
||||
'<td>Bestätigt — direkt in Schnitt-Timeline übernehmen</td></tr>'
|
||||
)
|
||||
parts.append(
|
||||
'<tr><td><span class="badge q">?</span></td>'
|
||||
'<td>Vorläufig — Phase im NLE prüfen, Source-In ggf. nachjustieren</td></tr>'
|
||||
'<td>Vorläufig — Phase und Aktion im NLE visuell prüfen</td></tr>'
|
||||
)
|
||||
parts.append(
|
||||
'<tr><td><span class="badge man">MAN.</span></td>'
|
||||
'<td>Kein automatischer Treffer — manuell setzen oder Schwarzfade</td></tr>'
|
||||
'<td>Kein Treffer — manuell suchen oder Schwarzbild einfügen</td></tr>'
|
||||
)
|
||||
parts.append('</tbody></table>')
|
||||
|
||||
# Overview table
|
||||
parts.append('<h2>Übersicht</h2>')
|
||||
parts.append('<div class="table-container">')
|
||||
parts.append(
|
||||
'<table class="ov"><thead><tr>'
|
||||
'<th>Beat</th><th>Trailer In–Out (TC)</th><th>Dauer</th>'
|
||||
'<th>Source In (TC)</th><th>Scene</th><th>Score</th><th>Status</th>'
|
||||
'<th>Beat</th><th>Trailer TC In–Out</th><th>Dauer</th>'
|
||||
'<th>Source TC In</th><th>Scene</th><th>Score</th><th>Status</th>'
|
||||
'</tr></thead><tbody>'
|
||||
)
|
||||
for r in rows:
|
||||
@@ -787,10 +859,11 @@ def render_html(
|
||||
f'<td><span class="badge {bcls}">{r.status}</span></td>'
|
||||
f'</tr>'
|
||||
)
|
||||
parts.append('</tbody></table>')
|
||||
parts.append('</tbody></table></div>')
|
||||
|
||||
# Per-beat cards
|
||||
parts.append('<h2>Beat-Details</h2>')
|
||||
parts.append('<div class="beats-grid">')
|
||||
for r in rows:
|
||||
ti = smpte(r.trailer_in_s, trailer_fps)
|
||||
to = smpte(r.trailer_out_s, trailer_fps)
|
||||
@@ -839,13 +912,13 @@ def render_html(
|
||||
|
||||
# Left col: trailer info
|
||||
parts.append('<div>')
|
||||
parts.append(f'<div class="kv"><b>Trailer</b> <span class="tc">{ti}–{to}</span>'
|
||||
f' <span style="color:var(--mut)">({dur:.2f}s)</span></div>')
|
||||
parts.append(f'<div class="kv"><b>Trailer</b> <div class="kv-val"><span class="tc">{ti}–{to}</span>'
|
||||
f' <span style="color:var(--mut)">({dur:.2f}s)</span></div></div>')
|
||||
if r.phase:
|
||||
parts.append(f'<div class="kv"><b>Phase</b> {_he(r.phase)}</div>')
|
||||
parts.append(f'<div class="kv"><b>Phase</b> <span class="kv-val">{_he(r.phase)}</span></div>')
|
||||
if r.composition:
|
||||
extra = f", {r.setting}" if r.setting else ""
|
||||
parts.append(f'<div class="kv"><b>Bild</b> {_he(r.composition + extra)}</div>')
|
||||
parts.append(f'<div class="kv"><b>Bild</b> <span class="kv-val">{_he(r.composition + extra)}</span></div>')
|
||||
parts.append('</div>')
|
||||
|
||||
# Right col: source info
|
||||
@@ -859,10 +932,10 @@ def render_html(
|
||||
))
|
||||
scene_str = f"Scenes {', '.join(all_scenes)} · {r.num_segments} Segmente"
|
||||
parts.append(
|
||||
f'<div class="kv"><b>Source</b> <span class="tc">{si}</span>'
|
||||
f' <span style="color:var(--mut)">(multi-shot)</span></div>'
|
||||
f'<div class="kv"><b>Source</b> <div class="kv-val"><span class="tc">{si}</span>'
|
||||
f' <span style="color:var(--mut)">(multi-shot)</span></div></div>'
|
||||
)
|
||||
parts.append(f'<div class="kv"><b>Scene</b> {scene_str}</div>')
|
||||
parts.append(f'<div class="kv"><b>Scene</b> <span class="kv-val">{scene_str}</span></div>')
|
||||
# Segment list
|
||||
parts.append('<ul class="seg-list">')
|
||||
for i, seg in enumerate(r.segments):
|
||||
@@ -882,11 +955,11 @@ def render_html(
|
||||
else:
|
||||
parts.append(
|
||||
f'<div class="kv"><b>Source</b>'
|
||||
f' <span class="tc">{si}–{so}</span></div>'
|
||||
f' <div class="kv-val"><span class="tc">{si}–{so}</span></div></div>'
|
||||
)
|
||||
parts.append(
|
||||
f'<div class="kv"><b>Scene</b> {r.scene_id}'
|
||||
f' · Score {r.score:.3f}</div>'
|
||||
f'<div class="kv"><b>Scene</b> <span class="kv-val">{r.scene_id}'
|
||||
f' · Score {r.score:.3f}</span></div>'
|
||||
)
|
||||
if r.score > 0 and r.score < 0.65:
|
||||
parts.append(
|
||||
@@ -904,6 +977,7 @@ def render_html(
|
||||
parts.append('</div>') # .beat-meta
|
||||
parts.append('</div>') # .beat
|
||||
|
||||
parts.append('</div>') # .beats-grid
|
||||
parts.append(HTML_FOOT)
|
||||
return "".join(parts)
|
||||
|
||||
@@ -966,8 +1040,13 @@ def main() -> int:
|
||||
)
|
||||
(project_root / "CUTTER_REPORT.md").write_text(md, encoding="utf-8")
|
||||
(project_root / "CUTTER_REPORT.html").write_text(html, encoding="utf-8")
|
||||
legacy_path = project_root / "output" / "report" / "match_report.html"
|
||||
legacy_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
legacy_path.write_text(html, encoding="utf-8")
|
||||
|
||||
print(f"Wrote {project_root / 'CUTTER_REPORT.md'}")
|
||||
print(f"Wrote {project_root / 'CUTTER_REPORT.html'}")
|
||||
print(f"Wrote {legacy_path}")
|
||||
return 0
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user