Files
DumasNodes/dumas_h3_beat_prompt.py
T

134 lines
3.5 KiB
Python

import json
_DEFAULT_BEAT = "Describe this beat."
_DEFAULT_STATE = {"scene": "", "character_sheet": "", "beats": [{"text": _DEFAULT_BEAT}]}
_LEGACY_DIRECTIVE_PREFIXES = (
"seconds",
"duration",
"continuity",
"ref_mode",
"ref_noise_aug",
"anchor_add",
"overall_soundscape",
"soundscape",
"non_diegetic_music",
"music",
"wardrobe",
"enter",
"exit",
)
def _clone_default_state():
return {
"scene": "",
"character_sheet": "",
"beats": [{"text": _DEFAULT_BEAT}],
}
def _strip_legacy_directives(text):
"""Remove directives from the abandoned Dumas Long Videos fork.
The upstream Long Videos node sends unknown field labels to the model as text,
so this builder strips the old managed controls rather than emitting prompts
that ask H3 to draw labels such as "seconds:" or "music:" in the frame.
"""
kept = []
for line in str(text or "").splitlines():
lowered = line.strip().lower()
if any(lowered.startswith(f"{name}:") for name in _LEGACY_DIRECTIVE_PREFIXES):
continue
kept.append(line)
return "\n".join(kept).strip()
def _parse_beat_prompt_state(value):
if isinstance(value, dict):
raw = value
else:
text = str(value or "").strip()
if not text:
return _clone_default_state()
try:
raw = json.loads(text)
except Exception:
return _clone_default_state()
scene = str(raw.get("scene") or "")
character_sheet = str(raw.get("character_sheet") or "")
beats = []
for item in list(raw.get("beats") or []):
if isinstance(item, dict):
text = str(item.get("text") or "")
else:
text = str(item or "")
beats.append({"text": text})
if not beats:
beats = [{"text": _DEFAULT_BEAT}]
return {
"scene": scene,
"character_sheet": character_sheet,
"beats": beats,
}
def _assemble_beat_prompt(state):
parsed = _parse_beat_prompt_state(state)
chunks = []
scene = str(parsed.get("scene") or "").strip()
if scene:
chunks.append(scene)
character_sheet = str(parsed.get("character_sheet") or "").strip()
if character_sheet:
chunks.append(character_sheet)
for beat in parsed["beats"]:
text = _strip_legacy_directives(beat.get("text") or "")
if text:
chunks.append(text)
return "\n\n".join(chunks)
class DumasH3BeatPromptNode:
DESCRIPTION = (
"Build an upstream MiniMax H3 Long Videos prompt: optional scene paragraph, "
"optional character sheet, then one blank-line-separated textbox per beat. "
"Per-beat helpers only emit directives the upstream node understands."
)
RETURN_TYPES = ("STRING",)
RETURN_NAMES = ("prompt",)
FUNCTION = "build_prompt"
CATEGORY = "Dumas/MiniMax"
@classmethod
def INPUT_TYPES(cls):
return {
"required": {},
"hidden": {
"H3BeatPromptState": ("STRING", {"default": json.dumps(_DEFAULT_STATE)}),
},
}
def build_prompt(self, H3BeatPromptState=""):
return (_assemble_beat_prompt(H3BeatPromptState),)
NODE_CLASS_MAPPINGS = {
"DumasH3BeatPrompt": DumasH3BeatPromptNode,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"DumasH3BeatPrompt": "Dumas H3 Beat Prompt",
}
__all__ = [
"DumasH3BeatPromptNode",
"NODE_CLASS_MAPPINGS",
"NODE_DISPLAY_NAME_MAPPINGS",
"_assemble_beat_prompt",
"_parse_beat_prompt_state",
]