Spaces:
Paused
Paused
| #!/usr/bin/env python3 | |
| # -*- coding: utf-8 -*- | |
| from __future__ import annotations | |
| import os | |
| from pathlib import Path | |
| from typing import List, Optional | |
| import torch | |
| from PIL import Image | |
| from diffusers.models import ( | |
| AutoencoderKL, | |
| ControlNetModel, | |
| ) | |
| from diffusers import ( | |
| DPMSolverMultistepScheduler, | |
| DDIMScheduler, | |
| ) | |
| from diffusers.pipelines.pipeline_utils import DiffusionPipeline | |
| from diffusers.models.unets.unet_motion_model import MotionAdapter | |
| APP_HOME = Path(os.environ.get("APP_HOME", "/app")) | |
| OUT_DIR = APP_HOME / "outputs" / "anime_service" | |
| OUT_DIR.mkdir(parents=True, exist_ok=True) | |
| # IDs padrão (editáveis na UI) | |
| MOTION_ID = os.environ.get("MOTION_ID", "guoyww/animatediff-motion-adapter-v1-5-2") | |
| CONTROLNET_ID = os.environ.get("CONTROLNET_ID", "lllyasviel/control_v11p_sd15_openpose") | |
| VAE_ID = os.environ.get("VAE_ID", "stabilityai/sd-vae-ft-mse") | |
| MODEL_ID = os.environ.get("MODEL_ID", "SG161222/Realistic_Vision_V5.1_noVAE") | |
| TORCH_DTYPE = ( | |
| torch.float16 | |
| if os.environ.get("TORCH_DTYPE", "bfloat16").lower() in ("fp16", "float16", "half") | |
| else torch.bfloat16 | |
| ) | |
| class AnimateDiffControlNetService: | |
| def __init__(self): | |
| self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu") | |
| self.pipe: DiffusionPipeline | None = None | |
| def setup( | |
| self, | |
| model_id: str = MODEL_ID, | |
| motion_id: str = MOTION_ID, | |
| controlnet_id: str = CONTROLNET_ID, | |
| vae_id: str = VAE_ID, | |
| dtype: torch.dtype = TORCH_DTYPE, | |
| ) -> str: | |
| # Carrega adaptador de movimento, controlnet e VAE | |
| adapter = MotionAdapter.from_pretrained(motion_id) | |
| controlnet = ControlNetModel.from_pretrained(controlnet_id, torch_dtype=dtype) | |
| vae = AutoencoderKL.from_pretrained(vae_id, torch_dtype=dtype) | |
| # Monta pipeline custom AnimateDiff + ControlNet | |
| pipe = DiffusionPipeline.from_pretrained( | |
| model_id, | |
| motion_adapter=adapter, | |
| controlnet=controlnet, | |
| vae=vae, | |
| custom_pipeline="pipeline_animatediff_controlnet", # requer arquivo/pipeline correspondente | |
| ).to(device=self.device, dtype=dtype) | |
| # Scheduler robusto: tenta DPMSolverMultistep; se config estiver incompatível, cai para DDIM | |
| try: | |
| pipe.scheduler = DPMSolverMultistepScheduler.from_pretrained( | |
| model_id, | |
| subfolder="scheduler", | |
| clip_sample=False, | |
| timestep_spacing="linspace", | |
| steps_offset=1, | |
| beta_schedule="linear", | |
| ) | |
| except Exception: | |
| pipe.scheduler = DDIMScheduler.from_pretrained(model_id, subfolder="scheduler") | |
| # Slicing de VAE | |
| try: | |
| pipe.vae.enable_slicing() | |
| except Exception: | |
| pass | |
| self.pipe = pipe | |
| return f"AnimeDiff pronto em {self.device} (dtype={dtype})" | |
| def generate_frames( | |
| self, | |
| conditioning_frames: List[Image.Image], | |
| prompt: str, | |
| negative_prompt: str = "", | |
| width: int = 512, | |
| height: int = 768, | |
| num_inference_steps: int = 12, | |
| guidance_scale: float = 7.5, | |
| seed: Optional[int] = None, | |
| ) -> List[str]: | |
| assert self.pipe is not None, "Chame setup() antes." | |
| gen = None | |
| if seed is not None and seed >= 0: | |
| gen = torch.Generator(device=self.device).manual_seed(int(seed)) | |
| result = self.pipe( | |
| prompt=prompt, | |
| negative_prompt=(negative_prompt or None), | |
| width=int(width), | |
| height=int(height), | |
| conditioning_frames=conditioning_frames, | |
| num_inference_steps=int(num_inference_steps), | |
| guidance_scale=float(guidance_scale), | |
| generator=gen, | |
| output_type="pil", | |
| ) | |
| frames = result.frames[0] | |
| out_dir = OUT_DIR / "session" | |
| out_dir.mkdir(parents=True, exist_ok=True) | |
| paths: List[str] = [] | |
| for i, img in enumerate(frames): | |
| p = out_dir / f"frame_{i:02d}.png" | |
| img.save(p) | |
| paths.append(str(p)) | |
| return paths | |