diff --git a/dumas_h3_longvideos.py b/dumas_h3_longvideos.py index 6a20804..dfe1312 100644 --- a/dumas_h3_longvideos.py +++ b/dumas_h3_longvideos.py @@ -3247,65 +3247,79 @@ def dialogue_filler_warnings(beats, seconds_per_shot): return out -def annotate_script_guards(gens, anatomy_shots, anatomy_mode): - """Human-readable guard report prepended to the script socket. - - The `script` output is for inspection, not for feeding back into the node, so - a compact report there is the quickest way to see whether a guard actually made - it into each shot's prompt text. - """ - lines = [] - if anatomy_mode == "off": - lines.append("# anatomy_guard: off") - elif anatomy_shots: - lines.append( - "# anatomy_guard: injected on shot(s) " - + ",".join(str(n) for n in anatomy_shots) - ) - else: - lines.append("# anatomy_guard: no shots matched the auto gate") - body = "\n---\n".join(gens) - 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 = [] - normalized_slots = _normalized_ref_slots(ref_slots) - for shot_index, gen in enumerate(gens or [], 1): - tagged = _slot_refs_for_text(gen, normalized_slots) - named = _named_refs_for_text(gen, normalized_slots, kinds=("character", "location")) - 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 _annotate_shot_refs(gen, ref_slots, shot_index): + normalized_slots = _normalized_ref_slots(ref_slots) + tagged = _slot_refs_for_text(gen, normalized_slots) + named = _named_refs_for_text(gen, normalized_slots, kinds=("character", "location")) + 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: + return f"# shot {shot_index} refs: none", "References used: none" + labels = [] + info_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})") + info_labels.append(f" {label} (matched by {source})") + return ( + f"# shot {shot_index} refs: " + "; ".join(labels), + "References used: " + "; ".join(info_labels), + ) + + +def _annotate_shot_guard(shot_index, anatomy_shots, anatomy_mode): + anatomy_set = set(anatomy_shots or []) + if anatomy_mode == "off": + return "Anatomy guard: off" + if shot_index in anatomy_set: + return "Anatomy guard: injected into this prompt" + if anatomy_mode == "forced": + return "Anatomy guard: forced on, but nothing was injected" + return "Anatomy guard: not injected for this prompt" + + +def annotate_script_debug(gens, anatomy_shots, anatomy_mode, ref_slots): + blocks = [] + for shot_index, gen in enumerate(gens or [], 1): + _ref_report, ref_info = _annotate_shot_refs(gen, ref_slots, shot_index) + blocks.append( + "\n".join( + [ + f"Prompt {shot_index}", + gen, + "", + f"Beat {shot_index} info", + _annotate_shot_guard(shot_index, anatomy_shots, anatomy_mode), + ref_info, + ] + ) + ) + if blocks: + return "\n\n---\n\n".join(blocks) + if anatomy_mode == "off": + return "Beat info\nAnatomy guard: off\nReferences used: none" + return "Beat info\nAnatomy guard: not injected for this prompt\nReferences used: none" + + +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): + shot_report, _shot_info = _annotate_shot_refs(gen, ref_slots, shot_index) + lines.append(shot_report) + return "\n".join(lines) def speech_flags(beats): diff --git a/tests/test_dumas_h3_longvideos.py b/tests/test_dumas_h3_longvideos.py index 7846b97..b7934a8 100644 --- a/tests/test_dumas_h3_longvideos.py +++ b/tests/test_dumas_h3_longvideos.py @@ -599,6 +599,25 @@ class DumasH3LongVideosHelperTests(unittest.TestCase): self.assertIn("# shot 1 refs: Picture 1 Mara (by name); Picture 2 Hangar (by name)", report) self.assertIn("# shot 2 refs: none", report) + def test_annotate_script_debug_groups_each_prompt_with_its_beat_info(self): + refs = [ + {"kind": "character", "image": "img1", "name": "Mara"}, + {"kind": "location", "image": "img2", "name": "Hangar"}, + ] + + report = self.module.annotate_script_debug( + ["[Generation 1] Mara waits in the Hangar.", "[Generation 2] Nobody else is here."], + [1], + "auto", + refs, + ) + + self.assertIn("Prompt 1\n[Generation 1] Mara waits in the Hangar.", report) + self.assertIn("Beat 1 info\nAnatomy guard: injected into this prompt", report) + self.assertIn("References used: Mara (matched by name); Hangar (matched by name)", report) + self.assertIn("Prompt 2\n[Generation 2] Nobody else is here.", report) + self.assertIn("Beat 2 info\nAnatomy guard: not injected for this prompt\nReferences used: none", report) + def test_input_types_expose_nine_ref_slots(self): optional = self.module.H3LongVideos.INPUT_TYPES()["optional"] @@ -1038,9 +1057,9 @@ class DumasH3LongVideosHelperTests(unittest.TestCase): plan_only=True, ) - 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("Prompt 1", result[3]) + self.assertIn("Beat 1 info\nAnatomy guard: not injected for this prompt\nReferences used: none", result[3]) + self.assertIn("Beat 2 info\nAnatomy guard: injected into this prompt\nReferences used: none", result[3]) self.assertIn( "[Generation 1] editorial room, soft practical lighting. Francine stands alone.", result[3],