Files
exportAnything-ComfyUI-Diff…/tests/test_model_target_profile_nodes.py
danrh 8f23c95546 Add synchronized music-video production planning
Add measured-audio, timed-lyrics, H3 lane planning, identity relay, upload-song routing, model-specific camera contracts, LTX long-horizon planning, workflow migrations, the validated V6 example, regression coverage, and an Unreleased changelog.
2026-08-23 08:03:39 -05:00

290 lines
12 KiB
Python

from __future__ import annotations
import importlib.util
import json
import sys
import unittest
import uuid
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
COMFY_ROOT = ROOT.parents[1]
def load_nodes_module():
if str(COMFY_ROOT) not in sys.path:
sys.path.insert(0, str(COMFY_ROOT))
package_name = f"diffusiongemma_model_targets_{uuid.uuid4().hex}"
spec = importlib.util.spec_from_file_location(
package_name,
ROOT / "__init__.py",
submodule_search_locations=[str(ROOT)],
)
if spec is None or spec.loader is None:
raise RuntimeError("could not load DiffusionGemma Prompt Builder")
package = importlib.util.module_from_spec(spec)
sys.modules[package_name] = package
spec.loader.exec_module(package)
return sys.modules[f"{package_name}.nodes"]
class ModelTargetProfileNodeTests(unittest.TestCase):
@classmethod
def setUpClass(cls) -> None:
cls.nodes = load_nodes_module()
def test_each_model_node_exposes_only_relevant_controls(self) -> None:
ltx = self.nodes.DiffusionGemmaLTX25TargetProfile.INPUT_TYPES()
self.assertEqual(
list(ltx["required"]),
[
"generation_mode",
"target_duration_seconds",
"style_guidance",
"audio_mode",
"audio_guidance",
"negative_prompt_mode",
"negative_prompt_guidance",
"long_horizon_mode",
],
)
self.assertEqual(list(ltx["optional"]), ["camera_capability"])
camera_choices, camera_config = ltx["optional"]["camera_capability"]
self.assertEqual(
camera_choices,
["Stable / base model", "Advanced / controlled camera"],
)
self.assertEqual(camera_config["default"], "Stable / base model")
h3 = self.nodes.DiffusionGemmaMiniMaxH3TargetProfile.INPUT_TYPES()
self.assertEqual(
list(h3["required"]),
[
"generation_mode",
"target_duration_seconds",
"audio_mode",
"audio_guidance",
"shot_count",
"custom_shot_count",
"dialogue_mode",
"dialogue_line_count",
"dialogue_guidance",
"negative_prompt_mode",
"negative_prompt_guidance",
],
)
self.assertEqual(list(h3["optional"]), ["shot_count_override"])
override_type, override_config = h3["optional"]["shot_count_override"]
self.assertEqual(override_type, "INT")
self.assertEqual(override_config["default"], 0)
self.assertIn("native [Shot N]", override_config["tooltip"])
self.assertIn("never connect a Project Master generation-lane count", override_config["tooltip"])
ideogram = self.nodes.DiffusionGemmaIdeogram4TargetProfile.INPUT_TYPES()
self.assertEqual(
list(ideogram["required"]),
[
"aspect_ratio",
"render_style",
"exact_text",
"json_output",
"negative_prompt_mode",
"negative_prompt_guidance",
],
)
self.assertNotIn("optional", ideogram)
self.assertNotIn("audio_mode", ideogram["required"])
self.assertNotIn("audio_guidance", ideogram["required"])
self.assertNotIn("target_duration_seconds", ideogram["required"])
def test_ltx_node_builds_the_same_canonical_config_as_the_legacy_surface(self) -> None:
config, payload_json = self.nodes.DiffusionGemmaLTX25TargetProfile().build(
generation_mode="Image to video",
target_duration_seconds=8.0,
style_guidance="restrained live-action thriller",
audio_mode="explicit_sound_design",
audio_guidance="dry footsteps and one exact off-screen line",
negative_prompt_mode="custom",
negative_prompt_guidance="no captions",
)
legacy, legacy_json = self.nodes.DiffusionGemmaTargetProfile().build(
target_profile="ltx",
audio_mode="explicit_sound_design",
audio_guidance="dry footsteps and one exact off-screen line",
target_duration_seconds=8.0,
ltx_style="restrained live-action thriller",
ideogram_aspect_ratio="1:1",
ideogram_render_style="",
ideogram_exact_text="",
ideogram_json_output=True,
negative_prompt_mode="custom",
negative_prompt_guidance="no captions",
ltx_generation_mode="Image to video",
)
self.assertEqual(config, legacy)
self.assertEqual(json.loads(payload_json), json.loads(legacy_json))
self.assertEqual(config.target_profile, "ltx")
self.assertEqual(config.ltx_generation_mode, "image_to_video")
self.assertEqual(config.ltx_camera_capability, "stable")
def test_h3_node_preserves_audio_shot_dialogue_and_negative_controls(self) -> None:
config, payload_json = self.nodes.DiffusionGemmaMiniMaxH3TargetProfile().build(
generation_mode="ref2va",
target_duration_seconds=20.0,
audio_mode="explicit_sound_design",
audio_guidance="wind and measured footsteps",
shot_count="custom",
custom_shot_count=17,
dialogue_mode="required",
dialogue_line_count=3,
dialogue_guidance="English; protagonist S1; three terse lines",
negative_prompt_mode="custom",
negative_prompt_guidance="no subtitles",
)
payload = json.loads(payload_json)
self.assertEqual(config.target_profile, "minimax_h3")
self.assertEqual(config.minimax_h3_mode, "ref2va")
self.assertEqual(config.minimax_h3_shot_count, "17")
self.assertEqual(config.minimax_h3_dialogue_mode, "required")
self.assertEqual(config.minimax_h3_dialogue_line_count, 3)
self.assertEqual(payload["negative_prompt_guidance"], "no subtitles")
overridden, overridden_json = self.nodes.DiffusionGemmaMiniMaxH3TargetProfile().build(
generation_mode="ref2va",
target_duration_seconds=25.0,
audio_mode="auto_scene_audio",
audio_guidance="locked soundtrack",
shot_count="auto",
custom_shot_count=12,
dialogue_mode="off",
dialogue_line_count=2,
dialogue_guidance="",
negative_prompt_mode="auto",
negative_prompt_guidance="",
shot_count_override=2,
)
self.assertEqual(overridden.minimax_h3_shot_count, "2")
self.assertEqual(json.loads(overridden_json)["minimax_h3_shot_count"], "2")
with self.assertRaisesRegex(ValueError, "conflicts with audio_mode=visual_only"):
self.nodes.DiffusionGemmaMiniMaxH3TargetProfile().build(
generation_mode="t2va",
target_duration_seconds=5.0,
audio_mode="visual_only",
audio_guidance="",
shot_count="auto",
custom_shot_count=12,
dialogue_mode="required",
dialogue_line_count=1,
dialogue_guidance="",
negative_prompt_mode="auto",
negative_prompt_guidance="",
)
def test_ideogram_node_has_visual_defaults_and_no_hidden_audio_request(self) -> None:
config, payload_json = self.nodes.DiffusionGemmaIdeogram4TargetProfile().build(
aspect_ratio="4:3",
render_style="editorial poster illustration",
exact_text="DIFFUSION GEMMA",
json_output=True,
negative_prompt_mode="custom",
negative_prompt_guidance="no extra text or logos",
)
payload = json.loads(payload_json)
self.assertEqual(config.target_profile, "ideogram4")
self.assertEqual(config.audio_mode, "visual_only")
self.assertEqual(config.audio_guidance, "")
self.assertEqual(config.target_duration_seconds, 0.0)
self.assertEqual(config.ideogram_aspect_ratio, "4:3")
self.assertEqual(config.ideogram_exact_text, "DIFFUSION GEMMA")
self.assertEqual(payload["ideogram_render_style"], "editorial poster illustration")
def test_dedicated_nodes_share_the_downstream_type_and_legacy_stays_loadable(self) -> None:
node_ids = {
"DiffusionGemmaLTX25TargetProfile",
"DiffusionGemmaMiniMaxH3TargetProfile",
"DiffusionGemmaIdeogram4TargetProfile",
}
for node_id in node_ids:
with self.subTest(node_id=node_id):
node_class = self.nodes.NODE_CLASS_MAPPINGS[node_id]
self.assertEqual(node_class.RETURN_TYPES, (self.nodes.TARGET_PROFILE_TYPE, "STRING"))
self.assertEqual(
node_class.RETURN_NAMES,
("target_profile_config", "target_profile_json"),
)
self.assertIn(node_id, self.nodes.NODE_DISPLAY_NAME_MAPPINGS)
self.assertIn("DiffusionGemmaTargetProfile", self.nodes.NODE_CLASS_MAPPINGS)
self.assertTrue(self.nodes.DiffusionGemmaTargetProfile.DEPRECATED)
self.assertIn("Legacy", self.nodes.NODE_DISPLAY_NAME_MAPPINGS["DiffusionGemmaTargetProfile"])
def test_ideogram_resolution_ignores_splitter_widget_override(self) -> None:
context = self.nodes.GemmaContext()
target = self.nodes._target_profile_config_to_dict(
self.nodes.TargetProfileConfig(
target_profile="ideogram4",
ideogram_aspect_ratio="4:3",
)
)
resolved = self.nodes._splitter_resolution_aspect_ratio(
"16:9 (Widescreen)",
context,
target,
"4:3",
)
self.assertEqual(resolved, ("4:3", "ideogram_target_profile"))
def test_h3_auto_resolution_does_not_follow_reference_media_dimensions(self) -> None:
context = self.nodes.GemmaContext(
media_metadata={
"width": 1920,
"height": 1080,
"ltx_first_frame_width": 1080,
"ltx_first_frame_height": 1920,
}
)
target = self.nodes._target_profile_config_to_dict(
self.nodes.TargetProfileConfig(target_profile="minimax_h3")
)
resolved = self.nodes._splitter_resolution_aspect_ratio(
self.nodes.RESOLUTION_SELECTOR_AUTO,
context,
target,
"4:3",
)
self.assertEqual(resolved, ("4:3", "legacy_target_profile"))
def test_ltx_auto_aliases_and_invalid_values_do_not_silently_select_square(self) -> None:
context = self.nodes.GemmaContext()
target = self.nodes._target_profile_config_to_dict(
self.nodes.TargetProfileConfig(target_profile="ltx")
)
for requested in (
"auto",
"AUTO (TARGET/SOURCE)",
"not-a-resolution",
):
with self.subTest(requested=requested):
aspect_ratio, source = self.nodes._splitter_resolution_aspect_ratio(
requested,
context,
target,
"1:1",
)
self.assertEqual((aspect_ratio, source), ("16:9", "ltx_default"))
self.assertEqual(
self.nodes._resolution_selector_dimensions(aspect_ratio, 0.5, 32),
(960, 544),
)
if __name__ == "__main__":
unittest.main()