playreel/backend/services/analyze_poster.py

53 lines
2.4 KiB
Python

"""④ analyze — 검수 게이트에 올릴 재료를 만든다.
여기서 사람이 움직일 요소를 확정하고, 그 승인이 14크레딧 지출을 확정한다.
실제 프롬프트는 ⑤ motion에서 사람이 고른 키로 다시 쓴다.
"""
import re
from pathlib import Path
from PIL import Image
from models.analysis import FixedLayer, MotionChoice, PosterAnalysis
from services.motion import plan_motion
from utils.higgsfield import KLING_3_0
from utils.image import review_grid_overlay
MOTION_LABELS = {
"ray": "광선", "snow": "", "fog": "안개", "glow": "발광", "light": "조명",
"star": "", "cloud": "구름", "smoke": "연기", "water": "", "wave": "파도",
"moon": "", "sun": "", "flag": "깃발", "foliage": "나뭇잎", "vessel": "",
"crowd": "사람 무리", "wheel": "바퀴·관람차", "firework": "불꽃놀이",
}
FIXED_LABELS = {"title": "제목", "logo": "로고", "date": "일시",
"person": "인물", "sponsor": "후원 바"}
# veo·seedance가 디즈니 IP 작품을 거부한 이력이 있어, 크레딧을 쓰기 전에 경고만 띄운다.
# 한국어 표기 몇 개만 보는 어림짐작이라 판정이 아니다.
IP_RISK_PATTERN = re.compile(r"디즈니|겨울왕국|라이온킹|알라딘|마블")
def motion_choices(kept, dropped) -> list[MotionChoice]:
"""VLM이 고른 것은 켜고, 확신 없어 탈락한 것은 꺼서 함께 보여준다."""
chosen = {item.motion for item in kept}
choices = [MotionChoice(key=item.motion, label=MOTION_LABELS.get(item.motion, item.motion),
what=item.what, on=True) for item in kept]
choices += [MotionChoice(key=item.motion, label=MOTION_LABELS.get(item.motion, item.motion),
what=item.what, on=False)
for item in dropped if item.motion not in chosen]
return choices
async def analyze_poster(poster: Path | Image.Image, title: str = "") -> PosterAnalysis:
source = (Image.open(poster) if isinstance(poster, Path) else poster).convert("RGB")
plan = await plan_motion(source)
return PosterAnalysis(
grid=review_grid_overlay(source),
movable=motion_choices(plan.kept, plan.dropped),
fixed=[FixedLayer(key=key, label=label) for key, label in FIXED_LABELS.items()],
model=KLING_3_0,
ip_risk=bool(IP_RISK_PATTERN.search(title)),
has_qr=plan.has_qr,
plan=plan,
)