Video Face Swap
The best open source video face swap workflow. Upload a driving video and a picture of your characters face. This LTX 2.3 lora was trained by @Alissonerdx.
Inside the workflow
A read-only preview of the source template. Open it in Cloud to explore the nodes and make changes.
Loading the graph
Open full-size workflow preview- Output
- Video
- Author
- Rob
Call this model from your code
The request below uses the same prepared graph and settings as the Playground. Each submission can start a paid run; do not automatically retry a submission whose outcome is unknown.
What you need
- Runs on
- Comfy Cloud
- Key
- COMFY_API_KEY
- Your files
- Uploaded by the code from their urls
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({
"269": {
"class_type": "LoadImage",
"inputs": {
"image": "UPLOADED_face_image_FILENAME"
},
"_meta": {
"title": "Load Face Reference"
}
},
"341": {
"class_type": "SaveVideo",
"inputs": {
"filename_prefix": "video/LTX_2.3_ia2v",
"format": "auto",
"format.codec": "auto",
"video": [
"408",
0
]
},
"_meta": {
"title": "Final Video"
}
},
"345": {
"class_type": "VHS_LoadVideo",
"inputs": {
"custom_height": 0,
"custom_width": 0,
"force_rate": [
"358",
0
],
"format": "AnimateDiff",
"frame_load_cap": [
"627",
1
],
"select_every_nth": 1,
"skip_first_frames": 0,
"video": "UPLOADED_video_FILENAME"
},
"_meta": {
"title": "Load Video"
}
},
"358": {
"class_type": "PrimitiveFloat",
"inputs": {
"value": 24
},
"_meta": {
"title": "FPS"
}
},
"360": {
"class_type": "ReservedRegionFrameComposer",
"inputs": {
"chroma_b": 0,
"chroma_g": 255,
"chroma_r": 0,
"face_align_cross": "center",
"face_align_main": "center",
"face_distribution": "all_faces_every_frame",
"face_gap_px": 12,
"face_images": [
"269",
0
],
"face_padding_px": 12,
"face_scale_pct": 100,
"frames": [
"361",
0
],
"interval_frames": 12,
"overflow_mode": "loop",
"region_position": "left",
"region_size_px": 256,
"stack_direction": "auto"
},
"_meta": {
"title": "Reserved Region Frame Composer"
}
},
"361": {
"class_type": "ImageResizeKJv2",
"inputs": {
"crop_position": "center",
"device": "cpu",
"divisible_by": 32,
"height": [
"362",
0
],
"image": [
"345",
0
],
"keep_proportion": "resize",
"pad_color": "0, 0, 0",
"upscale_method": "lanczos",
"width": [
"362",
0
]
},
"_meta": {
"title": "Resize Image v2"
}
},
"362": {
"class_type": "PrimitiveInt",
"inputs": {
"value": 1280
},
"_meta": {
"title": "Base Resolution (Largest Size)"
}
},
"365": {
"class_type": "VHS_VideoCombine",
"inputs": {
"audio": [
"345",
2
],
"crf": 19,
"filename_prefix": "comparison",
"format": "video/h264-mp4",
"frame_rate": [
"358",
0
],
"images": [
"360",
0
],
"loop_count": 0,
"pingpong": False,
"pix_fmt": "yuv420p",
"save_metadata": True,
"save_output": True,
"trim_to_audio": False
},
"_meta": {
"title": "Control Video Preview"
}
},
"408": {
"class_type": "CreateVideo",
"inputs": {
"audio": [
"345",
2
],
"bit_depth": 8,
"codec": "none",
"color_space": "sRGB",
"fps": [
"358",
0
],
"images": [
"641:545",
0
]
},
"_meta": {
"title": "Create Video"
}
},
"427": {
"class_type": "PrimitiveFloat",
"inputs": {
"value": 5
},
"_meta": {
"title": "Duration (Seconds)"
}
},
"559": {
"class_type": "VHS_VideoCombine",
"inputs": {
"audio": [
"345",
2
],
"crf": 19,
"filename_prefix": "comparison",
"format": "video/h264-mp4",
"frame_rate": [
"358",
0
],
"images": [
"560",
0
],
"loop_count": 0,
"pingpong": False,
"pix_fmt": "yuv420p",
"save_metadata": True,
"save_output": True,
"trim_to_audio": False
},
"_meta": {
"title": "Video Combine Comparison"
}
},
"560": {
"class_type": "ImageConcanate",
"inputs": {
"direction": "right",
"image1": [
"360",
0
],
"image2": [
"641:545",
0
],
"match_image_size": True
},
"_meta": {
"title": "ImageConcanate"
}
},
"627": {
"class_type": "ComfyMathExpression",
"inputs": {
"expression": "((a * b) // 8) * 8 + 1",
"values.a": [
"427",
0
],
"values.b": [
"358",
0
]
},
"_meta": {
"title": "Total Frames"
}
},
"641:377": {
"class_type": "RandomNoise",
"inputs": {
"noise_seed": 48738923203162
},
"_meta": {
"title": "RandomNoise"
}
},
"641:382": {
"class_type": "SamplerCustomAdvanced",
"inputs": {
"guider": [
"641:396",
0
],
"latent_image": [
"641:416",
0
],
"noise": [
"641:377",
0
],
"sampler": [
"641:386",
0
],
"sigmas": [
"641:575",
0
]
},
"_meta": {
"title": "SamplerCustomAdvanced"
}
},
"641:383": {
"class_type": "LTXVCropGuides",
"inputs": {
"latent": [
"641:392",
0
],
"negative": [
"641:440",
1
],
"positive": [
"641:440",
0
]
},
"_meta": {
"title": "LTXVCropGuides"
}
},
"641:386": {
"class_type": "KSamplerSelect",
"inputs": {
"sampler_name": "euler_ancestral_cfg_pp"
},
"_meta": {
"title": "KSamplerSelect"
}
},
"641:390": {
"class_type": "LTXVConditioning",
"inputs": {
"frame_rate": [
"358",
0
],
"negative": [
"641:395",
0
],
"positive": [
"641:481",
0
]
},
"_meta": {
"title": "LTXVConditioning"
}
},
"641:392": {
"class_type": "LTXVSeparateAVLatent",
"inputs": {
"av_latent": [
"641:382",
0
]
},
"_meta": {
"title": "Separate AV Latent"
}
},
"641:395": {
"class_type": "CLIPTextEncode",
"inputs": {
"clip": [
"641:477",
0
],
"text": ""
},
"_meta": {
"title": "CLIP Text Encode (Negative Prompt)"
}
},
"641:396": {
"class_type": "CFGGuider",
"inputs": {
"cfg": 1,
"model": [
"641:573",
0
],
"negative": [
"641:440",
1
],
"positive": [
"641:440",
0
]
},
"_meta": {
"title": "CFG Guider"
}
},
"641:400": {
"class_type": "SolidMask",
"inputs": {
"height": [
"641:471",
1
],
"value": 0,
"width": [
"641:471",
0
]
},
"_meta": {
"title": "Create Solid Mask"
}
},
"641:414": {
"class_type": "TrimAudioDuration",
"inputs": {
"audio": [
"345",
2
],
"duration": [
"427",
0
],
"start_index": 0
},
"_meta": {
"title": "Trim Audio Duration"
}
},
"641:415": {
"class_type": "LTXVAudioVAEEncode",
"inputs": {
"audio": [
"641:414",
0
],
"audio_vae": [
"641:480",
0
]
},
"_meta": {
"title": "LTXV Audio VAE Encode"
}
},
"641:416": {
"class_type": "LTXVConcatAVLatent",
"inputs": {
"audio_latent": [
"641:417",
0
],
"video_latent": [
"641:440",
2
]
},
"_meta": {
"title": "Concat AV Latent"
}
},
"641:417": {
"class_type": "SetLatentNoiseMask",
"inputs": {
"mask": [
"641:400",
0
],
"samples": [
"641:415",
0
]
},
"_meta": {
"title": "Set Latent Noise Mask"
}
},
"641:418": {
"class_type": "LatentUpscaleModelLoader",
"inputs": {
"model_name": "ltx-2.3-spatial-upscaler-x2-1.1.safetensors"
},
"_meta": {
"title": "Load Latent Upscale Model"
}
},
"641:440": {
"class_type": "LTXVAddGuideMulti",
"inputs": {
"latent": [
"641:443",
0
],
"negative": [
"641:390",
1
],
"num_guides": "1",
"num_guides.frame_idx_1": 0,
"num_guides.image_1": [
"360",
0
],
"num_guides.strength_1": 1,
"positive": [
"641:390",
0
],
"vae": [
"641:479",
0
]
},
"_meta": {
"title": "LTXVAddGuideMulti"
}
},
"641:443": {
"class_type": "VAEEncode",
"inputs": {
"pixels": [
"360",
0
],
"vae": [
"641:479",
0
]
},
"_meta": {
"title": "VAE Encode"
}
},
"641:471": {
"class_type": "GetImageSize",
"inputs": {
"image": [
"360",
0
]
},
"_meta": {
"title": "Get Image Size"
}
},
"641:477": {
"class_type": "DualCLIPLoader",
"inputs": {
"clip_name1": "gemma_3_12B_it_fp8_scaled.safetensors",
"clip_name2": "ltx-2.3_text_projection_bf16.safetensors",
"device": "default",
"type": "ltxv"
},
"_meta": {
"title": "Load CLIP (Dual)"
}
},
"641:478": {
"class_type": "UNETLoader",
"inputs": {
"unet_name": "ltx-2.3-22b-distilled_transformer_only_fp8_input_scaled_v3.safetensors",
"weight_dtype": "default"
},
"_meta": {
"title": "Load Diffusion Model"
}
},
"641:479": {
"class_type": "VAELoaderKJ",
"inputs": {
"device": "main_device",
"vae_name": "LTX23_video_vae_bf16.safetensors",
"weight_dtype": "bf16"
},
"_meta": {
"title": "VAELoader KJ"
}
},
"641:480": {
"class_type": "VAELoaderKJ",
"inputs": {
"device": "main_device",
"vae_name": "LTX23_audio_vae_bf16.safetensors",
"weight_dtype": "bf16"
},
"_meta": {
"title": "VAELoader KJ"
}
},
"641:481": {
"class_type": "CLIPTextEncode",
"inputs": {
"clip": [
"641:477",
0
],
"text": [
"641:498",
0
]
},
"_meta": {
"title": "Encode Positive"
}
},
"641:498": {
"class_type": "PrimitiveStringMultiline",
"inputs": {
"value": "The young woman with long, wavy ginger hair, a densely freckled face, blue-green eyes and pale, natural skin is standing on a sunlit golf fairway, looking into the camera. Her copper waves fall from under a deep purple leather bucket hat onto the shoulders of a white Swiss-dot high-collared blouse, and she wears brown leather gloves. She lifts the golf driver off her shoulders, swings it once in a relaxed half-arc, rests it on one shoulder, laughs and speaks while the camera arcs around her and pushes in to a close-up. Bright midday sun, deep-blue sky, pine trees softly blurred behind her. Her freckles and features stay sharp and consistent in every frame, with realistic lip movement. Photorealistic, natural skin texture."
},
"_meta": {
"title": "Positive Prompt"
}
},
"641:545": {
"class_type": "VAEDecode",
"inputs": {
"samples": [
"641:383",
2
],
"vae": [
"641:479",
0
]
},
"_meta": {
"title": "VAE Decode"
}
},
"641:573": {
"class_type": "LoraLoaderModelOnly",
"inputs": {
"lora_name": "ltx23-head_swap_v3_rank_adaptive_fro_098.safetensors",
"model": [
"641:478",
0
],
"strength_model": 1
},
"_meta": {
"title": "Load LoRA"
}
},
"641:575": {
"class_type": "BasicScheduler",
"inputs": {
"denoise": 1,
"model": [
"641:573",
0
],
"scheduler": "bong_tangent",
"steps": 8
},
"_meta": {
"title": "BasicScheduler"
}
}
})
workflow.set_input("269", "image", client.assets.from_url("https://comfy.org/catalog-media/97f668e5-901f-42b2-961d-47039cfd6a2c/fa2b20ed-2ba6-4783-b676-ddc358244949"))
workflow.set_input("345", "video", client.assets.from_url("https://comfy.org/catalog-media/e5e95c05-0dbc-49f8-b6a6-b03e20a06e4a/9e76d5e9-b473-4d3b-98ec-4ac4c4249071"))
# run() also passes api_key to the partner nodes in this workflow.
job = client.run(workflow, api_key=api_key)
for output in job.get_outputs("341"):
output.to_file(output.name)
for output in job.get_outputs("559"):
output.to_file(output.name)Try an example
About this workflow
This ComfyUI workflow performs a video face swap by applying a single reference face image to every frame of a driving video, then recombining the frames into a final clip. The pipeline starts with VHS_LoadVideo to decode the source video into frames and LoadImage to bring in your reference face. A custom face-swap node (dc72113f-8276-4a5a-af12-85d6bec89ed5) drives the actual face transfer using the LTX 2.3 LoRA trained by @Alissonerdx. PrimitiveFloat and PrimitiveInt nodes expose key controls (such as effect strength, region size, or frame step), while ImageResizeKJv2 aligns the reference face scale to your target footage.
To stabilize results and keep the background intact, the workflow composites the generated face back onto each original frame with ReservedRegionFrameComposer. This node lets you define a consistent “reserved” region for the face and blend it with feathered edges, reducing temporal jitter and preserving hair, lighting, and scene motion. ComfyMathExpression is used to derive dynamic values (for example, region size from video resolution), keeping settings in sync with whatever you load.
For quality checks, ImageConcanate can assemble side-by-side previews of original vs. swapped frames. Finally, the processed frames are encoded to a video using VHS_VideoCombine or CreateVideo, and the result is written out with SaveVideo.
Questions & answers
Install Video Helper Suite (for VHS_LoadVideo and VHS_VideoCombine) and place the LTX 2.3 LoRA by @Alissonerdx into ComfyUI/models/loras. The custom face-swap node (dc72113f-8276-4a5a-af12-85d6bec89ed5) applies the LoRA during inference, so you don’t need to manually add samplers in this template.
Keep the ReservedRegionFrameComposer region stable around the target face and increase feathering for smoother edges. Use a high-quality, front-facing reference image with similar lighting to the driving video. Avoid aggressive frame skipping; process every frame for best temporal consistency.
Yes, route frames through VHS_VideoCombine and enable or supply the original audio if the node configuration supports it. If your setup doesn’t mux audio in ComfyUI, export the video and add audio back in a video editor.
Adjust the region size/position in ReservedRegionFrameComposer and rescale the reference face with ImageResizeKJv2. Use PrimitiveFloat/PrimitiveInt controls (and ComfyMathExpression where provided) to keep the region proportional to the video resolution, then preview a short segment before exporting.