Spaces:
Paused
Paused
Download app.py from launch-calcium/Comfyui2: direct link, hf CLI and curl.
- Browser
- Download file 9.8 kB
-
https://huggingface.co/spaces/launch-calcium/Comfyui2/resolve/main/app.py
- Command line
-
hf download hf://spaces/launch-calcium/Comfyui2/app.py
-
curl -L -o app.py https://huggingface.co/spaces/launch-calcium/Comfyui2/resolve/main/app.py
9.8 kB
| # app.py | |
| import json | |
| from pathlib import Path | |
| import gradio as gr | |
| from huggingface_hub import snapshot_download | |
| TEMPLATE_FILENAME = "workflow_template.json" | |
| TEMPLATE_PATH = Path(__file__).parent / TEMPLATE_FILENAME | |
| # Minimal embedded template (used if workflow_template.json is missing). | |
| # This mirrors the flattened template structure used earlier but kept compact. | |
| DEFAULT_TEMPLATE = { | |
| "id": "minimax-music3-template", | |
| "revision": 1, | |
| "last_node_id": 43, | |
| "last_link_id": 63, | |
| "nodes": [ | |
| { | |
| "id": 6, | |
| "type": "UNETLoader", | |
| "pos": [700, 380], | |
| "size": [480, 90], | |
| "widgets_values": ["minimax_music3_dit_fp16.safetensors","default"], | |
| "widgets_values_named": {"unet_name":"minimax_music3_dit_fp16.safetensors","weight_dtype":"default"} | |
| }, | |
| { | |
| "id": 3, | |
| "type": "CLIPLoader", | |
| "pos": [700, 550], | |
| "size": [480, 120], | |
| "widgets_values": ["minimax_music3_text_encoder_pruned_int8_convrot.safetensors","minimax","default"], | |
| "widgets_values_named": {"clip_name":"minimax_music3_text_encoder_pruned_int8_convrot.safetensors","type":"minimax","device":"default"} | |
| }, | |
| { | |
| "id": 7, | |
| "type": "VAELoader", | |
| "pos": [700, 750], | |
| "size": [480, 70], | |
| "widgets_values": ["minimax_music3_dav.safetensors"], | |
| "widgets_values_named": {"vae_name":"minimax_music3_dav.safetensors"} | |
| }, | |
| { | |
| "id": 13, | |
| "type": "MiniMaxMusic3TextEncode", | |
| "pos": [1230, 380], | |
| "size": [500, 750], | |
| "showAdvanced": True, | |
| "widgets_values": ["", "", 222, "fixed", 60, 1.7, 50], | |
| "widgets_values_named": {"caption":"","lyrics":"","seed":222,"control_after_generate":"fixed","max_duration":60,"cfg_scale":1.7,"top_k":50} | |
| }, | |
| { | |
| "id": 15, | |
| "type": "EmptyMiniMaxMusic3LatentAudio", | |
| "pos": [1780, 380], | |
| "size": [350, 90], | |
| "widgets_values": [60, 1], | |
| "widgets_values_named": {"seconds": 60, "batch_size": 1} | |
| }, | |
| { | |
| "id": 10, | |
| "type": "ConditioningZeroOut", | |
| "pos": [1820, 540], | |
| "size": [230, 40] | |
| }, | |
| { | |
| "id": 9, | |
| "type": "KSampler", | |
| "pos": [2170, 380], | |
| "size": [370, 270], | |
| "widgets_values": [1111111112, "fixed", 30, 1.7, "euler", "simple", 1], | |
| "widgets_values_named": {"seed":1111111112,"control_after_generate":"fixed","steps":30,"cfg":1.7,"sampler_name":"euler","scheduler":"simple","denoise":1} | |
| }, | |
| { | |
| "id": 12, | |
| "type": "VAEDecodeAudio", | |
| "pos": [2590, 380], | |
| "size": [230, 60] | |
| }, | |
| { | |
| "id": 42, | |
| "type": "VAEDecodeAudioTiled", | |
| "pos": [2580, 500], | |
| "size": [280, 110], | |
| "widgets_values": [1536, 64], | |
| "widgets_values_named": {"tile_size":1536,"overlap":64} | |
| }, | |
| { | |
| "id": 43, | |
| "type": "ComfySwitchNode", | |
| "pos": [2600, 690], | |
| "size": [270, 110], | |
| "widgets_values": [True], | |
| "widgets_values_named": {"switch": True} | |
| }, | |
| { | |
| "id": 38, | |
| "type": "SeedNode", | |
| "pos": [890, 970], | |
| "size": [270, 90], | |
| "widgets_values": [1040288205448534, "fixed"], | |
| "widgets_values_named": {"seed": 1040288205448534, "fixed": "fixed"} | |
| }, | |
| { | |
| "id": 35, | |
| "type": "SaveAudioAdvanced", | |
| "pos": [2150, 630], | |
| "size": [750, 160], | |
| "widgets_values": ["audio/audio_minimax_music3","mp3","V0"], | |
| "widgets_values_named": {"filename_prefix":"audio/audio_minimax_music3","format":"mp3","format.quality":"V0"} | |
| } | |
| ], | |
| "links": [ | |
| [18, 3, 0, 13, 0, "CLIP"], | |
| [16, 13, 0, 10, 0, "CONDITIONING"], | |
| [20, 13, 1, 15, 0, "FLOAT"], | |
| [6, 6, 0, 9, 0, "MODEL"], | |
| [15, 13, 0, 9, 1, "CONDITIONING"], | |
| [8, 10, 0, 9, 2, "CONDITIONING"], | |
| [21, 15, 0, 9, 3, "LATENT"], | |
| [12, 9, 0, 12, 0, "LATENT"], | |
| [13, 7, 0, 12, 1, "VAE"], | |
| [52, 38, 0, 13, 3, "INT"], | |
| [53, 38, 0, 9, 4, "INT"], | |
| [58, 9, 0, 42, 0, "LATENT"], | |
| [59, 7, 0, 42, 1, "VAE"], | |
| [60, 12, 0, 43, 0, "AUDIO"], | |
| [61, 42, 0, 43, 1, "AUDIO"], | |
| [62, 43, 0, 35, 0, "AUDIO"] | |
| ], | |
| "extra": {"frontendVersion": "1.49.6"}, | |
| "version": 0.4 | |
| } | |
| def load_template(): | |
| # Try reading an external template file first | |
| if TEMPLATE_PATH.exists(): | |
| try: | |
| return json.loads(TEMPLATE_PATH.read_text(encoding="utf-8")) | |
| except Exception as e: | |
| # fallback to embedded template if parsing fails | |
| print("Failed to parse workflow_template.json, using embedded template:", e) | |
| return DEFAULT_TEMPLATE.copy() | |
| def generate_workflow(caption, lyrics, max_duration, seed, unet_name, clip_name, vae_name, tiled_decode, filename_prefix="audio/audio_minimax_music3"): | |
| # Load the template JSON (dict) | |
| template = load_template() | |
| # Update loaders' widget filenames | |
| for node in template.get("nodes", []): | |
| if node.get("type") == "UNETLoader": | |
| node.setdefault("widgets_values", [unet_name, "default"]) | |
| node.setdefault("widgets_values_named", {})["unet_name"] = unet_name | |
| if node.get("type") == "CLIPLoader": | |
| node.setdefault("widgets_values", [clip_name, "minimax", "default"]) | |
| node.setdefault("widgets_values_named", {})["clip_name"] = clip_name | |
| if node.get("type") == "VAELoader": | |
| node.setdefault("widgets_values", [vae_name]) | |
| node.setdefault("widgets_values_named", {})["vae_name"] = vae_name | |
| # MiniMax text encode node | |
| if node.get("type") == "MiniMaxMusic3TextEncode": | |
| node.setdefault("widgets_values_named", {})["caption"] = caption | |
| node["widgets_values_named"]["lyrics"] = lyrics | |
| node["widgets_values_named"]["max_duration"] = float(max_duration) | |
| node["widgets_values_named"]["seed"] = int(seed) | |
| # keep widgets_values aligned if present | |
| node["widgets_values"] = [ | |
| caption, | |
| lyrics, | |
| int(seed), | |
| node.get("widgets_values", [None,])[3] if len(node.get("widgets_values", []))>3 else "fixed", | |
| float(max_duration), | |
| node.get("widgets_values", [None,])[5] if len(node.get("widgets_values", []))>5 else 1.7, | |
| node.get("widgets_values", [None,])[6] if len(node.get("widgets_values", []))>6 else 50 | |
| ] | |
| # Empty latent seconds | |
| if node.get("type") == "EmptyMiniMaxMusic3LatentAudio": | |
| node.setdefault("widgets_values_named", {})["seconds"] = float(max_duration) | |
| node["widgets_values"] = [float(max_duration), 1] | |
| # ComfySwitch: switch widget | |
| if node.get("type") == "ComfySwitchNode": | |
| node.setdefault("widgets_values", [bool(tiled_decode)]) | |
| node.setdefault("widgets_values_named", {})["switch"] = bool(tiled_decode) | |
| # SaveAudioAdvanced filename | |
| if node.get("type") == "SaveAudioAdvanced": | |
| node.setdefault("widgets_values_named", {})["filename_prefix"] = filename_prefix | |
| node["widgets_values"] = [filename_prefix, "mp3", "V0"] | |
| # Write out file | |
| out_dir = Path("outputs") | |
| out_dir.mkdir(exist_ok=True) | |
| out_path = out_dir / f"minimax_workflow_{int(seed)}.json" | |
| out_path.write_text(json.dumps(template, indent=2, ensure_ascii=False), encoding="utf-8") | |
| return str(out_path) | |
| def download_models(repo_id="Comfy-Org/MiniMax-Music-3", revision="main"): | |
| out_dir = Path("models_download") | |
| out_dir.mkdir(exist_ok=True) | |
| try: | |
| path = snapshot_download(repo_id=repo_id, revision=revision, cache_dir=str(out_dir)) | |
| return f"Downloaded to {path}" | |
| except Exception as e: | |
| return f"Failed to download models: {e}" | |
| with gr.Blocks(title="MiniMax Music 3 β ComfyUI workflow generator") as demo: | |
| gr.Markdown("# MiniMax Music 3 β ComfyUI workflow generator") | |
| with gr.Row(): | |
| with gr.Column(): | |
| caption = gr.Textbox(label="Caption (Global metadata β Vocal details β Arrangement)", value="Global Metadata: Mainstream pop... ", lines=6) | |
| lyrics = gr.Textbox(label="Lyrics (use [Intro]/[Verse]/[Chorus] tags)", value="[Intro]\nOh-oh-oh...", lines=8) | |
| max_duration = gr.Number(label="Max duration (seconds)", value=60) | |
| seed = gr.Number(label="Seed (integer)", value=1040288205448534) | |
| unet_name = gr.Textbox(label="UNET model filename", value="minimax_music3_dit_fp16.safetensors") | |
| clip_name = gr.Textbox(label="Text encoder filename", value="minimax_music3_text_encoder_pruned_int8_convrot.safetensors") | |
| vae_name = gr.Textbox(label="VAE filename", value="minimax_music3_dav.safetensors") | |
| tiled_decode = gr.Checkbox(label="Tiled decode (reduces VRAM)", value=True) | |
| filename_prefix = gr.Textbox(label="SaveAudio filename prefix", value="audio/audio_minimax_music3") | |
| generate_btn = gr.Button("Generate workflow JSON") | |
| download_models_btn = gr.Button("Download model weights to runner (optional)") | |
| with gr.Column(): | |
| out_file = gr.File(label="Generated workflow JSON") | |
| status = gr.Textbox(label="Status / Messages", interactive=False) | |
| def on_generate(caption, lyrics, max_duration, seed, unet_name, clip_name, vae_name, tiled_decode, filename_prefix): | |
| path = generate_workflow(caption, lyrics, max_duration, seed, unet_name, clip_name, vae_name, tiled_decode, filename_prefix) | |
| return path, f"Workflow generated: {path} β download and open in ComfyUI / tensor.art." | |
| generate_btn.click(on_generate, inputs=[caption, lyrics, max_duration, seed, unet_name, clip_name, vae_name, tiled_decode, filename_prefix], outputs=[out_file, status]) | |
| download_models_btn.click(lambda: download_models(), outputs=[status]) | |
| if __name__ == "__main__": | |
| demo.launch() |