Files
automatic/modules/ltx/ltx_ui.py
T
CalamitousFelicitousness acac6157b0 refactor(ltx): unify tab across 0.9.x and 2.x pipeline families
Rework the LTX Video tab so one UI handles every registered variant
(0.9.0 through 2.3, Dev/Distilled/SDNQ-4Bit, T2V/I2V/Condition). Per-
variant behavior is driven from a single capability lookup rather than
substring matching on model names scattered across the backend.

- modules/ltx/ltx_capabilities.py: new module computing family, is_i2v,
  distilled, supports_input_media, supports_multi_condition,
  supports_image_cond_noise_scale, supports_decode_timestep,
  supports_stg, supports_audio, supports_frame_rate_kwarg, and the
  default CFG / steps / sampler_shift for a given model name by reading
  its registered repo_cls in models_def.

- modules/ltx/ltx_ui.py: capability-gated UI. Selecting a model rewires
  accordion visibility, slider interactivity, and defaults via a single
  model.change handler. New controls: dedicated image input slot inside
  the LTX tab (replaces the disconnected shared init_image for I2V),
  condition strength slider, CFG / sampler shift / dynamic shift
  sliders that were previously unreachable. Input media accordion
  restructured so the image slot is always-visible while the video /
  gallery prefix tabs only appear on Condition pipelines.

- modules/ltx/ltx_process.py: route the base pass through
  processing.process_images(p) so LTX inherits standard scheduler
  wiring, extra_networks activation, VAE handling, and error plumbing
  from StableDiffusionProcessingVideo. The multi-pass latent path
  (upsample / refine) stays on direct pipeline calls for latent
  re-entry. Refine noise control gets family-specific kwargs:
  denoise_strength for 0.9.x LTXConditionPipeline, noise_scale for all
  2.x pipelines; the prior strength= injection crashed on 2.x and only
  affected conditioning intensity on 0.9.x. Add torch_gc between every
  stage boundary (base to upsample to refine to vae decode) so the
  CUDA allocator cache does not retain the prior pass's allocations
  across stages. Remove the TypeError fallback that silently passed
  raw latents to save_video when VAE decode returned None on OOM;
  those errors now surface cleanly.

- modules/ltx/ltx_util.py: get_conditions grows a family parameter and
  builds LTX2VideoCondition (frames, index, strength) for 2.x or
  LTXVideoCondition (image, video, frame_index, strength) for 0.9.x.
  get_bucket floors to max(32, vae_spatial_compression_ratio) since LTX
  pipelines validate divisibility by 32 regardless of family.

- modules/video_models/video_overrides.py: extend the I2V generator
  reset to cover LTX2ImageToVideoPipeline and both Condition classes.
  Keep the strength= kwarg injection gated to 0.9.x
  LTXConditionPipeline only; LTX2ConditionPipeline.__call__ does not
  accept it (per-condition strength lives on the LTX2VideoCondition
  dataclass instead).
2026-04-17 00:37:15 +01:00

156 lines
8.4 KiB
Python

import os
import gradio as gr
from modules import ui_sections
from modules.logger import log
from modules.video_models.models_def import models
from modules.ltx import ltx_process, ltx_capabilities
debug = log.trace if os.environ.get('SD_VIDEO_DEBUG', None) is not None else lambda *args, **kwargs: None
def _model_change(model_name: str):
caps = ltx_capabilities.get_caps(model_name)
if caps is None:
return (
gr.update(visible=False), # input_media_accordion
gr.update(visible=False), # multi_condition_group
gr.update(visible=False), # upsample_accordion
gr.update(visible=False), # refine_accordion
gr.update(value=False), # upsample_enable (reset)
gr.update(value=False), # refine_enable (reset)
gr.update(interactive=True), # refine_strength
gr.update(), # guidance_scale
gr.update(), # steps
gr.update(), # sampler_shift
gr.update(interactive=False), # decode_timestep
gr.update(interactive=False), # image_cond_noise_scale
gr.update(visible=False), # audio_accordion
)
# distilled 2.x variants use a fixed canonical refine schedule; the strength slider is meaningless there
refine_strength_interactive = not (caps.family == '2.x' and caps.is_distilled)
return (
gr.update(visible=caps.supports_input_media),
gr.update(visible=caps.supports_multi_condition),
gr.update(visible=True),
gr.update(visible=True),
gr.update(value=False),
gr.update(value=False),
gr.update(interactive=refine_strength_interactive),
gr.update(value=caps.default_cfg),
gr.update(value=caps.default_steps),
gr.update(value=caps.default_sampler_shift),
gr.update(interactive=caps.supports_decode_timestep),
gr.update(interactive=caps.supports_image_cond_noise_scale),
gr.update(visible=caps.supports_audio),
)
def create_ui(prompt, negative, styles, overrides, init_image, _init_strength, last_image, mp4_fps, mp4_interpolate, mp4_codec, mp4_ext, mp4_opt, mp4_video, mp4_frames, mp4_sf, width, height, frames, seed):
with gr.Row():
with gr.Column(variant='compact', elem_id="ltx_settings", elem_classes=['settings-column'], scale=1):
with gr.Row():
generate = gr.Button('Generate', elem_id="ltx_generate_btn", variant='primary', visible=False)
with gr.Row():
ltx_models = [m.name for m in models['LTX Video']] if 'LTX Video' in models else ['None']
model = gr.Dropdown(label='LTX model', choices=ltx_models, value=ltx_models[0], elem_id="ltx_model")
input_media_accordion = gr.Accordion(open=False, label="Input media", elem_id='ltx_input_media_accordion', visible=False)
with input_media_accordion:
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')
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')
with gr.Tabs():
with gr.Tab('Video prefix', id='ltx_condition_video_tab'):
condition_video = gr.Video(label='Video', type='filepath', elem_id="ltx_condition_video", width=256, height=256, source='upload')
with gr.Row():
condition_video_frames = gr.Slider(label='LTX frames number', minimum=-1, maximum=1024, step=1, value=-1, elem_id="ltx_condition_video_frames")
condition_video_skip = gr.Slider(label='LTX frames skip', minimum=0, maximum=1024, step=1, value=0, elem_id="ltx_condition_video_sip")
with gr.Tab('Gallery prefix', id='ltx_condition_batch_tab'):
condition_files = gr.Files(label="Image Batch", interactive=True, elem_id="ltx_condition_batch")
upsample_accordion = gr.Accordion(open=False, label="Upsample", elem_id='ltx_upsample_accordion')
with upsample_accordion:
with gr.Row():
upsample_enable = gr.Checkbox(label='LTX enable upsampling', value=False, elem_id="ltx_upsample_enable")
upsample_ratio = gr.Slider(label='LTX upsample ratio', minimum=1.0, maximum=4.0, step=0.1, value=2.0, elem_id="ltx_upsample_ratio")
refine_accordion = gr.Accordion(open=False, label="Refine", elem_id='ltx_refine_accordion')
with refine_accordion:
with gr.Row():
refine_enable = gr.Checkbox(label='LTX enable refine', value=False, elem_id="ltx_refine_enable")
refine_strength = gr.Slider(label='LTX refine strength', minimum=0.1, maximum=1.0, step=0.05, value=0.4, elem_id="ltx_refine_strength")
parameters_accordion = gr.Accordion(open=False, label="Advanced", elem_id='ltx_parameters_accordion')
with parameters_accordion:
steps, sampler_index = ui_sections.create_sampler_and_steps_selection(None, "ltx", default_steps=40)
with gr.Row():
guidance_scale = gr.Slider(label='LTX guidance scale', minimum=0.0, maximum=14.0, step=0.1, value=4.0, elem_id="ltx_guidance_scale")
with gr.Row():
sampler_shift = gr.Slider(label='LTX sampler shift', minimum=-1.0, maximum=20.0, step=0.1, value=-1.0, elem_id="ltx_sampler_shift")
dynamic_shift = gr.Checkbox(label='LTX dynamic shift', value=False, elem_id="ltx_dynamic_shift")
with gr.Row():
decode_timestep = gr.Slider(label='LTX decode timestep', minimum=0.0, maximum=1.0, step=0.01, value=0.05, elem_id="ltx_decode_timestep")
image_cond_noise_scale = gr.Slider(label='LTX image cond noise scale', minimum=0.0, maximum=1.0, step=0.005, value=0.025, elem_id="ltx_image_cond_noise_scale")
audio_accordion = gr.Accordion(open=False, label="Audio", elem_id='ltx_audio_accordion', visible=False)
with audio_accordion:
with gr.Row():
audio_enable = gr.Checkbox(label='LTX enable audio', value=False, elem_id="ltx_audio_enable")
with gr.Column(elem_id='ltx-output-column', scale=2) as _column_output:
with gr.Row():
video = gr.Video(label="Output", show_label=False, elem_id='ltx_output_video', elem_classes=['control-image'], height=512, autoplay=False)
with gr.Row():
text = gr.HTML('', elem_id='ltx_generation_info', show_label=False)
model.change(
fn=_model_change,
inputs=[model],
outputs=[
input_media_accordion,
multi_condition_group,
upsample_accordion,
refine_accordion,
upsample_enable,
refine_enable,
refine_strength,
guidance_scale,
steps,
sampler_shift,
decode_timestep,
image_cond_noise_scale,
audio_accordion,
],
)
task_id = gr.Textbox(visible=False, value='')
ui_state = gr.Textbox(visible=False, value='')
state_inputs = [task_id, ui_state]
video_inputs = [
model,
prompt, negative, styles,
width, height, frames,
steps, sampler_index,
guidance_scale, sampler_shift, dynamic_shift,
seed,
upsample_enable, upsample_ratio,
refine_enable, refine_strength,
ltx_condition_strength, ltx_init_image, init_image, last_image, condition_files, condition_video, condition_video_frames, condition_video_skip,
decode_timestep, image_cond_noise_scale,
mp4_fps, mp4_interpolate, mp4_codec, mp4_ext, mp4_opt, mp4_video, mp4_frames, mp4_sf,
audio_enable,
overrides,
]
video_outputs = [
video,
text,
]
video_dict = dict(
fn=ltx_process.run_ltx,
_js="submit_ltx",
inputs=state_inputs + video_inputs,
outputs=video_outputs,
show_progress='hidden',
)
generate.click(**video_dict)