Motinon-AI / app.py
patrickluan11's picture
Update app.py
5131c02 verified
Raw History Blame Contribute Delete
4.44 kB
import os
import tempfile
from pathlib import Path
# Alguns workers ZeroGPU não expõem NVML completamente ao allocator do PyTorch.
# Desativar o cache evita o assert interno sem alterar o resultado do modelo.
os.environ.setdefault("PYTORCH_NO_CUDA_MEMORY_CACHING", "1")
import gradio as gr
import spaces
import torch
from diffusers import CogVideoXImageToVideoPipeline
from diffusers.utils import export_to_video
from PIL import Image
MODEL_ID = "THUDM/CogVideoX1.5-5B-I2V"
PIPE = None
def get_pipeline():
global PIPE
if PIPE is None:
print(f"Loading model: {MODEL_ID}", flush=True)
PIPE = CogVideoXImageToVideoPipeline.from_pretrained(
MODEL_ID,
torch_dtype=torch.bfloat16,
)
# O offload evita mover o pipeline inteiro de uma só vez para a GPU.
PIPE.enable_model_cpu_offload()
print("Model loaded on CUDA", flush=True)
return PIPE
def resize_image(image: Image.Image, profile: str):
image = image.convert("RGB")
if profile == "1080p (upscale final)":
target_w, target_h = 1360, 768
else:
target_w, target_h = 1024, 576
target_ratio = target_w / target_h
source_ratio = image.width / image.height
if source_ratio > target_ratio:
crop_h = image.height
crop_w = round(crop_h * target_ratio)
else:
crop_w = image.width
crop_h = round(crop_w / target_ratio)
left = max(0, (image.width - crop_w) // 2)
top = max(0, (image.height - crop_h) // 2)
image = image.crop((left, top, left + crop_w, top + crop_h))
return image.resize((target_w, target_h), Image.Resampling.LANCZOS), target_w, target_h
@spaces.GPU(duration=120)
def generate_video(image, prompt, profile, seed):
if image is None:
raise gr.Error("Envie uma imagem antes de gerar o vídeo.")
if not prompt or not prompt.strip():
raise gr.Error("Escreva o movimento que você quer animar.")
try:
pipeline = get_pipeline()
image, width, height = resize_image(image, profile)
generator = torch.Generator(device="cuda").manual_seed(int(seed or 42))
full_prompt = (
"Animate the input image while preserving the exact identity, face, clothing, "
"objects, composition and environment. Focus on natural physically plausible motion. "
+ prompt.strip()
+ ". Smooth cinematic movement, consistent character, no morphing, no extra limbs, "
"no scene change, no camera cuts."
)
result = pipeline(
prompt=full_prompt,
image=image,
height=height,
width=width,
num_frames=33,
guidance_scale=6.0,
num_inference_steps=12,
generator=generator,
)
frames = result.frames[0]
output_path = Path(tempfile.mkdtemp(prefix="motion-ai-")) / "motion.mp4"
export_to_video(frames, str(output_path), fps=8)
return str(output_path)
except Exception as exc:
print(f"Generation error: {type(exc).__name__}: {exc}", flush=True)
raise gr.Error(f"Não foi possível gerar o vídeo: {type(exc).__name__}: {exc}") from exc
with gr.Blocks(title="Motion AI Studio") as demo:
gr.Markdown(
"# Motion AI Studio\n"
"Anime uma imagem com um comando de movimento. A GPU é solicitada somente ao gerar."
)
with gr.Row():
with gr.Column():
image = gr.Image(type="pil", label="Imagem de referência")
prompt = gr.Textbox(
label="Descreva o movimento",
placeholder="Ex.: o homem começa a correr pela rua enquanto a câmera o acompanha lateralmente",
lines=4,
)
profile = gr.Radio(
["720p", "1080p (upscale final)"],
value="720p",
label="Perfil",
)
seed = gr.Number(value=42, precision=0, label="Seed")
generate = gr.Button("Gerar animação", variant="primary")
with gr.Column():
result = gr.Video(label="Resultado", format="mp4")
gr.Markdown(
"**Recomendação:** comece com 720p. O perfil 1080p gera a animação em resolução menor."
)
generate.click(generate_video, [image, prompt, profile, seed], result)
if __name__ == "__main__":
demo.queue(max_size=8).launch()