Files
hfut-bishe/python_server/app/routes/animate.py
T
2026-05-14 13:30:06 +08:00

136 lines
4.6 KiB
Python

from __future__ import annotations
import datetime
import shutil
from typing import Any, Dict
from fastapi import APIRouter
from PIL import Image
from ..deps import OUTPUT_DIR
from ..schemas import AnimateRequest, CharacterAnimateRequest
from ..services.animation_frames import (
align_size_to_input,
composite_rgba_on_background,
list_png_frames,
parse_background_color,
remove_background_by_color,
)
from ..services.image_io import b64_to_pil_image, pil_image_to_png_b64
from ..services.predictors import get_animation_predictor
router = APIRouter(tags=["animation"])
@router.post("/animate")
def animate(req: AnimateRequest) -> Dict[str, Any]:
try:
model_name = req.model_name or "animatediff"
predictor = get_animation_predictor(model_name)
result_path = predictor(
prompt=req.prompt,
negative_prompt=req.negative_prompt or "",
num_inference_steps=req.num_inference_steps,
guidance_scale=req.guidance_scale,
width=req.width,
height=req.height,
video_length=req.video_length,
seed=req.seed,
)
out_dir = OUTPUT_DIR / "animation"
out_dir.mkdir(parents=True, exist_ok=True)
ts = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
out_path = out_dir / f"{model_name}_{ts}.gif"
shutil.copy2(result_path, out_path)
return {"success": True, "output_path": str(out_path)}
except Exception as e:
return {"success": False, "error": str(e)}
@router.post("/animate/character_sequence")
def animate_character_sequence(req: CharacterAnimateRequest) -> Dict[str, Any]:
"""
输入透明背景角色 PNG,生成 30fps 角色动画 PNG 序列。
流程:
- 将角色合成到指定纯色背景,作为 AnimateDiff 控制图
- 以与输入相近、且对齐到 8 的分辨率生成 PNG 序列
- 对每帧按背景色做色键剔除,返回透明背景 PNG 序列给前端
"""
try:
model_name = req.model_name or "animatediff"
background_rgb = parse_background_color(req.background_color)
character = b64_to_pil_image(req.image_b64).convert("RGBA")
orig_w, orig_h = character.size
run_w, run_h = align_size_to_input(orig_w, orig_h, req.max_side)
predictor = get_animation_predictor(model_name)
out_dir = OUTPUT_DIR / "animation" / "character_sequence"
out_dir.mkdir(parents=True, exist_ok=True)
ts = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
seq_dir = out_dir / f"{model_name}_{ts}"
seq_dir.mkdir(parents=True, exist_ok=True)
control_path = seq_dir / "control_image.png"
control_image = composite_rgba_on_background(
character,
background_rgb,
size=(run_w, run_h),
)
control_image.save(control_path)
frame_dir = predictor(
prompt=req.prompt,
negative_prompt=req.negative_prompt or "",
num_inference_steps=req.num_inference_steps,
guidance_scale=req.guidance_scale,
width=run_w,
height=run_h,
video_length=req.video_length,
seed=req.seed,
control_image_path=str(control_path),
output_format="png_sequence",
)
frame_paths = list_png_frames(frame_dir)
if not frame_paths:
raise FileNotFoundError(f"未找到 AnimateDiff 输出 PNG 序列: {frame_dir}")
frames: list[dict[str, Any]] = []
processed_dir = seq_dir / "frames"
processed_dir.mkdir(parents=True, exist_ok=True)
for idx, frame_path in enumerate(frame_paths):
frame = remove_background_by_color(
Image.open(frame_path),
background_rgb,
tolerance=req.background_tolerance,
output_size=(orig_w, orig_h),
)
out_path = processed_dir / f"{idx:04d}.png"
frame.save(out_path)
frames.append(
{
"index": idx,
"image_b64": pil_image_to_png_b64(frame),
"path": str(out_path),
}
)
return {
"success": True,
"fps": 30,
"frame_count": len(frames),
"width": orig_w,
"height": orig_h,
"background_color": req.background_color,
"output_dir": str(processed_dir),
"frames": frames,
}
except Exception as e:
return {"success": False, "error": str(e), "frames": []}