YAML Metadata Warning:empty or missing yaml metadata in repo card
Check out the documentation for more information.
LTX-V2V Reference-Conditioned Video-to-Video
Video-to-video generation on top of LTX-2 (Lightricks' DiT-based video+audio diffusion model): given a source video (structure/motion signal) and a first-frame reference image (target identity/appearance), generate an output video that follows the source video's motion while adopting the reference image's look.
This repo is a trimmed-down, sharable fork containing:
packages/ltx-core,packages/ltx-pipelinesβ the LTX-2 inference stack, with a small local patch to the reference-conditioning position logic (see below) that is not in upstreamLightricks/LTX-2.packages/ltx-trainerβ LTX-2's LoRA/IC-LoRA training stack, with a matching patch on the training side, plus areference_imagedataset column so you can train on (video, reference_image, caption) triples.scripts/infer_v2v.pyβ new inference script for the video+ref-image β video workflow described above.configs/*.yaml,scripts/train_ic_lora.shβ training configs and launcher for the two custom IC-LoRA variants this workflow needs.data/β dataset format documentation and example JSONL rows.
Not included: pretrained IC-LoRA checkpoints. The ones we trained
internally were trained on licensed movie footage, so we're not redistributing
the weights β train your own with scripts/train_ic_lora.sh on your own data
(see data/README.md).
Quick start
./setup.sh # uv sync + download ~90GB of base-model weights
source .venv/bin/activate
# 1. Prepare your data (see data/README.md for the JSONL schema)
scripts/preprocess_dataset.sh data/dataset.jsonl "1920x1024x233"
# 2. Train an IC-LoRA (defaults to configs/v2v_reference_ic_lora.yaml)
scripts/train_ic_lora.sh --config configs/v2v_reference_ic_lora.yaml
# 3. Run inference with your trained checkpoint
python scripts/infer_v2v.py \
--input-video my_source_video.mp4 \
--ref-image my_reference.png \
--prompt "A woman in a red dress walks through a garden, camera tracking steadily." \
--structure-lora packages/ltx-trainer/outputs/v2v_reference_ic_lora/checkpoints/lora_weights_step_08000.safetensors \
--output out.mp4
setup.sh downloads gated/large weights from HuggingFace β if a download
401s, run huggingface-cli login (or set HF_TOKEN) first.
How it works
Input video β output video
The source video is never fed to the transformer as raw pixels through a
special "V2V" code path β there isn't one. It goes through the exact same
video_conditioning mechanism IC-LoRA uses for any reference signal
(ICLoraPipeline.__call__'s video_conditioning: list[tuple[path, strength]]
parameter, in packages/ltx-pipelines/src/ltx_pipelines/ic_lora.py): it's
VAE-encoded, patchified into tokens exactly like the noisy video latent, and
appended to the same self-attention token sequence the denoiser attends
over (VideoConditionByReferenceLatent,
packages/ltx-core/src/ltx_core/conditioning/types/reference_video_cond.py).
A LoRA (--structure-lora) is what actually teaches the model to use those
extra tokens meaningfully β the base model has no opinion on what an arbitrary
appended video means.
Important: reference_video/--input-video is not necessarily raw source
footage β it's whatever structure/motion signal your LoRA was trained to
condition on (pose render, edge map, or the raw video itself, if that's what
you trained with). See data/README.md β "About reference_video".
Two ways to position-encode the reference image
This is the part that's easy to get subtly wrong, so it's exposed as an
explicit --pos-mode flag in scripts/infer_v2v.py rather than being baked in
silently:
--pos-mode first-frame (default) β the reference image reuses the
denoised video's own frame-0 RoPE position. Implemented via ICLoraPipeline's
native images: list[(path, frame_idx, strength)] parameter β this is a
base-model capability, no LoRA required. Code path:
combined_image_conditionings() in
packages/ltx-pipelines/src/ltx_pipelines/utils/helpers.py β
VideoConditionByLatentIndex / VideoConditionByKeyframeIndex in
packages/ltx-core/src/ltx_core/conditioning/types/. Because the reference
image is placed at the exact same absolute time position as frame 0, RoPE's
relative-rotation attention naturally treats it as "this is what frame 0 looks
like" rather than as a separate signal.
--pos-mode reference β the reference image gets its own, disjoint
RoPE position range instead of sharing frame 0's. Implemented via
video_conditioning β VideoConditionByReferenceLatent
(packages/ltx-core/src/ltx_core/conditioning/types/reference_video_cond.py).
This is the path our custom IC-LoRAs (configs/ref_image_ic_lora.yaml,
configs/v2v_reference_ic_lora.yaml) are actually trained against, and is
generally the better-performing option once you have a matching LoRA β the
base model alone doesn't know what to do with a disjoint-position reference
block; it has to be taught.
Why "disjoint" instead of just also using t=0: giving the reference image the
same absolute position as the target's frame 0 means RoPE's relative rotation
between them is zero β the attention mechanism can't tell the reference
token and the target's own frame-0 token apart by position alone. The fix
(originally a local patch on top of upstream LTX-2, in
reference_video_cond.py):
# packages/ltx-core/src/ltx_core/conditioning/types/reference_video_cond.py
REF_POSITION_TICK_SECONDS = 1e-3
current_min_time = latent_state.positions[:, 0, ...].min()
ref_max_time = positions[:, 0, ...].max()
positions[:, 0, ...] = positions[:, 0, ...] - ref_max_time + current_min_time - REF_POSITION_TICK_SECONDS
This shifts the whole reference block to end 1ms before whatever's
already the earliest time position in the sequence β a strictly ordered,
non-overlapping range. The training side
(packages/ltx-trainer/src/ltx_trainer/training_strategies/flexible.py,
_apply_reference_condition) applies the identical tick-offset logic, so a
LoRA trained with this code sees the same position layout at train and
inference time. If you ever modify one side, modify the other β they must stay
in parity.
When two reference-type conditions are stacked (as in
configs/v2v_reference_ic_lora.yaml, which uses both reference_video and
reference_image), each additional block is slotted immediately before
whatever range is already occupied, so every block β target frames, structure
reference, appearance reference β ends up in its own strictly-ordered slice of
time, none of them aliasing.
Conditioning strength (separate from position mode)
Two independent knobs control how strongly a reference item affects the output, orthogonal to which position mode you use:
--ref-strength/--structure-strength(denoise strength,strengthinConditioningItem): controls how much noise the conditioning latent itself gets mixed with during denoising (GaussianNoiser.torch.lerp, inpackages/ltx-core/src/ltx_core/components/noisers.py).1.0= the reference stays exactly at its clean VAE-encoded value; lower values let the model deviate from it.--conditioning-attention-strength: scales how strongly conditioning tokens attend to/from the noisy tokens in self-attention, independent of the noise blending above.
Repository layout
setup.sh one-click env + weights setup
pyproject.toml, uv.lock workspace definition (uv)
packages/
ltx-core/ LTX-2 model + conditioning + RoPE (patched)
ltx-pipelines/ ICLoraPipeline and friends
ltx-trainer/ LoRA / IC-LoRA training stack (patched)
configs/
v2v_reference_ic_lora.yaml train on (video, reference_video, reference_image, caption)
ref_image_ic_lora.yaml simpler: train on (video, reference_image, caption) only
accelerate/ accelerate configs referenced by the trainer docs
scripts/
train_ic_lora.sh resolves __REPO_ROOT__ in a config, launches training
preprocess_dataset.sh wraps packages/ltx-trainer/scripts/process_dataset.py
infer_v2v.py video + reference image -> video, --pos-mode switch
data/
README.md dataset JSONL schema, resolution buckets, etc.
dataset.jsonl example rows: video + reference_video + reference_image + caption
dataset_image_only.jsonl example rows: video + reference_image + caption
weights/ populated by setup.sh (gitignored β large binaries)
Which config to train
- Have paired (source video, structure signal, reference image) data? Use
configs/v2v_reference_ic_lora.yamlβ trains one LoRA that uses both--input-videoand--ref-imageat inference. - Only have (video, reference image) pairs, no structure video? Use
configs/ref_image_ic_lora.yamlβ simpler, doesn't need--input-videoat all (pass an empty/no-op structure conditioning, or adaptscripts/infer_v2v.pyto drop--structure-lora/--input-videofor this case).
See data/README.md for the exact JSONL schema, column-to-encoder mapping, and
resolution-bucket rules.
Known limitations / things we couldn't fully verify
- We could not confirm from the LTX-2 repo alone whether the public
LTX-2.3-22b-IC-LoRA-Union-ControlLoRA expects raw source video or a preprocessed control map (depth/pose/edge) for itsvideo_conditioninginput β its model card (not vendored here) would be the source of truth if you want to use it instead of training your own structure-control LoRA. reference_video/--input-videopreprocessing (pose/edge/depth extraction) is out of scope for this repo β bring your own, or use the basic Canny-edgecompute_reference()inpackages/ltx-trainer/scripts/process_videos.pyas a starting point.