Parser fix - allow one cut bool per scene

This commit is contained in:
aimfordeb
2025-12-26 15:17:03 +02:00
committed by GitHub
parent 08b51f2d43
commit 32eaa10fcc
+108 -46
View File
@@ -29,8 +29,7 @@ MAX_KEYFRAME_NUM = 3
ADAPTIVE_ALPHA = 0.01
HPSV3_QUALITY_THRESHOLD = 3.0
class FunPackStoryMemJSONConverter:
class FunPackStoryMemJSONConverterQwenOutput:
"""
FunPack StoryMem LoRA JSON Converter - supports both manual input and Qwen-generated output.
@@ -40,6 +39,8 @@ class FunPackStoryMemJSONConverter:
- Splits into up to 3 separate scene JSONs (one per output)
- Each output contains story_name, story_overview + exactly ONE scene
- Extra scenes beyond 3 are ignored
- Handles 'cut' as single bool by replicating it across all prompts in the scene
- Forces first cut to True per StoryMem guidelines
If 'use_qwen_output' is false → falls back to original manual scene inputs.
@@ -52,7 +53,7 @@ class FunPackStoryMemJSONConverter:
"scene_num": int,
"video_prompts": list[str],
"first_frame_prompt": list[str],
"cut": list[bool]
"cut": list[bool] or bool # Now supports single bool
},
...
]
@@ -64,38 +65,75 @@ class FunPackStoryMemJSONConverter:
"required": {
"use_qwen_output": ("BOOLEAN", {
"default": False,
"label_on": "Use Qwen Output",
"label_off": "Manual Input",
"tooltip": "When enabled: ignores all manual scene inputs below! Uses 'qwen_output' string instead."
"label_on": "Use Qwen Output (ignores manual scenes below)",
"label_off": "Use Manual Scene Inputs",
"tooltip": "Toggle between Qwen-generated full story JSON or classic manual per-scene setup"
}),
# ── Qwen mode inputs ───────────────────────────────────────
"qwen_output": ("STRING", {
"multiline": True,
"default": "",
"tooltip": "Paste/connect the raw string output from Kijai's Qwen prompt enhancer node here"
"tooltip": "Connect STRING output from Kijai's WanVideoPromptExtender / Qwen enhancer here.\nDrag wire onto the left side of this box to convert to input socket."
}),
"story_name": ("STRING", {
"default": "",
"tooltip": "Story title (used in all output JSONs)"
}),
"story_overview": ("STRING", {
"multiline": True,
"default": "",
"tooltip": "Overall story summary (shared across all scenes)"
}),
# Scene 1
"scene1_video_prompts": ("STRING", {
"multiline": True,
"default": "",
"tooltip": "One prompt per line (each = 5-sec clip)"
}),
"scene1_first_frame_prompts": ("STRING", {
"multiline": True,
"default": "",
"tooltip": "First frame description per prompt (must match count)"
}),
"scene1_cut_values": ("STRING", {
"default": "",
"tooltip": "Comma-separated booleans: true,false,true,... (must match prompt count; first usually true)"
}),
# Scene 2
"scene2_video_prompts": ("STRING", {
"multiline": True,
"default": "",
"tooltip": "One prompt per line (each = 5-sec clip)"
}),
"scene2_first_frame_prompts": ("STRING", {
"multiline": True,
"default": "",
"tooltip": "First frame description per prompt (must match count)"
}),
"scene2_cut_values": ("STRING", {
"default": "",
"tooltip": "Comma-separated booleans: true,false,true,... (must match prompt count; first usually true)"
}),
# Scene 3
"scene3_video_prompts": ("STRING", {
"multiline": True,
"default": "",
"tooltip": "One prompt per line (each = 5-sec clip)"
}),
"scene3_first_frame_prompts": ("STRING", {
"multiline": True,
"default": "",
"tooltip": "First frame description per prompt (must match count)"
}),
"scene3_cut_values": ("STRING", {
"default": "",
"tooltip": "Comma-separated booleans: true,false,true,... (must match prompt count; first usually true)"
}),
# ── Manual mode inputs (hidden in practice by warning) ─────
"story_name": ("STRING", {"default": "My Story Title"}),
"story_overview": ("STRING", {"multiline": True, "default": "Brief description..."}),
"scene1_video_prompts": ("STRING", {"multiline": True, "default": "prompt 1\nprompt 2\nprompt 3"}),
"scene1_first_frames": ("STRING", {"multiline": True, "default": "frame 1\nframe 2\nframe 3"}),
"scene1_cuts": ("STRING", {"default": "true, false, false"}),
"scene2_video_prompts": ("STRING", {"multiline": True, "default": ""}),
"scene2_first_frames": ("STRING", {"multiline": True, "default": ""}),
"scene2_cuts": ("STRING", {"default": ""}),
"scene3_video_prompts": ("STRING", {"multiline": True, "default": ""}),
"scene3_first_frames": ("STRING", {"multiline": True, "default": ""}),
"scene3_cuts": ("STRING", {"default": ""}),
},
"optional": {
"qwen_warning": ("STRING", {
"qwen_mode_notice": ("STRING", {
"multiline": True,
"default": "!!! QWEN MODE ACTIVE !!!\nAll inputs below are IGNORED\nConnect Qwen output above.",
"tooltip": "Visible reminder - only relevant when use_qwen_output = True"
"default": "!!! QWEN MODE ACTIVE !!!\nAll manual inputs below are completely ignored\nConnect the Qwen JSON string above.",
"tooltip": "Reminder - only visible/meaningful when use_qwen_output = True"
})
}
}
@@ -110,10 +148,10 @@ class FunPackStoryMemJSONConverter:
use_qwen_output,
qwen_output,
story_name, story_overview,
scene1_video_prompts, scene1_first_frames, scene1_cuts,
scene2_video_prompts, scene2_first_frames, scene2_cuts,
scene3_video_prompts, scene3_first_frames, scene3_cuts,
qwen_warning=""):
scene1_video_prompts, scene1_first_frame_prompts, scene1_cut_values,
scene2_video_prompts, scene2_first_frame_prompts, scene2_cut_values,
scene3_video_prompts, scene3_first_frame_prompts, scene3_cut_values,
qwen_mode_notice=""):
if use_qwen_output:
if not qwen_output.strip():
@@ -141,20 +179,38 @@ class FunPackStoryMemJSONConverter:
scene_num = scene.get("scene_num", i+1)
video_prompts = scene.get("video_prompts", [])
first_frame_prompt = scene.get("first_frame_prompt", [])
first_frame_prompt = scene.get("first_frame_prompt", []) # Note: renamed from first_frame_prompts for consistency
cut = scene.get("cut", [])
# Basic validation
if not video_prompts:
continue
if len(video_prompts) != len(first_frame_prompt) or len(video_prompts) != len(cut):
num_prompts = len(video_prompts)
if len(first_frame_prompt) != num_prompts:
raise ValueError(
f"Scene {scene_num}: lengths mismatch → "
f"video_prompts({len(video_prompts)}), "
f"first_frame_prompt({len(first_frame_prompt)}), "
f"cut({len(cut)})"
f"Scene {scene_num}: video_prompts ({num_prompts}) ≠ "
f"first_frame_prompt ({len(first_frame_prompt)})"
)
# Handle cut: if single bool, replicate across all prompts
if isinstance(cut, bool):
cut = [cut] * num_prompts
elif not isinstance(cut, list):
raise ValueError(f"Scene {scene_num}: 'cut' must be bool or list of bools, got {type(cut)}")
# Check length after potential conversion
if len(cut) != num_prompts:
raise ValueError(
f"Scene {scene_num}: 'cut' length ({len(cut)}) ≠ "
f"prompt count ({num_prompts})"
)
# Force first cut to True (per StoryMem: "The first prompt in the story must always have True")
# Note: This applies per scene, but if the whole story is multi-scene, the first of scene 1 is the global first
if num_prompts > 0:
cut[0] = True
single_scene_data = {
"scene_num": scene_num,
"video_prompts": video_prompts,
@@ -176,9 +232,9 @@ class FunPackStoryMemJSONConverter:
else:
# ── Original manual mode ───────────────────────────────────────
scenes_input = [
(scene1_video_prompts, scene1_first_frames, scene1_cuts, 1),
(scene2_video_prompts, scene2_first_frames, scene2_cuts, 2),
(scene3_video_prompts, scene3_first_frames, scene3_cuts, 3),
(scene1_video_prompts, scene1_first_frame_prompts, scene1_cut_values, 1),
(scene2_video_prompts, scene2_first_frame_prompts, scene2_cut_values, 2),
(scene3_video_prompts, scene3_first_frame_prompts, scene3_cut_values, 3),
]
outputs = ["", "", ""]
@@ -199,24 +255,30 @@ class FunPackStoryMemJSONConverter:
f"first frames ({len(first_prompts)})"
)
num_prompts = len(video_prompts)
if cuts_text.strip():
cuts_str = [c.strip().lower() for c in cuts_text.split(",")]
cuts_str = [c.strip().lower() for c in cuts_text.split(",") if c.strip()]
cuts = []
for c in cuts_str:
if c in ("true", "t", "1", "yes"):
cuts.append(True)
elif c in ("false", "f", "0", "no", ""):
elif c in ("false", "f", "0", "no"):
cuts.append(False)
else:
raise ValueError(f"Invalid cut value in manual scene {scene_num}: '{c}'")
else:
cuts = [False] * len(video_prompts)
cuts = [False] * num_prompts
if len(cuts) != len(video_prompts):
if len(cuts) != num_prompts:
raise ValueError(
f"Manual scene {scene_num}: cut count ({len(cuts)}) ≠ prompt count"
)
# Force first cut to True for consistency
if num_prompts > 0:
cuts[0] = True
scene = {
"scene_num": scene_num,
"video_prompts": video_prompts,
@@ -233,7 +295,6 @@ class FunPackStoryMemJSONConverter:
outputs[i] = json.dumps(full_json, indent=2, ensure_ascii=False)
return tuple(outputs)
class FunPackImg2LatentInterpolation:
@classmethod
def INPUT_TYPES(cls):
@@ -892,3 +953,4 @@ NODE_DISPLAY_NAME_MAPPINGS = {