Comfyui2 / app.py
launch-calcium's picture
Update app.py
01b05ce verified
Raw History Blame Contribute Delete
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()