Seamless Video Transitions - LTX 2.3 FLW LoRA
Upload two input videos and generate a seamless transition between the two shots.
About
This ComfyUI workflow creates a seamless, learned transition between two input clips using an LTX 2.3 FLW LoRA. You feed one video into each LoadVideo node, and a dedicated transition node (UUID: 63482e7d-23c9-4396-a7a2-cd4ca2316eac) synthesizes a smooth handoff from the end of the first shot to the start of the second. The SaveVideo node then assembles the processed frames into a single output clip. A built-in MarkdownNote provides an at-a-glance guide to recommended settings and practical tips.
Technically, the workflow performs diffusion-based video-to-video generation guided by the LTX 2.3 FLW LoRA to harmonize color, texture, and motion across the cut. For temporal consistency, inference runs over the full frame range (not just the transition window). That design keeps the look stable across the entire sequence but also means longer clips and higher resolutions will increase runtime and VRAM use. You’ll typically set the in/out ranges for each source clip, define a transition length, and adjust the LoRA influence/strength and seed in the transition node.
SaveVideo controls the output frame rate and file name for export.
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
- Rob
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({
"116": {
"class_type": "SaveVideo",
"inputs": {
"codec": "auto",
"filename_prefix": "video/ComfyUI",
"format": "auto",
"format.codec": "auto",
"video": [
"108:122",
0
]
},
"_meta": {
"title": "Save Video"
}
},
"117": {
"class_type": "LoadVideo",
"inputs": {
"file": "7c1b43d5af480f29a8b75424ae10700e7216a1649759ed03aab61be8ee1a845e.mp4"
},
"_meta": {
"title": "Load Shot 1"
}
},
"118": {
"class_type": "LoadVideo",
"inputs": {
"file": "ab49a07d4e1faaa0f1f1cc60e3ad1edef70bdd41da40d1c0a27b0620a2e52493.mp4"
},
"_meta": {
"title": "Load Shot 2"
}
},
"108:1": {
"class_type": "DiffusionModelLoaderKJ",
"inputs": {
"compute_dtype": "bf16",
"enable_fp16_accumulation": False,
"model_name": "ltx-2.3-22b-dev_transformer_only_bf16.safetensors",
"patch_cublaslinear": False,
"sage_attention": "disabled",
"weight_dtype": "default"
},
"_meta": {
"title": "Diffusion Model Loader KJ"
}
},
"108:10": {
"class_type": "LTXVConditioning",
"inputs": {
"frame_rate": 24,
"negative": [
"108:9",
0
],
"positive": [
"108:8",
0
]
},
"_meta": {
"title": "LTXVConditioning"
}
},
"108:107": {
"class_type": "SimpleCalculatorKJ",
"inputs": {
"expression": "round((a + b + c - 1) / 8) * 8 + 1",
"variables.a": [
"108:109",
2
],
"variables.b": [
"108:111",
2
],
"variables.c": [
"108:125",
0
]
},
"_meta": {
"title": "TOTAL FRAMES"
}
},
"108:109": {
"class_type": "GetImageSize",
"inputs": {
"image": [
"108:120",
0
]
},
"_meta": {
"title": "Get Image Size"
}
},
"108:111": {
"class_type": "GetImageSize",
"inputs": {
"image": [
"108:121",
0
]
},
"_meta": {
"title": "Get Image Size"
}
},
"108:120": {
"class_type": "GetVideoComponents",
"inputs": {
"video": [
"117",
0
]
},
"_meta": {
"title": "Get Video Components"
}
},
"108:121": {
"class_type": "GetVideoComponents",
"inputs": {
"video": [
"118",
0
]
},
"_meta": {
"title": "Get Video Components"
}
},
"108:122": {
"class_type": "CreateVideo",
"inputs": {
"audio": [
"108:25",
0
],
"bit_depth": "auto",
"codec": "none",
"color_space": "sRGB",
"fps": 24,
"images": [
"108:24",
0
]
},
"_meta": {
"title": "Create Video"
}
},
"108:125": {
"class_type": "PrimitiveInt",
"inputs": {
"value": 48
},
"_meta": {
"title": "transition frames"
}
},
"108:13": {
"class_type": "LTXVAddGuide",
"inputs": {
"frame_idx": 0,
"image": [
"108:74",
0
],
"latent": [
"108:14",
0
],
"negative": [
"108:10",
1
],
"positive": [
"108:10",
0
],
"strength": 1,
"vae": [
"108:5",
0
]
},
"_meta": {
"title": "LTXVAddGuide"
}
},
"108:14": {
"class_type": "EmptyLTXVLatentVideo",
"inputs": {
"batch_size": 1,
"height": [
"108:74",
2
],
"length": [
"108:74",
3
],
"width": [
"108:74",
1
]
},
"_meta": {
"title": "Empty LTXV Latent Video"
}
},
"108:15": {
"class_type": "LTXVConcatAVLatent",
"inputs": {
"audio_latent": [
"108:29",
0
],
"video_latent": [
"108:13",
2
]
},
"_meta": {
"title": "Concat AV Latent"
}
},
"108:17": {
"class_type": "SamplerCustomAdvanced",
"inputs": {
"guider": [
"108:19",
0
],
"latent_image": [
"108:15",
0
],
"noise": [
"108:18",
0
],
"sampler": [
"108:20",
0
],
"sigmas": [
"108:21",
0
]
},
"_meta": {
"title": "SamplerCustomAdvanced"
}
},
"108:18": {
"class_type": "RandomNoise",
"inputs": {
"noise_seed": 240378926876440
},
"_meta": {
"title": "RandomNoise"
}
},
"108:19": {
"class_type": "CFGGuider",
"inputs": {
"cfg": 1,
"model": [
"108:4",
0
],
"negative": [
"108:13",
1
],
"positive": [
"108:13",
0
]
},
"_meta": {
"title": "CFG Guider"
}
},
"108:20": {
"class_type": "KSamplerSelect",
"inputs": {
"sampler_name": "euler_ancestral_cfg_pp"
},
"_meta": {
"title": "KSamplerSelect"
}
},
"108:21": {
"class_type": "BasicScheduler",
"inputs": {
"denoise": 1,
"model": [
"108:4",
0
],
"scheduler": "linear_quadratic",
"steps": 8
},
"_meta": {
"title": "BasicScheduler"
}
},
"108:23": {
"class_type": "LTXVSeparateAVLatent",
"inputs": {
"av_latent": [
"108:17",
0
]
},
"_meta": {
"title": "Separate AV Latent"
}
},
"108:24": {
"class_type": "VAEDecode",
"inputs": {
"samples": [
"108:31",
2
],
"vae": [
"108:5",
0
]
},
"_meta": {
"title": "VAE Decode"
}
},
"108:25": {
"class_type": "LTXVAudioVAEDecode",
"inputs": {
"audio_vae": [
"108:6",
0
],
"samples": [
"108:23",
1
]
},
"_meta": {
"title": "LTXV Audio VAE Decode"
}
},
"108:29": {
"class_type": "SetLatentNoiseMask",
"inputs": {
"mask": [
"108:30",
0
],
"samples": [
"108:98",
0
]
},
"_meta": {
"title": "Set Latent Noise Mask"
}
},
"108:30": {
"class_type": "SolidMask",
"inputs": {
"height": [
"108:74",
2
],
"value": 0,
"width": [
"108:74",
1
]
},
"_meta": {
"title": "Create Solid Mask"
}
},
"108:31": {
"class_type": "LTXVCropGuides",
"inputs": {
"latent": [
"108:23",
0
],
"negative": [
"108:13",
0
],
"positive": [
"108:13",
0
]
},
"_meta": {
"title": "LTXVCropGuides"
}
},
"108:33": {
"class_type": "DualCLIPLoader",
"inputs": {
"clip_name1": "gemma_3_12B_it.safetensors",
"clip_name2": "ltx-2.3_text_projection_bf16.safetensors",
"device": "default",
"type": "ltxv"
},
"_meta": {
"title": "Load CLIP (Dual)"
}
},
"108:4": {
"class_type": "LoraLoaderModelOnly",
"inputs": {
"lora_name": "ltx-2.3-22b-distilled-1.1_lora-dynamic_fro09_avg_rank_111_bf16.safetensors",
"model": [
"108:49",
0
],
"strength_model": 0.5
},
"_meta": {
"title": "Load LoRA"
}
},
"108:49": {
"class_type": "LoraLoaderModelOnly",
"inputs": {
"lora_name": "systms__SYSTMS-FLW-IC-LORA-LTX-23__SYSTMS_FLW_V1_LTX23.safetensors",
"model": [
"108:1",
0
],
"strength_model": 1
},
"_meta": {
"title": "Load LoRA"
}
},
"108:5": {
"class_type": "VAELoaderKJ",
"inputs": {
"device": "main_device",
"vae_name": "LTX23_video_vae_bf16.safetensors",
"weight_dtype": "bf16"
},
"_meta": {
"title": "VAELoader KJ"
}
},
"108:6": {
"class_type": "VAELoaderKJ",
"inputs": {
"device": "main_device",
"vae_name": "LTX23_audio_vae_bf16.safetensors",
"weight_dtype": "bf16"
},
"_meta": {
"title": "VAELoader KJ"
}
},
"108:61": {
"class_type": "GeminiNode",
"inputs": {
"model": "gemini-3-1-flash-lite",
"prompt": "Your task is to write an LTX 2.3 LoRA video prompt that describes the attached video, inferring a seamless morphing transition where the gray area is, using the following formula:\n\n[Action A] seamlessly transforms into [Action B]. \n[Subject A description] becomes [Subject B description]. \n[Setting A] shifts to [Setting B]. \n[Camera behavior across the transition]. \n[Lighting/mood continuity].\n\nHere is an example of what that might look like:\n\nA motocross rider performing a mid-air backflip seamlessly transforms into a man standing and holding a basketball. The upside-down rider on a dirt bike becomes a young man in a black t-shirt and white shorts. A mountainous landscape over a lake shifts to an outdoor urban basketball court with a city skyline. The camera holds steady through the morph, keeping the central figure framed. Soft evening daylight throughout, continuous and even.\n\nRETURN THE PROMPT ONLY",
"seed": 42,
"system_prompt": "",
"video": [
"108:63",
0
]
},
"_meta": {
"title": "Google Gemini"
}
},
"108:63": {
"class_type": "CreateVideo",
"inputs": {
"bit_depth": "auto",
"codec": "none",
"color_space": "sRGB",
"fps": 24,
"images": [
"108:74",
0
]
},
"_meta": {
"title": "Create Video"
}
},
"108:71": {
"class_type": "WanVideoVACEStartToEndFrame",
"inputs": {
"empty_frame_level": 0.5,
"end_image": [
"108:82",
0
],
"end_index": -1,
"num_frames": [
"108:107",
1
],
"start_image": [
"108:81",
0
],
"start_index": 0
},
"_meta": {
"title": "WanVideo VACE Start To End Frame"
}
},
"108:74": {
"class_type": "GetImageSizeAndCount",
"inputs": {
"image": [
"108:71",
0
]
},
"_meta": {
"title": "Get Image Size & Count"
}
},
"108:8": {
"class_type": "CLIPTextEncode",
"inputs": {
"clip": [
"108:33",
0
],
"text": [
"108:80",
0
]
},
"_meta": {
"title": "CLIP Text Encode (Prompt)"
}
},
"108:80": {
"class_type": "JoinStrings",
"inputs": {
"delimiter": " ",
"string1": [
"108:86",
0
],
"string2": [
"108:61",
0
]
},
"_meta": {
"title": "Join Strings"
}
},
"108:81": {
"class_type": "ImageResizeKJv2",
"inputs": {
"crop_position": "center",
"device": "cpu",
"divisible_by": 32,
"height": 0,
"image": [
"108:120",
0
],
"keep_proportion": "resize",
"pad_color": "0, 0, 0",
"upscale_method": "lanczos",
"width": 1280
},
"_meta": {
"title": "Resize Image v2"
}
},
"108:82": {
"class_type": "ImageResizeKJv2",
"inputs": {
"crop_position": "center",
"device": "cpu",
"divisible_by": 32,
"height": [
"108:81",
2
],
"image": [
"108:121",
0
],
"keep_proportion": "crop",
"pad_color": "0, 0, 0",
"upscale_method": "lanczos",
"width": [
"108:81",
1
]
},
"_meta": {
"title": "Resize Image v2"
}
},
"108:86": {
"class_type": "PrimitiveString",
"inputs": {
"value": "FLW,"
},
"_meta": {
"title": "Text"
}
},
"108:9": {
"class_type": "CLIPTextEncode",
"inputs": {
"clip": [
"108:33",
0
],
"text": ""
},
"_meta": {
"title": "CLIP Text Encode (Prompt)"
}
},
"108:96": {
"class_type": "LoadAudio",
"inputs": {
"audio": "ddc03eca36d627c209d416a515328420b2b52411ac4108498843d0316a22bb8c.mp3"
},
"_meta": {
"title": "Load Audio"
}
},
"108:98": {
"class_type": "LTXVAudioVAEEncode",
"inputs": {
"audio": [
"108:96",
0
],
"audio_vae": [
"108:6",
0
]
},
"_meta": {
"title": "LTXV Audio VAE Encode"
}
}
})
# run() also passes api_key to the partner nodes in this workflow.
job = client.run(workflow, api_key=api_key)Questions & answers
To maintain temporal consistency. Running inference across the full frame range helps keep the look stable and reduces popping before and after the handoff. If you need faster runs, trim your inputs to just the necessary ranges, reduce resolution/FPS, or shorten the clips.
Try increasing the transition duration, lowering the LoRA influence/strength, and locking the seed for repeatable results. Place the cut around frames with similar composition (camera angle, subject size), and ensure both inputs share the same resolution and FPS.
Requirements scale with resolution, clip length, and transition duration. Start with short clips at a modest resolution to gauge performance. Longer sequences or HD+ resolutions benefit from GPUs with more VRAM; if you hit limits, downscale or shorten the frame ranges.
The SaveVideo node outputs a silent video. If you need audio, export the video and reattach audio from one of the source clips (or a music track) in your video editor.