diff --git a/README.md b/README.md index 042a720..edd5813 100644 --- a/README.md +++ b/README.md @@ -58,9 +58,10 @@ - `Dumas H3 Prompt Curator` - Inputs: `action_prompt`, `anatomy_guard`, `subject_count_guard`, optional `anchor`, optional `soundscape`, optional `bgm`, optional `ref_1` through `ref_9` - - Outputs: `prompt`, `ref_image_1` through `ref_image_9`, `reference_count`, `debug` + - Outputs: `prompt`, `ref_image_1` through `ref_image_9`, `reference_count`, `debug`, `anchor`, `sounds`, `bgm`, `original_ref_1` through `original_ref_9`, `reference_description` - Builds one standalone MiniMax H3 prompt from your final action text plus structured character/location references. - The action text can mention references by character/location name, alias, ``, or ``. Only mentioned references are emitted, and the output images are compacted/renumbered so skipped inputs do not leave gaps. + - Extra component outputs expose the cleaned anchor, sounds, BGM, selected structured references in compacted order, and the compiled reference-description block used inside the prompt. - Adds curated reference context, anatomy guard text, optional subject-count guard text, anchor/style text, `overall_soundscape:` text, and `background_music:` text while respecting MiniMax H3's reference-generation shape: one prompt plus up to nine reference images. - `Dumas H3 Shot Length` diff --git a/dumas_image_nodes.py b/dumas_image_nodes.py index 421cdf3..b11c54f 100644 --- a/dumas_image_nodes.py +++ b/dumas_image_nodes.py @@ -1249,12 +1249,19 @@ def curate_h3_prompt( selected = _selected_prompt_refs(action_prompt, normalized_refs) picture_map = {slot_number: index for index, (slot_number, _ref) in enumerate(selected, 1)} action = _replace_reference_tags(action_prompt, picture_map) + anchor_text = _reference_text(anchor) + soundscape_text = _reference_text(soundscape) + bgm_text = _reference_text(bgm) + reference_description = "" + if selected: + reference_description = " ".join( + _reference_context(ref, picture_number) + for picture_number, (_slot, ref) in enumerate(selected, 1) + ) prompt_parts = [] - _append_prompt_section(prompt_parts, "Scene anchor", anchor) - if selected: - contexts = [_reference_context(ref, picture_number) for picture_number, (_slot, ref) in enumerate(selected, 1)] - _append_prompt_section(prompt_parts, "Reference context", " ".join(contexts)) + _append_prompt_section(prompt_parts, "Scene anchor", anchor_text) + _append_prompt_section(prompt_parts, "Reference context", reference_description) _append_prompt_section(prompt_parts, "Action", action) if anatomy_guard == "on" or (anatomy_guard == "auto" and any(ref.get("kind") == "character" for _slot, ref in selected)): _append_prompt_section(prompt_parts, "Anatomy guard", _ANATOMY_GUARD_TEXT) @@ -1262,14 +1269,16 @@ def curate_h3_prompt( subject_count_guard == "auto" and any(ref.get("kind") == "character" for _slot, ref in selected) ): _append_prompt_section(prompt_parts, "Subject count guard", _subject_count_guard_text(selected)) - _append_prompt_section(prompt_parts, "overall_soundscape", soundscape) - _append_prompt_section(prompt_parts, "background_music", bgm) + _append_prompt_section(prompt_parts, "overall_soundscape", soundscape_text) + _append_prompt_section(prompt_parts, "background_music", bgm_text) prompt = "\n\n".join(prompt_parts).strip() if len(prompt) > _H3_PROMPT_MAX_CHARS: prompt = prompt[: _H3_PROMPT_MAX_CHARS - 3].rstrip() + "..." images = [_reference_image(ref) for _slot, ref in selected] images.extend([None] * (_H3_PROMPT_REF_SLOTS - len(images))) + selected_refs = [ref for _slot, ref in selected] + selected_refs.extend([None] * (_H3_PROMPT_REF_SLOTS - len(selected_refs))) debug = ( f"Selected {len(selected)} reference(s): " + ", ".join( @@ -1279,7 +1288,17 @@ def curate_h3_prompt( if selected else "Selected 0 references." ) - return (prompt, *images[:_H3_PROMPT_REF_SLOTS], len(selected), debug) + return ( + prompt, + *images[:_H3_PROMPT_REF_SLOTS], + len(selected), + debug, + anchor_text, + soundscape_text, + bgm_text, + *selected_refs[:_H3_PROMPT_REF_SLOTS], + reference_description, + ) def _parse_positive_int(value): @@ -2666,7 +2685,13 @@ class DumasH3PromptCuratorNode: "compacted so only mentioned names, aliases, or explicit / " "tags are sent onward." ) - RETURN_TYPES = ("STRING",) + ("IMAGE",) * _H3_PROMPT_REF_SLOTS + ("INT", "STRING") + RETURN_TYPES = ( + ("STRING",) + + ("IMAGE",) * _H3_PROMPT_REF_SLOTS + + ("INT", "STRING", "STRING", "STRING", "STRING") + + (_REFERENCE_TYPE,) * _H3_PROMPT_REF_SLOTS + + ("STRING",) + ) RETURN_NAMES = ( "prompt", "ref_image_1", @@ -2680,6 +2705,19 @@ class DumasH3PromptCuratorNode: "ref_image_9", "reference_count", "debug", + "anchor", + "sounds", + "bgm", + "original_ref_1", + "original_ref_2", + "original_ref_3", + "original_ref_4", + "original_ref_5", + "original_ref_6", + "original_ref_7", + "original_ref_8", + "original_ref_9", + "reference_description", ) FUNCTION = "curate_prompt" CATEGORY = "Dumas/MiniMax" diff --git a/tests/test_dumas_image_nodes.py b/tests/test_dumas_image_nodes.py index 6503855..6ebed16 100644 --- a/tests/test_dumas_image_nodes.py +++ b/tests/test_dumas_image_nodes.py @@ -499,6 +499,17 @@ class DumasImageNodeTests(unittest.TestCase): self.assertIsNone(result[3]) self.assertEqual(result[10], 2) self.assertIn("input 3-> Coffee Shop", result[11]) + self.assertEqual(result[12], "grounded handheld thriller") + self.assertEqual(result[13], "steady rain") + self.assertEqual(result[14], "low suspense music") + self.assertEqual(result[15]["name"], "Dave") + self.assertIs(result[15]["image"], dave_image) + self.assertEqual(result[16]["name"], "Coffee Shop") + self.assertIs(result[16]["image"], cafe_image) + self.assertIsNone(result[17]) + self.assertIn(" Dave", result[24]) + self.assertIn(" Coffee Shop", result[24]) + self.assertIn("Location context for Coffee Shop", result[24]) def test_h3_prompt_curator_renumbers_explicit_reference_tags(self): node = self.image_nodes.DumasH3PromptCuratorNode() @@ -636,8 +647,11 @@ class DumasImageNodeTests(unittest.TestCase): def test_h3_prompt_curator_uses_documented_reference_limits(self): node = self.image_nodes.DumasH3PromptCuratorNode() - self.assertEqual(len(node.RETURN_TYPES), 12) + self.assertEqual(len(node.RETURN_TYPES), 25) self.assertEqual(node.RETURN_NAMES[1:10], tuple(f"ref_image_{i}" for i in range(1, 10))) + self.assertEqual(node.RETURN_NAMES[12:15], ("anchor", "sounds", "bgm")) + self.assertEqual(node.RETURN_NAMES[15:24], tuple(f"original_ref_{i}" for i in range(1, 10))) + self.assertEqual(node.RETURN_NAMES[24], "reference_description") def test_normalize_reference_upgrades_generic_summary_with_socket_picture_id(self): image = FakeTensorBatch()