CoolFace
Apppublic

system1-developer/ltx-video-distilled

sourceHugging Faceupdated 3mo agoView on Hugging Face
0likes
app.py521 linesDownload Raw Back to root
1import gradio as gr2import torch3import spaces4import numpy as np5import random6import os7import yaml8from pathlib import Path9import imageio10import tempfile11from PIL import Image12from huggingface_hub import hf_hub_download13import shutil14 15from inference import (16    create_ltx_video_pipeline,17    create_latent_upsampler,18    load_image_to_tensor_with_resize_and_crop,19    seed_everething,20    get_device,21    calculate_padding,22    load_media_file23)24from ltx_video.pipelines.pipeline_ltx_video import ConditioningItem, LTXMultiScalePipeline, LTXVideoPipeline25from ltx_video.utils.skip_layer_strategy import SkipLayerStrategy26 27config_file_path = "configs/ltxv-13b-0.9.8-distilled.yaml"28with open(config_file_path, "r") as file:29    PIPELINE_CONFIG_YAML = yaml.safe_load(file)30 31LTX_REPO = "Lightricks/LTX-Video"32MAX_IMAGE_SIZE = PIPELINE_CONFIG_YAML.get("max_resolution", 1280)33MAX_NUM_FRAMES = 25734 35FPS = 30.0 36 37# --- Global variables for loaded models ---38pipeline_instance = None39latent_upsampler_instance = None40models_dir = "downloaded_models_gradio_cpu_init"41Path(models_dir).mkdir(parents=True, exist_ok=True)42 43print("Downloading models (if not present)...")44distilled_model_actual_path = hf_hub_download(45    repo_id=LTX_REPO,46    filename=PIPELINE_CONFIG_YAML["checkpoint_path"],47    local_dir=models_dir,48    local_dir_use_symlinks=False49)50PIPELINE_CONFIG_YAML["checkpoint_path"] = distilled_model_actual_path51print(f"Distilled model path: {distilled_model_actual_path}")52 53SPATIAL_UPSCALER_FILENAME = PIPELINE_CONFIG_YAML["spatial_upscaler_model_path"]54spatial_upscaler_actual_path = hf_hub_download(55    repo_id=LTX_REPO,56    filename=SPATIAL_UPSCALER_FILENAME,57    local_dir=models_dir,58    local_dir_use_symlinks=False59)60PIPELINE_CONFIG_YAML["spatial_upscaler_model_path"] = spatial_upscaler_actual_path61print(f"Spatial upscaler model path: {spatial_upscaler_actual_path}")62 63print("Creating LTX Video pipeline on CPU...")64pipeline_instance = create_ltx_video_pipeline(65    ckpt_path=PIPELINE_CONFIG_YAML["checkpoint_path"],66    precision=PIPELINE_CONFIG_YAML["precision"],67    text_encoder_model_name_or_path=PIPELINE_CONFIG_YAML["text_encoder_model_name_or_path"],68    sampler=PIPELINE_CONFIG_YAML["sampler"],69    device="cpu",70    enhance_prompt=False,71    prompt_enhancer_image_caption_model_name_or_path=PIPELINE_CONFIG_YAML["prompt_enhancer_image_caption_model_name_or_path"],72    prompt_enhancer_llm_model_name_or_path=PIPELINE_CONFIG_YAML["prompt_enhancer_llm_model_name_or_path"],73)74print("LTX Video pipeline created on CPU.")75 76if PIPELINE_CONFIG_YAML.get("spatial_upscaler_model_path"):77    print("Creating latent upsampler on CPU...")78    latent_upsampler_instance = create_latent_upsampler(79        PIPELINE_CONFIG_YAML["spatial_upscaler_model_path"],80        device="cpu"81    )82    print("Latent upsampler created on CPU.")83 84target_inference_device = "cuda"85print(f"Target inference device: {target_inference_device}")86pipeline_instance.to(target_inference_device)87if latent_upsampler_instance: 88    latent_upsampler_instance.to(target_inference_device)89 90 91# --- Helper function for dimension calculation ---92MIN_DIM_SLIDER = 256  # As defined in the sliders minimum attribute93TARGET_FIXED_SIDE = 768 # Desired fixed side length as per requirement94 95def calculate_new_dimensions(orig_w, orig_h):96    """97    Calculates new dimensions for height and width sliders based on original media dimensions.98    Ensures one side is TARGET_FIXED_SIDE, the other is scaled proportionally,99    both are multiples of 32, and within [MIN_DIM_SLIDER, MAX_IMAGE_SIZE].100    """101    if orig_w == 0 or orig_h == 0:102        # Default to TARGET_FIXED_SIDE square if original dimensions are invalid103        return int(TARGET_FIXED_SIDE), int(TARGET_FIXED_SIDE)104 105    if orig_w >= orig_h:  # Landscape or square106        new_h = TARGET_FIXED_SIDE107        aspect_ratio = orig_w / orig_h108        new_w_ideal = new_h * aspect_ratio109        110        # Round to nearest multiple of 32111        new_w = round(new_w_ideal / 32) * 32112        113        # Clamp to [MIN_DIM_SLIDER, MAX_IMAGE_SIZE]114        new_w = max(MIN_DIM_SLIDER, min(new_w, MAX_IMAGE_SIZE))115        # Ensure new_h is also clamped (TARGET_FIXED_SIDE should be within these bounds if configured correctly)116        new_h = max(MIN_DIM_SLIDER, min(new_h, MAX_IMAGE_SIZE)) 117    else:  # Portrait118        new_w = TARGET_FIXED_SIDE119        aspect_ratio = orig_h / orig_w # Use H/W ratio for portrait scaling120        new_h_ideal = new_w * aspect_ratio121        122        # Round to nearest multiple of 32123        new_h = round(new_h_ideal / 32) * 32124        125        # Clamp to [MIN_DIM_SLIDER, MAX_IMAGE_SIZE]126        new_h = max(MIN_DIM_SLIDER, min(new_h, MAX_IMAGE_SIZE))127        # Ensure new_w is also clamped128        new_w = max(MIN_DIM_SLIDER, min(new_w, MAX_IMAGE_SIZE))129 130    return int(new_h), int(new_w)131 132def get_duration(prompt, negative_prompt, input_image_filepath, input_video_filepath,133             height_ui, width_ui, mode,134             duration_ui, # Removed ui_steps135             ui_frames_to_use,136             seed_ui, randomize_seed, ui_guidance_scale, improve_texture_flag,137             progress):138    if duration_ui > 7:139        return 75140    else:141        return 60142 143@spaces.GPU(duration=get_duration, size="xlarge")144def generate(prompt, negative_prompt, input_image_filepath=None, input_video_filepath=None,145             height_ui=512, width_ui=704, mode="text-to-video",146             duration_ui=2.0, 147             ui_frames_to_use=9,148             seed_ui=42, randomize_seed=True, ui_guidance_scale=3.0, improve_texture_flag=True,149             progress=gr.Progress(track_tqdm=True)):150    """151    Generate high-quality videos using LTX Video model with support for text-to-video, image-to-video, and video-to-video modes.152 153    Args:154        prompt (str): Text description of the desired video content. Required for all modes.155        negative_prompt (str): Text describing what to avoid in the generated video. Optional, can be empty string.156        input_image_filepath (str or None): Path to input image file. Required for image-to-video mode, None for other modes.157        input_video_filepath (str or None): Path to input video file. Required for video-to-video mode, None for other modes.158        height_ui (int): Height of the output video in pixels, must be divisible by 32. Default: 512.159        width_ui (int): Width of the output video in pixels, must be divisible by 32. Default: 704.160        mode (str): Generation mode. Required. One of "text-to-video", "image-to-video", or "video-to-video". Default: "text-to-video".161        duration_ui (float): Duration of the output video in seconds. Range: 0.3 to 8.5. Default: 2.0.162        ui_frames_to_use (int): Number of frames to use from input video. Only used in video-to-video mode. Must be N*8+1. Default: 9.163        seed_ui (int): Random seed for reproducible generation. Range: 0 to 2^32-1. Default: 42.164        randomize_seed (bool): Whether to use a random seed instead of seed_ui. Default: True.165        ui_guidance_scale (float): CFG scale controlling prompt influence. Range: 1.0 to 10.0. Higher values = stronger prompt influence. Default: 3.0.166        improve_texture_flag (bool): Whether to use multi-scale generation for better texture quality. Slower but higher quality. Default: True.167        progress (gr.Progress): Progress tracker for the generation process. Optional, used for UI updates.168 169    Returns:170        tuple: A tuple containing (output_video_path, used_seed) where output_video_path is the path to the generated video file and used_seed is the actual seed used for generation.171    """172 173    # Validate mode-specific required parameters174    if mode == "image-to-video":175        if not input_image_filepath:176            raise gr.Error("input_image_filepath is required for image-to-video mode")177    elif mode == "video-to-video":178        if not input_video_filepath:179            raise gr.Error("input_video_filepath is required for video-to-video mode")180    elif mode == "text-to-video":181        # No additional file inputs required for text-to-video182        pass183    else:184        raise gr.Error(f"Invalid mode: {mode}. Must be one of: text-to-video, image-to-video, video-to-video")185 186    if randomize_seed:187        seed_ui = random.randint(0, 2**32 - 1)188    seed_everething(int(seed_ui))189    190    target_frames_ideal = duration_ui * FPS191    target_frames_rounded = round(target_frames_ideal)192    if target_frames_rounded < 1: 193        target_frames_rounded = 1194    195    n_val = round((float(target_frames_rounded) - 1.0) / 8.0)196    actual_num_frames = int(n_val * 8 + 1)197 198    actual_num_frames = max(9, actual_num_frames)199    actual_num_frames = min(MAX_NUM_FRAMES, actual_num_frames)200    201    actual_height = int(height_ui)202    actual_width = int(width_ui)203 204    height_padded = ((actual_height - 1) // 32 + 1) * 32205    width_padded = ((actual_width - 1) // 32 + 1) * 32206    num_frames_padded = ((actual_num_frames - 2) // 8 + 1) * 8 + 1 207    if num_frames_padded != actual_num_frames:208        print(f"Warning: actual_num_frames ({actual_num_frames}) and num_frames_padded ({num_frames_padded}) differ. Using num_frames_padded for pipeline.")209    210    padding_values = calculate_padding(actual_height, actual_width, height_padded, width_padded)211 212    call_kwargs = {213        "prompt": prompt,214        "negative_prompt": negative_prompt,215        "height": height_padded,216        "width": width_padded,217        "num_frames": num_frames_padded, 218        "frame_rate": int(FPS), 219        "generator": torch.Generator(device=target_inference_device).manual_seed(int(seed_ui)),220        "output_type": "pt", 221        "conditioning_items": None,222        "media_items": None,223        "decode_timestep": PIPELINE_CONFIG_YAML["decode_timestep"],224        "decode_noise_scale": PIPELINE_CONFIG_YAML["decode_noise_scale"],225        "stochastic_sampling": PIPELINE_CONFIG_YAML["stochastic_sampling"],226        "image_cond_noise_scale": 0.15,227        "is_video": True,228        "vae_per_channel_normalize": True,229        "mixed_precision": (PIPELINE_CONFIG_YAML["precision"] == "mixed_precision"),230        "offload_to_cpu": False,231        "enhance_prompt": False,232    }233 234    stg_mode_str = PIPELINE_CONFIG_YAML.get("stg_mode", "attention_values")235    if stg_mode_str.lower() in ["stg_av", "attention_values"]:236        call_kwargs["skip_layer_strategy"] = SkipLayerStrategy.AttentionValues237    elif stg_mode_str.lower() in ["stg_as", "attention_skip"]:238        call_kwargs["skip_layer_strategy"] = SkipLayerStrategy.AttentionSkip239    elif stg_mode_str.lower() in ["stg_r", "residual"]:240        call_kwargs["skip_layer_strategy"] = SkipLayerStrategy.Residual241    elif stg_mode_str.lower() in ["stg_t", "transformer_block"]:242        call_kwargs["skip_layer_strategy"] = SkipLayerStrategy.TransformerBlock243    else:244        raise ValueError(f"Invalid stg_mode: {stg_mode_str}")245 246    if mode == "image-to-video" and input_image_filepath:247        try:248            media_tensor = load_image_to_tensor_with_resize_and_crop(249                input_image_filepath, actual_height, actual_width250            )251            media_tensor = torch.nn.functional.pad(media_tensor, padding_values)252            call_kwargs["conditioning_items"] = [ConditioningItem(media_tensor.to(target_inference_device), 0, 1.0)]253        except Exception as e:254            print(f"Error loading image {input_image_filepath}: {e}")255            raise gr.Error(f"Could not load image: {e}")256    elif mode == "video-to-video" and input_video_filepath:257        try:258            call_kwargs["media_items"] = load_media_file(259                media_path=input_video_filepath,260                height=actual_height, 261                width=actual_width,262                max_frames=int(ui_frames_to_use), 263                padding=padding_values264            ).to(target_inference_device)265        except Exception as e:266            print(f"Error loading video {input_video_filepath}: {e}")267            raise gr.Error(f"Could not load video: {e}")268 269    print(f"Moving models to {target_inference_device} for inference (if not already there)...")270    271    active_latent_upsampler = None272    if improve_texture_flag and latent_upsampler_instance:273        active_latent_upsampler = latent_upsampler_instance274 275    result_images_tensor = None276    if improve_texture_flag:277        if not active_latent_upsampler:278            raise gr.Error("Spatial upscaler model not loaded or improve_texture not selected, cannot use multi-scale.")279        280        multi_scale_pipeline_obj = LTXMultiScalePipeline(pipeline_instance, active_latent_upsampler)281        282        first_pass_args = PIPELINE_CONFIG_YAML.get("first_pass", {}).copy()283        first_pass_args["guidance_scale"] = float(ui_guidance_scale) # UI overrides YAML284        # num_inference_steps will be derived from len(timesteps) in the pipeline285        first_pass_args.pop("num_inference_steps", None)286 287 288        second_pass_args = PIPELINE_CONFIG_YAML.get("second_pass", {}).copy()289        second_pass_args["guidance_scale"] = float(ui_guidance_scale) # UI overrides YAML290        # num_inference_steps will be derived from len(timesteps) in the pipeline291        second_pass_args.pop("num_inference_steps", None)292        293        multi_scale_call_kwargs = call_kwargs.copy()294        multi_scale_call_kwargs.update({295            "downscale_factor": PIPELINE_CONFIG_YAML["downscale_factor"],296            "first_pass": first_pass_args,297            "second_pass": second_pass_args,298        })299        300        print(f"Calling multi-scale pipeline (eff. HxW: {actual_height}x{actual_width}, Frames: {actual_num_frames} -> Padded: {num_frames_padded}) on {target_inference_device}")301        result_images_tensor = multi_scale_pipeline_obj(**multi_scale_call_kwargs).images302    else:303        single_pass_call_kwargs = call_kwargs.copy()304        first_pass_config_from_yaml = PIPELINE_CONFIG_YAML.get("first_pass", {})305 306        single_pass_call_kwargs["timesteps"] = first_pass_config_from_yaml.get("timesteps")307        single_pass_call_kwargs["guidance_scale"] = float(ui_guidance_scale) # UI overrides YAML308        single_pass_call_kwargs["stg_scale"] = first_pass_config_from_yaml.get("stg_scale")309        single_pass_call_kwargs["rescaling_scale"] = first_pass_config_from_yaml.get("rescaling_scale")310        single_pass_call_kwargs["skip_block_list"] = first_pass_config_from_yaml.get("skip_block_list")311        312        # Remove keys that might conflict or are not used in single pass / handled by above313        single_pass_call_kwargs.pop("num_inference_steps", None) 314        single_pass_call_kwargs.pop("first_pass", None) 315        single_pass_call_kwargs.pop("second_pass", None)316        single_pass_call_kwargs.pop("downscale_factor", None)317        318        print(f"Calling base pipeline (padded HxW: {height_padded}x{width_padded}, Frames: {actual_num_frames} -> Padded: {num_frames_padded}) on {target_inference_device}")319        result_images_tensor = pipeline_instance(**single_pass_call_kwargs).images320 321    if result_images_tensor is None:322        raise gr.Error("Generation failed.")323 324    pad_left, pad_right, pad_top, pad_bottom = padding_values325    slice_h_end = -pad_bottom if pad_bottom > 0 else None326    slice_w_end = -pad_right if pad_right > 0 else None327    328    result_images_tensor = result_images_tensor[329        :, :, :actual_num_frames, pad_top:slice_h_end, pad_left:slice_w_end330    ]331 332    video_np = result_images_tensor[0].permute(1, 2, 3, 0).cpu().float().numpy()333    334    video_np = np.clip(video_np, 0, 1) 335    video_np = (video_np * 255).astype(np.uint8)336 337    temp_dir = tempfile.mkdtemp()338    timestamp = random.randint(10000,99999)339    output_video_path = os.path.join(temp_dir, f"output_{timestamp}.mp4")340    341    try:342        with imageio.get_writer(output_video_path, fps=call_kwargs["frame_rate"], macro_block_size=1) as video_writer:343            for frame_idx in range(video_np.shape[0]):344                progress(frame_idx / video_np.shape[0], desc="Saving video")345                video_writer.append_data(video_np[frame_idx])346    except Exception as e:347        print(f"Error saving video with macro_block_size=1: {e}")348        try:349            with imageio.get_writer(output_video_path, fps=call_kwargs["frame_rate"], format='FFMPEG', codec='libx264', quality=8) as video_writer:350                 for frame_idx in range(video_np.shape[0]):351                    progress(frame_idx / video_np.shape[0], desc="Saving video (fallback ffmpeg)")352                    video_writer.append_data(video_np[frame_idx])353        except Exception as e2:354            print(f"Fallback video saving error: {e2}")355            raise gr.Error(f"Failed to save video: {e2}")356            357    return output_video_path, seed_ui358 359def update_task_image():360    return "image-to-video"361 362def update_task_text():363    return "text-to-video"364 365def update_task_video():366    return "video-to-video"367 368# --- Gradio UI Definition ---369css="""370#col-container {371    margin: 0 auto;372    max-width: 900px;373}374"""375 376with gr.Blocks(css=css) as demo:377    gr.Markdown("# LTX Video 0.9.8 13B Distilled")378    gr.Markdown("Fast high quality video generation.**Update (17/07):** now with the new v0.9.8 for improved prompt understanding and detail generation" )379    gr.Markdown("[Model](https://huggingface.co/Lightricks/LTX-Video/blob/main/ltxv-13b-0.9.8-distilled.safetensors) [GitHub](https://github.com/Lightricks/LTX-Video) [Diffusers](https://huggingface.co/Lightricks/LTX-Video-0.9.8-13B-distilled#diffusers-🧨)")380    with gr.Row():381        with gr.Column():382            with gr.Tab("image-to-video") as image_tab:383                video_i_hidden = gr.Textbox(label="video_i", visible=False, value=None)384                image_i2v = gr.Image(label="Input Image", type="filepath", sources=["upload", "webcam", "clipboard"])385                i2v_prompt = gr.Textbox(label="Prompt", value="The creature from the image starts to move", lines=3)386                i2v_button = gr.Button("Generate Image-to-Video", variant="primary")387            with gr.Tab("text-to-video") as text_tab:388                image_n_hidden = gr.Textbox(label="image_n", visible=False, value=None)389                video_n_hidden = gr.Textbox(label="video_n", visible=False, value=None)390                t2v_prompt = gr.Textbox(label="Prompt", value="A majestic dragon flying over a medieval castle", lines=3)391                t2v_button = gr.Button("Generate Text-to-Video", variant="primary")392            with gr.Tab("video-to-video", visible=False) as video_tab:393                image_v_hidden = gr.Textbox(label="image_v", visible=False, value=None)394                video_v2v = gr.Video(label="Input Video", sources=["upload", "webcam"]) # type defaults to filepath395                frames_to_use = gr.Slider(label="Frames to use from input video", minimum=9, maximum=MAX_NUM_FRAMES, value=9, step=8, info="Number of initial frames to use for conditioning/transformation. Must be N*8+1.")396                v2v_prompt = gr.Textbox(label="Prompt", value="Change the style to cinematic anime", lines=3)397                v2v_button = gr.Button("Generate Video-to-Video", variant="primary")398 399            duration_input = gr.Slider(400                label="Video Duration (seconds)", 401                minimum=0.3, 402                maximum=8.5, 403                value=2,  404                step=0.1, 405                info=f"Target video duration (0.3s to 8.5s)"406            )407            improve_texture = gr.Checkbox(label="Improve Texture (multi-scale)", value=True,visible=False, info="Uses a two-pass generation for better quality, but is slower. Recommended for final output.")408 409        with gr.Column():410            output_video = gr.Video(label="Generated Video", interactive=False)411            # gr.DeepLinkButton()412 413    with gr.Accordion("Advanced settings", open=False):414        mode = gr.Dropdown(["text-to-video", "image-to-video", "video-to-video"], label="task", value="image-to-video", visible=False)415        negative_prompt_input = gr.Textbox(label="Negative Prompt", value="worst quality, inconsistent motion, blurry, jittery, distorted", lines=2)416        with gr.Row():417            seed_input = gr.Number(label="Seed", value=42, precision=0, minimum=0, maximum=2**32-1)418            randomize_seed_input = gr.Checkbox(label="Randomize Seed", value=True)419        with gr.Row(visible=False):420            guidance_scale_input = gr.Slider(label="Guidance Scale (CFG)", minimum=1.0, maximum=10.0, value=PIPELINE_CONFIG_YAML.get("first_pass", {}).get("guidance_scale", 1.0), step=0.1, info="Controls how much the prompt influences the output. Higher values = stronger influence.")421        with gr.Row():422            height_input = gr.Slider(label="Height", value=512, step=32, minimum=MIN_DIM_SLIDER, maximum=MAX_IMAGE_SIZE, info="Must be divisible by 32.")423            width_input = gr.Slider(label="Width", value=704, step=32, minimum=MIN_DIM_SLIDER, maximum=MAX_IMAGE_SIZE, info="Must be divisible by 32.")424 425 426    # --- Event handlers for updating dimensions on upload ---427    def handle_image_upload_for_dims(image_filepath, current_h, current_w):428        if not image_filepath:  # Image cleared or no image initially429            # Keep current slider values if image is cleared or no input430            return gr.update(value=current_h), gr.update(value=current_w)431        try:432            img = Image.open(image_filepath)433            orig_w, orig_h = img.size434            new_h, new_w = calculate_new_dimensions(orig_w, orig_h)435            return gr.update(value=new_h), gr.update(value=new_w)436        except Exception as e:437            print(f"Error processing image for dimension update: {e}")438            # Keep current slider values on error439            return gr.update(value=current_h), gr.update(value=current_w)440 441    def handle_video_upload_for_dims(video_filepath, current_h, current_w):442        if not video_filepath:  # Video cleared or no video initially443            return gr.update(value=current_h), gr.update(value=current_w)444        try:445            # Ensure video_filepath is a string for os.path.exists and imageio446            video_filepath_str = str(video_filepath) 447            if not os.path.exists(video_filepath_str):448                print(f"Video file path does not exist for dimension update: {video_filepath_str}")449                return gr.update(value=current_h), gr.update(value=current_w)450 451            orig_w, orig_h = -1, -1452            with imageio.get_reader(video_filepath_str) as reader:453                meta = reader.get_meta_data()454                if 'size' in meta:455                    orig_w, orig_h = meta['size']456                else:457                    # Fallback: read first frame if 'size' not in metadata458                    try:459                        first_frame = reader.get_data(0)460                        # Shape is (h, w, c) for frames461                        orig_h, orig_w = first_frame.shape[0], first_frame.shape[1]462                    except Exception as e_frame:463                        print(f"Could not get video size from metadata or first frame: {e_frame}")464                        return gr.update(value=current_h), gr.update(value=current_w)465            466            if orig_w == -1 or orig_h == -1: # If dimensions couldn't be determined467                 print(f"Could not determine dimensions for video: {video_filepath_str}")468                 return gr.update(value=current_h), gr.update(value=current_w)469 470            new_h, new_w = calculate_new_dimensions(orig_w, orig_h)471            return gr.update(value=new_h), gr.update(value=new_w)472        except Exception as e:473            # Log type of video_filepath for debugging if it's not a path-like string474            print(f"Error processing video for dimension update: {e} (Path: {video_filepath}, Type: {type(video_filepath)})")475            return gr.update(value=current_h), gr.update(value=current_w)476 477    478    image_i2v.upload(479        fn=handle_image_upload_for_dims,480        inputs=[image_i2v, height_input, width_input],481        outputs=[height_input, width_input]482    )483    video_v2v.upload(484        fn=handle_video_upload_for_dims,485        inputs=[video_v2v, height_input, width_input],486        outputs=[height_input, width_input]487    )488 489    image_tab.select(490        fn=update_task_image,491        outputs=[mode]492    )493    text_tab.select(494        fn=update_task_text,495        outputs=[mode]496    )497    498    t2v_inputs = [t2v_prompt, negative_prompt_input, image_n_hidden, video_n_hidden,499                  height_input, width_input, mode,500                  duration_input, frames_to_use, 501                  seed_input, randomize_seed_input, guidance_scale_input, improve_texture]502    503    i2v_inputs = [i2v_prompt, negative_prompt_input, image_i2v, video_i_hidden,504                  height_input, width_input, mode,505                  duration_input, frames_to_use, 506                  seed_input, randomize_seed_input, guidance_scale_input, improve_texture]507 508    v2v_inputs = [v2v_prompt, negative_prompt_input, image_v_hidden, video_v2v,509                  height_input, width_input, mode,510                  duration_input, frames_to_use, 511                  seed_input, randomize_seed_input, guidance_scale_input, improve_texture]512 513    t2v_button.click(fn=generate, inputs=t2v_inputs, outputs=[output_video, seed_input], api_name="text_to_video")514    i2v_button.click(fn=generate, inputs=i2v_inputs, outputs=[output_video, seed_input], api_name="image_to_video")515    v2v_button.click(fn=generate, inputs=v2v_inputs, outputs=[output_video, seed_input], api_name="video_to_video")516 517if __name__ == "__main__":518    if os.path.exists(models_dir) and os.path.isdir(models_dir):519        print(f"Model directory: {Path(models_dir).resolve()}")520    521    demo.queue().launch(debug=True, share=False, mcp_server=True)