CoolFace
Apppublic

Note1/LTX-2.3-Lora

sourceHugging Faceupdated 6mo agoView on Hugging Face
0likes
app.py867 linesDownload Raw Back to root
1import os2import subprocess3import sys4 5# Disable torch.compile / dynamo before any torch import6os.environ["TORCH_COMPILE_DISABLE"] = "1"7os.environ["TORCHDYNAMO_DISABLE"] = "1"8 9# Install xformers for memory-efficient attention10subprocess.run([sys.executable, "-m", "pip", "install", "xformers==0.0.32.post2", "--no-build-isolation"], check=False)11 12# Clone LTX-2 repo and install packages13LTX_REPO_URL = "https://github.com/Lightricks/LTX-2.git"14LTX_REPO_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "LTX-2")15 16LTX_COMMIT = "ae855f8538843825f9015a419cf4ba5edaf5eec2"  # known working commit with decode_video17 18if not os.path.exists(LTX_REPO_DIR):19    print(f"Cloning {LTX_REPO_URL}...")20    subprocess.run(["git", "clone", LTX_REPO_URL, LTX_REPO_DIR], check=True)21    subprocess.run(["git", "checkout", LTX_COMMIT], cwd=LTX_REPO_DIR, check=True)22 23print("Installing ltx-core and ltx-pipelines from cloned repo...")24subprocess.run(25    [sys.executable, "-m", "pip", "install", "--force-reinstall", "--no-deps", "-e",26     os.path.join(LTX_REPO_DIR, "packages", "ltx-core"),27     "-e", os.path.join(LTX_REPO_DIR, "packages", "ltx-pipelines")],28    check=True,29)30 31sys.path.insert(0, os.path.join(LTX_REPO_DIR, "packages", "ltx-pipelines", "src"))32sys.path.insert(0, os.path.join(LTX_REPO_DIR, "packages", "ltx-core", "src"))33 34import logging35import random36import tempfile37from pathlib import Path38import gc39import hashlib40 41import torch42torch._dynamo.config.suppress_errors = True43torch._dynamo.config.disable = True44 45import spaces46import gradio as gr47import numpy as np48from huggingface_hub import hf_hub_download, snapshot_download49 50from ltx_core.components.diffusion_steps import EulerDiffusionStep51from ltx_core.components.noisers import GaussianNoiser52from ltx_core.model.audio_vae import encode_audio as vae_encode_audio53from ltx_core.model.upsampler import upsample_video54from ltx_core.model.video_vae import TilingConfig, get_video_chunks_number, decode_video as vae_decode_video55from ltx_core.quantization import QuantizationPolicy56from ltx_core.types import Audio, AudioLatentShape, VideoPixelShape57from ltx_pipelines.distilled import DistilledPipeline58from ltx_pipelines.utils import euler_denoising_loop59from ltx_pipelines.utils.args import ImageConditioningInput60from ltx_pipelines.utils.constants import DISTILLED_SIGMA_VALUES, STAGE_2_DISTILLED_SIGMA_VALUES61from ltx_pipelines.utils.helpers import (62    cleanup_memory,63    combined_image_conditionings,64    denoise_video_only,65    encode_prompts,66    simple_denoising_func,67)68from ltx_pipelines.utils.media_io import decode_audio_from_file, encode_video69from ltx_core.loader.primitives import LoraPathStrengthAndSDOps70from ltx_core.loader.sd_ops import LTXV_LORA_COMFY_RENAMING_MAP71 72# Force-patch xformers attention into the LTX attention module.73from ltx_core.model.transformer import attention as _attn_mod74print(f"[ATTN] Before patch: memory_efficient_attention={_attn_mod.memory_efficient_attention}")75try:76    from xformers.ops import memory_efficient_attention as _mea77    _attn_mod.memory_efficient_attention = _mea78    print(f"[ATTN] After patch: memory_efficient_attention={_attn_mod.memory_efficient_attention}")79except Exception as e:80    print(f"[ATTN] xformers patch FAILED: {type(e).__name__}: {e}")81 82logging.getLogger().setLevel(logging.INFO)83 84MAX_SEED = np.iinfo(np.int32).max85DEFAULT_PROMPT = (86    "An astronaut hatches from a fragile egg on the surface of the Moon, "87    "the shell cracking and peeling apart in gentle low-gravity motion. "88    "Fine lunar dust lifts and drifts outward with each movement, floating "89    "in slow arcs before settling back onto the ground."90)91DEFAULT_FRAME_RATE = 24.092 93# Resolution presets: (width, height)94RESOLUTIONS = {95    "high": {"16:9": (1536, 1024), "9:16": (1024, 1536), "1:1": (1024, 1024), "9:7": (1408, 1088), "7:9": (1088, 1408), "19:13": (1472, 1008), "13:19": (1008, 1472)},96    "low": {"16:9": (768, 512), "9:16": (512, 768), "1:1": (768, 768), "9:7": (704, 544), "7:9": (544, 704), "19:13": (736, 504), "13:19": (504, 736)},97}98 99 100class LTX23DistilledA2VPipeline(DistilledPipeline):101    """DistilledPipeline with optional audio conditioning."""102 103    def __call__(104        self,105        prompt: str,106        seed: int,107        height: int,108        width: int,109        num_frames: int,110        frame_rate: float,111        images: list[ImageConditioningInput],112        audio_path: str | None = None,113        tiling_config: TilingConfig | None = None,114        enhance_prompt: bool = False,115    ):116        # Standard path when no audio input is provided.117        print(prompt)118        if audio_path is None:119            return super().__call__(120                prompt=prompt,121                seed=seed,122                height=height,123                width=width,124                num_frames=num_frames,125                frame_rate=frame_rate,126                images=images,127                tiling_config=tiling_config,128                enhance_prompt=enhance_prompt,129            )130 131        generator = torch.Generator(device=self.device).manual_seed(seed)132        noiser = GaussianNoiser(generator=generator)133        stepper = EulerDiffusionStep()134        dtype = torch.bfloat16135 136        (ctx_p,) = encode_prompts(137            [prompt],138            self.model_ledger,139            enhance_first_prompt=enhance_prompt,140            enhance_prompt_image=images[0].path if len(images) > 0 else None,141        )142        video_context, audio_context = ctx_p.video_encoding, ctx_p.audio_encoding143 144        video_duration = num_frames / frame_rate145        decoded_audio = decode_audio_from_file(audio_path, self.device, 0.0, video_duration)146        if decoded_audio is None:147            raise ValueError(f"Could not extract audio stream from {audio_path}")148 149        encoded_audio_latent = vae_encode_audio(decoded_audio, self.model_ledger.audio_encoder())150        audio_shape = AudioLatentShape.from_duration(batch=1, duration=video_duration, channels=8, mel_bins=16)151        expected_frames = audio_shape.frames152        actual_frames = encoded_audio_latent.shape[2]153 154        if actual_frames > expected_frames:155            encoded_audio_latent = encoded_audio_latent[:, :, :expected_frames, :]156        elif actual_frames < expected_frames:157            pad = torch.zeros(158                encoded_audio_latent.shape[0],159                encoded_audio_latent.shape[1],160                expected_frames - actual_frames,161                encoded_audio_latent.shape[3],162                device=encoded_audio_latent.device,163                dtype=encoded_audio_latent.dtype,164            )165            encoded_audio_latent = torch.cat([encoded_audio_latent, pad], dim=2)166 167        video_encoder = self.model_ledger.video_encoder()168        transformer = self.model_ledger.transformer()169        stage_1_sigmas = torch.tensor(DISTILLED_SIGMA_VALUES, device=self.device)170 171        def denoising_loop(sigmas, video_state, audio_state, stepper):172            return euler_denoising_loop(173                sigmas=sigmas,174                video_state=video_state,175                audio_state=audio_state,176                stepper=stepper,177                denoise_fn=simple_denoising_func(178                    video_context=video_context,179                    audio_context=audio_context,180                    transformer=transformer,181                ),182            )183 184        stage_1_output_shape = VideoPixelShape(185            batch=1,186            frames=num_frames,187            width=width // 2,188            height=height // 2,189            fps=frame_rate,190        )191        stage_1_conditionings = combined_image_conditionings(192            images=images,193            height=stage_1_output_shape.height,194            width=stage_1_output_shape.width,195            video_encoder=video_encoder,196            dtype=dtype,197            device=self.device,198        )199        video_state = denoise_video_only(200            output_shape=stage_1_output_shape,201            conditionings=stage_1_conditionings,202            noiser=noiser,203            sigmas=stage_1_sigmas,204            stepper=stepper,205            denoising_loop_fn=denoising_loop,206            components=self.pipeline_components,207            dtype=dtype,208            device=self.device,209            initial_audio_latent=encoded_audio_latent,210        )211 212        torch.cuda.synchronize()213        cleanup_memory()214 215        upscaled_video_latent = upsample_video(216            latent=video_state.latent[:1],217            video_encoder=video_encoder,218            upsampler=self.model_ledger.spatial_upsampler(),219        )220        stage_2_sigmas = torch.tensor(STAGE_2_DISTILLED_SIGMA_VALUES, device=self.device)221        stage_2_output_shape = VideoPixelShape(batch=1, frames=num_frames, width=width, height=height, fps=frame_rate)222        stage_2_conditionings = combined_image_conditionings(223            images=images,224            height=stage_2_output_shape.height,225            width=stage_2_output_shape.width,226            video_encoder=video_encoder,227            dtype=dtype,228            device=self.device,229        )230        video_state = denoise_video_only(231            output_shape=stage_2_output_shape,232            conditionings=stage_2_conditionings,233            noiser=noiser,234            sigmas=stage_2_sigmas,235            stepper=stepper,236            denoising_loop_fn=denoising_loop,237            components=self.pipeline_components,238            dtype=dtype,239            device=self.device,240            noise_scale=stage_2_sigmas[0],241            initial_video_latent=upscaled_video_latent,242            initial_audio_latent=encoded_audio_latent,243        )244 245        torch.cuda.synchronize()246        del transformer247        del video_encoder248        cleanup_memory()249 250        decoded_video = vae_decode_video(251            video_state.latent,252            self.model_ledger.video_decoder(),253            tiling_config,254            generator,255        )256        original_audio = Audio(257            waveform=decoded_audio.waveform.squeeze(0),258            sampling_rate=decoded_audio.sampling_rate,259        )260        return decoded_video, original_audio261 262 263# Model repos264LTX_MODEL_REPO = "Lightricks/LTX-2.3"265GEMMA_REPO ="Lightricks/gemma-3-12b-it-qat-q4_0-unquantized"266 267 268# Download model checkpoints269print("=" * 80)270print("Downloading LTX-2.3 distilled model + Gemma...")271print("=" * 80)272 273# LoRA cache directory and currently-applied key274LORA_CACHE_DIR = Path("lora_cache")275LORA_CACHE_DIR.mkdir(exist_ok=True)276current_lora_key: str | None = None277 278PENDING_LORA_KEY: str | None = None279PENDING_LORA_STATE: dict[str, torch.Tensor] | None = None280PENDING_LORA_STATUS: str = "No LoRA state prepared yet."281 282weights_dir = Path("weights")283weights_dir.mkdir(exist_ok=True)284checkpoint_path = hf_hub_download(285    repo_id=LTX_MODEL_REPO,286    filename="ltx-2.3-22b-distilled.safetensors",287    local_dir=str(weights_dir),288    local_dir_use_symlinks=False,289)290spatial_upsampler_path = hf_hub_download(repo_id=LTX_MODEL_REPO, filename="ltx-2.3-spatial-upscaler-x2-1.0.safetensors")291gemma_root = snapshot_download(repo_id=GEMMA_REPO)292 293# ---- Insert block (LoRA downloads) between lines 268 and 269 ----294# LoRA repo + download the requested LoRA adapters295LORA_REPO = "rahul7star/Ltx-2-3-Lora-Collection"296 297print("=" * 80)298print("Downloading LoRA adapters from dagloop5/LoRA...")299print("=" * 80)300paste_lora_path = hf_hub_download(repo_id=LORA_REPO, filename="ltx23-kabapaste.safetensors")301ruri_lora_path = hf_hub_download(repo_id=LORA_REPO, filename="LTX23-ruri.safetensors")302transit_lora_path = hf_hub_download(repo_id="valiantcat/LTX-2.3-Transition-LORA", filename="ltx2.3-transition.safetensors")303pose_lora_path = hf_hub_download(repo_id=LORA_REPO, filename="LTX2_3_NSFW_furry_concat_v2.safetensors")304general_lora_path = hf_hub_download(repo_id=LORA_REPO, filename="LTX2.3_Reasoning_V1.safetensors")305motion_lora_path = hf_hub_download(repo_id=LORA_REPO, filename="motion_helper.safetensors")306dreamlay_lora_path = hf_hub_download(repo_id=LORA_REPO, filename="DR34ML4Y_LTXXX_PREVIEW_RC1.safetensors") # m15510n4ry, bl0wj0b, d0ubl3_bj, d0gg1e, c0wg1rl307mself_lora_path = hf_hub_download(repo_id=LORA_REPO, filename="Furry Hyper Masturbation - LTX-2 I2V v1.safetensors") # Hyperfap308dramatic_lora_path = hf_hub_download(repo_id=LORA_REPO, filename="LTX-2.3 - Orgasm.safetensors") # "[He | She] is having am orgasm." (am or an?)309fluid_lora_path = hf_hub_download(repo_id=LORA_REPO, filename="cr3ampi3_animation_i2v_ltx2_v1.0.safetensors") # cr3ampi3 animation., missionary animation, doggystyle bouncy animation, double penetration animation310liquid_lora_path = hf_hub_download(repo_id=LORA_REPO, filename="liquid_wet_dr1pp_ltx2_v1.0_scaled.safetensors") # wet dr1pp311demopose_lora_path = hf_hub_download(repo_id=LORA_REPO, filename="ltx23__demopose_d3m0p0s3.safetensors") # d3m0p0s3312 313print(f"Paste LoRA: {paste_lora_path }")314print(f"Ruri LoRA: {ruri_lora_path }")315print(f"Transit LoRA: {transit_lora_path }")316print(f"Pose LoRA: {pose_lora_path}")317print(f"General LoRA: {general_lora_path}")318print(f"Motion LoRA: {motion_lora_path}")319print(f"Dreamlay LoRA: {dreamlay_lora_path}")320print(f"Mself LoRA: {mself_lora_path}")321print(f"Dramatic LoRA: {dramatic_lora_path}")322print(f"Fluid LoRA: {fluid_lora_path}")323print(f"Liquid LoRA: {liquid_lora_path}")324print(f"Demopose LoRA: {demopose_lora_path}")325# ----------------------------------------------------------------326 327print(f"Checkpoint: {checkpoint_path}")328print(f"Spatial upsampler: {spatial_upsampler_path}")329print(f"Gemma root: {gemma_root}")330 331# Initialize pipeline WITH text encoder and optional audio support332# ---- Replace block (pipeline init) lines 275-281 ----333pipeline = LTX23DistilledA2VPipeline(334    distilled_checkpoint_path=checkpoint_path,335    spatial_upsampler_path=spatial_upsampler_path,336    gemma_root=gemma_root,337    loras=[],338    quantization=QuantizationPolicy.fp8_cast(),  # keep FP8 quantization unchanged339)340# ----------------------------------------------------------------341 342def _make_lora_key(paste_strength: float,ruri_strength: float,transit_strength: float,pose_strength: float, general_strength: float, motion_strength: float, dreamlay_strength: float, mself_strength: float, dramatic_strength: float, fluid_strength: float, liquid_strength: float, demopose_strength: float) -> tuple[str, str]:343    ps = round(float(paste_strength), 2)344    rr = round(float(ruri_strength), 2)345    rt = round(float(transit_strength), 2)346    rp = round(float(pose_strength), 2)347    rg = round(float(general_strength), 2)348    rm = round(float(motion_strength), 2)349    rd = round(float(dreamlay_strength), 2)350    rs = round(float(mself_strength), 2)351    rr = round(float(dramatic_strength), 2)352    rf = round(float(fluid_strength), 2)353    rl = round(float(liquid_strength), 2)354    ro = round(float(demopose_strength), 2)355    key_str = f"{paste_lora_path}:{ps}|{ruri_lora_path}:{rr}|{transit_lora_path}:{rt}|{pose_lora_path}:{rp}|{general_lora_path}:{rg}|{motion_lora_path}:{rm}|{dreamlay_lora_path}:{rd}|{mself_lora_path}:{rs}|{dramatic_lora_path}:{rr}|{fluid_lora_path}:{rf}|{liquid_lora_path}:{rl}|{demopose_lora_path}:{ro}"356    key = hashlib.sha256(key_str.encode("utf-8")).hexdigest()357    return key, key_str358 359 360def prepare_lora_cache(361    paste_strength: float,362    ruri_strength: float,363    transit_strength: float,364    pose_strength: float,365    general_strength: float,366    motion_strength: float,367    dreamlay_strength: float,368    mself_strength: float,369    dramatic_strength: float,370    fluid_strength: float,371    liquid_strength: float,372    demopose_strength: float,373    progress=gr.Progress(track_tqdm=True),374):375    """376    CPU-only step:377    - checks cache378    - loads cached fused transformer state_dict, or379    - builds fused transformer on CPU and saves it380    The resulting state_dict is stored in memory and can be applied later.381    """382    global PENDING_LORA_KEY, PENDING_LORA_STATE, PENDING_LORA_STATUS383 384    ledger = pipeline.model_ledger385    key, _ = _make_lora_key(paste_strength,ruri_strength,transit_strength,pose_strength, general_strength, motion_strength, dreamlay_strength, mself_strength, dramatic_strength, fluid_strength, liquid_strength, demopose_strength)386    cache_path = LORA_CACHE_DIR / f"{key}.pt"387 388    progress(0.05, desc="Preparing LoRA state")389    if cache_path.exists():390        try:391            progress(0.20, desc="Loading cached fused state")392            state = torch.load(cache_path, map_location="cpu")393            PENDING_LORA_KEY = key394            PENDING_LORA_STATE = state395            PENDING_LORA_STATUS = f"Loaded cached LoRA state: {cache_path.name}"396            return PENDING_LORA_STATUS397        except Exception as e:398            print(f"[LoRA] Cache load failed: {type(e).__name__}: {e}")399 400    entries = [401        (paste_lora_path, round(float(paste_strength), 2)),402        (ruri_lora_path, round(float(ruri_strength), 2)),403        (transit_lora_path, round(float(transit_strength), 2)),404        (pose_lora_path, round(float(pose_strength), 2)),405        (general_lora_path, round(float(general_strength), 2)),406        (motion_lora_path, round(float(motion_strength), 2)),407        (dreamlay_lora_path, round(float(dreamlay_strength), 2)),408        (mself_lora_path, round(float(mself_strength), 2)),409        (dramatic_lora_path, round(float(dramatic_strength), 2)),410        (fluid_lora_path, round(float(fluid_strength), 2)),411        (liquid_lora_path, round(float(liquid_strength), 2)),412        (demopose_lora_path, round(float(demopose_strength), 2)),413    ]414    loras_for_builder = [415        LoraPathStrengthAndSDOps(path, strength, LTXV_LORA_COMFY_RENAMING_MAP)416        for path, strength in entries417        if path is not None and float(strength) != 0.0418    ]419 420    if not loras_for_builder:421        PENDING_LORA_KEY = None422        PENDING_LORA_STATE = None423        PENDING_LORA_STATUS = "No non-zero LoRA strengths selected; nothing to prepare."424        return PENDING_LORA_STATUS425 426    tmp_ledger = None427    new_transformer_cpu = None428    try:429        progress(0.35, desc="Building fused CPU transformer")430        tmp_ledger = pipeline.model_ledger.__class__(431            dtype=ledger.dtype,432            device=torch.device("cpu"),433            checkpoint_path=str(checkpoint_path),434            spatial_upsampler_path=str(spatial_upsampler_path),435            gemma_root_path=str(gemma_root),436            loras=tuple(loras_for_builder),437            quantization=getattr(ledger, "quantization", None),438        )439        new_transformer_cpu = tmp_ledger.transformer()440 441        progress(0.70, desc="Extracting fused state_dict")442        state = new_transformer_cpu.state_dict()443        torch.save(state, cache_path)444 445        PENDING_LORA_KEY = key446        PENDING_LORA_STATE = state447        PENDING_LORA_STATUS = f"Built and cached LoRA state: {cache_path.name}"448        return PENDING_LORA_STATUS449 450    except Exception as e:451        import traceback452        print(f"[LoRA] Prepare failed: {type(e).__name__}: {e}")453        print(traceback.format_exc())454        PENDING_LORA_KEY = None455        PENDING_LORA_STATE = None456        PENDING_LORA_STATUS = f"LoRA prepare failed: {type(e).__name__}: {e}"457        return PENDING_LORA_STATUS458 459    finally:460        try:461            del new_transformer_cpu462        except Exception:463            pass464        try:465            del tmp_ledger466        except Exception:467            pass468        gc.collect()469 470 471def apply_prepared_lora_state_to_pipeline():472    """473    Fast step: copy the already prepared CPU state into the live transformer.474    This is the only part that should remain near generation time.475    """476    global current_lora_key, PENDING_LORA_KEY, PENDING_LORA_STATE477 478    if PENDING_LORA_STATE is None or PENDING_LORA_KEY is None:479        print("[LoRA] No prepared LoRA state available; skipping.")480        return False481 482    if current_lora_key == PENDING_LORA_KEY:483        print("[LoRA] Prepared LoRA state already active; skipping.")484        return True485 486    existing_transformer = _transformer487    existing_params = {name: param for name, param in existing_transformer.named_parameters()}488    existing_buffers = {name: buf for name, buf in existing_transformer.named_buffers()}489 490    with torch.no_grad():491        for k, v in PENDING_LORA_STATE.items():492            if k in existing_params:493                existing_params[k].data.copy_(v.to(existing_params[k].device))494            elif k in existing_buffers:495                existing_buffers[k].data.copy_(v.to(existing_buffers[k].device))496 497    current_lora_key = PENDING_LORA_KEY498    print("[LoRA] Prepared LoRA state applied to the pipeline."+current_lora_key)499    return True500 501# ---- REPLACE PRELOAD BLOCK START ----502# Preload all models for ZeroGPU tensor packing.503print("Preloading all models (including Gemma and audio components)...")504ledger = pipeline.model_ledger505 506# Save the original factory methods so we can rebuild individual components later.507# These are bound callables on ledger that will call the builder when invoked.508_orig_transformer_factory = ledger.transformer509_orig_video_encoder_factory = ledger.video_encoder510_orig_video_decoder_factory = ledger.video_decoder511_orig_audio_encoder_factory = ledger.audio_encoder512_orig_audio_decoder_factory = ledger.audio_decoder513_orig_vocoder_factory = ledger.vocoder514_orig_spatial_upsampler_factory = ledger.spatial_upsampler515_orig_text_encoder_factory = ledger.text_encoder516_orig_gemma_embeddings_factory = ledger.gemma_embeddings_processor517 518# Call the original factories once to create the cached instances we will serve by default.519_transformer = _orig_transformer_factory()520_video_encoder = _orig_video_encoder_factory()521_video_decoder = _orig_video_decoder_factory()522_audio_encoder = _orig_audio_encoder_factory()523_audio_decoder = _orig_audio_decoder_factory()524_vocoder = _orig_vocoder_factory()525_spatial_upsampler = _orig_spatial_upsampler_factory()526_text_encoder = _orig_text_encoder_factory()527_embeddings_processor = _orig_gemma_embeddings_factory()528 529# Replace ledger methods with lightweight lambdas that return the cached instances.530# We keep the original factories above so we can call them later to rebuild components.531ledger.transformer = lambda: _transformer532ledger.video_encoder = lambda: _video_encoder533ledger.video_decoder = lambda: _video_decoder534ledger.audio_encoder = lambda: _audio_encoder535ledger.audio_decoder = lambda: _audio_decoder536ledger.vocoder = lambda: _vocoder537ledger.spatial_upsampler = lambda: _spatial_upsampler538ledger.text_encoder = lambda: _text_encoder539ledger.gemma_embeddings_processor = lambda: _embeddings_processor540 541print("All models preloaded (including Gemma text encoder and audio encoder)!")542# ---- REPLACE PRELOAD BLOCK END ----543 544print("=" * 80)545print("Pipeline ready!")546print("=" * 80)547 548 549def log_memory(tag: str):550    if torch.cuda.is_available():551        allocated = torch.cuda.memory_allocated() / 1024**3552        peak = torch.cuda.max_memory_allocated() / 1024**3553        free, total = torch.cuda.mem_get_info()554        print(f"[VRAM {tag}] allocated={allocated:.2f}GB peak={peak:.2f}GB free={free / 1024**3:.2f}GB total={total / 1024**3:.2f}GB")555 556 557def detect_aspect_ratio(image) -> str:558    if image is None:559        return "16:9"560    if hasattr(image, "size"):561        w, h = image.size562    elif hasattr(image, "shape"):563        h, w = image.shape[:2]564    else:565        return "16:9"566    ratio = w / h567    candidates = {"16:9": 16 / 9, "9:16": 9 / 16, "1:1": 1.0}568    return min(candidates, key=lambda k: abs(ratio - candidates[k]))569 570 571def on_image_upload(first_image, last_image, high_res):572    ref_image = first_image if first_image is not None else last_image573    aspect = detect_aspect_ratio(ref_image)574    tier = "high" if high_res else "low"575    w, h = RESOLUTIONS[tier][aspect]576    return gr.update(value=w), gr.update(value=h)577 578 579def on_highres_toggle(first_image, last_image, high_res):580    ref_image = first_image if first_image is not None else last_image581    aspect = detect_aspect_ratio(ref_image)582    tier = "high" if high_res else "low"583    w, h = RESOLUTIONS[tier][aspect]584    return gr.update(value=w), gr.update(value=h)585 586 587def get_gpu_duration(588    first_image,589    last_image,590    input_audio,591    prompt: str,592    duration: float,593    gpu_duration: float,594    enhance_prompt: bool = True,595    seed: int = 42,596    randomize_seed: bool = True,597    height: int = 1024,598    width: int = 1536,599    paste_strength: float = 0.0,600    ruri_strength: float = 0.0,601    transit_strength: float = 0.0,602    pose_strength: float = 0.0,603    general_strength: float = 0.0,604    motion_strength: float = 0.0,605    dreamlay_strength: float = 0.0,606    mself_strength: float = 0.0,607    dramatic_strength: float = 0.0,608    fluid_strength: float = 0.0,609    liquid_strength: float = 0.0,610    demopose_strength: float = 0.0,611    progress=None,612):613    return int(gpu_duration)614 615@spaces.GPU(duration=get_gpu_duration)616@torch.inference_mode()617def generate_video(618    first_image,619    last_image,620    input_audio,621    prompt: str,622    duration: float,623    gpu_duration: float,624    enhance_prompt: bool = True,625    seed: int = 42,626    randomize_seed: bool = True,627    height: int = 1024,628    width: int = 1536,629    paste_strength: float = 0.0,630    ruri_strength: float = 0.0,631    transit_strength: float = 0.0,632    pose_strength: float = 0.0,633    general_strength: float = 0.0,634    motion_strength: float = 0.0,635    dreamlay_strength: float = 0.0,636    mself_strength: float = 0.0,637    dramatic_strength: float = 0.0,638    fluid_strength: float = 0.0,639    liquid_strength: float = 0.0,640    demopose_strength: float = 0.0,641    progress=gr.Progress(track_tqdm=True),642):643    try:644        torch.cuda.reset_peak_memory_stats()645        log_memory("start")646 647        current_seed = random.randint(0, MAX_SEED) if randomize_seed else int(seed)648 649        frame_rate = DEFAULT_FRAME_RATE650        num_frames = int(duration * frame_rate) + 1651        num_frames = ((num_frames - 1 + 7) // 8) * 8 + 1652 653        print(f"Prompt:{prompt},Generating: {height}x{width}, {num_frames} frames ({duration}s), seed={current_seed},")654 655        images = []656        output_dir = Path("outputs")657        output_dir.mkdir(exist_ok=True)658 659        if first_image is not None:660            temp_first_path = output_dir / f"temp_first_{current_seed}.jpg"661            if hasattr(first_image, "save"):662                first_image.save(temp_first_path)663            else:664                temp_first_path = Path(first_image)665            images.append(ImageConditioningInput(path=str(temp_first_path), frame_idx=0, strength=1.0))666 667        if last_image is not None:668            temp_last_path = output_dir / f"temp_last_{current_seed}.jpg"669            if hasattr(last_image, "save"):670                last_image.save(temp_last_path)671            else:672                temp_last_path = Path(last_image)673            images.append(ImageConditioningInput(path=str(temp_last_path), frame_idx=num_frames - 1, strength=1.0))674 675        tiling_config = TilingConfig.default()676        video_chunks_number = get_video_chunks_number(num_frames, tiling_config)677 678        log_memory("before pipeline call")679 680        apply_prepared_lora_state_to_pipeline()681 682        video, audio = pipeline(683            prompt=prompt,684            seed=current_seed,685            height=int(height),686            width=int(width),687            num_frames=num_frames,688            frame_rate=frame_rate,689            images=images,690            audio_path=input_audio,691            tiling_config=tiling_config,692            enhance_prompt=enhance_prompt,693        )694 695        log_memory("after pipeline call")696 697        output_path = tempfile.mktemp(suffix=".mp4")698        encode_video(699            video=video,700            fps=frame_rate,701            audio=audio,702            output_path=output_path,703            video_chunks_number=video_chunks_number,704        )705 706        log_memory("after encode_video")707        return str(output_path), current_seed708 709    except Exception as e:710        import traceback711        log_memory("on error")712        print(f"Error: {str(e)}\n{traceback.format_exc()}")713        return None, current_seed714with gr.Blocks(title="LTX-2.3 Distilled") as demo:715    gr.Markdown("# LTX-2.3 F2LF with Fast Audio-Video Generation and Lora support")716    717 718    with gr.Row():719        with gr.Column():720            with gr.Row():721                first_image = gr.Image(label="First Frame (Optional)", type="pil")722                last_image = gr.Image(label="Last Frame (Optional)", type="pil")723            input_audio = gr.Audio(label="Audio Input (Optional)", type="filepath")724            prompt = gr.Textbox(725                label="Prompt",726                info="for best results - make it as elaborate as possible",727                value="Make this image come alive with cinematic motion, smooth animation",728                lines=3,729                placeholder="Describe the motion and animation you want...",730            )731            duration = gr.Slider(label="Duration (seconds)", minimum=1.0, maximum=30.0, value=10.0, step=0.1)732                733 734            generate_btn = gr.Button("Generate Video", variant="primary", size="lg")735 736            with gr.Accordion("Advanced Settings", open=True):737                seed = gr.Slider(label="Seed", minimum=0, maximum=MAX_SEED, value=10, step=1)738                randomize_seed = gr.Checkbox(label="Randomize Seed", value=True)739                with gr.Row():740                    width = gr.Number(label="Width", value=1536, precision=0)741                    height = gr.Number(label="Height", value=1024, precision=0)742                with gr.Row():743                    enhance_prompt = gr.Checkbox(label="Enhance Prompt", value=False)744                    high_res = gr.Checkbox(label="High Resolution", value=True)745                with gr.Column():746                    gr.Markdown("### LoRA adapter strengths (set to 0 to disable)")747                    paste_strength = gr.Slider(label="Paste anything Lora strength", minimum=0.0, maximum=2.0, value=0.0, step=0.01)748                    ruri_strength = gr.Slider(label="Ruri lady Lora strength", minimum=0.0, maximum=2.0, value=0.0, step=0.01)749                    transit_strength = gr.Slider(label="Transition Lora strength", minimum=0.0, maximum=2.0, value=0.0, step=0.01)750                    pose_strength = gr.Slider(label="Anthro Enhancer strength", minimum=0.0, maximum=2.0, value=0.0, step=0.01)751                    general_strength = gr.Slider(label="Reasoning Enhancer strength", minimum=0.0, maximum=2.0, value=0.0, step=0.01)752                    motion_strength = gr.Slider(label="Anthro Posing Helper strength", minimum=0.0, maximum=2.0, value=0.0, step=0.01)753                    dreamlay_strength = gr.Slider(label="Dreamfly strength", minimum=0.0, maximum=2.0, value=0.0, step=0.01)754                    mself_strength = gr.Slider(label="Mself strength", minimum=0.0, maximum=2.0, value=0.0, step=0.01)755                    dramatic_strength = gr.Slider(label="Dramatic strength", minimum=0.0, maximum=2.0, value=0.0, step=0.01)756                    fluid_strength = gr.Slider(label="Fluid Helper strength", minimum=0.0, maximum=2.0, value=0.0, step=0.01)757                    liquid_strength = gr.Slider(label="Liquid Helper strength", minimum=0.0, maximum=2.0, value=0.0, step=0.01)758                    demopose_strength = gr.Slider(label="Demopose Helper strength", minimum=0.0, maximum=2.0, value=0.0, step=0.01)759 760                prepare_lora_btn = gr.Button("Prepare / Load LoRA Cache", variant="secondary")761                lora_status = gr.Textbox(762                    label="LoRA Cache Status",763                    value="No LoRA state prepared yet.",764                    interactive=False,765                )766 767        with gr.Column():768            output_video = gr.Video(label="Generated Video", autoplay=False)769            gpu_duration = gr.Slider(770                label="ZeroGPU duration (seconds)",771                minimum=40.0,772                maximum=240.0,773                value=85.0,774                step=1.0,775            )776 777    gr.Examples(778        examples=[779            [780                "asc.jpg",781                None,782                None,783                "A low-angle wide shot establishes a winding, wet asphalt road flanked by a dense, dark forest...",784                3.0,785                80.0,786                False,787                42,788                True,789                1024,790                1024,791                1.0,792                0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,793            ],794            [None, "asc.jpg", "KABAPASTE The girl is squeezed from a tube like paste...", "paste lora"]795        ],796        inputs=[797            first_image, last_image, input_audio, prompt, duration, gpu_duration,798            enhance_prompt, seed, randomize_seed, height, width,799            paste_strength, pose_strength, general_strength, motion_strength,800            dreamlay_strength, mself_strength, dramatic_strength,801            fluid_strength, liquid_strength, demopose_strength,802        ],803    )804 805    # โœ… FIXED EXAMPLE OUTPUT SECTION (ONLY CHANGE)806    with gr.Column():807        gr.Markdown("## ๐ŸŽฌ Example Output")808 809        prompt_preview = gr.Textbox(810            label="Prompt",811            value="KABAPASTE The girl is squeezed from a tube like paste. The video is silent with the sound of rain and squeezing sounds. After the girl forms, her umbrella opens up.",812            interactive=False813        )814 815        video_preview = gr.Video(816            value="abc.mp4",   817            autoplay=True,818            interactive=False,819            width=150,820            height=150821             822   823   824        )825 826    first_image.change(827        fn=on_image_upload,828        inputs=[first_image, last_image, high_res],829        outputs=[width, height],830    )831 832    last_image.change(833        fn=on_image_upload,834        inputs=[first_image, last_image, high_res],835        outputs=[width, height],836    )837 838    high_res.change(839        fn=on_highres_toggle,840        inputs=[first_image, last_image, high_res],841        outputs=[width, height],842    )843 844    prepare_lora_btn.click(845        fn=prepare_lora_cache,846        inputs=[paste_strength, ruri_strength, transit_strength, pose_strength, general_strength, motion_strength, dreamlay_strength, mself_strength, dramatic_strength, fluid_strength, liquid_strength, demopose_strength],847        outputs=[lora_status],848    )849    850    generate_btn.click(851        fn=generate_video,852        inputs=[853            first_image, last_image, input_audio, prompt, duration, gpu_duration, enhance_prompt,854            seed, randomize_seed, height, width,855            paste_strength, ruri_strength, transit_strength, pose_strength, general_strength, motion_strength, dreamlay_strength, mself_strength, dramatic_strength, fluid_strength, liquid_strength, demopose_strength,856        ],857        outputs=[output_video, seed],858    )859 860    861css = """862.fillable{max-width: 1200px !important}863"""864 865if __name__ == "__main__":866    demo.launch(theme=gr.themes.Citrus(), css=css)867