From b02a480e08f51acca92f3058eb0c5886a4f69da0 Mon Sep 17 00:00:00 2001 From: CalamitousFelicitousness Date: Tue, 30 Jun 2026 22:32:18 +0100 Subject: [PATCH] feat(ltx): wire last-frame conditioning into LTX tab The LTX tab already collected a last-frame image but anchored every condition at index 0, so it never acted as a last frame. Build a separate condition for it at the final frame: index -1 for the 2.x family (latent index, negatives wrap) and num_frames-1 for 0.9 (pixel index). The Last image input now shows only for Condition models, the pipelines that accept multi-frame conditioning. --- modules/ltx/ltx_process.py | 6 ++---- modules/ltx/ltx_ui.py | 5 ++++- modules/ltx/ltx_util.py | 22 +++++++++++++++++----- 3 files changed, 23 insertions(+), 10 deletions(-) diff --git a/modules/ltx/ltx_process.py b/modules/ltx/ltx_process.py index 0acd6d3e0..4251e6b36 100644 --- a/modules/ltx/ltx_process.py +++ b/modules/ltx/ltx_process.py @@ -234,8 +234,6 @@ def run_ltx(task_id, condition_images = [] if ltx_init_image is not None: condition_images.append(ltx_init_image) - if condition_last is not None: - condition_images.append(condition_last) conditions = [] conditions_stage2 = [] if caps.supports_multi_condition: @@ -246,14 +244,14 @@ def run_ltx(task_id, base_w, base_h, condition_strength, condition_images, condition_files, condition_video, condition_video_frames, condition_video_skip, - family=caps.family, + family=caps.family, num_frames=get_frames(frames), condition_last=condition_last, ) if (final_w, final_h) != (base_w, base_h): conditions_stage2 = get_conditions( final_w, final_h, condition_strength, condition_images, condition_files, condition_video, condition_video_frames, condition_video_skip, - family=caps.family, + family=caps.family, num_frames=get_frames(frames), condition_last=condition_last, ) else: conditions_stage2 = conditions diff --git a/modules/ltx/ltx_ui.py b/modules/ltx/ltx_ui.py index e5b2f1582..4990a5796 100644 --- a/modules/ltx/ltx_ui.py +++ b/modules/ltx/ltx_ui.py @@ -16,6 +16,7 @@ def _model_change(model_name: str): return ( gr.update(visible=False), # input_media_accordion gr.update(visible=False), # multi_condition_group + gr.update(visible=False), # last_image gr.update(visible=False), # upsample_accordion gr.update(visible=False), # refine_accordion gr.update(value=False), # upsample_enable (reset) @@ -38,6 +39,7 @@ def _model_change(model_name: str): return ( gr.update(visible=caps.supports_input_media), gr.update(visible=caps.supports_multi_condition), + gr.update(visible=caps.supports_multi_condition), # last_image gr.update(visible=True), gr.update(visible=True), gr.update(value=False), @@ -73,7 +75,7 @@ def create_ui(prompt, negative, styles, overrides, mp4_fps, mp4_interpolate, mp4 ltx_init_image = gr.Image(label='Image', elem_id='ltx_init_image', type='pil', image_mode='RGB', width=256, height=256) ltx_condition_strength = gr.Slider(label='LTX input strength', minimum=0.0, maximum=1.0, step=0.05, value=1.0, elem_id='ltx_condition_strength') with gr.Row(): - last_image = gr.Image(label='Last image', elem_id='ltx_last_image', type='pil', image_mode='RGB', width=256, height=256) + last_image = gr.Image(label='Last image', elem_id='ltx_last_image', type='pil', image_mode='RGB', width=256, height=256, visible=False) multi_condition_group = gr.Group(visible=False) with multi_condition_group: gr.Markdown('**Prefix conditioning**: supply a video or gallery to anchor the opening frames', elem_id='ltx_prefix_conditioning_label') @@ -123,6 +125,7 @@ def create_ui(prompt, negative, styles, overrides, mp4_fps, mp4_interpolate, mp4 outputs=[ input_media_accordion, multi_condition_group, + last_image, upsample_accordion, refine_accordion, upsample_enable, diff --git a/modules/ltx/ltx_util.py b/modules/ltx/ltx_util.py index f1ef78391..8aa0b12ec 100644 --- a/modules/ltx/ltx_util.py +++ b/modules/ltx/ltx_util.py @@ -138,15 +138,15 @@ def _condition_cls(family: str): return LTXVideoCondition -def make_condition(condition_cls, family: str, frames, strength: float, is_video: bool): +def make_condition(condition_cls, family: str, frames, strength: float, is_video: bool, index: int = 0): if family == '2.x': - return condition_cls(frames=frames, index=0, strength=strength) + return condition_cls(frames=frames, index=index, strength=strength) if is_video: - return condition_cls(video=frames, frame_index=0, strength=strength) - return condition_cls(image=frames, frame_index=0, strength=strength) + return condition_cls(video=frames, frame_index=index, strength=strength) + return condition_cls(image=frames, frame_index=index, strength=strength) -def get_conditions(width, height, condition_strength, condition_images, condition_files, condition_video, condition_video_frames, condition_video_skip, family: str = '0.9'): +def get_conditions(width, height, condition_strength, condition_images, condition_files, condition_video, condition_video_frames, condition_video_skip, family: str = '0.9', num_frames=None, condition_last=None): condition_cls = _condition_cls(family) if condition_cls is None: return [] @@ -186,6 +186,18 @@ def get_conditions(width, height, condition_strength, condition_images, conditio log.debug(f'Video condition: family={family} frames={len(condition_frames)} size={condition_frames[0].size} strength={condition_strength}') except Exception as e: log.error(f'LTX condition video: {e}') + if condition_last is not None: + try: + if isinstance(condition_last, str): + from modules.api.api import decode_base64_to_image + condition_last = decode_base64_to_image(condition_last) + condition_last = condition_last.convert('RGB').resize((width, height), resample=Image.Resampling.LANCZOS) + # 2.x reads index as a latent index and accepts -1 for the final frame; 0.9 uses a pixel index. + last_index = -1 if family == '2.x' else max((num_frames or 1) - 1, 0) + conditions.append(make_condition(condition_cls, family, condition_last, condition_strength, is_video=False, index=last_index)) + log.debug(f'Video condition: family={family} last={condition_last.size} index={last_index} strength={condition_strength}') + except Exception as e: + log.error(f'LTX condition last image: {e}') return conditions