CoolFace
Apppublic

obvious-research/OnlyFlow

sourceHugging Facemitupdated 2y agoView on Hugging Face
1likes
app.py641 linesDownload Raw Back to root
1import os2 3import imageio4import numpy as np5import torch6import random7 8import spaces9 10import gradio as gr11 12import torchvision13import torchvision.transforms as T14from einops import rearrange15from huggingface_hub import hf_hub_download16from torchvision.models.optical_flow import raft_large, Raft_Large_Weights17from torchvision.utils import flow_to_image18 19from diffusers import AutoencoderKL, MotionAdapter, UNet2DConditionModel20from diffusers import DDIMScheduler21from transformers import CLIPTextModel, CLIPTokenizer22 23from onlyflow.models.flow_adaptor import FlowEncoder, FlowAdaptor24from onlyflow.models.unet import UNetMotionModel25from onlyflow.pipelines.pipeline_animation_long import FlowCtrlPipeline26from tools.optical_flow import get_optical_flow27 28 29def save_videos_grid(videos: torch.Tensor, path: str, rescale=False, n_rows=6, fps=8):30    videos = rearrange(videos, "b c t h w -> t b c h w")31    outputs = []32    for x in videos:33        x = torchvision.utils.make_grid(x, nrow=n_rows)34        x = x.transpose(0, 1).transpose(1, 2).squeeze(-1)35        if rescale:36            x = (x + 1.0) / 2.0  # -1,1 -> 0,137        x = (x * 255).numpy().astype(np.uint8)38        outputs.append(x)39 40    os.makedirs(os.path.dirname(path), exist_ok=True)41    imageio.mimsave(path, outputs, fps=fps)42 43css = """44.toolbutton {45    margin-buttom: 0em 0em 0em 0em;46    max-width: 2.5em;47    min-width: 2.5em !important;48    height: 2.5em;49}50"""51 52 53class AnimateController:54    def __init__(self):55 56        # config dirs57        self.basedir                = os.getcwd()58        self.stable_diffusion_dir   = os.path.join(self.basedir, "models", "StableDiffusion")59        self.motion_module_dir      = os.path.join(self.basedir, "models", "Motion_Module")60        self.personalized_model_dir = os.path.join(self.basedir, "models", "DreamBooth_LoRA")61        self.savedir                = os.path.join(self.basedir, "samples")62        os.makedirs(self.savedir, exist_ok=True)63 64 65        ckpt_path = hf_hub_download('obvious-research/onlyflow', 'weights_fp16.ckpt')66        ckpt = torch.load(ckpt_path, map_location="cpu", weights_only=True)67        self.flow_encoder_state_dict = ckpt['flow_encoder_state_dict']68        self.attention_processor_state_dict = ckpt['attention_processor_state_dict']69 70        self.tokenizer             = None71        self.text_encoder          = None72        self.vae                   = None73        self.unet                  = None74        self.motion_adapter        = None75 76    def update_base_model(self, base_model_id, progress=gr.Progress()):77 78        progress(0, desc="Starting...")79 80        self.tokenizer = CLIPTokenizer.from_pretrained(base_model_id, subfolder="tokenizer")81        self.text_encoder = CLIPTextModel.from_pretrained(base_model_id, subfolder="text_encoder")82        self.vae = AutoencoderKL.from_pretrained(base_model_id, subfolder="vae")83        self.unet = UNet2DConditionModel.from_pretrained(base_model_id, subfolder="unet")84 85        return base_model_id86 87    def update_motion_module(self, motion_module_id, progress=gr.Progress()):88        self.motion_adapter = MotionAdapter.from_pretrained(motion_module_id)89 90    def animate(91            self,92            id_base_model,93            id_motion_module,94            prompt_textbox_positive,95            prompt_textbox_negative,96            seed_textbox,97            input_video,98            height,99            width,100            flow_scale,101            cfg,102            diffusion_steps,103            temporal_ds,104            ctx_stride105    ):106        #if any([x is None for x in [self.tokenizer, self.text_encoder, self.vae, self.unet, self.motion_adapter]]) or isinstance(self.unet, str):107        self.update_base_model(id_base_model)108        self.update_motion_module(id_motion_module)109 110        self.unet = UNetMotionModel.from_unet2d(111            self.unet,112            motion_adapter=self.motion_adapter113        )114 115        self.raft = raft_large(weights=Raft_Large_Weights.DEFAULT, progress=False).eval()116 117        self.flow_encoder = FlowEncoder(118            downscale_factor=8,119            channels=[320, 640, 1280, 1280],120            nums_rb=2,121            ksize=1,122            sk=True,123            use_conv=False,124            compression_factor=1,125            temporal_attention_nhead=8,126            positional_embeddings="sinusoidal",127            num_positional_embeddings=16,128            checkpointing=False129        ).eval()130 131        self.vae.requires_grad_(False)132        self.text_encoder.requires_grad_(False)133        self.unet.requires_grad_(False)134        self.raft.requires_grad_(False)135        self.flow_encoder.requires_grad_(False)136 137        self.unet.set_all_attn(138            flow_channels=[320, 640, 1280, 1280],139            add_spatial=False,140            add_temporal=True,141            encoder_only=False,142            query_condition=True,143            key_value_condition=True,144            flow_scale=1.0,145        )146 147        self.flow_adaptor = FlowAdaptor(self.unet, self.flow_encoder).eval()148 149        # load the flow encoder weights150        pose_enc_m, pose_enc_u = self.flow_adaptor.flow_encoder.load_state_dict(151            self.flow_encoder_state_dict,152            strict=False153        )154        assert len(pose_enc_m) == 0 and len(pose_enc_u) == 0155 156        # load the attention processor weights157        _, attention_processor_u = self.flow_adaptor.unet.load_state_dict(158            self.attention_processor_state_dict,159            strict=False160        )161        assert len(attention_processor_u) == 0162 163        pipeline = FlowCtrlPipeline(164            vae=self.vae,165            text_encoder=self.text_encoder,166            tokenizer=self.tokenizer,167            unet=self.unet,168            motion_adapter=self.motion_adapter,169            flow_encoder=self.flow_encoder,170            scheduler=DDIMScheduler.from_pretrained(id_base_model, subfolder="scheduler"),171        )172 173        if int(seed_textbox) > 0:174            seed = int(seed_textbox)175        else:176            seed = random.randint(1, int(1e16))177 178        return animate_diffusion(seed, pipeline, self.raft, input_video, prompt_textbox_positive, prompt_textbox_negative, width, height, flow_scale, cfg, diffusion_steps, temporal_ds, ctx_stride)179 180@spaces.GPU(duration=150)181def animate_diffusion(seed, pipeline, raft_model, base_video, prompt_textbox, negative_prompt_textbox, width_slider, height_slider, flow_scale, cfg, diffusion_steps, temporal_ds, context_stride):182    savedir = './samples'183    device = "cuda" if torch.cuda.is_available() else "mps" if torch.backends.mps.is_available() else "cpu"184    generator = torch.Generator(device="cpu")185    generator.manual_seed(seed)186 187    raft_model = raft_model.to(device)188    pipeline = pipeline.to(device)189 190    pixel_values = torchvision.io.read_video(base_video, output_format="TCHW", pts_unit='sec')[0][::temporal_ds]191    print("Video loaded, shape:", pixel_values.shape)192    if width_slider/height_slider > pixel_values.shape[3]/pixel_values.shape[2]:193        print("Resizing video to fit width cause input video is not wide enough")194        temp_height = int(width_slider * pixel_values.shape[2]/pixel_values.shape[3])195        temp_width = width_slider196    else:197        print("Resizing video to fit height cause input video is not tall enough")198        temp_height = height_slider199        temp_width = int(height_slider * pixel_values.shape[3]/pixel_values.shape[2])200    print("Resizing video to:", temp_height, temp_width)201    pixel_values = T.Resize((temp_height, temp_width))(pixel_values)202    pixel_values = T.CenterCrop((height_slider, width_slider))(pixel_values)203    pixel_values = T.ConvertImageDtype(torch.float32)(pixel_values)[None, ...].contiguous().to(device)204 205    save_sample_path_input = os.path.join(savedir, f"input.mp4")206    pixel_values_save = pixel_values[0] * 255207    pixel_values_save = pixel_values_save.cpu()208    pixel_values_save = torch.permute(pixel_values_save, (0, 2, 3, 1))209    torchvision.io.write_video(save_sample_path_input, pixel_values_save, fps=8)210    del pixel_values_save211 212    print("Video loaded, shape:", pixel_values.shape)213    flow = get_optical_flow(214        raft_model,215        (pixel_values * 2) - 1,216        pixel_values.shape[1] - 1,217        encode_chunk_size=16,218    ).to('cpu')219 220    sample_flow = (flow_to_image(rearrange(flow[0], "c f h w -> f c h w")))  # N, 3, H, W221    save_sample_path_flow = os.path.join(savedir, f"flow.mp4")222    sample_flow = (sample_flow).cpu().to(torch.uint8).permute(0, 2, 3, 1)223    torchvision.io.write_video(save_sample_path_flow, sample_flow, fps=8)224    del sample_flow225 226    original_flow_shape = flow.shape227    print("Optical flow computed, shape:", flow.shape)228    if flow.shape[2] < 16:229        print("Video is too short, padding to 16 frames")230        video_length = 16231        n = 16 - flow.shape[2]232        # create a tensor containing the last frame optical flow repeated n times233        to_add = flow[:, :, -1].unsqueeze(2).expand(-1, -1, n, -1, -1)234        flow = torch.cat([flow, to_add], dim=2).to(device)235    elif flow.shape[2] > 16:236        print("Video is too long, enabling windowing")237        print("Enabling model CPU offload")238        pipeline.enable_model_cpu_offload()239        print("Enabling VAE slicing")240        pipeline.enable_vae_slicing()241        print("Enabling VAE tiling")242        pipeline.enable_vae_tiling()243 244        print("Enabling free noise")245        pipeline.enable_free_noise(246            context_length=16,247            context_stride=context_stride,248        )249 250        import math251 252        def find_divisors(n: int):253            """254            Return sorted list of all positive divisors of n.255            Uses a sqrt(n) approach for efficiency.256            """257            divs = set()258            limit = int(math.isqrt(n))259            for i in range(1, limit + 1):260                if n % i == 0:261                    divs.add(i)262                    divs.add(n // i)263            return sorted(divs)264 265        def multiples_in_range(k: int, min_val: int, max_val: int):266            """267            Return all multiples of k within [min_val, max_val].268            """269            if k == 0:270                return []271 272            # First multiple of k >= min_val273            start = ((min_val + k - 1) // k) * k274            # Last multiple of k <= max_val275            end = (max_val // k) * k276 277            return list(range(start, end + 1, k)) if start <= end else []278 279        def adjust_video_length(original_length: int,280                                context_stride: int,281                                chunk_size: int,282                                temporal_split_size: int) -> int:283            """284            Find the minimal video_length >= original_length satisfying:285              1) (video_length - 16) is divisible by context_stride.286              2) EITHER (2*video_length) is divisible by temporal_split_size287                 OR (2*video_length) is divisible by chunk_size288                 (when 2*video_length is not multiple of temporal_split_size).289            """290 291            # We start at least at 16 (though in practice original_length likely > 16)292            candidate = max(original_length, 16)293 294            # We want (candidate - 16) % context_stride == 0295            # so let n be the multiple to step.296            # n is how many times we add `context_stride` beyond 16.297            # This ensures (candidate - 16) is a multiple of context_stride.298            # Then we check the second condition, else keep stepping.299 300            # If candidate < 16, bump it to 16301            if candidate < 16:302                candidate = 16303 304            # Make sure we jump to the correct "starting multiple" of context_stride305            offset = (candidate - 16) % context_stride306            if offset != 0:307                candidate += (context_stride - offset)  # jump to the next multiple308 309            while True:310                # Condition: (candidate - 16) is multiple of context_stride (already enforced by stepping)311                # Check second part:312                # - if (2*candidate) % temporal_split_size == 0, we are good313                # - else we require (2*candidate) % chunk_size == 0314                twoL = 2 * candidate315                if (twoL % temporal_split_size == 0) or (twoL % chunk_size == 0):316                    return candidate317 318                # Go to next valid candidate319                candidate += context_stride320 321        def find_valid_configs(original_video_length: int,322                               width: int,323                               height: int,324                               context_stride: int):325            """326            Generate all valid tuples (chunk_size, spatial_split_size, temporal_split_size, video_length)327            subject to the constraints:328              1) chunk_size divides temporal_split_size329              2) chunk_size divides spatial_split_size330              3) chunk_size divides (2 * (width//64) * (height//64))331              4) if (2*video_length) % temporal_split_size != 0, then chunk_size divides (2*video_length)332              5) context_stride divides (video_length - 16)333              6) 128 <= spatial_split_size <= 512334              7) 1 <= temporal_split_size <= 32335              8) 1 <= chunk_size <= 16336 337            We allow increasing original_video_length minimally if needed to satisfy constraints #4 and #5.338            """339 340            factor = 2 * (width // 64) * (height // 64)341 342            # 1) find all possible chunk_size as divisors of factor, in [1..16]343            possible_chunks = [d for d in find_divisors(factor) if 1 <= d <= 32]344 345            # For storing results346            valid_tuples = []347 348            for chunk_size in possible_chunks:349                # 2) generate all spatial_split_size in [128..512] that are multiples of chunk_size350                spatial_splits = multiples_in_range(chunk_size, 480, 512)351 352                # 3) generate all temporal_split_size in [1..32] that are multiples of chunk_size353                temporal_splits = multiples_in_range(chunk_size, 1, 32)354 355                for ssp in spatial_splits:356                    for tsp in temporal_splits:357                        # 4) & 5) Adjust video_length minimally to satisfy constraints358                        final_length = adjust_video_length(original_video_length,359                                                           context_stride,360                                                           chunk_size,361                                                           tsp)362                        # Now we have a valid (chunk_size, ssp, tsp, final_length)363                        valid_tuples.append((chunk_size, ssp, tsp, final_length))364 365            return valid_tuples366 367        def find_pareto_optimal(configs):368            """369            Given a list of tuples (chunk_size, spatial_split_size, temporal_split_size, video_length),370            return the Pareto-optimal subset under the criteria:371              - chunk_size: larger is better372              - spatial_split_size: larger is better373              - temporal_split_size: larger is better374              - video_length: smaller is better375            """376 377            def dominates(A, B):378                cA, sA, tA, lA = A379                cB, sB, tB, lB = B380 381                # A dominates B if:382                #   cA >= cB, sA >= sB, tA >= tB, and lA <= lB383                #   AND at least one of these is a strict inequality.384 385                better_or_equal = (cA >= cB) and (tA >= tB) and (lA <= lB)386                strictly_better = (cA > cB) or (tA > tB) or (lA < lB)387 388                return better_or_equal and strictly_better389 390            pareto = []391            for i, cfg_i in enumerate(configs):392                # Check if cfg_i is dominated by any cfg_j393                is_dominated = False394                for j, cfg_j in enumerate(configs):395                    if i == j:396                        continue397                    if dominates(cfg_j, cfg_i):398                        is_dominated = True399                        break400                if not is_dominated:401                    pareto.append(cfg_i)402 403            return pareto404 405        print("Finding valid configurations...")406        valid_configs = find_valid_configs(407            original_video_length=flow.shape[2],408            width=width_slider,409            height=height_slider,410            context_stride=context_stride411        )412 413        print("Found", len(valid_configs), "valid configurations")414        print("Finding Pareto-optimal configurations...")415        pareto_optimal = find_pareto_optimal(valid_configs)416 417        print("Found", pareto_optimal)418 419        criteria = lambda cs, sss, tss, vl: cs + tss - 3 * int(abs(flow.shape[2] - vl) / 10)420        pareto_optimal.sort(key=lambda x: criteria(*x), reverse=True)421 422        print("Found sorted", pareto_optimal)423 424        solution = pareto_optimal[0]425        chunk_size, spatial_split_size, temporal_split_size, video_length = solution426 427        n = video_length - original_flow_shape[2]428        to_add = flow[:, :, -1].unsqueeze(2).expand(-1, -1, n, -1, -1)429        flow = torch.cat([flow, to_add], dim=2)430 431        pipeline.enable_free_noise_split_inference(432            temporal_split_size=temporal_split_size,433            spatial_split_size=spatial_split_size434        )435        pipeline.unet.enable_forward_chunking(chunk_size)436 437        print("Chunking enabled with chunk size:", chunk_size)438        print("Temporal split size:", temporal_split_size)439        print("Spatial split size:", spatial_split_size)440        print("Context stride:", context_stride)441        print("Temporal downscale:", temporal_ds)442        print("Video length:", video_length)443        print("Flow shape:", flow.shape)444    else:445        print("Video is just right, no padding or windowing needed")446        flow = flow.to(device)447        video_length = flow.shape[2]448 449    sample_vid = pipeline(450        prompt_textbox,451        negative_prompt=negative_prompt_textbox,452        optical_flow=flow,453        num_inference_steps=diffusion_steps,454        guidance_scale=cfg,455        width=width_slider,456        height=height_slider,457        num_frames=video_length,458        val_scale_factor_temporal=flow_scale,459        generator=generator,460    ).frames[0]461 462    del flow463    if device == "cuda":464        torch.cuda.synchronize()465        torch.cuda.empty_cache()466 467    save_sample_path_video = os.path.join(savedir, f"sample.mp4")468    sample_vid = sample_vid[:original_flow_shape[2]] * 255.469    sample_vid = sample_vid.cpu().numpy()470    sample_vid = np.transpose(sample_vid, axes=(0, 2, 3, 1))471    torchvision.io.write_video(save_sample_path_video, sample_vid, fps=8)472 473    return gr.Video(value=save_sample_path_flow), gr.Video(value=save_sample_path_video)474 475controller = AnimateController()476 477 478def find_closest_ratio(target_ratio):479    width_list = list(reversed(range(256, 1025, 64)))480    height_list = list(reversed(range(256, 1025, 64)))481    ratio_list = [(h, w, w/h) for h in height_list for w in width_list]482    ratio_list.sort(key=lambda x: abs(x[2] - target_ratio))483    ratio_list = list(filter(lambda x: x[2] == ratio_list[0][2], ratio_list))484    ratio_list.sort(key=lambda x: abs(x[0]*x[1] - 512*512))485    return ratio_list[0][:2]486 487 488def find_dimension(video):489    import av490    container = av.open(open(video, 'rb'))491    height, width = container.streams.video[0].height, container.streams.video[0].width492    target_ratio = width / height493    return find_closest_ratio(target_ratio)494 495 496def ui():497    with gr.Blocks(css=css) as demo:498        gr.Markdown(499            """500            # <p style="text-align:center;">OnlyFlow: Optical Flow based Motion Conditioning for Video Diffusion Models</p>501            Mathis Koroglu, Hugo Caselles-Dupré, Guillaume Jeanneret Sanmiguel, Matthieu Cord<br>502            [Arxiv Report](https://arxiv.org/abs/2411.10501) | [Project Page](https://obvious-research.github.io/onlyflow/) | [Github](https://github.com/obvious-research/onlyflow/)503            """504        )505        gr.Markdown(506            """507            ### Quick Start:508 509            1. Select desired `Base Model`.510            2. Select `Motion Module`. We recommend trying guoyww/animatediff-motion-adapter-v1-5-3 for the best results.511            3. Provide `Positive Prompt` and `Negative Prompt`. You are encouraged to refer to each model's webpage on HuggingFace Hub or CivitAI to learn how to write prompts for them.512            4. Upload a video to extract optical flow from.513            5. Select a 'Flow Scale' to modulate the input video optical flow conditioning.514            6. Select a 'CFG' and 'Diffusion Steps' to control the quality of the generated video and prompt adherence.515            7. Select a 'Temporal Downsample' to reduce the number of frames in the input video.516            8. If you want to use a custom dimension, check the `Custom Dimension` box and adjust the `Width` and `Height` sliders.517            9. If the video is too long, you can adjust the generation window offset with the `Context Stride` slider.518            10. Click `Generate`, wait for ~1/3 min, and enjoy the result!519 520            If you have any error concerning GPU limits, please try again later when your ZeroGPU quota is reset, or try with a shorter video.521            Otherwise, you can also duplicate this space and select a custom GPU plan.522            """523        )524        with gr.Row():525            with gr.Column():526 527                gr.Markdown("# INPUTS")528 529                with gr.Row(equal_height=True, show_progress=True):530                    base_model = gr.Dropdown(531                        label="Select or type a base model id",532                        choices=[533                        "stable-diffusion-v1-5/stable-diffusion-v1-5",534                        "digiplay/Photon_v1",535                    ],536                        interactive=True,537                        scale=4,538                        allow_custom_value=True,539                        show_label=True540                    )541                    base_model_btn = gr.Button(value="Update", scale=1, size='lg')542                with gr.Row(equal_height=True, show_progress=True):543                    motion_module  = gr.Dropdown(544                        label="Select or type a motion module id",545                        choices=[546                            "guoyww/animatediff-motion-adapter-v1-5-3",547                            "guoyww/animatediff-motion-adapter-v1-5-2"548                        ],549                        interactive=True,550                        scale=4551                    )552                    motion_module_btn = gr.Button(value="Update", scale=1, size='lg')553 554                base_model_btn.click(fn=controller.update_base_model, inputs=[base_model])555                motion_module_btn.click(fn=controller.update_motion_module, inputs=[motion_module])556 557                prompt_textbox_positive = gr.Textbox(label="Positive Prompt", lines=3)558                prompt_textbox_negative = gr.Textbox(label="Negative Prompt", lines=2, value="worst quality, low quality, nsfw, logo")559 560                flow_scale = gr.Slider(label="Flow Scale", value=1.0, minimum=0, maximum=2, step=0.025)561                diffusion_steps = gr.Slider(label="Diffusion Steps", value=25, minimum=0, maximum=100, step=1)562                cfg = gr.Slider(label="CFG", value=7.5, minimum=0, maximum=30, step=0.1)563 564                temporal_ds = gr.Slider(label="Temporal Downsample", value=1, minimum=1, maximum=30, step=1)565 566                input_video = gr.Video(label="Input Video", interactive=True)567                ctx_stride = gr.State(12)568 569                with gr.Accordion("Advanced", open=False):570                    use_custom_dim = gr.Checkbox(label="Custom Dimension", value=False)571 572                    with gr.Row(equal_height=True):573 574                        height, width = gr.State(512), gr.State(512)575 576                        @gr.render(inputs=[use_custom_dim, input_video])577                        def render_custom_dim(use_custom_dim, input_video):578                            if input_video is not None:579                                loc_height, loc_width = find_dimension(input_video)580                            else:581                                loc_height, loc_width = 512, 512582                            slider_width = gr.Slider(label="Width", value=loc_width, minimum=256, maximum=1024,583                                                     step=64, visible=use_custom_dim)584                            slider_height = gr.Slider(label="Height", value=loc_height, minimum=256, maximum=1024,585                                                      step=64, visible=use_custom_dim)586 587                            slider_width.change(lambda x: x, inputs=[slider_width], outputs=[width])588                            slider_height.change(lambda x: x, inputs=[slider_height], outputs=[height])589 590 591                    with gr.Row():592                        @gr.render(inputs=input_video)593                        def render_ctx_stride(input_video):594                            if input_video is not None:595                                video  = open(input_video, 'rb')596                                import av597                                container = av.open(video)598                                num_frames = container.streams.video[0].frames599                                if num_frames > 17:600                                    stride_slider = gr.Slider(label="Context Stride", value=12, minimum=1, maximum=16, step=1)601                                    stride_slider.input(lambda x: x, inputs=[stride_slider], outputs=[ctx_stride])602                                if num_frames > 32:603                                    gr.Warning(f"Video is long ({num_frames} frames), consider using a shorter video, increasing the context stride, or selecting a custom GPU plan.")604                                elif num_frames > 64:605                                    raise gr.Error(f"Video is too long ({num_frames} frames), please use a shorter video, increase the context stride, or select a custom GPU plan. The current parameters won't allow generation on ZeroGPU.")606 607                    with gr.Row(equal_height=True):608                        seed_textbox = gr.Textbox(label="Seed",  value='-1')609 610                        seed_button  = gr.Button(value="\U0001F3B2", elem_classes="toolbutton")611                        seed_button.click(612                            fn=lambda: random.randint(1, int(1e16)),613                            inputs=[],614                            outputs=[seed_textbox]615                        )616 617                with gr.Row():618                    clear_btn = gr.ClearButton(value="Clear & Reset", size='lg', variant='secondary', scale=1)619                    generate_button = gr.Button(value="Generate", variant='primary', scale=2, size='lg')620 621                    clear_btn.add([base_model, motion_module, input_video, prompt_textbox_positive, prompt_textbox_negative, seed_textbox, use_custom_dim, ctx_stride])622 623            with gr.Column():624 625                gr.Markdown("# OUTPUTS")626 627                result_optical_flow = gr.Video(label="Optical Flow", interactive=False)628                result_video = gr.Video(label="Generated Animation", interactive=False)629 630            inputs  = [base_model, motion_module, prompt_textbox_positive, prompt_textbox_negative, seed_textbox, input_video, height, width, flow_scale, cfg, diffusion_steps, temporal_ds, ctx_stride]631            outputs = [result_optical_flow, result_video]632 633            generate_button.click(fn=controller.animate, inputs=inputs, outputs=outputs)634 635    return demo636 637 638if __name__ == "__main__":639    demo = ui()640    demo.queue(max_size=20)641    demo.launch()