#!/usr/bin/env python3 """ rematch_frames.py — remake each named July frame example against the CURRENT pipeline. For a file named _<...>_.jpg, find a second in this film's replay where that actor is drawn in the matching class (FP for *_fpi_*, TP for *_tp/perfect*), extract + annotate it, and write it over the doc asset. Reports which July examples no longer reproduce (honest — the config/model changed). Needs the per-film raw replay (experiments/dumps + replay --raw-out already run by regen_frame_examples.sh into the scratch predictions). Reads those. """ from __future__ import annotations import json, sys, subprocess, re from pathlib import Path sys.path.insert(0, "scripts/optimizer"); sys.path.insert(0, "scripts/validation") import dump_error_frames as D from second_score import load_second_timeline, _match SP = Path("/tmp/claude-1000/-home-dtourolle-Development-scene-actor-extraction/" "c579f8cf-2974-4cbd-be88-afec68dbbf58/scratchpad") ASSETS = Path("docs/assets/images") LUT = json.load(open("experiments/file-lut.json")) FILMS = json.load(open("experiments/manifests/films_LVFace_opencv5.json")) XR = {f["slug"]: f["xray"] for f in FILMS} # filename → (film slug, actor substring, class). class: "fp" | "tp". # actor substring is matched case-insensitively against drawn names. JOBS = { "lord_of_war_fpi_reddick.jpg": ("Lord_of_War", "reddick", "fp"), "lord_of_war_fpi_shumbris.jpg": ("Lord_of_War", "shumbris", "fp"), "lord_of_war_fpi_reagan_photo.jpg": ("Lord_of_War", "reagan", "fp"), "lovelace_fpi_sevigny.jpg": ("Lovelace", "sevigny", "fp"), "lovelace_robert_patrick_fpi.jpg": ("Lovelace", "patrick", "fp"), "lovelace_perfect_second.jpg": ("Lovelace", None, "tp"), "lovelace_polygraph_bridged.jpg": ("Lovelace", None, "tp"), "many_saints_fpi_deschanel.jpg": ("The_Many_Saints_of_Newark", "deschanel", "fp"), "many_saints_fpi_gardner.jpg": ("The_Many_Saints_of_Newark", "gardner", "fp"), "many_saints_fpi_yates.jpg": ("The_Many_Saints_of_Newark", "yates", "fp"), "many_saints_outofcast_fpi.jpg": ("The_Many_Saints_of_Newark", None, "fp"), "scarface_fpi_alley.jpg": ("Scarface", "alley", "fp"), "downton_crew_fn.jpg": ("Downton_Abbey__A_New_Era", None, "tp"), "downton_wedding_couple.jpg": ("Downton_Abbey__A_New_Era", None, "tp"), "downton_tp_example.jpg": ("Downton_Abbey__A_New_Era", None, "tp"), "valerian_screen_call.jpg": ("Valerian_and_the_City_of_a_Thousand_Plan", None, "tp"), "cafe_society_rapid_cut.jpg": ("Café_Society", None, "tp"), # germar_beats_xray / downton_funeral_19of20 are July-narrative-specific; skip. } def gt_keysets(slug): tl, _, _ = load_second_timeline(XR[slug]) return tl def main(): made, missing = [], [] for fname, (slug, actor, cls) in JOBS.items(): raw = SP / f"{slug}_raw.jsonl" if not raw.exists(): missing.append((fname, "no raw replay")); continue tl = gt_keysets(slug) best = None # (t, actor_dict, fp_keys) for line in open(raw): d = json.loads(line) t = int(d["timestamp_sec"]) drawn = [a for a in d.get("visible_actors", []) if a.get("actor_idx", -1) >= 0] if not drawn: continue gt = tl.get(t, []) fp_keys = {D._name_key(a["name"]) for a in drawn if not any(D._name_key(a["name"]) in g for g in gt)} for a in drawn: nk = D._name_key(a["name"]); is_fp = nk in fp_keys if actor and actor not in a["name"].lower(): continue match = (is_fp if cls == "fp" else not is_fp) if not match: continue # prefer high similarity + a clean single-subject frame score = a["similarity"] - 0.05*len(drawn) if best is None or score > best[3]: best = (t, d, fp_keys, score) if best is None: missing.append((fname, f"no current {cls} for {actor or 'any'}")); continue t, d, fp_keys, _ = best # FN names at t: X-Ray scene cast whose keyset matches no drawn face. gt = tl.get(t, []) drawn_keys = [set(D._name_key(a["name"]).replace("name:", "") for _ in [0]) for a in d.get("visible_actors", []) if a.get("actor_idx", -1) >= 0] drawn_ks = [D._name_key(a["name"]) for a in d.get("visible_actors", []) if a.get("actor_idx", -1) >= 0] fn_names = [] for ga in gt: if not any(dk in ga for dk in drawn_ks): readable = sorted(x for x in ga if not x.startswith("imdb:") and not x.startswith("tmdb:") and not x.startswith("jf:")) if readable: fn_names.append(readable[0]) out = ASSETS / fname try: D.extract_frame(LUT[slug], t, out) D.draw_annotations(out, d["visible_actors"], fp_keys=fp_keys, fn_names=fn_names) made.append((fname, slug, t)) except subprocess.CalledProcessError: missing.append((fname, "ffmpeg failed")) print("=== remade ===") for f, s, t in made: print(f" {f} ({s} t={t}s)") print("=== no current equivalent (left as-is / flag in doc) ===") for f, why in missing: print(f" {f} — {why}") if __name__ == "__main__": main()