From 57541ee8474ac5f1ca977d8a00feb020051e35ed Mon Sep 17 00:00:00 2001 From: Chris Dumas Date: Thu, 27 Aug 2026 07:29:02 +0000 Subject: [PATCH] Show anatomy guard in H3 script output --- dumas_h3_longvideos.py | 35 ++++++++++++++++++++++++------- tests/test_dumas_h3_longvideos.py | 1 + 2 files changed, 29 insertions(+), 7 deletions(-) diff --git a/dumas_h3_longvideos.py b/dumas_h3_longvideos.py index 604e7ce..0cf3871 100644 --- a/dumas_h3_longvideos.py +++ b/dumas_h3_longvideos.py @@ -3225,7 +3225,7 @@ def continuity_warnings(gens): return out -def dialogue_filler_warnings(beats, seconds_per_shot): +def dialogue_filler_warnings(beats, seconds_per_shot): """Dialogue shots with far more time than their line, which H3 fills with speech. dialogue_fit_warnings covers the opposite error -- a line too long for its shot, @@ -3249,10 +3249,31 @@ def dialogue_filler_warnings(beats, seconds_per_shot): if gap >= 3.0 and sec > spoken * 2: out.append(f"shot {i}: {spoken:.1f}s of dialogue in a {sec:.1f}s shot -- {gap:.1f}s of " f"unscripted audio the model will fill with more speech") - return out - - -def speech_flags(beats): + 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 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 audio of non-speech shots -- a deterministic fix when H3 vocalizes anyway.""" @@ -6433,7 +6454,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, "\n---\n".join(gens), max(plan_lens), + return (ph_img, ph_audio, plan, annotate_script_guards(gens, anatomy_shots, anatomy_mode), max(plan_lens), sum(plan_lens), shots, total, float(fps), int(fps), _empty_av_latent(w, h, 5, fps)[0], global_soundscape, [], []) @@ -6740,7 +6761,7 @@ class H3LongVideos: watermark_opacity, watermark_margin, intro_text, intro_seconds, intro_fade, intro_size, intro_position, overlay_font, overlay_stroke) - script = "\n---\n".join(gens) + script = annotate_script_guards(gens, anatomy_shots, anatomy_mode) 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 458c577..a19cf0c 100644 --- a/tests/test_dumas_h3_longvideos.py +++ b/tests/test_dumas_h3_longvideos.py @@ -459,6 +459,7 @@ class DumasH3LongVideosHelperTests(unittest.TestCase): plan_only=True, ) + self.assertIn("# anatomy_guard: injected on shot(s) 2", result[3]) self.assertIn( "[Generation 1] editorial room, soft practical lighting. Francine stands alone.", result[3],