CoolFace
Apppublic

sutarman-gaming/random

sourceHugging Faceupdated 6mo agoView on Hugging Face
0likes
app.py708 linesDownload Raw Back to root
1import os2import spaces3import shutil4import subprocess5import sys6import copy7import random8import tempfile9import warnings10import time11import gc12import uuid13from tqdm import tqdm14 15import cv216import numpy as np17import torch18import torch._dynamo19from huggingface_hub import list_models20from torch.nn import functional as F21from PIL import Image22 23import gradio as gr24from diffusers import (25    FlowMatchEulerDiscreteScheduler,26    SASolverScheduler,27    DEISMultistepScheduler,28    DPMSolverMultistepInverseScheduler,29    UniPCMultistepScheduler,30    DPMSolverMultistepScheduler,31    DPMSolverSinglestepScheduler,32)33from diffusers.pipelines.wan.pipeline_wan_i2v import WanImageToVideoPipeline34from diffusers.utils.export_utils import export_to_video35 36from torchao.quantization import quantize_, Float8DynamicActivationFloat8WeightConfig, Int8WeightOnlyConfig37import aoti38 39os.environ["TOKENIZERS_PARALLELISM"] = "true"40warnings.filterwarnings("ignore")41IS_ZERO_GPU = bool(os.getenv("SPACES_ZERO_GPU"))42 43if IS_ZERO_GPU:44    print("Loading...")45    subprocess.run("rm -rf /data-nvme/zerogpu-offload/*", env={}, shell=True)46 47# --- FRAME EXTRACTION JS & LOGIC ---48 49# JS to grab timestamp from the output video50get_timestamp_js = """51function() {52    // Select the video element specifically inside the component with id 'generated-video'53    const video = document.querySelector('#generated-video video');54    55    if (video) {56        console.log("Video found! Time: " + video.currentTime);57        return video.currentTime;58    } else {59        console.log("No video element found.");60        return 0;61    }62}63"""64 65 66def extract_frame(video_path, timestamp):67    # Safety check: if no video is present68    if not video_path:69        return None70    71    print(f"Extracting frame at timestamp: {timestamp}") 72    73    cap = cv2.VideoCapture(video_path)74    75    if not cap.isOpened():76        return None77 78    # Calculate frame number79    fps = cap.get(cv2.CAP_PROP_FPS)80    target_frame_num = int(float(timestamp) * fps)81    82    # Cap total frames to prevent errors at the very end of video83    total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))84    if target_frame_num >= total_frames:85        target_frame_num = total_frames - 186    87    # Set position88    cap.set(cv2.CAP_PROP_POS_FRAMES, target_frame_num)89    ret, frame = cap.read()90    cap.release()91    92    if ret:93        # Convert from BGR (OpenCV) to RGB (Gradio)94        # Gradio Image component handles Numpy array -> PIL conversion automatically95        return cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)96    97    return None98 99# --- END FRAME EXTRACTION LOGIC ---100 101 102def clear_vram():103    gc.collect()104    torch.cuda.empty_cache()105 106 107# RIFE108if not os.path.exists("RIFEv4.26_0921.zip"):109    print("Downloading RIFE Model...")110    subprocess.run([111        "wget", "-q",112        "https://huggingface.co/r3gm/RIFE/resolve/main/RIFEv4.26_0921.zip",113        "-O", "RIFEv4.26_0921.zip"114    ], check=True)115    subprocess.run(["unzip", "-o", "RIFEv4.26_0921.zip"], check=True)116 117# sys.path.append(os.getcwd())118 119from train_log.RIFE_HDv3 import Model120device = torch.device("cuda" if torch.cuda.is_available() else "cpu")121rife_model = Model()122rife_model.load_model("train_log", -1)123rife_model.eval()124 125 126@torch.no_grad()127def interpolate_bits(frames_np, multiplier=2, scale=1.0):128    """129    Interpolation maintaining Numpy Float 0-1 format.130    Args:131        frames_np: Numpy Array (Time, Height, Width, Channels) - Float32 [0.0, 1.0]132        multiplier: int (2, 4, 8)133    Returns:134        List of Numpy Arrays (Height, Width, Channels) - Float32 [0.0, 1.0]135    """136    137    # Handle input shape138    if isinstance(frames_np, list):139        # Convert list of arrays to one big array for easier shape handling if needed, 140        # but here we just grab dims from first frame141        T = len(frames_np)142        H, W, C = frames_np[0].shape143    else:144        T, H, W, C = frames_np.shape145 146    # 1. No Interpolation Case147    if multiplier < 2:148        # Just convert 4D array to list of 3D arrays149        if isinstance(frames_np, np.ndarray):150            return list(frames_np)151        return frames_np152 153    n_interp = multiplier - 1154    155    # Pre-calc padding for RIFE (requires dimensions divisible by 32/scale)156    tmp = max(128, int(128 / scale))157    ph = ((H - 1) // tmp + 1) * tmp158    pw = ((W - 1) // tmp + 1) * tmp159    padding = (0, pw - W, 0, ph - H)160 161    # Helper: Numpy (H, W, C) Float -> Tensor (1, C, H, W) Half162    def to_tensor(frame_np):163        # frame_np is float32 0-1164        t = torch.from_numpy(frame_np).to(device)165        # HWC -> CHW166        t = t.permute(2, 0, 1).unsqueeze(0)167        return F.pad(t, padding).half()168 169    # Helper: Tensor (1, C, H, W) Half -> Numpy (H, W, C) Float170    def from_tensor(tensor):171        # Crop padding172        t = tensor[0, :, :H, :W]173        # CHW -> HWC174        t = t.permute(1, 2, 0)175        # Keep as float32, range 0-1176        return t.float().cpu().numpy()177 178    def make_inference(I0, I1, n):179        if rife_model.version >= 3.9:180            res = []181            for i in range(n):182                res.append(rife_model.inference(I0, I1, (i+1) * 1. / (n+1), scale))183            return res184        else:185            middle = rife_model.inference(I0, I1, scale)186            if n == 1:187                return [middle]188            first_half = make_inference(I0, middle, n=n//2)189            second_half = make_inference(middle, I1, n=n//2)190            if n % 2:191                return [*first_half, middle, *second_half]192            else:193                return [*first_half, *second_half]194 195    output_frames = []196 197    # Process Frames198    # Load first frame into GPU199    I1 = to_tensor(frames_np[0])200 201    total_steps = T - 1202 203    with tqdm(total=total_steps, desc="Interpolating", unit="frame") as pbar:204    205        for i in range(total_steps):206            I0 = I1207            # Add original frame to output208            output_frames.append(from_tensor(I0))209    210            # Load next frame211            I1 = to_tensor(frames_np[i+1])212    213            # Generate intermediate frames214            mid_tensors = make_inference(I0, I1, n_interp)215    216            # Append intermediate frames217            for mid in mid_tensors:218                output_frames.append(from_tensor(mid))219 220            if (i + 1) % 50 == 0:221                pbar.update(50)222        pbar.update(total_steps % 50)223        224        # Add the very last frame225        output_frames.append(from_tensor(I1))226    227    # Cleanup228    del I0, I1, mid_tensors229    torch.cuda.empty_cache()230 231    return output_frames232 233 234# WAN235 236# ORG_NAME = "TestOrganizationPleaseIgnore"237MODEL_ID = "TestOrganizationPleaseIgnore/WAMU-Merge-VisualEffects_WAN2.2_I2V_LIGHTNING" #"Wan-AI/Wan2.2-I2V-A14B-Diffusers"238# MODEL_ID = os.getenv("REPO_ID") or random.choice(239#     list(list_models(author=ORG_NAME, filter='diffusers:WanImageToVideoPipeline'))240# ).modelId241# CACHE_DIR = os.path.expanduser("~/.cache/huggingface/")242 243LORA_MODELS = [244    # {245    #     "repo_id": "exampleuser/example_lora_1",246    #     "high_tr": "example_lora_1_high.safetensors",247    #     "low_tr": "example_lora_1_low.safetensors",248    #     "high_scale": 0.5,249    #     "low_scale": 0.5250    # },251    # {252    #     "repo_id": "exampleuser/example_lora_2",253    #     "high_tr": "subfolder/example_lora_2_high.safetensors",254    #     "low_tr": "subfolder/example_lora_2_low.safetensors",255    #     "high_scale": 0.4,256    #     "low_scale": 0.4257    # },258]259 260MAX_DIM = 832261MIN_DIM = 480262SQUARE_DIM = 640263MULTIPLE_OF = 16264MAX_SEED = np.iinfo(np.int32).max265 266FIXED_FPS = 16267MIN_FRAMES_MODEL = 8268MAX_FRAMES_MODEL = 160269 270MIN_DURATION = round(MIN_FRAMES_MODEL / FIXED_FPS, 1)271MAX_DURATION = round(MAX_FRAMES_MODEL / FIXED_FPS, 1)272 273SCHEDULER_MAP = {274    "FlowMatchEulerDiscrete": FlowMatchEulerDiscreteScheduler,275    "SASolver": SASolverScheduler,276    "DEISMultistep": DEISMultistepScheduler,277    "DPMSolverMultistepInverse": DPMSolverMultistepInverseScheduler,278    "UniPCMultistep": UniPCMultistepScheduler,279    "DPMSolverMultistep": DPMSolverMultistepScheduler,280    "DPMSolverSinglestep": DPMSolverSinglestepScheduler,281}282 283pipe = WanImageToVideoPipeline.from_pretrained(284    MODEL_ID,285    torch_dtype=torch.bfloat16,286).to('cuda')287original_scheduler = copy.deepcopy(pipe.scheduler)288 289for i, lora in enumerate(LORA_MODELS):290    name_high_tr = lora["high_tr"].split(".")[0].split("/")[-1] + "Hh"291    name_low_tr = lora["low_tr"].split(".")[0].split("/")[-1] + "Ll"292    293    try: 294        pipe.load_lora_weights(295            lora["repo_id"],296            weight_name=lora["high_tr"],297            adapter_name=name_high_tr298        )299    300        kwargs_lora = {"load_into_transformer_2": True}301        pipe.load_lora_weights(302            lora["repo_id"],303            weight_name=lora["low_tr"],304            adapter_name=name_low_tr,305            **kwargs_lora306        )307    308        pipe.set_adapters([name_high_tr, name_low_tr], adapter_weights=[1.0, 1.0])309    310        pipe.fuse_lora(adapter_names=[name_high_tr], lora_scale=lora["high_scale"], components=["transformer"])311        pipe.fuse_lora(adapter_names=[name_low_tr], lora_scale=lora["low_scale"], components=["transformer_2"])312    313        pipe.unload_lora_weights()314 315        print(f"Applied: {lora['high_tr']}, hs={lora['high_scale']}/ls={lora['low_scale']}, {i+1}/{len(LORA_MODELS)}") 316    except Exception as e:317        print("Error:", str(e))318        print("Failed LoRA:", name_high_tr)319        pipe.unload_lora_weights()320 321# if os.path.exists(CACHE_DIR):322#     shutil.rmtree(CACHE_DIR)323#     print("Deleted Hugging Face cache.")324# else:325#     print("No hub cache found.")326 327quantize_(pipe.text_encoder, Int8WeightOnlyConfig())328torch._dynamo.reset()329quantize_(pipe.transformer, Float8DynamicActivationFloat8WeightConfig())330torch._dynamo.reset()331quantize_(pipe.transformer_2, Float8DynamicActivationFloat8WeightConfig())332torch._dynamo.reset()333 334aoti.aoti_blocks_load(pipe.transformer, 'zerogpu-aoti/Wan2', variant='fp8da')335aoti.aoti_blocks_load(pipe.transformer_2, 'zerogpu-aoti/Wan2', variant='fp8da')336 337# pipe.vae.enable_slicing()338# pipe.vae.enable_tiling()339 340default_prompt_i2v = "make , cinematic motion, smooth animation"341default_negative_prompt = "่‰ฒ่ฐƒ่‰ณไธฝ, ่ฟ‡ๆ›, ้™ๆ€, ็ป†่Š‚ๆจก็ณŠไธๆธ…, ๅญ—ๅน•, ้ฃŽๆ ผ, ไฝœๅ“, ็”ปไฝœ, ็”ป้ข, ้™ๆญข, ๆ•ดไฝ“ๅ‘็ฐ, ๆœ€ๅทฎ่ดจ้‡, ไฝŽ่ดจ้‡, JPEGๅŽ‹็ผฉๆฎ‹็•™, ไธ‘้™‹็š„, ๆฎ‹็ผบ็š„, ๅคšไฝ™็š„ๆ‰‹ๆŒ‡, ็”ปๅพ—ไธๅฅฝ็š„ๆ‰‹้ƒจ, ็”ปๅพ—ไธๅฅฝ็š„่„ธ้ƒจ, ็•ธๅฝข็š„, ๆฏๅฎน็š„, ๅฝขๆ€็•ธๅฝข็š„่‚ขไฝ“, ๆ‰‹ๆŒ‡่žๅˆ, ้™ๆญขไธๅŠจ็š„็”ป้ข, ๆ‚ไนฑ็š„่ƒŒๆ™ฏ, ไธ‰ๆก่…ฟ, ่ƒŒๆ™ฏไบบๅพˆๅคš, ๅ€’็€่ตฐ"342 343 344def model_title():345    repo_name = MODEL_ID.split('/')[-1].replace("_", " ")346    url = f"https://huggingface.co/{MODEL_ID}"347    return f"## This space is currently running [{repo_name}]({url}) ๐Ÿข"348 349 350def resize_image(image: Image.Image) -> Image.Image:351    width, height = image.size352    if width == height:353        return image.resize((SQUARE_DIM, SQUARE_DIM), Image.LANCZOS)354    355    aspect_ratio = width / height356    MAX_ASPECT_RATIO = MAX_DIM / MIN_DIM357    MIN_ASPECT_RATIO = MIN_DIM / MAX_DIM358 359    image_to_resize = image360    if aspect_ratio > MAX_ASPECT_RATIO:361        target_w, target_h = MAX_DIM, MIN_DIM362        crop_width = int(round(height * MAX_ASPECT_RATIO))363        left = (width - crop_width) // 2364        image_to_resize = image.crop((left, 0, left + crop_width, height))365    elif aspect_ratio < MIN_ASPECT_RATIO:366        target_w, target_h = MIN_DIM, MAX_DIM367        crop_height = int(round(width / MIN_ASPECT_RATIO))368        top = (height - crop_height) // 2369        image_to_resize = image.crop((0, top, width, top + crop_height))370    else:371        if width > height:372            target_w = MAX_DIM373            target_h = int(round(target_w / aspect_ratio))374        else:375            target_h = MAX_DIM376            target_w = int(round(target_h * aspect_ratio))377 378    final_w = round(target_w / MULTIPLE_OF) * MULTIPLE_OF379    final_h = round(target_h / MULTIPLE_OF) * MULTIPLE_OF380    final_w = max(MIN_DIM, min(MAX_DIM, final_w))381    final_h = max(MIN_DIM, min(MAX_DIM, final_h))382    return image_to_resize.resize((final_w, final_h), Image.LANCZOS)383 384 385def resize_and_crop_to_match(target_image, reference_image):386    ref_width, ref_height = reference_image.size387    target_width, target_height = target_image.size388    scale = max(ref_width / target_width, ref_height / target_height)389    new_width, new_height = int(target_width * scale), int(target_height * scale)390    resized = target_image.resize((new_width, new_height), Image.Resampling.LANCZOS)391    left, top = (new_width - ref_width) // 2, (new_height - ref_height) // 2392    return resized.crop((left, top, left + ref_width, top + ref_height))393 394 395def get_num_frames(duration_seconds: float):396    return 1 + int(np.clip(397        int(round(duration_seconds * FIXED_FPS)),398        MIN_FRAMES_MODEL,399        MAX_FRAMES_MODEL,400    ))401 402 403def get_inference_duration(404    resized_image,405    processed_last_image,406    prompt,407    steps,408    negative_prompt,409    num_frames,410    guidance_scale,411    guidance_scale_2,412    current_seed,413    scheduler_name,414    flow_shift,415    frame_multiplier,416    quality,417    duration_seconds,418    progress419):420    BASE_FRAMES_HEIGHT_WIDTH = 81 * 832 * 624421    BASE_STEP_DURATION = 15422    width, height = resized_image.size423    factor = num_frames * width * height / BASE_FRAMES_HEIGHT_WIDTH424    step_duration = BASE_STEP_DURATION * factor ** 1.5425    gen_time = int(steps) * step_duration426 427    if guidance_scale > 1:428        gen_time = gen_time * 1.8429 430    frame_factor = frame_multiplier // FIXED_FPS431    if frame_factor > 1:432        total_out_frames = (num_frames * frame_factor) - num_frames433        inter_time = (total_out_frames * 0.02)434        gen_time += inter_time435 436    return 10 + gen_time437 438 439@spaces.GPU(duration=60)440def run_inference(441    resized_image,442    processed_last_image,443    prompt,444    steps,445    negative_prompt,446    num_frames,447    guidance_scale,448    guidance_scale_2,449    current_seed,450    scheduler_name,451    flow_shift,452    frame_multiplier,453    quality,454    duration_seconds,455    progress=gr.Progress(track_tqdm=True),456):457    scheduler_class = SCHEDULER_MAP.get(scheduler_name)458    if scheduler_class.__name__ != pipe.scheduler.config._class_name or flow_shift != pipe.scheduler.config.get("flow_shift", "shift"):459        config = copy.deepcopy(original_scheduler.config)460        if scheduler_class == FlowMatchEulerDiscreteScheduler:461            config['shift'] = flow_shift462        else:463            config['flow_shift'] = flow_shift464        pipe.scheduler = scheduler_class.from_config(config)465 466    clear_vram()467 468    task_name = str(uuid.uuid4())[:8]469    print(f"Generating {num_frames} frames, task: {task_name}, {duration_seconds}, {resized_image.size}")470    start = time.time()471    result = pipe(472        image=resized_image,473        last_image=processed_last_image,474        prompt=prompt,475        negative_prompt=negative_prompt,476        height=resized_image.height,477        width=resized_image.width,478        num_frames=num_frames,479        guidance_scale=float(guidance_scale),480        guidance_scale_2=float(guidance_scale_2),481        num_inference_steps=int(steps),482        generator=torch.Generator(device="cuda").manual_seed(current_seed),483        output_type="np" 484    )485    print("gen time passed:", time.time() - start)486    487    raw_frames_np = result.frames[0]  # Returns (T, H, W, C) float32488    pipe.scheduler = original_scheduler489 490    frame_factor = frame_multiplier // FIXED_FPS491    if frame_factor > 1:492        start = time.time()493        print(f"Processing frames (RIFE Multiplier: {frame_factor}x)...")494        rife_model.device()495        rife_model.flownet = rife_model.flownet.half()496        final_frames = interpolate_bits(raw_frames_np, multiplier=int(frame_factor))497        print("Interpolation time passed:", time.time() - start)498    else:499        final_frames = list(raw_frames_np)500 501    final_fps = FIXED_FPS * int(frame_factor)502 503    with tempfile.NamedTemporaryFile(suffix=".mp4", delete=False) as tmpfile:504        video_path = tmpfile.name505 506    start = time.time()507    with tqdm(total=3, desc="Rendering Media", unit="clip") as pbar:508        pbar.update(2)509        export_to_video(final_frames, video_path, fps=final_fps, quality=quality)510        pbar.update(1)511    print(f"Export time passed, {final_fps} FPS:", time.time() - start)512 513    return video_path, task_name514 515 516def generate_video(517    input_image,518    last_image,519    prompt,520    steps=4,521    negative_prompt=default_negative_prompt,522    duration_seconds=MAX_DURATION,523    guidance_scale=1,524    guidance_scale_2=1,525    seed=42,526    randomize_seed=False,527    quality=5,528    scheduler="UniPCMultistep",529    flow_shift=6.0,530    frame_multiplier=16,531    video_component=True,532    progress=gr.Progress(track_tqdm=True),533):534    """535    Generate a video from an input image using the Wan 2.2 14B I2V model with Lightning LoRA.536    This function takes an input image and generates a video animation based on the provided537    prompt and parameters. It uses an FP8 qunatized Wan 2.2 14B Image-to-Video model in with Lightning LoRA538    for fast generation in 4-8 steps.539    Args:540        input_image (PIL.Image): The input image to animate. Will be resized to target dimensions.541        last_image (PIL.Image, optional): The optional last image for the video.542        prompt (str): Text prompt describing the desired animation or motion.543        steps (int, optional): Number of inference steps. More steps = higher quality but slower.544            Defaults to 4. Range: 1-30.545        negative_prompt (str, optional): Negative prompt to avoid unwanted elements.546            Defaults to default_negative_prompt (contains unwanted visual artifacts).547        duration_seconds (float, optional): Duration of the generated video in seconds.548            Defaults to 2. Clamped between MIN_FRAMES_MODEL/FIXED_FPS and MAX_FRAMES_MODEL/FIXED_FPS.549        guidance_scale (float, optional): Controls adherence to the prompt. Higher values = more adherence.550            Defaults to 1.0. Range: 0.0-20.0.551        guidance_scale_2 (float, optional): Controls adherence to the prompt. Higher values = more adherence.552            Defaults to 1.0. Range: 0.0-20.0.553        seed (int, optional): Random seed for reproducible results. Defaults to 42.554            Range: 0 to MAX_SEED (2147483647).555        randomize_seed (bool, optional): Whether to use a random seed instead of the provided seed.556            Defaults to False.557        quality (float, optional): Video output quality. Default is 5. Uses variable bit rate.558            Highest quality is 10, lowest is 1.559        scheduler (str, optional): The name of the scheduler to use for inference. Defaults to "UniPCMultistep".560        flow_shift (float, optional): The flow shift value for compatible schedulers. Defaults to 6.0.561        frame_multiplier (int, optional): The int value for fps enhancer562        video_component(bool, optional): Show video player in output.563            Defaults to True.564        progress (gr.Progress, optional): Gradio progress tracker. Defaults to gr.Progress(track_tqdm=True).565    Returns:566        tuple: A tuple containing:567            - video_path (str): Path for the video component.568            - video_path (str): Path for the file download component. Attempt to avoid reconversion in video component.569            - current_seed (int): The seed used for generation.570    Raises:571        gr.Error: If input_image is None (no image uploaded).572    Note:573        - Frame count is calculated as duration_seconds * FIXED_FPS (24)574        - Output dimensions are adjusted to be multiples of MOD_VALUE (32)575        - The function uses GPU acceleration via the @spaces.GPU decorator576        - Generation time varies based on steps and duration (see get_duration function)577    """578    579    if input_image is None:580        raise gr.Error("Please upload an input image.")581 582    num_frames = get_num_frames(duration_seconds)583    current_seed = random.randint(0, MAX_SEED) if randomize_seed else int(seed)584    resized_image = resize_image(input_image)585 586    processed_last_image = None587    if last_image:588        processed_last_image = resize_and_crop_to_match(last_image, resized_image)589 590    video_path, task_n = run_inference(591        resized_image,592        processed_last_image,593        prompt,594        steps,595        negative_prompt,596        num_frames,597        guidance_scale,598        guidance_scale_2,599        current_seed,600        scheduler,601        flow_shift,602        frame_multiplier,603        quality,604        duration_seconds,605        progress,606    )607    print(f"GPU complete: {task_n}")608 609    return (video_path if video_component else None), video_path, current_seed610 611 612CSS = """613#hidden-timestamp {614    opacity: 0;615    height: 0px;616    width: 0px;617    margin: 0px;618    padding: 0px;619    overflow: hidden;620    position: absolute;621    pointer-events: none;622}623"""624 625 626with gr.Blocks(delete_cache=(3600, 10800)) as demo:627    # gr.Markdown(model_title())628    gr.Markdown("Runs in ~50s on MIG'd H200 for a 4s output")629 630    with gr.Row():631        with gr.Column():632            input_image_component = gr.Image(type="pil", label="Input Image", sources=["upload", "clipboard"])633            last_image_component = gr.Image(type="pil", label="Last Image (Optional)", sources=["upload", "clipboard"])634            prompt_input = gr.Textbox(label="Prompt", value=default_prompt_i2v)635            duration_seconds_input = gr.Slider(minimum=MIN_DURATION, maximum=MAX_DURATION, step=0.1, value=4, label="Duration (seconds)", info=f"Clamped to model's {MIN_FRAMES_MODEL}-{MAX_FRAMES_MODEL} frames at {FIXED_FPS}fps.")636            with gr.Accordion("Advanced Settings", open=False):637                frame_multi = gr.Dropdown(638                choices=[FIXED_FPS, FIXED_FPS*2, FIXED_FPS*4, FIXED_FPS*8],639                value=FIXED_FPS,640                label="Video Fluidity"641                )642                negative_prompt_input = gr.Textbox(label="Negative Prompt", value=default_negative_prompt, info="Used if any Guidance Scale > 1.", lines=3)643                quality_slider = gr.Slider(minimum=1, maximum=10, step=1, value=6, label="Video Quality", info="If set to 10, the generated video may be too large and won't play in the Gradio preview.")644                seed_input = gr.Slider(label="Seed", minimum=0, maximum=MAX_SEED, step=1, value=42, interactive=True)645                randomize_seed_checkbox = gr.Checkbox(label="Randomize seed", value=True, interactive=True)646                steps_slider = gr.Slider(minimum=1, maximum=30, step=1, value=4, label="Inference Steps")647                guidance_scale_input = gr.Slider(minimum=0.0, maximum=10.0, step=0.5, value=1, label="Guidance Scale - high noise stage", info="Values above 1 increase GPU usage and may take longer to process.")648                guidance_scale_2_input = gr.Slider(minimum=0.0, maximum=10.0, step=0.5, value=1, label="Guidance Scale 2 - low noise stage")649                scheduler_dropdown = gr.Dropdown(650                    label="Scheduler",651                    choices=list(SCHEDULER_MAP.keys()),652                    value="UniPCMultistep",653                    info="Select a custom scheduler."654                )655                flow_shift_slider = gr.Slider(minimum=0.5, maximum=15.0, step=0.1, value=3.0, label="Flow Shift")656                play_result_video = gr.Checkbox(label="Display result", value=True, interactive=True)657 658 659            generate_button = gr.Button("Generate Video", variant="primary")660 661        with gr.Column():662            # ASSIGNED elem_id="generated-video" so JS can find it663            video_output = gr.Video(label="Generated Video", autoplay=True, sources=["upload"], buttons=["download", "share"], interactive=True, elem_id="generated-video")664            665            # --- Frame Grabbing UI ---666            with gr.Row():667                grab_frame_btn = gr.Button("๐Ÿ“ธ Use Current Frame as Input", variant="secondary")668                timestamp_box = gr.Number(value=0, label="Timestamp", visible=True, elem_id="hidden-timestamp")669            # -------------------------670            671            file_output = gr.File(label="Download Video")672 673    ui_inputs = [674        input_image_component, last_image_component, prompt_input, steps_slider,675        negative_prompt_input, duration_seconds_input,676        guidance_scale_input, guidance_scale_2_input, seed_input, randomize_seed_checkbox,677        quality_slider, scheduler_dropdown, flow_shift_slider, frame_multi,678        play_result_video679    ]680    681    generate_button.click(682        fn=generate_video, 683        inputs=ui_inputs, 684        outputs=[video_output, file_output, seed_input]685    )686    687    # --- Frame Grabbing Events ---688    # 1. Click button -> JS runs -> puts time in hidden number box689    grab_frame_btn.click(690        fn=None,691        inputs=None,692        outputs=[timestamp_box],693        js=get_timestamp_js694    )695    696    # 2. Hidden number box changes -> Python runs -> puts frame in Input Image697    timestamp_box.change(698        fn=extract_frame,699        inputs=[video_output, timestamp_box],700        outputs=[input_image_component]701    )702 703if __name__ == "__main__":704    demo.queue().launch(705        mcp_server=True,706        css=CSS,707        show_error=True,708    )