Download gen_reference.py from Dcbuilder831/MiniMax-H3-Longvideos: direct link, hf CLI and curl.
- Browser
- Download file 7.38 kB
-
https://huggingface.co/Dcbuilder831/MiniMax-H3-Longvideos/resolve/main/gen_reference.py
- Command line
-
hf download hf://Dcbuilder831/MiniMax-H3-Longvideos/gen_reference.py
-
curl -L -o gen_reference.py https://huggingface.co/Dcbuilder831/MiniMax-H3-Longvideos/resolve/main/gen_reference.py
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) | |