sutarman-gaming/random
0
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 )