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
コードからこのモデルを呼び出す
以下のリクエストでは、Playground と同じ準備済みグラフと設定を使用します。送信ごとに有料実行が開始される場合があります。結果が不明な送信を自動的に再試行しないでください。
What you need
- Runs on
- Comfy Cloud
- キー
- COMFY_API_KEY
- ファイル
- コードによって URL からアップロードされます
有料の 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.