85 lines
3.5 KiB
Python
85 lines
3.5 KiB
Python
"""Swap the third-party masked-audio node for this pack's drop-in in every example workflow.
|
|
|
|
MiniMaxH3SongMaskedAVContext (ComfyUI-H3-Motion-Context-MultiRef)
|
|
-> H3MaskedSongLatent (nodes/h3/masked_song.py)
|
|
|
|
Both nodes share the input names, widget order (clip_start_seconds,
|
|
context_length, source_fps, crop) and the three outputs (latent, trim_frames,
|
|
clip_audio), so only the node's type changes; the new widgets (preroll_seconds,
|
|
lookahead_seconds, audio_denoise) are appended with their defaults so the saved
|
|
widget list matches what the front-end expects. Links, positions and groups
|
|
are left untouched. The `Needs ComfyUI-H3-Motion-Context-MultiRef` line in the
|
|
workflow notes is updated too.
|
|
|
|
Usage: python scripts/swap_masked_song_node.py # all examples/h3/*.json + api/*.json
|
|
python scripts/swap_masked_song_node.py NAME.json # just one
|
|
python scripts/swap_masked_song_node.py --dry-run # report only
|
|
"""
|
|
import glob
|
|
import json
|
|
import os
|
|
import sys
|
|
|
|
EX = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "examples", "h3")
|
|
OLD, NEW = "MiniMaxH3SongMaskedAVContext", "H3MaskedSongLatent"
|
|
NEW_WIDGETS = [1.0, 0.2, 0.0, 0.15, 0.2] # preroll_seconds, lookahead_seconds, audio_denoise, gap_denoise, gate_hold_seconds
|
|
NEW_INPUTS = {"preroll_seconds": 1.0, "lookahead_seconds": 0.2, "audio_denoise": 0.0,
|
|
"gap_denoise": 0.15, "gate_hold_seconds": 0.2}
|
|
NOTE_OLD = "Needs ComfyUI-H3-Motion-Context-MultiRef and audio-separation-nodes-comfyui."
|
|
NOTE_NEW = "Needs audio-separation-nodes-comfyui (the masked-audio node is this pack's own H3 Masked Song Latent)."
|
|
|
|
|
|
def swap_ui(wf):
|
|
n = 0
|
|
for node in wf.get("nodes", []):
|
|
if node.get("type") == OLD:
|
|
node["type"] = NEW
|
|
wv = node.get("widgets_values")
|
|
if isinstance(wv, list) and len(wv) == 4:
|
|
wv.extend(NEW_WIDGETS)
|
|
props = node.get("properties") or {}
|
|
if props.get("Node name for S&R") == OLD:
|
|
props["Node name for S&R"] = NEW
|
|
n += 1
|
|
if node.get("type") == "MarkdownNote":
|
|
wv = node.get("widgets_values") or []
|
|
if wv and isinstance(wv[0], str) and NOTE_OLD in wv[0]:
|
|
wv[0] = wv[0].replace(NOTE_OLD, NOTE_NEW)
|
|
return n
|
|
|
|
|
|
def swap_api(wf):
|
|
n = 0
|
|
for node in wf.values():
|
|
if isinstance(node, dict) and node.get("class_type") == OLD:
|
|
node["class_type"] = NEW
|
|
inputs = node.setdefault("inputs", {})
|
|
for k, v in NEW_INPUTS.items():
|
|
inputs.setdefault(k, v)
|
|
n += 1
|
|
return n
|
|
|
|
|
|
def main(argv):
|
|
dry = "--dry-run" in argv
|
|
names = [a for a in argv if not a.startswith("--")]
|
|
paths = ([os.path.join(EX, n) if not os.path.isabs(n) else n for n in names]
|
|
or sorted(glob.glob(os.path.join(EX, "*.json")) + glob.glob(os.path.join(EX, "api", "*.json"))))
|
|
total = 0
|
|
for path in paths:
|
|
raw = open(path, encoding="utf-8").read()
|
|
wf = json.loads(raw)
|
|
n = swap_ui(wf) if "nodes" in wf else swap_api(wf)
|
|
if not n:
|
|
continue
|
|
total += n
|
|
print(f"{'would swap' if dry else 'swapped'} {n} node(s) in {os.path.relpath(path, EX)}")
|
|
if not dry:
|
|
indent = 2 if raw.lstrip().startswith('{\n "') else 4
|
|
open(path, "w", encoding="utf-8", newline="\n").write(json.dumps(wf, indent=indent, ensure_ascii=False) + "\n")
|
|
print(f"{total} node(s) in total")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main(sys.argv[1:])
|