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.
查看工作流内部
源模板的只读预览。在 Cloud 中打开,即可查看节点并进行修改。
Loading the graph
Open full-size workflow preview- 输出
- 视频
- 作者
- Rob
从代码中调用该模型
以下请求使用与体验区相同的预设图和设置。每次提交都可能开始一次付费运行;如果提交结果未知,请勿自动重试。
What you need
- Runs on
- Comfy Cloud
- 密钥
- COMFY_API_KEY
- 你的文件
- 由代码根据链接上传
需要付费的 Cloud 方案和额度。
# 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)试试一个示例
关于此工作流
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.
常见问答
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.