Spaces:
Running on Zero
Running on Zero
multimodalart HF Staff
Size GPU durations to measured times; cap frames at 65; better video example
0e2afa4 verified Download app.py from hugging-apps/vitronedit: direct link, hf CLI and curl.
- Browser
- Download file 23 kB
-
https://huggingface.co/spaces/hugging-apps/vitronedit/resolve/main/app.py
- Command line
-
hf download hf://spaces/hugging-apps/vitronedit/app.py
-
curl -L -o app.py https://huggingface.co/spaces/hugging-apps/vitronedit/resolve/main/app.py
23 kB
| import json | |
| import os | |
| os.environ.setdefault("PYTORCH_CUDA_ALLOC_CONF", "expandable_segments:True") | |
| import spaces # must be imported before torch / CUDA-touching libs | |
| import random | |
| import re | |
| import tempfile | |
| import time | |
| from types import SimpleNamespace | |
| import einops | |
| import gradio as gr | |
| import imageio | |
| import numpy as np | |
| import torch | |
| from huggingface_hub import snapshot_download | |
| from PIL import Image | |
| from torchvision import transforms | |
| import hyvideo.commons | |
| import hyvideo.pipelines.hunyuan_video_pipeline as hv_pipeline_module | |
| from hyvideo.commons import auto_offload_model | |
| from hyvideo.commons.infer_state import initialize_infer_state | |
| from hyvideo.pipelines.hunyuan_video_pipeline import HunyuanVideo_1_5_Pipeline | |
| from univedit.data.preprocessing import IMAGE_CLIP_FRAMES, resize_and_center_crop_image | |
| # --------------------------------------------------------------------------- | |
| # Weights (downloaded at startup) | |
| # --------------------------------------------------------------------------- | |
| CKPT_DIR = os.path.abspath("ckpts/omniweaving") | |
| BASE_REPO = "tencent/HY-OmniWeaving" | |
| UNIVEDIT_REPO = "SanRiiiii/UniVEdit" | |
| SIGLIP_VISION_CONFIG = { | |
| "architectures": ["SiglipVisionModel"], | |
| "attention_dropout": 0.0, | |
| "hidden_act": "gelu_pytorch_tanh", | |
| "hidden_size": 1152, | |
| "image_size": 384, | |
| "intermediate_size": 4304, | |
| "layer_norm_eps": 1e-06, | |
| "model_type": "siglip_vision_model", | |
| "num_attention_heads": 16, | |
| "num_channels": 3, | |
| "num_hidden_layers": 27, | |
| "patch_size": 14, | |
| } | |
| def _link(src, dst): | |
| os.makedirs(os.path.dirname(dst), exist_ok=True) | |
| if not os.path.lexists(dst): | |
| os.symlink(src, dst) | |
| def prepare_weights() -> str: | |
| # OmniWeaving base: transformer, VAE, scheduler and the fine-tuned MLLM text encoder. | |
| base = snapshot_download( | |
| BASE_REPO, | |
| allow_patterns=[ | |
| "config.json", | |
| "scheduler/*", | |
| "transformer/*", | |
| "vae/*", | |
| "text_encoder/ckpt/*", | |
| ], | |
| ) | |
| _link(os.path.join(base, "config.json"), os.path.join(CKPT_DIR, "config.json")) | |
| for sub in ("scheduler", "transformer", "vae"): | |
| _link(os.path.join(base, sub), os.path.join(CKPT_DIR, sub)) | |
| te_ckpt = os.path.join(base, "text_encoder", "ckpt", "text_encoder_model.safetensors") | |
| _link(te_ckpt, os.path.join(CKPT_DIR, "text_encoder", "ckpt", "text_encoder_model.safetensors")) | |
| # Qwen2.5-VL-7B-Instruct: config / tokenizer / processor only. The OmniWeaving | |
| # fine-tuned MLLM checkpoint holds the full weights in HF key format, so it is | |
| # used directly as the model weights (no need to fetch the 16 GB base model). | |
| qwen = snapshot_download( | |
| "Qwen/Qwen2.5-VL-7B-Instruct", | |
| allow_patterns=["*.json", "*.txt"], | |
| ignore_patterns=["model.safetensors.index.json"], | |
| ) | |
| llm_dir = os.path.join(CKPT_DIR, "text_encoder", "llm") | |
| os.makedirs(llm_dir, exist_ok=True) | |
| for f in os.listdir(qwen): | |
| if f != "model.safetensors.index.json": | |
| _link(os.path.join(qwen, f), os.path.join(llm_dir, f)) | |
| _link(te_ckpt, os.path.join(llm_dir, "model.safetensors")) | |
| # Glyph-SDXL-v2 byT5 encoder. | |
| glyph = snapshot_download("multimodalart/glyph-sdxl-v2-byt5-small", allow_patterns=["assets/*", "pytorch_model.bin"]) | |
| glyph_root = os.path.join(CKPT_DIR, "text_encoder", "Glyph-SDXL-v2") | |
| for f in ("color_idx.json", "multilingual_10-lang_idx.json"): | |
| _link(os.path.join(glyph, "assets", f), os.path.join(glyph_root, "assets", f)) | |
| _link(os.path.join(glyph, "pytorch_model.bin"), os.path.join(glyph_root, "checkpoints", "byt5_model.pt")) | |
| # SigLIP vision encoder. OmniWeaving points at FLUX.1-Redux-dev/image_encoder, which is | |
| # the (gated) bf16 copy of google/siglip-so400m-patch14-384's vision tower; we use the | |
| # ungated original with the same vision config + preprocessor. | |
| siglip = snapshot_download( | |
| "google/siglip-so400m-patch14-384", allow_patterns=["model.safetensors", "preprocessor_config.json"] | |
| ) | |
| siglip_dir = os.path.join(CKPT_DIR, "vision_encoder", "siglip") | |
| enc_dir = os.path.join(siglip_dir, "image_encoder") | |
| os.makedirs(enc_dir, exist_ok=True) | |
| with open(os.path.join(enc_dir, "config.json"), "w") as f: | |
| json.dump(SIGLIP_VISION_CONFIG, f) | |
| _link(os.path.join(siglip, "model.safetensors"), os.path.join(enc_dir, "model.safetensors")) | |
| _link( | |
| os.path.join(siglip, "preprocessor_config.json"), | |
| os.path.join(siglip_dir, "feature_extractor", "preprocessor_config.json"), | |
| ) | |
| # UniVEdit: LoRA + modality token embeddings. | |
| univedit = snapshot_download(UNIVEDIT_REPO, allow_patterns=["geo3d/*"]) | |
| return os.path.join(univedit, "geo3d") | |
| UNIVEDIT_DIR = prepare_weights() | |
| # --------------------------------------------------------------------------- | |
| # Pipeline | |
| # --------------------------------------------------------------------------- | |
| # ZeroGPU has no GPU in the main process: report a large card so the pipeline | |
| # picks the non-tiled-small VAE config, and never enables offloading. | |
| _fake_mem = lambda device=None: 96 * 1024**3 # noqa: E731 | |
| hyvideo.commons.get_gpu_memory = _fake_mem | |
| hv_pipeline_module.get_gpu_memory = _fake_mem | |
| # Single-process inference: no sequence parallelism. The repo's ParallelDims builds a | |
| # torch DeviceMesh (real CUDA init), which ZeroGPU's main process cannot do, so we | |
| # register an equivalent no-parallelism state directly. | |
| import hyvideo.commons.parallel_states as _ps | |
| setattr( | |
| _ps, | |
| "__parallel_dims", | |
| SimpleNamespace(sp=1, world_size=1, dp_replicate=1, sp_rank=0, sp_group=None, sp_enabled=False, dp_enabled=False), | |
| ) | |
| initialize_infer_state( | |
| SimpleNamespace( | |
| sage_blocks_range="0-53", | |
| no_cache_block_id="53", | |
| use_sageattn=False, | |
| enable_torch_compile=False, | |
| enable_cache=False, | |
| cache_type="deepcache", | |
| cache_start_step=11, | |
| cache_end_step=45, | |
| total_steps=50, | |
| cache_step_interval=4, | |
| use_fp8_gemm=False, | |
| quant_type="fp8-per-token-sgl", | |
| include_patterns="double_blocks", | |
| ) | |
| ) | |
| # The MLLM weights are already loaded from the fine-tuned checkpoint (see above). | |
| HunyuanVideo_1_5_Pipeline._load_text_encoder_ckpt = staticmethod(lambda text_encoder, ckpt_path: None) | |
| pipe = HunyuanVideo_1_5_Pipeline.create_pipeline( | |
| pretrained_model_name_or_path=CKPT_DIR, | |
| transformer_dtype=torch.bfloat16, | |
| device=torch.device("cpu"), | |
| transformer_init_device=torch.device("cpu"), | |
| pipeline_config="omniweaving", | |
| ) | |
| pipe.apply_infer_optimization(infer_state=None, enable_offloading=False, enable_group_offloading=False) | |
| pipe.load_modality_token_embeddings(UNIVEDIT_DIR, strict=True) | |
| pipe.transformer.load_lora_adapter( | |
| pretrained_model_name_or_path_or_dict=UNIVEDIT_DIR, | |
| prefix=None, | |
| adapter_name="default", | |
| weight_name="pytorch_lora_weights.safetensors", | |
| use_safetensors=True, | |
| ) | |
| # Merge the LoRA into the base weights (same math, no per-layer PEFT overhead at inference). | |
| pipe.transformer.fuse_lora(lora_scale=1.0) | |
| pipe.transformer.unload_lora() | |
| pipe.to("cuda") | |
| pipe.execution_device = torch.device("cuda") | |
| pipe.noise_init_device = torch.device("cuda") | |
| print("UniVEdit pipeline ready.") | |
| # --------------------------------------------------------------------------- | |
| # Helpers (ported 1:1 from univedit/inference/batch_infer.py) | |
| # --------------------------------------------------------------------------- | |
| IMAGE_EDIT_BUCKETS = ((352, 624), (432, 576), (480, 480), (576, 432), (624, 352)) # (width, height) | |
| VIDEO_EDIT_360P_BUCKETS = {"1:1": (480, 480), "16:9": (352, 624), "9:16": (624, 352)} # (height, width) | |
| MAX_SEED = 2**31 - 1 | |
| DEFAULT_STEPS = 50 | |
| DEFAULT_GUIDANCE = 6.0 | |
| DEFAULT_SHIFT = 5.0 | |
| def replace_reference_placeholder(text: str) -> str: | |
| text = re.sub(r"\b(?:a|an|the)\s*\[\s*v\s*\*\s*\]", "the reference image", text, flags=re.IGNORECASE) | |
| text = re.sub(r"\[\s*v\s*\*\s*\]", "the reference image", text, flags=re.IGNORECASE) | |
| text = re.sub(r"\s+([,.;:!?])", r"\1", text) | |
| text = re.sub(r"\s{2,}", " ", text) | |
| return text.strip() | |
| def image_bucket_size(img: Image.Image): | |
| ratio = img.width / img.height | |
| w, h = min(IMAGE_EDIT_BUCKETS, key=lambda s: abs((s[0] / s[1]) - ratio)) | |
| return h, w | |
| def video_bucket_size(width: int, height: int): | |
| ratio = width / height | |
| _, (h, w) = min(VIDEO_EDIT_360P_BUCKETS.items(), key=lambda it: abs((it[1][1] / it[1][0]) - ratio)) | |
| return h, w | |
| def encode_frames_to_latents(frames: torch.Tensor, *, scale: bool) -> torch.Tensor: | |
| pixel_values = transforms.Normalize(mean=[0.5] * 3, std=[0.5] * 3)(frames) | |
| pixel_values = pixel_values.unsqueeze(0).transpose(1, 2).to(pipe.execution_device) | |
| with ( | |
| torch.no_grad(), | |
| torch.autocast(device_type="cuda", dtype=torch.float16, enabled=True), | |
| auto_offload_model(pipe.vae, pipe.execution_device, enabled=False), | |
| pipe.vae.memory_efficient_context(), | |
| ): | |
| latents = pipe.vae.encode(pixel_values).latent_dist.mode() | |
| if scale: | |
| if getattr(pipe.vae.config, "shift_factor", None): | |
| latents = (latents - pipe.vae.config.shift_factor) * pipe.vae.config.scaling_factor | |
| else: | |
| latents = latents * pipe.vae.config.scaling_factor | |
| return latents | |
| def to_pil_first_frame(video: torch.Tensor) -> Image.Image: | |
| frame = video[0, :, 0] if video.ndim == 5 else video[0] | |
| frame = (frame * 255).clamp(0, 255).to(torch.uint8) | |
| return Image.fromarray(einops.rearrange(frame, "c h w -> h w c").cpu().numpy()) | |
| def resolve_seed(seed, randomize): | |
| return random.randint(0, MAX_SEED) if randomize else int(seed) | |
| # --------------------------------------------------------------------------- | |
| # Inference | |
| # --------------------------------------------------------------------------- | |
| # measured ~60s for 50 steps (+~15%) | |
| def edit_image( | |
| source_image: Image.Image, | |
| instruction: str, | |
| reference_image: Image.Image | None = None, | |
| steps: int = DEFAULT_STEPS, | |
| guidance_scale: float = DEFAULT_GUIDANCE, | |
| flow_shift: float = DEFAULT_SHIFT, | |
| seed: int = 123, | |
| randomize_seed: bool = False, | |
| progress=gr.Progress(track_tqdm=True), | |
| ): | |
| """Edit an image with a text instruction (optionally guided by a reference image). | |
| Args: | |
| source_image: the image to edit. | |
| instruction: the editing instruction, e.g. "replace the scarf with a hat". With a reference | |
| image, refer to it as "[V*]" (e.g. "change the dog to a [V*]"). | |
| reference_image: optional reference image (switches to reference-guided editing, ti_i2i). | |
| steps: number of denoising steps. | |
| guidance_scale: classifier-free guidance scale. | |
| flow_shift: timestep shift of the sampling schedule. | |
| seed: random seed. | |
| randomize_seed: pick a random seed instead of `seed`. | |
| Returns: | |
| The edited image and the seed that was used. | |
| """ | |
| t0 = time.time() | |
| if source_image is None: | |
| raise gr.Error("Please upload an image to edit.") | |
| if not instruction or not instruction.strip(): | |
| raise gr.Error("Please enter an editing instruction.") | |
| seed = resolve_seed(seed, randomize_seed) | |
| source_image = source_image.convert("RGB") | |
| task = "ti_i2i" if reference_image is not None else "i2i" | |
| prompt = replace_reference_placeholder(instruction) if task == "ti_i2i" else instruction.strip() | |
| h, w = image_bucket_size(source_image) | |
| resized = resize_and_center_crop_image(source_image, h, w) | |
| clip = transforms.ToTensor()(resized).unsqueeze(0).repeat(IMAGE_CLIP_FRAMES, 1, 1, 1) | |
| latents = encode_frames_to_latents(clip, scale=True) | |
| kwargs = dict( | |
| prompt=prompt, | |
| negative_prompt="", | |
| aspect_ratio=f"{w}:{h}", | |
| num_inference_steps=int(steps), | |
| guidance_scale=float(guidance_scale), | |
| flow_shift=float(flow_shift), | |
| video_length=IMAGE_CLIP_FRAMES, | |
| seed=seed, | |
| output_type="pt", | |
| return_dict=True, | |
| deepstack=[8, 16, 24], | |
| setclip=True, | |
| enable_vae_tile_parallelism=False, # single process, no torch.distributed | |
| task_type=task, | |
| source_image=source_image, | |
| source_image_latents=latents, | |
| target_height=h, | |
| target_width=w, | |
| ) | |
| if task == "ti_i2i": | |
| kwargs["reference_image"] = reference_image.convert("RGB") | |
| t1 = time.time() | |
| with torch.no_grad(): | |
| out = pipe(**kwargs) | |
| print(f"[timing] edit_image: prep {t1 - t0:.1f}s, pipe {time.time() - t1:.1f}s") | |
| return to_pil_first_frame(out.videos), seed | |
| def _video_duration(source_video, instruction, reference_image=None, num_frames=33, steps=DEFAULT_STEPS, *args, **kwargs): | |
| # Calibrated on the live Space: ~1.15 s/step for a 5-frame image clip and ~2.9 s/step | |
| # for 33 frames at 16:9 (50 steps -> ~148 s); ~10% margin on top. | |
| lat_frames = (max(5, min(65, int(num_frames))) - 1) // 4 + 1 | |
| tokens = lat_frames * 900 | |
| per_step = 0.55 + 0.000306 * tokens + 1.5e-9 * tokens**2 | |
| return int(5 + 1.1 * int(steps) * per_step) | |
| def edit_video( | |
| source_video: str, | |
| instruction: str, | |
| reference_image: Image.Image | None = None, | |
| num_frames: int = 33, | |
| steps: int = DEFAULT_STEPS, | |
| guidance_scale: float = DEFAULT_GUIDANCE, | |
| flow_shift: float = DEFAULT_SHIFT, | |
| seed: int = 123, | |
| randomize_seed: bool = False, | |
| progress=gr.Progress(track_tqdm=True), | |
| ): | |
| """Edit a short video with a text instruction (optionally guided by a reference image). | |
| Args: | |
| source_video: path of the video to edit (the first `num_frames` frames are edited). | |
| instruction: the editing instruction, e.g. "Replace the brown bear with a panda.". | |
| reference_image: optional reference image (switches to reference-guided video editing, tiv2v). | |
| num_frames: number of frames to edit (4n+1, at most 65). | |
| steps: number of denoising steps. | |
| guidance_scale: classifier-free guidance scale. | |
| flow_shift: timestep shift of the sampling schedule. | |
| seed: random seed. | |
| randomize_seed: pick a random seed instead of `seed`. | |
| Returns: | |
| Path of the edited mp4 video and the seed that was used. | |
| """ | |
| from decord import VideoReader | |
| t0 = time.time() | |
| if not source_video: | |
| raise gr.Error("Please upload a video to edit.") | |
| if not instruction or not instruction.strip(): | |
| raise gr.Error("Please enter an editing instruction.") | |
| seed = resolve_seed(seed, randomize_seed) | |
| num_frames = int(num_frames) | |
| num_frames = max(5, min(65, ((num_frames - 1) // 4) * 4 + 1)) | |
| task = "tiv2v" if reference_image is not None else "v2v" | |
| vr = VideoReader(source_video) | |
| total = len(vr) | |
| if total == 0: | |
| raise gr.Error("The video has no frames.") | |
| fps = round(vr.get_avg_fps()) or 12 | |
| indices = list(range(min(total, num_frames))) + [total - 1] * max(0, num_frames - total) | |
| frames = torch.from_numpy(vr.get_batch(indices).asnumpy()).permute(0, 3, 1, 2).contiguous() / 255.0 | |
| del vr | |
| height, width = frames.shape[-2:] | |
| th, tw = video_bucket_size(width, height) | |
| if th / height > tw / width: | |
| resize_size = th, int(width * th / height) | |
| else: | |
| resize_size = int(height * tw / width), tw | |
| frames = transforms.Compose( | |
| [transforms.Resize(resize_size, antialias=True), transforms.CenterCrop((th, tw))] | |
| )(frames) | |
| latents = encode_frames_to_latents(frames, scale=False) | |
| kwargs = dict( | |
| prompt=instruction.strip(), | |
| negative_prompt="", | |
| aspect_ratio=f"{tw}:{th}", | |
| num_inference_steps=int(steps), | |
| guidance_scale=float(guidance_scale), | |
| flow_shift=float(flow_shift), | |
| video_length=num_frames, | |
| seed=seed, | |
| output_type="pt", | |
| return_dict=True, | |
| deepstack=[8, 16, 24], | |
| setclip=True, | |
| enable_vae_tile_parallelism=False, # single process, no torch.distributed | |
| task_type=task, | |
| source_video=[source_video], | |
| source_video_latents=latents, | |
| ) | |
| if task == "tiv2v": | |
| ref_path = tempfile.NamedTemporaryFile(suffix=".png", delete=False).name | |
| reference_image.convert("RGB").save(ref_path) | |
| kwargs["reference_image"] = ref_path | |
| t1 = time.time() | |
| with torch.no_grad(): | |
| out = pipe(**kwargs) | |
| print(f"[timing] edit_video: prep {t1 - t0:.1f}s, pipe {time.time() - t1:.1f}s ({num_frames} frames)") | |
| video = out.videos | |
| if video.ndim == 5: | |
| video = video[0] | |
| vid = (video * 255).clamp(0, 255).to(torch.uint8) | |
| vid = einops.rearrange(vid, "c f h w -> f h w c").cpu().numpy() | |
| out_path = tempfile.NamedTemporaryFile(suffix=".mp4", delete=False).name | |
| imageio.mimwrite(out_path, list(vid), fps=fps, quality=8) | |
| return out_path, seed | |
| # --------------------------------------------------------------------------- | |
| # UI | |
| # --------------------------------------------------------------------------- | |
| CSS = """ | |
| #col-container { max-width: 1200px; margin: 0 auto; } | |
| .dark .gradio-container { color: var(--body-text-color); } | |
| """ | |
| with gr.Blocks() as demo: | |
| with gr.Column(elem_id="col-container"): | |
| gr.Markdown( | |
| "# VitronEdit (UniVEdit): universal visual editing\n" | |
| "Edit images and videos with a text instruction, and optionally a reference image. " | |
| "This is a LoRA plus modality tokens on top of " | |
| "[HY-OmniWeaving](https://huggingface.co/tencent/HY-OmniWeaving) (HunyuanVideo-1.5).\n\n" | |
| "[Paper](https://huggingface.co/papers/2610.27049) · " | |
| "[Code](https://github.com/Vitron-LLM/VitronEdit-Code) · " | |
| "[Model](https://huggingface.co/SanRiiiii/UniVEdit)" | |
| ) | |
| with gr.Tabs(): | |
| # ---------------- Image editing ---------------- | |
| with gr.Tab("Image editing"): | |
| with gr.Row(): | |
| with gr.Column(): | |
| img_src = gr.Image(label="Image to edit", type="pil", height=380) | |
| img_prompt = gr.Textbox( | |
| label="Instruction", | |
| placeholder='e.g. "replace the scarf with a hat". With a reference image, call it [V*].', | |
| lines=2, | |
| ) | |
| with gr.Accordion("Reference image (optional)", open=False): | |
| img_ref = gr.Image(label="Reference image", type="pil", height=240) | |
| img_btn = gr.Button("Edit image", variant="primary") | |
| with gr.Accordion("Advanced settings", open=False): | |
| img_steps = gr.Slider(10, 50, value=DEFAULT_STEPS, step=1, label="Steps") | |
| img_cfg = gr.Slider(1.0, 10.0, value=DEFAULT_GUIDANCE, step=0.5, label="Guidance scale") | |
| img_shift = gr.Slider(1.0, 10.0, value=DEFAULT_SHIFT, step=0.5, label="Flow shift") | |
| img_seed = gr.Slider(0, MAX_SEED, value=123, step=1, label="Seed") | |
| img_rand = gr.Checkbox(label="Randomize seed", value=False) | |
| with gr.Column(): | |
| img_out = gr.Image(label="Edited image", height=480) | |
| img_btn.click( | |
| edit_image, | |
| inputs=[img_src, img_prompt, img_ref, img_steps, img_cfg, img_shift, img_seed, img_rand], | |
| outputs=[img_out, img_seed], | |
| api_name="edit_image", | |
| ) | |
| gr.Examples( | |
| examples=[ | |
| ["examples/cat_tabby.jpg", "add a small red bow tie under the cat's chin"], | |
| ["examples/living_room_blue_couch.jpg", "replace the blue sofa with a brown leather sofa"], | |
| ["examples/rubber_ducks.jpg", "remove the smallest rubber duck on the left"], | |
| ["examples/lighthouse_coast.jpg", "Make the style of the image a watercolor painting"], | |
| ], | |
| inputs=[img_src, img_prompt], | |
| outputs=[img_out, img_seed], | |
| fn=edit_image, | |
| cache_examples=True, | |
| cache_mode="lazy", | |
| label="Image examples", | |
| ) | |
| # ---------------- Video editing ---------------- | |
| with gr.Tab("Video editing"): | |
| with gr.Row(): | |
| with gr.Column(): | |
| vid_src = gr.Video(label="Video to edit", height=340) | |
| vid_prompt = gr.Textbox( | |
| label="Instruction", | |
| placeholder='e.g. "Replace the brown bear with a panda."', | |
| lines=2, | |
| ) | |
| with gr.Accordion("Reference image (optional)", open=False): | |
| vid_ref = gr.Image(label="Reference image", type="pil", height=240) | |
| vid_btn = gr.Button("Edit video", variant="primary") | |
| with gr.Accordion("Advanced settings", open=False): | |
| vid_frames = gr.Slider(5, 65, value=33, step=4, label="Frames (4n+1)") | |
| vid_steps = gr.Slider(10, 50, value=DEFAULT_STEPS, step=1, label="Steps") | |
| vid_cfg = gr.Slider(1.0, 10.0, value=DEFAULT_GUIDANCE, step=0.5, label="Guidance scale") | |
| vid_shift = gr.Slider(1.0, 10.0, value=DEFAULT_SHIFT, step=0.5, label="Flow shift") | |
| vid_seed = gr.Slider(0, MAX_SEED, value=123, step=1, label="Seed") | |
| vid_rand = gr.Checkbox(label="Randomize seed", value=False) | |
| with gr.Column(): | |
| vid_out = gr.Video(label="Edited video", autoplay=True, height=420) | |
| vid_btn.click( | |
| edit_video, | |
| inputs=[vid_src, vid_prompt, vid_ref, vid_frames, vid_steps, vid_cfg, vid_shift, vid_seed, vid_rand], | |
| outputs=[vid_out, vid_seed], | |
| api_name="edit_video", | |
| ) | |
| gr.Examples( | |
| examples=[ | |
| ["examples/cat.mp4", "Replace the cat with a tiger."], | |
| ["examples/border_collie_running.mp4", "Replace the dog with a red fox."], | |
| ], | |
| inputs=[vid_src, vid_prompt], | |
| outputs=[vid_out, vid_seed], | |
| fn=edit_video, | |
| cache_examples=True, | |
| cache_mode="lazy", | |
| label="Video examples", | |
| ) | |
| if __name__ == "__main__": | |
| demo.queue().launch(theme=gr.themes.Citrus(), css=CSS, mcp_server=True) | |