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.
This commit is contained in:
CalamitousFelicitousness
2026-06-30 22:32:18 +01:00
parent 888cdd1eca
commit b02a480e08
3 changed files with 23 additions and 10 deletions
+2 -4
View File
@@ -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
+4 -1
View File
@@ -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,
+17 -5
View File
@@ -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