MiniMax-H3-Longvideos / gen_reference.py
Dcbuilder831's picture
Duplicate from Smite79/MiniMax-H3-Longvideos
0f52e37
Raw
History Blame Contribute Delete
7.38 kB
r"""Regenerate the field tables in REFERENCE.md from the node's own schema.
REFERENCE.md went stale because it was hand-maintained alongside a 69-field node:
it documented a `total_seconds` input that had been gone for months and was missing
17 fields, including every guard. Hand-copying a schema is a losing game, so the
field-by-field part is generated from INPUT_TYPES and RETURN_NAMES instead. The
prose around it stays hand-written -- reasoning is the part worth writing by hand.
Everything between the AUTOGEN markers is replaced; everything else is untouched.
python gen_reference.py # rewrite REFERENCE.md in place
python gen_reference.py --check # exit 1 if it is out of date (for CI)
Run it from the node directory with ComfyUI importable (the portable python works:
..\..\..\python_embeded\python.exe gen_reference.py
).
"""
import importlib.util
import os
import sys
HERE = os.path.dirname(os.path.abspath(__file__))
BEGIN = "<!-- AUTOGEN:FIELDS BEGIN -- edit gen_reference.py, not this block -->"
END = "<!-- AUTOGEN:FIELDS END -->"
# Which group each input belongs to, so the tables read as sections rather than as
# one 69-row dump. First match wins; anything unmatched lands in "Other".
GROUPS = [
("Wiring", ["model", "clip", "vae", "audio_vae", "first_frame",
"ref_image_1", "ref_image_2", "ref_image_3", "ref_image_4"]),
("The scene", ["prompt", "character_memory", "anchor_override", "beat_split",
"per_beat_length"]),
("Size and length", ["resolution", "megapixels", "shot_seconds",
"allow_oversize_shots", "allow_res_backoff", "fps"]),
("Sampling", ["steps", "cfg", "sampler_name", "scheduler", "seed",
"vary_seed_per_shot", "apply_model_sampling", "shift_video",
"shift_audio"]),
("Chaining shots", ["trim_seam", "handoff_offset", "cleanup_between_shots",
"vram_headroom_gb"]),
("Audio", ["global_soundscape", "non_diegetic_music", "auto_soundscape",
"auto_silence_nonspeech", "mute_nonspeech_audio", "mute_fade_ms"]),
("Consistency guards", ["subject_count_guard", "anatomy_guard", "solidity_guard",
"motion_guard", "contact_guard", "lock_restraints",
"auto_wardrobe", "auto_props", "prevent_nudity",
"exposed_terms"]),
("Reference images", ["ref_mode", "ref_image_size", "ref_noise_aug"]),
("Decode and upscale", ["decode_tile_frames", "decode_tile_size", "upscale",
"upscale_model", "upscale_target_short_edge",
"upscale_batch"]),
("Overlays", ["watermark_text", "watermark_position", "watermark_size",
"watermark_opacity", "watermark_margin", "intro_text",
"intro_position", "intro_seconds", "intro_fade", "intro_size",
"overlay_font", "overlay_stroke"]),
("Preview", ["plan_only"]),
]
def load_node():
sys.path.insert(0, os.path.dirname(os.path.dirname(HERE))) # custom_nodes/
spec = importlib.util.spec_from_file_location(
"_h3ref", os.path.join(HERE, "__init__.py"),
submodule_search_locations=[HERE])
mod = importlib.util.module_from_spec(spec)
sys.modules["_h3ref"] = mod
spec.loader.exec_module(mod)
return mod.NODE_CLASS_MAPPINGS
def describe(spec):
"""(type, constraints) for one input's schema tuple."""
t, opts = spec[0], (spec[1] if len(spec) > 1 and isinstance(spec[1], dict) else {})
if isinstance(t, list):
return "choice", ", ".join(f"`{o}`" for o in t)
bits = []
if opts.get("forceInput"):
bits.append("**input socket**")
if "default" in opts and not opts.get("forceInput"):
d = opts["default"]
bits.append(f"default `{d!r}`" if isinstance(d, str) else f"default `{d}`")
if "min" in opts or "max" in opts:
bits.append(f"range `{opts.get('min', '-')}`–`{opts.get('max', '-')}`")
if opts.get("multiline"):
bits.append("multiline")
return t if isinstance(t, str) else "?", ", ".join(bits) or "—"
def tooltip_of(spec):
opts = spec[1] if len(spec) > 1 and isinstance(spec[1], dict) else {}
tip = " ".join((opts.get("tooltip") or "").split())
return tip or "_(no tooltip)_"
def build(cls):
it = cls.INPUT_TYPES()
req, opt = it.get("required", {}), it.get("optional", {})
everything = {**req, **opt}
seen, out = set(), []
out.append(f"There are **{len(everything)}** inputs and "
f"**{len(cls.RETURN_NAMES)}** outputs. Required inputs are marked "
f"**R**; everything else is optional and has a working default.\n")
for title, names in GROUPS:
rows = [n for n in names if n in everything]
if not rows:
continue
seen.update(rows)
out.append(f"### {title}\n")
out.append("| field | | type | constraints |")
out.append("|---|---|---|---|")
for n in rows:
t, c = describe(everything[n])
out.append(f"| `{n}` | {'**R**' if n in req else ''} | {t} | {c} |")
out.append("")
for n in rows:
tip = tooltip_of(everything[n])
if tip != "_(no tooltip)_":
out.append(f"**`{n}`** — {tip}\n")
leftover = [n for n in everything if n not in seen]
if leftover:
out.append("### Other\n")
out.append("| field | | type | constraints |")
out.append("|---|---|---|---|")
for n in leftover:
t, c = describe(everything[n])
out.append(f"| `{n}` | {'**R**' if n in req else ''} | {t} | {c} |")
out.append("")
for n in leftover:
tip = tooltip_of(everything[n])
if tip != "_(no tooltip)_":
out.append(f"**`{n}`** — {tip}\n")
out.append("### Outputs\n")
out.append("| slot | name | type |")
out.append("|---|---|---|")
for i, (n, t) in enumerate(zip(cls.RETURN_NAMES, cls.RETURN_TYPES)):
out.append(f"| {i} | `{n}` | {t} |")
out.append("")
out.append("Outputs are only ever **appended**. A workflow stores an output link "
"by slot index, so inserting one would silently re-target every wire "
"after it.\n")
return "\n".join(out)
def main(check_only=False):
mapping = load_node()
body = build(mapping["H3LongVideos"])
path = os.path.join(HERE, "REFERENCE.md")
with open(path, encoding="utf-8") as f:
doc = f.read()
if BEGIN not in doc or END not in doc:
sys.exit(f"Markers not found in REFERENCE.md. Add:\n{BEGIN}\n{END}")
head, rest = doc.split(BEGIN, 1)
_, tail = rest.split(END, 1)
new = f"{head}{BEGIN}\n\n{body}\n{END}{tail}"
if new == doc:
print("REFERENCE.md is up to date.")
return
if check_only:
sys.exit("REFERENCE.md is OUT OF DATE. Run: python gen_reference.py")
with open(path, "w", encoding="utf-8") as f:
f.write(new)
print(f"REFERENCE.md field tables regenerated "
f"({len(mapping['H3LongVideos'].INPUT_TYPES().get('required', {}))} required + "
f"{len(mapping['H3LongVideos'].INPUT_TYPES().get('optional', {}))} optional).")
if __name__ == "__main__":
main("--check" in sys.argv)