# 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({
"27": {
"class_type": "SaveImage",
"inputs": {
"filename_prefix": "Storyboard",
"images": [
"32",
0
]
},
"_meta": {
"title": "Save Image"
}
},
"29": {
"class_type": "StringConcatenate",
"inputs": {
"delimiter": "",
"string_a": "Use the first input image only as the grid, generate black and white hand-sketched storyboards within each box, with simple lines and no complicated details. Consistent style across all frames, and put the shot description underneath each frame in helvetica font. Leave the box blank if it has less then six shots. Storyboard is as following:",
"string_b": "Shot1 - A spotless retro-futuristic kitchen emerges from a soft fade. Curved cabinetry, chrome appliances with rounded edges, warm cream and sage tones. A bag of Comfy coffee sits on the counter, logo facing camera.\n\nShot2 - A hand scoop whole Arabica beans from the Comfy bag into a sleek retro grinder, in an unhurried motion. The beans are rich and glossy.\n\nShot3 - Dark espresso blooms into a rounded and matte ceramic cup. Steam curls upward against the clean kitchen backdrop.\n\nShot4 - Person holds the cup in both hands, standing at a large curved kitchen window. Soft morning light spills in. Eyes close briefly in comfort.\n\nShot5 - Hard cut to a clean studio card: the Comfy coffee bag centered on a tabletop. Logo fades in last. Hold."
},
"_meta": {
"title": "Concatenate Text"
}
},
"32": {
"class_type": "OpenAIGPTImage1",
"inputs": {
"background": "auto",
"custom_height": 1024,
"custom_width": 1024,
"image": [
"38",
0
],
"model": "gpt-image-2",
"n": 1,
"prompt": [
"29",
0
],
"quality": "high",
"seed": 1055583003,
"size": "1536x1024"
},
"_meta": {
"title": "OpenAI GPT Image 2"
}
},
"36": {
"class_type": "LoadImage",
"inputs": {
"image": "UPLOADED_image_FILENAME"
},
"_meta": {
"title": "GRID"
}
},
"38": {
"class_type": "BatchImagesNode",
"inputs": {
"images.image0": [
"36",
0
],
"images.image1": [
"41",
0
]
},
"_meta": {
"title": "Batch Images"
}
},
"41": {
"class_type": "LoadImage",
"inputs": {
"image": "UPLOADED_image_2_FILENAME"
},
"_meta": {
"title": "PRODUCT IMAGE (OPTIONAL)"
}
},
"46": {
"class_type": "SaveImage",
"inputs": {
"filename_prefix": "Styleframe",
"images": [
"60",
0
]
},
"_meta": {
"title": "Save Image"
}
},
"51": {
"class_type": "BatchImagesNode",
"inputs": {
"images.image0": [
"57",
0
],
"images.image1": [
"53",
0
]
},
"_meta": {
"title": "Batch Images"
}
},
"53": {
"class_type": "LoadImage",
"inputs": {
"image": "UPLOADED_image_3_FILENAME"
},
"_meta": {
"title": "MOODBOARD (OPTIONAL)"
}
},
"57": {
"class_type": "LoadImage",
"inputs": {
"image": "UPLOADED_image_4_FILENAME"
},
"_meta": {
"title": "STORYBOARD SKETCH"
}
},
"60": {
"class_type": "GeminiImage2Node",
"inputs": {
"aspect_ratio": "auto",
"images": [
"51",
0
],
"model": "gemini-3-pro-image-preview",
"prompt": "Reimagine the storyboard sketch of the first image to high-end commercial production photography. And use any additional images as the aesthetic, lighting, and color grade inspirations.",
"resolution": "1K",
"response_modalities": "IMAGE+TEXT",
"seed": 367674127099842,
"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)"
}
}
})
# Replace each example.com URL with a public URL of your file.
workflow.set_input("36", "image", client.assets.from_url("https://example.com/replace-with-a-url/image"))
workflow.set_input("41", "image", client.assets.from_url("https://example.com/replace-with-a-url/image_2"))
workflow.set_input("53", "image", client.assets.from_url("https://example.com/replace-with-a-url/image_3"))
workflow.set_input("57", "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)