Looped Restyler
Upload a starting image and its prompt to generate a looping video from the first to the last frame.
About
The 'Looped Restyler' workflow in ComfyUI is designed to transform a static image into a dynamic looping video. By utilizing advanced AI models like Nano Banana, Google Gemini, and Wan2.2, this workflow seamlessly transitions from the first frame of an image to the last, creating a continuous loop.
The process begins with loading an initial image and its corresponding prompt, which guides the transformation process. The workflow employs nodes such as LoadImage to import the starting image, and SaveVideo to compile the frames into a video format. The node identified by 3313ce14-acfd-4a9c-9f0c-a1b01147e934 plays a crucial role in generating intermediate frames that ensure a smooth transition between the start and end of the sequence. PreviewImage is used to monitor the progress and make necessary adjustments before finalizing the output.
What you provide
- Reading the workflow
Runs in ComfyUI. This workflow can't run on this page. Open it in Comfy Cloud with everything loaded, or download it for your own ComfyUI.
Try in CloudInside the workflow
A read-only preview of the node graph. Open it in Comfy Cloud or download it to edit the nodes.
Loading the graph
Open full-size workflow preview- Author
- Julien Durand | MJM
Requires custom nodes
- Reading the workflow
Use this workflow from code
Generated from the workflow graph and inputs when possible; stored examples are shown otherwise. Review credentials and endpoints before running code, which may incur charges.
What you need
- Runs on
- Comfy Cloud
- Key
- COMFY_API_KEY
Needs a paid Cloud plan and credits.
# 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({
"4": {
"class_type": "LoadImage",
"inputs": {
"image": "looped_restyler_init.png"
},
"_meta": {
"title": "Load Image"
}
},
"80": {
"class_type": "SaveVideo",
"inputs": {
"filename_prefix": "video/ComfyUI",
"format": "auto",
"format.codec": "auto",
"video": [
"89:79",
0
]
},
"_meta": {
"title": "Save Video"
}
},
"90": {
"class_type": "PreviewImage",
"inputs": {
"images": [
"89:82",
0
]
},
"_meta": {
"title": "Preview Image"
}
},
"89:27": {
"class_type": "SplitImageWithAlpha",
"inputs": {
"image": [
"89:88",
0
]
},
"_meta": {
"title": "Split Image with Alpha"
}
},
"89:5": {
"class_type": "GeminiImageNode",
"inputs": {
"aspect_ratio": "auto",
"images": [
"89:87",
0
],
"model": "gemini-2.5-flash-image",
"prompt": "A pure illustrated anime image. \n\nThe camera angle is from the back of the character, a back-facing shot.\n\n2D line-art with cel-shaded color fills, expressive outlines, and stylized anime anatomy. Scene is drawn entirely as an illustration. Absolutely no realism, no photo textures, no 3D render qualities — strictly anime illustration style.",
"response_modalities": "IMAGE+TEXT",
"seed": 754210146642470,
"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 (Google Gemini Image)"
}
},
"89:6": {
"class_type": "PreviewImage",
"inputs": {
"images": [
"89:88",
0
]
},
"_meta": {
"title": "Preview Image"
}
},
"89:73:57": {
"class_type": "CLIPTextEncode",
"inputs": {
"clip": [
"89:73:69",
0
],
"text": "色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部,畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走"
},
"_meta": {
"title": "CLIP Text Encode (Negative Prompt)"
}
},
"89:73:58": {
"class_type": "CLIPTextEncode",
"inputs": {
"clip": [
"89:73:69",
0
],
"text": [
"89:81",
0
]
},
"_meta": {
"title": "CLIP Text Encode (Positive Prompt)"
}
},
"89:73:59": {
"class_type": "UNETLoader",
"inputs": {
"unet_name": "wan2.2_i2v_high_noise_14B_fp8_scaled.safetensors",
"weight_dtype": "default"
},
"_meta": {
"title": "Load Diffusion Model"
}
},
"89:73:60": {
"class_type": "UNETLoader",
"inputs": {
"unet_name": "wan2.2_i2v_low_noise_14B_fp8_scaled.safetensors",
"weight_dtype": "default"
},
"_meta": {
"title": "Load Diffusion Model"
}
},
"89:73:61": {
"class_type": "LoraLoaderModelOnly",
"inputs": {
"lora_name": "wan2.2_i2v_lightx2v_4steps_lora_v1_low_noise.safetensors",
"model": [
"89:73:60",
0
],
"strength_model": 1
},
"_meta": {
"title": "Load LoRA"
}
},
"89:73:62": {
"class_type": "LoraLoaderModelOnly",
"inputs": {
"lora_name": "wan2.2_i2v_lightx2v_4steps_lora_v1_high_noise.safetensors",
"model": [
"89:73:59",
0
],
"strength_model": 1
},
"_meta": {
"title": "Load LoRA"
}
},
"89:73:63": {
"class_type": "ModelSamplingSD3",
"inputs": {
"model": [
"89:73:62",
0
],
"shift": 5
},
"_meta": {
"title": "ModelSamplingSD3"
}
},
"89:73:64": {
"class_type": "ModelSamplingSD3",
"inputs": {
"model": [
"89:73:61",
0
],
"shift": 5
},
"_meta": {
"title": "ModelSamplingSD3"
}
},
"89:73:65": {
"class_type": "CreateVideo",
"inputs": {
"bit_depth": "auto",
"codec": "none",
"color_space": "sRGB",
"fps": 24,
"images": [
"89:73:71",
0
]
},
"_meta": {
"title": "Create Video"
}
},
"89:73:66": {
"class_type": "KSamplerAdvanced",
"inputs": {
"add_noise": "enable",
"cfg": 1,
"end_at_step": 2,
"latent_image": [
"89:73:67",
2
],
"model": [
"89:73:63",
0
],
"negative": [
"89:73:67",
1
],
"noise_seed": 803169991734672,
"positive": [
"89:73:67",
0
],
"return_with_leftover_noise": "enable",
"sampler_name": "euler",
"scheduler": "simple",
"start_at_step": 0,
"steps": 4
},
"_meta": {
"title": "KSampler (Advanced)"
}
},
"89:73:67": {
"class_type": "WanFirstLastFrameToVideo",
"inputs": {
"batch_size": 1,
"end_image": [
"89:27",
0
],
"height": [
"89:87",
2
],
"length": 81,
"negative": [
"89:73:57",
0
],
"positive": [
"89:73:58",
0
],
"start_image": [
"89:87",
0
],
"vae": [
"89:73:70",
0
],
"width": [
"89:87",
1
]
},
"_meta": {
"title": "WanFirstLastFrameToVideo"
}
},
"89:73:68": {
"class_type": "KSamplerAdvanced",
"inputs": {
"add_noise": "disable",
"cfg": 1,
"end_at_step": 10000,
"latent_image": [
"89:73:66",
0
],
"model": [
"89:73:64",
0
],
"negative": [
"89:73:67",
1
],
"noise_seed": 0,
"positive": [
"89:73:67",
0
],
"return_with_leftover_noise": "disable",
"sampler_name": "euler",
"scheduler": "simple",
"start_at_step": 2,
"steps": 4
},
"_meta": {
"title": "KSampler (Advanced)"
}
},
"89:73:69": {
"class_type": "CLIPLoader",
"inputs": {
"clip_name": "umt5_xxl_fp8_e4m3fn_scaled.safetensors",
"device": "default",
"type": "wan"
},
"_meta": {
"title": "Load CLIP"
}
},
"89:73:70": {
"class_type": "VAELoader",
"inputs": {
"vae_name": "wan_2.1_vae.safetensors"
},
"_meta": {
"title": "Load VAE"
}
},
"89:73:71": {
"class_type": "VAEDecode",
"inputs": {
"samples": [
"89:73:68",
0
],
"vae": [
"89:73:70",
0
]
},
"_meta": {
"title": "VAE Decode"
}
},
"89:76": {
"class_type": "GetVideoComponents",
"inputs": {
"video": [
"89:73:65",
0
]
},
"_meta": {
"title": "Get Video Components"
}
},
"89:77": {
"class_type": "ImageBatch",
"inputs": {
"image1": [
"89:76",
0
],
"image2": [
"89:78",
0
]
},
"_meta": {
"title": "Batch Images (DEPRECATED)"
}
},
"89:78": {
"class_type": "GetVideoComponents",
"inputs": {
"video": [
"89:91:56",
0
]
},
"_meta": {
"title": "Get Video Components"
}
},
"89:79": {
"class_type": "CreateVideo",
"inputs": {
"bit_depth": "auto",
"codec": "none",
"color_space": "sRGB",
"fps": 24,
"images": [
"89:77",
0
]
},
"_meta": {
"title": "Create Video"
}
},
"89:81": {
"class_type": "GeminiNode",
"inputs": {
"images": [
"89:82",
0
],
"model": "gemini-2.5-pro",
"prompt": "I am prompting a video model that understands natural language.\n\nI need a prompt that describes the transitions from image on the left to image on the right.\n\nJust give me the prompt and nothing else.",
"seed": 941151948383059,
"system_prompt": ""
},
"_meta": {
"title": "Google Gemini"
}
},
"89:82": {
"class_type": "ImageStitch",
"inputs": {
"direction": "right",
"image1": [
"89:87",
0
],
"image2": [
"89:88",
0
],
"match_image_size": True,
"spacing_color": "white",
"spacing_width": 0
},
"_meta": {
"title": "Stitch Images"
}
},
"89:83": {
"class_type": "GeminiNode",
"inputs": {
"images": [
"89:82",
0
],
"model": "gemini-2.5-pro",
"prompt": "I am prompting a video model that understands natural language.\n\nI need a prompt that describes the transitions from image on the right to image on the left.\n\nJust give me the prompt and nothing else.",
"seed": 23952267132674,
"system_prompt": ""
},
"_meta": {
"title": "Google Gemini"
}
},
"89:84": {
"class_type": "PreviewImage",
"inputs": {
"images": [
"89:82",
0
]
},
"_meta": {
"title": "Preview Image"
}
},
"89:85": {
"class_type": "ImageScaleToTotalPixels",
"inputs": {
"image": [
"4",
0
],
"megapixels": 1,
"resolution_steps": 1,
"upscale_method": "lanczos"
},
"_meta": {
"title": "Scale Image to Total Pixels"
}
},
"89:87": {
"class_type": "GetImageSizeAndCount",
"inputs": {
"image": [
"89:85",
0
]
},
"_meta": {
"title": "Get Image Size & Count"
}
},
"89:88": {
"class_type": "ImageScaleToTotalPixels",
"inputs": {
"image": [
"89:5",
0
],
"megapixels": 1,
"resolution_steps": 1,
"upscale_method": "lanczos"
},
"_meta": {
"title": "Scale Image to Total Pixels"
}
},
"89:91:29": {
"class_type": "CLIPTextEncode",
"inputs": {
"clip": [
"89:91:53",
0
],
"text": "色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部,畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走"
},
"_meta": {
"title": "CLIP Text Encode (Negative Prompt)"
}
},
"89:91:33": {
"class_type": "VAEDecode",
"inputs": {
"samples": [
"89:91:42",
0
],
"vae": [
"89:91:54",
0
]
},
"_meta": {
"title": "VAE Decode"
}
},
"89:91:38": {
"class_type": "KSamplerAdvanced",
"inputs": {
"add_noise": "enable",
"cfg": 1,
"end_at_step": 2,
"latent_image": [
"89:91:40",
2
],
"model": [
"89:91:51",
0
],
"negative": [
"89:91:40",
1
],
"noise_seed": 757394574321365,
"positive": [
"89:91:40",
0
],
"return_with_leftover_noise": "enable",
"sampler_name": "euler",
"scheduler": "simple",
"start_at_step": 0,
"steps": 4
},
"_meta": {
"title": "KSampler (Advanced)"
}
},
"89:91:40": {
"class_type": "WanFirstLastFrameToVideo",
"inputs": {
"batch_size": 1,
"end_image": [
"89:87",
0
],
"height": [
"89:87",
2
],
"length": 81,
"negative": [
"89:91:29",
0
],
"positive": [
"89:91:41",
0
],
"start_image": [
"89:27",
0
],
"vae": [
"89:91:54",
0
],
"width": [
"89:87",
1
]
},
"_meta": {
"title": "WanFirstLastFrameToVideo"
}
},
"89:91:41": {
"class_type": "CLIPTextEncode",
"inputs": {
"clip": [
"89:91:53",
0
],
"text": [
"89:83",
0
]
},
"_meta": {
"title": "CLIP Text Encode (Positive Prompt)"
}
},
"89:91:42": {
"class_type": "KSamplerAdvanced",
"inputs": {
"add_noise": "disable",
"cfg": 1,
"end_at_step": 10000,
"latent_image": [
"89:91:38",
0
],
"model": [
"89:91:52",
0
],
"negative": [
"89:91:40",
1
],
"noise_seed": 0,
"positive": [
"89:91:40",
0
],
"return_with_leftover_noise": "disable",
"sampler_name": "euler",
"scheduler": "simple",
"start_at_step": 2,
"steps": 4
},
"_meta": {
"title": "KSampler (Advanced)"
}
},
"89:91:47": {
"class_type": "UNETLoader",
"inputs": {
"unet_name": "wan2.2_i2v_high_noise_14B_fp8_scaled.safetensors",
"weight_dtype": "default"
},
"_meta": {
"title": "Load Diffusion Model"
}
},
"89:91:48": {
"class_type": "UNETLoader",
"inputs": {
"unet_name": "wan2.2_i2v_low_noise_14B_fp8_scaled.safetensors",
"weight_dtype": "default"
},
"_meta": {
"title": "Load Diffusion Model"
}
},
"89:91:49": {
"class_type": "LoraLoaderModelOnly",
"inputs": {
"lora_name": "wan2.2_i2v_lightx2v_4steps_lora_v1_low_noise.safetensors",
"model": [
"89:91:48",
0
],
"strength_model": 1
},
"_meta": {
"title": "Load LoRA"
}
},
"89:91:50": {
"class_type": "LoraLoaderModelOnly",
"inputs": {
"lora_name": "wan2.2_i2v_lightx2v_4steps_lora_v1_high_noise.safetensors",
"model": [
"89:91:47",
0
],
"strength_model": 1
},
"_meta": {
"title": "Load LoRA"
}
},
"89:91:51": {
"class_type": "ModelSamplingSD3",
"inputs": {
"model": [
"89:91:50",
0
],
"shift": 5
},
"_meta": {
"title": "ModelSamplingSD3"
}
},
"89:91:52": {
"class_type": "ModelSamplingSD3",
"inputs": {
"model": [
"89:91:49",
0
],
"shift": 5
},
"_meta": {
"title": "ModelSamplingSD3"
}
},
"89:91:53": {
"class_type": "CLIPLoader",
"inputs": {
"clip_name": "umt5_xxl_fp8_e4m3fn_scaled.safetensors",
"device": "default",
"type": "wan"
},
"_meta": {
"title": "Load CLIP"
}
},
"89:91:54": {
"class_type": "VAELoader",
"inputs": {
"vae_name": "wan_2.1_vae.safetensors"
},
"_meta": {
"title": "Load VAE"
}
},
"89:91:56": {
"class_type": "CreateVideo",
"inputs": {
"bit_depth": "auto",
"codec": "none",
"color_space": "sRGB",
"fps": 24,
"images": [
"89:91:33",
0
]
},
"_meta": {
"title": "Create Video"
}
}
})
# run() also passes api_key to the partner nodes in this workflow.
job = client.run(workflow, api_key=api_key)Questions & answers
High-resolution images with clear focal points tend to produce the best results, as they provide more detail for the AI to work with.
Yes, you can adjust the frame rate in the SaveVideo node to control the speed of the loop.
This workflow focuses on visual output. However, you can add audio using video editing software after exporting the video.
Use the PreviewImage node to check transitions between frames and adjust parameters in the 3313ce14-acfd-4a9c-9f0c-a1b01147e934 node for smoother results.