# Python 3.10+: pip install comfy-sdk==0.4.0
import os
from comfy_sdk import Comfy
api_key = os.environ.get("COMFY_API_KEY", "").strip()
if not api_key: raise RuntimeError("Set COMFY_API_KEY")
os.environ.setdefault("COMFY_BASE_URL", "https://cloud.comfy.org")
client = Comfy(api_key=api_key)
workflow = client.workflows.from_json({
"2": {
"class_type": "SaveVideo",
"inputs": {
"codec": "auto",
"filename_prefix": "video/video/MiniMax_H3_flf2v_768P",
"format": "auto",
"format.codec": "auto",
"video": [
"30",
0
]
},
"_meta": {
"title": "Save Video"
}
},
"7": {
"class_type": "MinimaxHailuo03ContextIRNode",
"inputs": {
"model": "MiniMax H3",
"model.duration": 5,
"model.prompt": [
"14",
0
],
"model.ratio": "adaptive"
},
"_meta": {
"title": "MiniMax H3 Context IR (Prompt Enhancer)"
}
},
"8": {
"class_type": "SaveText",
"inputs": {
"filename_prefix": "H3_prompt",
"format": "txt",
"text": [
"12",
0
]
},
"_meta": {
"title": "Save Text"
}
},
"10": {
"class_type": "MinimaxHailuo03RegenerateNode",
"inputs": {
"model": "MiniMax H3",
"model.prompt": [
"8",
0
],
"model.resolution": "2K",
"video": [
"2",
0
],
"watermark": False
},
"_meta": {
"title": "MiniMax H3 Regenerate to 2K"
}
},
"11": {
"class_type": "SaveVideo",
"inputs": {
"codec": "auto",
"filename_prefix": "video/Minimax_h3_max",
"format": "auto",
"format.codec": "auto",
"video": [
"13",
0
]
},
"_meta": {
"title": "Save Video"
}
},
"12": {
"class_type": "ComfySwitchNode",
"inputs": {
"on_false": [
"14",
0
],
"on_true": [
"7",
0
],
"switch": [
"18",
0
]
},
"_meta": {
"title": "If/Else Switch"
}
},
"13": {
"class_type": "ComfySwitchNode",
"inputs": {
"on_false": [
"30",
0
],
"on_true": [
"10",
0
],
"switch": [
"19",
0
]
},
"_meta": {
"title": "If/Else Switch"
}
},
"14": {
"class_type": "PrimitiveStringMultiline",
"inputs": {
"value": "[Segment 1]\nStyle: dream physics, deep-blue monochrome with a misty diffusion in the glow, shadows keeping faint layers instead of pure black, spatial scale distortion, photoreal texture.\nAction: [0s-1.5s] The projected face breathes with light shifts, dust drifting, his silhouette still; [1.5s-4s] The camera pulls back slowly and the giant face shrinks with it, revealing that this colossal face floats on the wall of a vast bedroom: a projection inside a dream-room, his silhouette standing at the bedside looking up; [4s-8s] The camera keeps pulling back past the room's boundary as the walls curl inward like paper, folding into the darkness of a second dream layer, the giant face shrinking with the curling walls into a floating glowing mask suspended in the void, the man motionless and looking up throughout, the frame ending with the mask hovering directly above him. No text. Camera: one continuous pull-back, slight warping as the space folds, no cuts. Audio: a low drone wrapped in an underwater-feeling muted reverb, paper-like stretching as the space curls, one distant bell.\n\n[Segment 2]\nStyle: same dreamy haze, the blue one shade darker, a deeper dream layer.\nAction: [0s-2s] The glowing mask rotates slowly above him; he raises a hand, and the instant his fingertips near it the whole space collapses in scale: the mask becomes a reflection inside a pupil, his body edge receding into silhouette; [2s-5s] The viewpoint reveals itself as his eye all along: the frame wavers slightly, we have been looking through his pupil, the blue iris texture rings into view at the frame's edge; [5s-8s] The giant face in the pupil breathes once with light, the dream begins to dissolve: the image tears slightly like a bad signal, the reflection flickers twice, locking finally on the extreme eye close-up, a deeper blue surfacing slowly in the depths of the iris. No text. Camera: an objective view snapping into pupil-POV, the switch made through a slight signal-tear, no cuts. Audio: a heartbeat approaching to become the main beat, one falling bass note as the mask shrinks, signal noise through the tear, every sound sucked away on the last beat leaving a faint ringing in the ears.\n\n[Shared]\nDreamy haze over the blue monochrome, faint layers in the shadows, photoreal texture, spatial scale distorting with each dream layer. Negative: no water ripples, no third-person frontal face, no color shift away from the blue monochrome, no text overlays, no subtitles, no dissolve transitions, no extra light sources, no logo."
},
"_meta": {
"title": "Text (Prompt)"
}
},
"18": {
"class_type": "PrimitiveBoolean",
"inputs": {
"value": False
},
"_meta": {
"title": "Boolean (Prompt Enhance)"
}
},
"19": {
"class_type": "PrimitiveBoolean",
"inputs": {
"value": False
},
"_meta": {
"title": "Boolean (Upscale to 2K)"
}
},
"28": {
"class_type": "LoadImage",
"inputs": {
"image": "UPLOADED_image_FILENAME"
},
"_meta": {
"title": "Load Image (First Frame)"
}
},
"29": {
"class_type": "LoadImage",
"inputs": {
"image": "UPLOADED_image_2_FILENAME"
},
"_meta": {
"title": "Load Image (Last Frame)"
}
},
"30": {
"class_type": "MinimaxHailuo03FirstLastFrameNode",
"inputs": {
"first_frame": [
"28",
0
],
"last_frame": [
"29",
0
],
"model": "MiniMax H3 Max",
"model.duration": 5,
"model.prompt": [
"12",
0
],
"model.prompt_expansion_mode": "balanced",
"model.resolution": "768P",
"seed": 4098290433,
"watermark": False
},
"_meta": {
"title": "MiniMax H3 First-Last-Frame to Video"
}
}
})
# Replace each example.com URL with a public URL of your file.
workflow.set_input("28", "image", client.assets.from_url("https://example.com/replace-with-a-url/image"))
workflow.set_input("29", "image", client.assets.from_url("https://example.com/replace-with-a-url/image_2"))
# run() also passes api_key to the partner nodes in this workflow.
job = client.run(workflow, api_key=api_key)