Spaces:
Running on Zero
Running on Zero
| # app.py | |
| import uuid | |
| import gc | |
| from pathlib import Path | |
| import numpy as np | |
| import torch | |
| import gradio as gr | |
| import spaces | |
| import os | |
| from diffusers import ( | |
| WanImageToVideoPipeline, | |
| WanTransformer3DModel, | |
| FlowMatchEulerDiscreteScheduler, | |
| AutoencoderKLWan, | |
| ) | |
| from diffusers.utils import export_to_video | |
| from copyright_classifier import contains_copyrighted_ip | |
| BASE_MODEL_ID = "Wan-AI/Wan2.2-I2V-A14B-Diffusers" | |
| I2V_TRANSFORMER_REPO = "aidealab/AnimeGen-I2V" | |
| OUTPUT_DIR = Path("outputs") | |
| OUTPUT_DIR.mkdir(exist_ok=True) | |
| token = os.getenv("HF_TOKEN") | |
| NG_WORDS=eval(os.getenv("NG_WORD")) | |
| NG_WORDS.extend(eval(os.getenv("NG_WORD_JA"))) | |
| def clear_memory(): | |
| gc.collect() | |
| if torch.cuda.is_available(): | |
| torch.cuda.empty_cache() | |
| torch.cuda.ipc_collect() | |
| def load_pipeline(): | |
| scheduler = FlowMatchEulerDiscreteScheduler(shift=3.0) | |
| transformer_high = WanTransformer3DModel.from_pretrained( | |
| I2V_TRANSFORMER_REPO, | |
| subfolder="transformer", | |
| torch_dtype=torch.bfloat16, | |
| ) | |
| transformer_low = WanTransformer3DModel.from_pretrained( | |
| I2V_TRANSFORMER_REPO, | |
| subfolder="transformer_2", | |
| torch_dtype=torch.bfloat16, | |
| ) | |
| vae = AutoencoderKLWan.from_pretrained( | |
| BASE_MODEL_ID, | |
| subfolder="vae", | |
| torch_dtype=torch.float32, | |
| ) | |
| pipe = WanImageToVideoPipeline.from_pretrained( | |
| BASE_MODEL_ID, | |
| transformer=transformer_high, | |
| transformer_2=transformer_low, | |
| scheduler=scheduler, | |
| vae=vae, | |
| torch_dtype=torch.bfloat16, | |
| ) | |
| pipe.load_lora_weights( | |
| "lightx2v/Wan2.2-Lightning", | |
| weight_name=( | |
| "Wan2.2-I2V-A14B-4steps-lora-rank64-Seko-V1/" | |
| "high_noise_model.safetensors" | |
| ), | |
| adapter_name="high", | |
| ) | |
| pipe.load_lora_weights( | |
| "lightx2v/Wan2.2-Lightning", | |
| weight_name=( | |
| "Wan2.2-I2V-A14B-4steps-lora-rank64-Seko-V1/" | |
| "low_noise_model.safetensors" | |
| ), | |
| adapter_name="low", | |
| load_into_transformer_2=True, | |
| ) | |
| pipe.set_adapters( | |
| ["high", "low"], | |
| adapter_weights=[1.0, 1.0], | |
| ) | |
| transformer_high.enable_layerwise_casting( | |
| storage_dtype=torch.float8_e4m3fn, | |
| compute_dtype=torch.bfloat16, | |
| ) | |
| transformer_low.enable_layerwise_casting( | |
| storage_dtype=torch.float8_e4m3fn, | |
| compute_dtype=torch.bfloat16, | |
| ) | |
| pipe.enable_model_cpu_offload() | |
| return pipe | |
| def resize_first_and_last_images(first_image, last_image, max_area): | |
| aspect_ratio = first_image.height / first_image.width | |
| mod_value = pipe.vae_scale_factor_spatial * pipe.transformer.config.patch_size[1] | |
| height = round(np.sqrt(max_area * aspect_ratio)) | |
| width = round(np.sqrt(max_area / aspect_ratio)) | |
| height = max(mod_value, height // mod_value * mod_value) | |
| width = max(mod_value, width // mod_value * mod_value) | |
| first_image = first_image.resize((width, height)) | |
| last_image = last_image.resize((width, height)) | |
| return first_image, last_image, width, height | |
| # 起動時にロード | |
| pipe = load_pipeline() | |
| def generate_video( | |
| first_image, | |
| last_image, | |
| prompt, | |
| negative_prompt, | |
| ): | |
| if first_image is None: | |
| raise gr.Error("Please upload the first image.") | |
| if last_image is None: | |
| raise gr.Error("Please upload the last image.") | |
| clear_memory() | |
| max_area = 832*480 | |
| first_image, last_image, width, height = resize_first_and_last_images( | |
| first_image, | |
| last_image, | |
| max_area, | |
| ) | |
| num_frames = int(16 * 3 + 1) | |
| full_prompt = "Japanese anime style, " + prompt.strip() | |
| # prompt filtering | |
| print(NG_WORDS) | |
| prompt_for_check = full_prompt.lower() | |
| for word in NG_WORDS: | |
| if word in prompt_for_check: | |
| print(f"error: {word} .") | |
| raise Exception() | |
| # LLM filtering | |
| result = contains_copyrighted_ip(full_prompt) | |
| if(result): | |
| print(f"error: {full_prompt} .") | |
| raise Exception() | |
| frames = pipe( | |
| image=first_image, | |
| last_image=last_image, | |
| prompt=full_prompt, | |
| negative_prompt=negative_prompt, | |
| height=height, | |
| width=width, | |
| num_frames=num_frames, | |
| guidance_scale=1.0, | |
| num_inference_steps=4, | |
| ).frames[0] | |
| output_path = OUTPUT_DIR / f"{uuid.uuid4().hex}.mp4" | |
| export_to_video(frames, str(output_path), fps=16) | |
| clear_memory() | |
| return str(output_path) | |
| default_prompt = "Make this character press both hands together in prayer and close their eyes." | |
| default_negative_prompt = "3d, cg, photo, stop, wait" | |
| with gr.Blocks(title="AnimeGen Frame Interpolation") as demo: | |
| gr.Markdown("# AnimeGen Frame Interpolation") | |
| gr.Markdown( | |
| "Generate a video from a first frame and a last frame using AnimeGen I2V." | |
| ) | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| with gr.Row(): | |
| first_image = gr.Image( | |
| label="First image", | |
| type="pil", | |
| ) | |
| last_image = gr.Image( | |
| label="Last image", | |
| type="pil", | |
| ) | |
| prompt = gr.Textbox( | |
| label="Prompt", | |
| value=default_prompt, | |
| lines=4, | |
| ) | |
| negative_prompt = gr.Textbox( | |
| label="Negative prompt", | |
| value=default_negative_prompt, | |
| lines=2, | |
| ) | |
| generate_button = gr.Button("Generate", variant="primary") | |
| with gr.Column(scale=1): | |
| video = gr.Video(label="Output") | |
| generate_button.click( | |
| fn=generate_video, | |
| inputs=[ | |
| first_image, | |
| last_image, | |
| prompt, | |
| negative_prompt, | |
| ], | |
| outputs=[ | |
| video, | |
| ], | |
| ) | |
| if __name__ == "__main__": | |
| demo.queue(max_size=10).launch() |