#!/usr/bin/env python3 """Build review sheets for every L110 before/after passage capture.""" from __future__ import annotations import subprocess from pathlib import Path ROOT = Path(__file__).resolve().parents[4] CAPTURES = Path(__file__).resolve().parent / "captures" SHEETS = CAPTURES / "contact-sheets" GROUPS = { "openair-view-1": ("*-openair-*-view-1.png", "3x3"), "openair-view-2": ("*-openair-*-view-2.png", "3x3"), "openair-view-3": ("*-openair-*-view-3.png", "3x3"), "openair-closeups": ("*-openair-*-closeup-*.png", "3x4"), "mouths": ("*-*-*-*-mouth.png", "3x2"), "stations": ("*-station-*.png", "3x3"), } def build(variant: str, group: str, pattern: str, layout: str) -> int: files = sorted((CAPTURES / variant).glob(pattern)) if not files: raise RuntimeError(f"no images matched {variant}/{pattern}") columns, rows = (int(value) for value in layout.split("x")) scale = "480:270" output = SHEETS / f"{variant}-{group}.png" command = ["ffmpeg", "-hide_banner", "-loglevel", "error", "-y", "-f", "image2", "-pattern_type", "glob", "-framerate", "1", "-i", str(CAPTURES / variant / pattern), "-vf", f"scale={scale},tile={columns}x{rows}:nb_frames={columns*rows}:padding=6:margin=6", "-frames:v", "1", str(output)] result = subprocess.run(command, cwd=ROOT, check=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) if result.returncode: raise RuntimeError(f"ffmpeg failed for {variant}/{group}: {result.stderr.strip()}") return len(files) def main() -> int: SHEETS.mkdir(parents=True, exist_ok=True) rows = [] for variant in ("before", "after"): for group, (pattern, layout) in GROUPS.items(): count = build(variant, group, pattern, layout) rows.append(f"- [{variant} {group}]({variant}-{group}.png) — {count} views") (SHEETS / "INDEX.md").write_text("# L110 capture contact sheets\n\n" + "\n".join(rows) + "\n", encoding="utf-8") print(f"contact sheets={len(rows)} captures={sum( int(row.rsplit('— ', 1)[1].split()[0]) for row in rows)}") return 0 if __name__ == "__main__": raise SystemExit(main())