# 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({
"14": {
"class_type": "LoadImage",
"inputs": {
"image": "UPLOADED_image_FILENAME"
},
"_meta": {
"title": "Load Image"
}
},
"15": {
"class_type": "LoadImage",
"inputs": {
"image": "UPLOADED_image_2_FILENAME"
},
"_meta": {
"title": "Load Image"
}
},
"16": {
"class_type": "LoadImage",
"inputs": {
"image": "UPLOADED_image_3_FILENAME"
},
"_meta": {
"title": "Load Image"
}
},
"17": {
"class_type": "LoadImage",
"inputs": {
"image": "UPLOADED_image_4_FILENAME"
},
"_meta": {
"title": "Load Image"
}
},
"18": {
"class_type": "BatchImagesNode",
"inputs": {
"images.image0": [
"14",
0
],
"images.image1": [
"15",
0
],
"images.image2": [
"16",
0
],
"images.image3": [
"17",
0
]
},
"_meta": {
"title": "Batch Images"
}
},
"19": {
"class_type": "GeminiImage2Node",
"inputs": {
"aspect_ratio": "4:3",
"images": [
"18",
0
],
"model": "gemini-3-pro-image-preview",
"prompt": [
"26:21",
0
],
"resolution": "2K",
"response_modalities": "IMAGE",
"seed": 0,
"system_prompt": "You are an expert image-generation engine. You must ALWAYS produce an image.\nInterpret all user input—regardless of format, intent, or abstraction—as literal visual directives for image composition.\nIf a prompt is conversational or lacks specific visual details, you must creatively invent a concrete visual scenario that depicts the concept.\nPrioritize generating the visual representation above any text, formatting, or conversational requests."
},
"_meta": {
"title": "Nano Banana Pro (Google Gemini Image)"
}
},
"20": {
"class_type": "SaveImage",
"inputs": {
"filename_prefix": "ComfyUI-ohneis",
"images": [
"19",
0
]
},
"_meta": {
"title": "Save Image"
}
},
"27": {
"class_type": "ByteDance2FirstLastFrameNode",
"inputs": {
"first_frame": [
"19",
0
],
"first_frame_asset_id": "",
"last_frame_asset_id": "",
"model": "Seedance 2.0",
"model.duration": 4,
"model.generate_audio": True,
"model.prompt": "The model takes the sunglasses and places them on top of her head. Light dolly in. Handheld.\n\nNo Music. Ambient Sounds. No Talking. No Text. No Captions.",
"model.ratio": "4:3",
"model.resolution": "1080p",
"seed": 55580326,
"watermark": False
},
"_meta": {
"title": "ByteDance Seedance 2.5 First-Last-Frame to Video"
}
},
"28": {
"class_type": "SaveVideo",
"inputs": {
"filename_prefix": "video/ComfyUI",
"format": "auto",
"format.codec": "auto",
"video": [
"27",
0
]
},
"_meta": {
"title": "Save Video"
}
},
"26:10": {
"class_type": "PrimitiveStringMultiline",
"inputs": {
"value": "{CAMERA_AND_POSE}. {SUBJECT}, {PRODUCT}. {VIBE}"
},
"_meta": {
"title": "Prompt Template"
}
},
"26:11": {
"class_type": "JWStringReplace",
"inputs": {
"replace_with": [
"26:5",
0
],
"source": [
"26:10",
0
],
"to_replace": "{CAMERA_AND_POSE}"
},
"_meta": {
"title": "Replace {CAMERA_AND_POSE}"
}
},
"26:12": {
"class_type": "JWStringReplace",
"inputs": {
"replace_with": [
"26:7",
0
],
"source": [
"26:11",
0
],
"to_replace": "{SUBJECT}"
},
"_meta": {
"title": "Replace {SUBJET}"
}
},
"26:13": {
"class_type": "JWStringReplace",
"inputs": {
"replace_with": [
"26:8",
0
],
"source": [
"26:12",
0
],
"to_replace": "{PRODUCT}"
},
"_meta": {
"title": "Replace {PRODUCT}"
}
},
"26:21": {
"class_type": "JWStringReplace",
"inputs": {
"replace_with": [
"26:6",
0
],
"source": [
"26:13",
0
],
"to_replace": "{VIBE}"
},
"_meta": {
"title": "Replace {VIBE}"
}
},
"26:5": {
"class_type": "PrimitiveStringMultiline",
"inputs": {
"value": "Cinematic medium shot portrait, crop from waist to crown, head in three-quarter profile, one hand gently raised near the jawline to showcase accessories, gaze directed slightly off-camera, slight low-angle approximately 10 degrees below eye-level, approximately 85mm equivalent focal length, shallow depth of field, background completely defocused, neutral composed expression"
},
"_meta": {
"title": "CAMERA_AND_POSE"
}
},
"26:6": {
"class_type": "PrimitiveStringMultiline",
"inputs": {
"value": "dressed in a white wrap dress, lightweight fabric with slight drape, open water surface behind subject rendered as soft flat warm-gray shimmer, golden dusk light from upper-left creating warm directional highlight across left side of face and jaw, right side receiving cool ambient fill from water reflection, dual warm-cool color temperature split, Fujifilm Eterna 500 color profile, cool gray-green water background with Eterna's flat tonal response, bleached soft highlights, melancholic introspective high fashion editorial mood"
},
"_meta": {
"title": "VIBE"
}
},
"26:7": {
"class_type": "PrimitiveStringMultiline",
"inputs": {
"value": "beautiful Black woman late-20s, deep warm brown skin tone with smooth even texture, closely cropped natural dark hair"
},
"_meta": {
"title": "SUBJECT"
}
},
"26:8": {
"class_type": "PrimitiveStringMultiline",
"inputs": {
"value": "prominently wearing attached product"
},
"_meta": {
"title": "PRODUCT"
}
}
})
# Replace each example.com URL with a public URL of your file.
workflow.set_input("14", "image", client.assets.from_url("https://example.com/replace-with-a-url/image"))
workflow.set_input("15", "image", client.assets.from_url("https://example.com/replace-with-a-url/image_2"))
workflow.set_input("16", "image", client.assets.from_url("https://example.com/replace-with-a-url/image_3"))
workflow.set_input("17", "image", client.assets.from_url("https://example.com/replace-with-a-url/image_4"))
# run() also passes api_key to the partner nodes in this workflow.
job = client.run(workflow, api_key=api_key)