From 32fc9b39dfc13ca5a34549ca414a75fb747344d5 Mon Sep 17 00:00:00 2001 From: Chris Dumas Date: Thu, 27 Aug 2026 07:48:04 +0000 Subject: [PATCH] Report per-shot ref matches in H3 script --- dumas_h3_longvideos.py | 43 +++++++++++++++++++++++++++++-- tests/test_dumas_h3_longvideos.py | 2 ++ 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/dumas_h3_longvideos.py b/dumas_h3_longvideos.py index 6fb9fd7..d397975 100644 --- a/dumas_h3_longvideos.py +++ b/dumas_h3_longvideos.py @@ -3273,6 +3273,45 @@ def annotate_script_guards(gens, anatomy_shots, anatomy_mode): return "\n".join(lines + ["", body]) if body else "\n".join(lines) +def annotate_script_debug(gens, anatomy_shots, anatomy_mode, ref_slots): + parts = [annotate_script_guards(gens, anatomy_shots, anatomy_mode)] + ref_report = annotate_script_refs(gens, ref_slots) + if ref_report: + parts.insert(1, ref_report) + return "\n".join(part for part in parts if part) + + +def annotate_script_refs(gens, ref_slots): + """Per-shot reference routing summary for the script socket.""" + lines = [] + for shot_index, gen in enumerate(gens or [], 1): + tagged = _slot_refs_for_text(gen, ref_slots) + named = _named_character_refs_for_text(gen, ref_slots) + seen = set() + merged = [] + for slot_number, ref in tagged: + key = (slot_number, "tag") + if key in seen: + continue + seen.add(key) + merged.append((slot_number, ref, "tag")) + for slot_number, ref in named: + key = (slot_number, "name") + if key in seen or any(slot_number == existing[0] for existing in merged): + continue + seen.add(key) + merged.append((slot_number, ref, "name")) + if not merged: + lines.append(f"# shot {shot_index} refs: none") + continue + labels = [] + for slot_number, ref, source in merged: + label = _reference_text(ref.get("name")) or _reference_text(ref.get("id")) or f"reference {slot_number}" + labels.append(f"Picture {slot_number} {label} (by {source})") + lines.append(f"# shot {shot_index} refs: " + "; ".join(labels)) + return "\n".join(lines) + + def speech_flags(beats): """Per-beat: does it contain scripted (quoted) dialogue? Same rule the prompt builder uses to decide silencing, exposed so the renderer can also MUTE the @@ -6455,7 +6494,7 @@ class H3LongVideos: # correctly-SHAPED empty one rather than None: a downstream LATENT input # would choke on None, and this keeps the preview wireable exactly like # a real run. - return (ph_img, ph_audio, plan, annotate_script_guards(gens, anatomy_shots, anatomy_mode), max(plan_lens), + return (ph_img, ph_audio, plan, annotate_script_debug(gens, anatomy_shots, anatomy_mode, ref_slots), max(plan_lens), sum(plan_lens), shots, total, float(fps), int(fps), _empty_av_latent(w, h, 5, fps)[0], global_soundscape, [], []) @@ -6762,7 +6801,7 @@ class H3LongVideos: watermark_opacity, watermark_margin, intro_text, intro_seconds, intro_fade, intro_size, intro_position, overlay_font, overlay_stroke) - script = annotate_script_guards(gens, anatomy_shots, anatomy_mode) + script = annotate_script_debug(gens, anatomy_shots, anatomy_mode, ref_slots) actual = all_frames.shape[0] / fps uniform_len = len(set(shot_lens)) == 1 shape_str = (f"{len(gens)} shot(s) x {shot_lens[0]}f (~{shot_lens[0] / fps:.1f}s each) " diff --git a/tests/test_dumas_h3_longvideos.py b/tests/test_dumas_h3_longvideos.py index de5f524..88394d1 100644 --- a/tests/test_dumas_h3_longvideos.py +++ b/tests/test_dumas_h3_longvideos.py @@ -473,6 +473,8 @@ class DumasH3LongVideosHelperTests(unittest.TestCase): ) self.assertIn("# anatomy_guard: injected on shot(s) 2", result[3]) + self.assertIn("# shot 1 refs: none", result[3]) + self.assertIn("# shot 2 refs: none", result[3]) self.assertIn( "[Generation 1] editorial room, soft practical lighting. Francine stands alone.", result[3],