Restore legacy H3 ref image inputs

This commit is contained in:
2026-08-28 08:34:11 +00:00
parent 37f8ec51c8
commit a10a5d7dcf
2 changed files with 125 additions and 3 deletions
+20 -1
View File
@@ -5731,6 +5731,15 @@ class H3LongVideos:
"ref_7": ("REFERENCE", {"tooltip": "Reference object for <Picture 7>."}), "ref_7": ("REFERENCE", {"tooltip": "Reference object for <Picture 7>."}),
"ref_8": ("REFERENCE", {"tooltip": "Reference object for <Picture 8>."}), "ref_8": ("REFERENCE", {"tooltip": "Reference object for <Picture 8>."}),
"ref_9": ("REFERENCE", {"tooltip": "Reference object for <Picture 9>."}), "ref_9": ("REFERENCE", {"tooltip": "Reference object for <Picture 9>."}),
"ref_image_1": ("*", {"tooltip": "Legacy alias for ref_1. Accepts old IMAGE wiring or a REFERENCE payload."}),
"ref_image_2": ("*", {"tooltip": "Legacy alias for ref_2. Accepts old IMAGE wiring or a REFERENCE payload."}),
"ref_image_3": ("*", {"tooltip": "Legacy alias for ref_3. Accepts old IMAGE wiring or a REFERENCE payload."}),
"ref_image_4": ("*", {"tooltip": "Legacy alias for ref_4. Accepts old IMAGE wiring or a REFERENCE payload."}),
"ref_image_5": ("*", {"tooltip": "Legacy alias for ref_5. Accepts old IMAGE wiring or a REFERENCE payload."}),
"ref_image_6": ("*", {"tooltip": "Legacy alias for ref_6. Accepts old IMAGE wiring or a REFERENCE payload."}),
"ref_image_7": ("*", {"tooltip": "Legacy alias for ref_7. Accepts old IMAGE wiring or a REFERENCE payload."}),
"ref_image_8": ("*", {"tooltip": "Legacy alias for ref_8. Accepts old IMAGE wiring or a REFERENCE payload."}),
"ref_image_9": ("*", {"tooltip": "Legacy alias for ref_9. Accepts old IMAGE wiring or a REFERENCE payload."}),
"plan_only": ("BOOLEAN", {"default": False, "plan_only": ("BOOLEAN", {"default": False,
"tooltip": "Preview the shot split WITHOUT rendering. Uses THIS node's own settings (no " "tooltip": "Preview the shot split WITHOUT rendering. Uses THIS node's own settings (no "
"second node, no duplicate entry): returns the plan in 'info' and the " "second node, no duplicate entry): returns the plan in 'info' and the "
@@ -6245,6 +6254,9 @@ class H3LongVideos:
ref_1=None, ref_2=None, ref_3=None, ref_4=None, ref_1=None, ref_2=None, ref_3=None, ref_4=None,
ref_5=None, ref_6=None, ref_7=None, ref_8=None, ref_5=None, ref_6=None, ref_7=None, ref_8=None,
ref_9=None, ref_9=None,
ref_image_1=None, ref_image_2=None, ref_image_3=None, ref_image_4=None,
ref_image_5=None, ref_image_6=None, ref_image_7=None, ref_image_8=None,
ref_image_9=None,
ref_mode="where tagged", ref_image_size="match", ref_noise_aug=0.999, ref_mode="where tagged", ref_image_size="match", ref_noise_aug=0.999,
detail_pass=False, detail_sampler_name="euler", detail_scheduler="karras", detail_pass=False, detail_sampler_name="euler", detail_scheduler="karras",
detail_steps=8, detail_denoise=0.4, detail_steps=8, detail_denoise=0.4,
@@ -6267,9 +6279,16 @@ class H3LongVideos:
f"computed at {H3_FPS}; set your video-save node to {H3_FPS} too") f"computed at {H3_FPS}; set your video-save node to {H3_FPS} too")
fps = H3_FPS fps = H3_FPS
w, h = parse_resolution(resolution) w, h = parse_resolution(resolution)
direct_ref_slots = ( legacy_ref_slots = (
ref_image_1, ref_image_2, ref_image_3, ref_image_4, ref_image_5,
ref_image_6, ref_image_7, ref_image_8, ref_image_9,
)
direct_ref_slots = tuple(
current if current is not None else legacy
for current, legacy in zip((
ref_1, ref_2, ref_3, ref_4, ref_5, ref_1, ref_2, ref_3, ref_4, ref_5,
ref_6, ref_7, ref_8, ref_9, ref_6, ref_7, ref_8, ref_9,
), legacy_ref_slots)
) )
ref_slots = direct_ref_slots ref_slots = direct_ref_slots
direct_ref_count = len(_connected_refs(direct_ref_slots)) direct_ref_count = len(_connected_refs(direct_ref_slots))
+104 -1
View File
@@ -369,6 +369,12 @@ class DumasH3LongVideosHelperTests(unittest.TestCase):
for index in range(1, 10): for index in range(1, 10):
self.assertIn(f"ref_{index}", optional) self.assertIn(f"ref_{index}", optional)
def test_input_types_keep_legacy_ref_image_aliases(self):
optional = self.module.H3LongVideos.INPUT_TYPES()["optional"]
for index in range(1, 10):
self.assertIn(f"ref_image_{index}", optional)
def test_node_appends_per_beat_list_outputs_without_reordering_existing_slots(self): def test_node_appends_per_beat_list_outputs_without_reordering_existing_slots(self):
self.assertEqual( self.assertEqual(
self.module.H3LongVideos.RETURN_NAMES[-2:], self.module.H3LongVideos.RETURN_NAMES[-2:],
@@ -418,7 +424,104 @@ class DumasH3LongVideosHelperTests(unittest.TestCase):
self.assertIn("Persistent appearance for Mara: silver hair.", context) self.assertIn("Persistent appearance for Mara: silver hair.", context)
self.assertIn("Persistent wardrobe/style for Mara: red jacket.", context) self.assertIn("Persistent wardrobe/style for Mara: red jacket.", context)
self.assertIn("Character notes for Mara: wears a long grey coat.", context) self.assertIn("Character notes for Mara: wears a long grey coat.", context)
self.assertIn("Location context for Hangar: wet concrete floor.", context)
def test_run_uses_legacy_ref_image_inputs_when_new_slots_are_empty(self):
calls = {}
original_parse_resolution = self.module.parse_resolution
original_connected_refs = self.module._connected_refs
original_reference_character_memory = self.module._reference_character_memory
original_vram_gb = self.module.vram_gb
original_dit_resident_gb = self.module.dit_resident_gb
original_lora_overhead_gb = self.module.lora_overhead_gb
original_resolve_shot_frames = self.module.resolve_shot_frames
original_lora_active = self.module.lora_active
original_sla_pairing = self.module.sla_pairing
original_apply_h3_model_sampling = self.module.apply_h3_model_sampling
original_split_paragraphs = self.module.split_paragraphs
original_expand_beats = self.module.expand_beats
original_anchor_warnings = self.module.anchor_warnings
original_anchor_contributes_nothing = self.module.anchor_contributes_nothing
original_anchor_is_action_beat = self.module.anchor_is_action_beat
original_distribute_generations = self.module.distribute_generations
original_continuity_warnings = self.module.continuity_warnings
original_speech_flags = self.module.speech_flags
original_annotate_script_debug = self.module.annotate_script_debug
original_empty_av_latent = self.module._empty_av_latent
original_torch_zeros = getattr(self.module.torch, "zeros", None)
try:
self.module.torch.zeros = lambda shape: shape
self.module.parse_resolution = lambda _resolution: (640, 360)
self.module._connected_refs = lambda refs: [ref for ref in refs if ref is not None]
self.module._reference_character_memory = lambda refs: (calls.setdefault("refs", tuple(refs)), "")[1]
self.module.vram_gb = lambda: (0, 0)
self.module.dit_resident_gb = lambda _model: 0
self.module.lora_overhead_gb = lambda _model: 0
self.module.resolve_shot_frames = lambda *args, **kwargs: (53, "")
self.module.lora_active = lambda _model: False
self.module.sla_pairing = lambda *_args, **_kwargs: ("", False, "")
self.module.apply_h3_model_sampling = lambda model, *_args: (model, "")
self.module.split_paragraphs = lambda _prompt, _sep: ["Anchor.", "Beat."]
self.module.expand_beats = lambda beat_paras, _split: (list(beat_paras), "")
self.module.anchor_warnings = lambda _anchor: []
self.module.anchor_contributes_nothing = lambda *_args, **_kwargs: False
self.module.anchor_is_action_beat = lambda *_args, **_kwargs: False
self.module.distribute_generations = lambda _anchor, beats, *_args, **_kwargs: list(beats)
self.module.continuity_warnings = lambda _gens: []
self.module.speech_flags = lambda _beats: []
self.module.annotate_script_debug = lambda *_args, **_kwargs: "script"
self.module._empty_av_latent = lambda *_args, **_kwargs: ({"samples": "latent"}, 5)
clip = types.SimpleNamespace(
tokenize=lambda text, **kwargs: text,
encode_from_tokens_scheduled=lambda tokens: tokens,
)
result = self.module.H3LongVideos().run(
model=object(),
clip=clip,
vae=object(),
audio_vae=object(),
prompt="Anchor only.",
resolution="16:9",
steps=6,
cfg=1,
sampler_name="res_multistep",
scheduler="simple",
seed=1,
plan_only=True,
ref_image_1={"image": "legacy-1"},
ref_image_3={"image": "legacy-3"},
)
self.assertEqual(calls["refs"][0]["image"], "legacy-1")
self.assertIsNone(calls["refs"][1])
self.assertEqual(calls["refs"][2]["image"], "legacy-3")
self.assertEqual(result[2].count("ref2va: 2 reference image(s)"), 1)
finally:
self.module.parse_resolution = original_parse_resolution
self.module._connected_refs = original_connected_refs
self.module._reference_character_memory = original_reference_character_memory
self.module.vram_gb = original_vram_gb
self.module.dit_resident_gb = original_dit_resident_gb
self.module.lora_overhead_gb = original_lora_overhead_gb
self.module.resolve_shot_frames = original_resolve_shot_frames
self.module.lora_active = original_lora_active
self.module.sla_pairing = original_sla_pairing
self.module.apply_h3_model_sampling = original_apply_h3_model_sampling
self.module.split_paragraphs = original_split_paragraphs
self.module.expand_beats = original_expand_beats
self.module.anchor_warnings = original_anchor_warnings
self.module.anchor_contributes_nothing = original_anchor_contributes_nothing
self.module.anchor_is_action_beat = original_anchor_is_action_beat
self.module.distribute_generations = original_distribute_generations
self.module.continuity_warnings = original_continuity_warnings
self.module.speech_flags = original_speech_flags
self.module.annotate_script_debug = original_annotate_script_debug
self.module._empty_av_latent = original_empty_av_latent
if original_torch_zeros is None:
delattr(self.module.torch, "zeros")
else:
self.module.torch.zeros = original_torch_zeros
def test_reference_context_matches_tagged_character_without_name_in_text(self): def test_reference_context_matches_tagged_character_without_name_in_text(self):
refs = [ refs = [