Restore long videos release snapshot
This commit is contained in:
@@ -160,41 +160,6 @@ class DumasH3LongVideosHelperTests(unittest.TestCase):
|
||||
"keyframe carry",
|
||||
)
|
||||
|
||||
def test_timing_summary_reports_retry_and_bucket_totals(self):
|
||||
note = self.module._format_timing_note([
|
||||
{
|
||||
"shot": 1,
|
||||
"total": 12.4,
|
||||
"retry_elapsed": 1.2,
|
||||
"attempts": 2,
|
||||
"sample": 8.0,
|
||||
"detail_sample": 0.5,
|
||||
"decode_video": 2.1,
|
||||
"decode_audio": 0.4,
|
||||
"cleanup": 0.2,
|
||||
},
|
||||
{
|
||||
"shot": 2,
|
||||
"total": 7.6,
|
||||
"retry_elapsed": 0.0,
|
||||
"attempts": 1,
|
||||
"sample": 6.5,
|
||||
"decode_video": 0.5,
|
||||
"decode_audio": 0.3,
|
||||
"cleanup": 0.1,
|
||||
},
|
||||
])
|
||||
|
||||
self.assertIn("timing: 2 shot(s) total 20.0s", note)
|
||||
self.assertIn("sample 14.5s", note)
|
||||
self.assertIn("decode video 2.6s", note)
|
||||
self.assertIn("decode audio 0.7s", note)
|
||||
self.assertIn("cleanup 0.3s", note)
|
||||
self.assertIn("retry elapsed 1.2s", note)
|
||||
self.assertIn("detail 0.5s", note)
|
||||
self.assertIn("retries 1", note)
|
||||
self.assertIn("slowest shot 1 12.4s", note)
|
||||
|
||||
def test_detail_pass_refines_video_but_preserves_audio(self):
|
||||
class FakeTensor:
|
||||
def __init__(self, name):
|
||||
@@ -226,7 +191,6 @@ class DumasH3LongVideosHelperTests(unittest.TestCase):
|
||||
original_cleanup = self.module._deep_cleanup
|
||||
original_nested = getattr(self.module.comfy.nested_tensor, "NestedTensor", None)
|
||||
try:
|
||||
sentinel_model = object()
|
||||
self.module.comfy.nested_tensor.NestedTensor = FakeNestedTensor
|
||||
|
||||
def common_ksampler(*args, **kwargs):
|
||||
@@ -244,7 +208,7 @@ class DumasH3LongVideosHelperTests(unittest.TestCase):
|
||||
self.module._deep_cleanup = lambda: None
|
||||
|
||||
result = self.module.H3LongVideos()._render(
|
||||
model=sentinel_model,
|
||||
model=object(),
|
||||
clip=types.SimpleNamespace(
|
||||
tokenize=lambda text, **kwargs: text,
|
||||
encode_from_tokens_scheduled=lambda tokens: tokens,
|
||||
@@ -290,95 +254,6 @@ class DumasH3LongVideosHelperTests(unittest.TestCase):
|
||||
else:
|
||||
self.module.comfy.nested_tensor.NestedTensor = original_nested
|
||||
|
||||
def test_render_retries_decode_with_tiling_after_decode_oom(self):
|
||||
class FakeTensor:
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
|
||||
def detach(self):
|
||||
return self
|
||||
|
||||
def to(self, *args, **kwargs):
|
||||
return self
|
||||
|
||||
class FakeNestedTensor:
|
||||
def __init__(self, parts):
|
||||
self._parts = tuple(parts)
|
||||
self.is_nested = True
|
||||
|
||||
def unbind(self):
|
||||
return self._parts
|
||||
|
||||
decode_calls = []
|
||||
cleanup_calls = []
|
||||
first_out = {"samples": FakeNestedTensor((FakeTensor("v1"), FakeTensor("a1")))}
|
||||
|
||||
original_common_ksampler = self.module.nodes.common_ksampler
|
||||
original_build = self.module._build_shot_conditioning
|
||||
original_evict = self.module._evict_all_but
|
||||
original_decode_video = self.module._decode_video
|
||||
original_decode_audio = self.module._decode_audio
|
||||
original_cleanup = self.module._deep_cleanup
|
||||
original_soft_empty_cache = self.module.mm.soft_empty_cache
|
||||
original_nested = getattr(self.module.comfy.nested_tensor, "NestedTensor", None)
|
||||
try:
|
||||
sentinel_model = object()
|
||||
self.module.comfy.nested_tensor.NestedTensor = FakeNestedTensor
|
||||
self.module.nodes.common_ksampler = lambda *args, **kwargs: (first_out,)
|
||||
self.module._build_shot_conditioning = lambda *_args, **_kwargs: (
|
||||
"cond",
|
||||
{"samples": FakeNestedTensor((FakeTensor("basev"), FakeTensor("basea")))},
|
||||
)
|
||||
self.module._evict_all_but = lambda *_args, **_kwargs: None
|
||||
|
||||
def decode_video(_vae, out_latent, tiled, free_first=None, tile_t=None, tile_xy=None):
|
||||
decode_calls.append((tiled, free_first, tile_t, tile_xy))
|
||||
if len(decode_calls) == 1:
|
||||
raise RuntimeError("CUDA out of memory during decode")
|
||||
return out_latent
|
||||
|
||||
self.module._decode_video = decode_video
|
||||
self.module._decode_audio = lambda _vae, out_latent: out_latent
|
||||
self.module.mm.soft_empty_cache = lambda *args, **kwargs: cleanup_calls.append((args, kwargs))
|
||||
self.module._deep_cleanup = lambda: None
|
||||
|
||||
result = self.module.H3LongVideos()._render(
|
||||
model=sentinel_model,
|
||||
clip=types.SimpleNamespace(
|
||||
tokenize=lambda text, **kwargs: text,
|
||||
encode_from_tokens_scheduled=lambda tokens: tokens,
|
||||
),
|
||||
vae=object(),
|
||||
audio_vae=object(),
|
||||
negative="negative",
|
||||
prompt="beat",
|
||||
w=128,
|
||||
h=64,
|
||||
ln=24,
|
||||
fps=24,
|
||||
tiled=False,
|
||||
sa=(123, 20, 1.0, "res_multistep", "simple", 1.0),
|
||||
handoff=None,
|
||||
)
|
||||
|
||||
self.assertEqual(len(decode_calls), 2)
|
||||
self.assertEqual(decode_calls[0], (False, sentinel_model, 0, 0))
|
||||
self.assertEqual(decode_calls[1], (True, None, 16, 256))
|
||||
self.assertTrue(cleanup_calls)
|
||||
self.assertIs(result[1], first_out)
|
||||
finally:
|
||||
self.module.nodes.common_ksampler = original_common_ksampler
|
||||
self.module._build_shot_conditioning = original_build
|
||||
self.module._evict_all_but = original_evict
|
||||
self.module._decode_video = original_decode_video
|
||||
self.module._decode_audio = original_decode_audio
|
||||
self.module._deep_cleanup = original_cleanup
|
||||
self.module.mm.soft_empty_cache = original_soft_empty_cache
|
||||
if original_nested is None:
|
||||
delattr(self.module.comfy.nested_tensor, "NestedTensor")
|
||||
else:
|
||||
self.module.comfy.nested_tensor.NestedTensor = original_nested
|
||||
|
||||
def test_detail_pass_treats_falsey_strings_as_disabled(self):
|
||||
calls = []
|
||||
original_common_ksampler = self.module.nodes.common_ksampler
|
||||
@@ -513,26 +388,24 @@ class DumasH3LongVideosHelperTests(unittest.TestCase):
|
||||
)
|
||||
self.assertEqual(dropped, [4])
|
||||
|
||||
def test_resolve_shot_references_uses_named_characters_without_picture_tags(self):
|
||||
def test_resolve_prompt_refs_keeps_named_character_images_alongside_tagged_location(self):
|
||||
refs = [
|
||||
{"kind": "character", "image": "img1", "name": "Mara"},
|
||||
{"kind": "character", "image": "img2", "name": "Jon"},
|
||||
{"kind": "location", "image": "img3", "name": "Hangar"},
|
||||
]
|
||||
|
||||
text, references, dropped, shot_tag_driven, mode_eff = self.module.resolve_shot_references(
|
||||
"[Generation 1] Mara crosses the hangar.",
|
||||
text, references, dropped = self.module.resolve_prompt_refs(
|
||||
"Mara and Jon argue inside <Picture 3>.",
|
||||
refs,
|
||||
"auto ref2v",
|
||||
0,
|
||||
None,
|
||||
)
|
||||
|
||||
self.assertEqual(text, "[Generation 1] Mara crosses the hangar.")
|
||||
self.assertEqual([self.module._reference_image(ref) for ref in references], ["img1"])
|
||||
self.assertEqual(text, "Mara and Jon argue inside <Picture 1>.")
|
||||
self.assertEqual(
|
||||
[self.module._reference_image(ref) for ref in references],
|
||||
["img3", "img1", "img2"],
|
||||
)
|
||||
self.assertEqual(dropped, [])
|
||||
self.assertFalse(shot_tag_driven)
|
||||
self.assertEqual(mode_eff, "auto ref2v")
|
||||
|
||||
def test_shot_references_uses_all_connected_sparse_slots(self):
|
||||
refs = [
|
||||
@@ -559,24 +432,12 @@ class DumasH3LongVideosHelperTests(unittest.TestCase):
|
||||
for index in range(1, 10):
|
||||
self.assertIn(f"ref_{index}", optional)
|
||||
|
||||
names = list(optional)
|
||||
ref_positions = [names.index(f"ref_{index}") for index in range(1, 10)]
|
||||
self.assertEqual(ref_positions, list(range(ref_positions[0], ref_positions[0] + 9)))
|
||||
|
||||
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_shot_seconds_tooltip_describes_ceiling_behavior(self):
|
||||
optional = self.module.H3LongVideos.INPUT_TYPES()["optional"]
|
||||
tooltip = optional["shot_seconds"][1]["tooltip"]
|
||||
|
||||
self.assertIn("GLOBAL per-shot ceiling", tooltip)
|
||||
self.assertIn("not 'force every beat to exactly this length'", tooltip)
|
||||
self.assertIn("hard ~15.1s single-shot limit", tooltip)
|
||||
|
||||
def test_run_defaults_match_declared_ref_widget_defaults(self):
|
||||
node = self.module.H3LongVideos()
|
||||
optional = node.INPUT_TYPES()["optional"]
|
||||
@@ -636,32 +497,6 @@ class DumasH3LongVideosHelperTests(unittest.TestCase):
|
||||
self.assertIn("Persistent wardrobe/style for Mara: red jacket.", context)
|
||||
self.assertIn("Character notes for Mara: wears a long grey coat.", context)
|
||||
|
||||
def test_reference_context_matches_location_names_without_picture_tag(self):
|
||||
refs = [
|
||||
{"kind": "location", "image": "img2", "name": "Hangar", "aliases": ["loading bay"], "description": "wet concrete floor", "general": "cold industrial lighting"},
|
||||
]
|
||||
|
||||
context = self.module._reference_context_for_text(
|
||||
"[Generation 1] They argue in the hangar near the loading bay.",
|
||||
refs,
|
||||
)
|
||||
|
||||
self.assertIn("Location context for Hangar: wet concrete floor.", context)
|
||||
self.assertIn("Location notes for Hangar: cold industrial lighting.", context)
|
||||
|
||||
def test_reference_context_skips_ambiguous_name_matches(self):
|
||||
refs = [
|
||||
{"kind": "character", "image": "img1", "name": "Alex", "description": "short dark hair"},
|
||||
{"kind": "character", "image": "img2", "name": "Alex", "description": "tall blond hair"},
|
||||
]
|
||||
|
||||
context = self.module._reference_context_for_text(
|
||||
"[Generation 1] Alex enters the room.",
|
||||
refs,
|
||||
)
|
||||
|
||||
self.assertEqual(context, "")
|
||||
|
||||
def test_run_uses_legacy_ref_image_inputs_when_new_slots_are_empty(self):
|
||||
calls = {}
|
||||
original_parse_resolution = self.module.parse_resolution
|
||||
@@ -773,36 +608,6 @@ class DumasH3LongVideosHelperTests(unittest.TestCase):
|
||||
self.assertIn("Persistent appearance for Mara: silver hair.", context)
|
||||
self.assertIn("Persistent wardrobe/style for Mara: red jacket.", context)
|
||||
|
||||
def test_reference_context_can_skip_character_wardrobe_when_live_memory_is_explicit(self):
|
||||
refs = [
|
||||
{"kind": "character", "image": "img1", "name": "Mara", "description": "silver hair", "wardrobe": "red jacket"},
|
||||
]
|
||||
|
||||
context = self.module._reference_context_for_text(
|
||||
"[Generation 1] Mara walks into the room.",
|
||||
refs,
|
||||
include_character_wardrobe=False,
|
||||
)
|
||||
|
||||
self.assertIn("Persistent appearance for Mara: silver hair.", context)
|
||||
self.assertNotIn("Persistent wardrobe/style for Mara: red jacket.", context)
|
||||
|
||||
def test_resolve_tagged_refs_drops_reference_without_image(self):
|
||||
refs = [
|
||||
{"kind": "character", "image": None, "name": "Mara"},
|
||||
{"kind": "character", "image": "img2", "name": "Jon"},
|
||||
]
|
||||
|
||||
rewritten, matched, dropped = self.module.resolve_tagged_refs(
|
||||
"[Generation 1] <Picture 1> faces <Picture 2>.",
|
||||
refs,
|
||||
)
|
||||
|
||||
self.assertEqual(rewritten, "[Generation 1] faces <Picture 1>.")
|
||||
self.assertEqual(dropped, [1])
|
||||
self.assertEqual(len(matched), 1)
|
||||
self.assertEqual(matched[0]["name"], "Jon")
|
||||
|
||||
def test_reference_context_injects_immediately_after_generation_label(self):
|
||||
block = (
|
||||
"[Generation 1] Classic sitcom lighting and staging. "
|
||||
|
||||
Reference in New Issue
Block a user