o2o-castad-backend/app/utils/prompts/schemas/image.py

143 lines
7.5 KiB
Python

from pydantic import BaseModel, Field
from typing import List, Optional
from enum import StrEnum, auto
class SpaceType(StrEnum):
"""이미지가 촬영된 공간·장소 유형. LLM이 이미지를 보고 해당하는 공간 태그를 선택하는 데 사용."""
exterior_front = auto()
exterior_night = auto()
exterior_aerial = auto()
exterior_sign = auto()
garden = auto()
entrance = auto()
lobby = auto()
reception = auto()
hallway = auto()
bedroom = auto()
livingroom = auto()
kitchen = auto()
dining = auto()
room = auto()
bathroom = auto()
amenity = auto()
view_window = auto()
view_ocean = auto()
view_city = auto()
view_mountain = auto()
balcony = auto()
cafe = auto()
lounge = auto()
rooftop = auto()
pool = auto()
breakfast_hall = auto()
spa = auto()
fitness = auto()
bbq = auto()
terrace = auto()
glamping = auto()
neighborhood = auto()
landmark = auto()
detail_welcome = auto()
detail_beverage = auto()
detail_lighting = auto()
detail_decor = auto()
detail_tableware = auto()
class Subject(StrEnum):
"""이미지 내 주요 피사체 유형. 화면에 무엇이 담겨 있는지를 분류하는 태그."""
empty_space = auto()
exterior_building = auto()
architecture_detail = auto()
decoration = auto()
furniture = auto()
food_dish = auto()
nature = auto()
signage = auto()
amenity_item = auto()
person = auto()
class Camera(StrEnum):
"""이미지의 촬영 기법·구도·조명 특성. 영상 편집 시 컷의 시각적 스타일을 구분하는 태그."""
wide_angle = auto()
tight_crop = auto()
panoramic = auto()
symmetrical = auto()
leading_line = auto()
golden_hour = auto()
night_shot = auto()
high_contrast = auto()
low_light = auto()
drone_shot = auto()
has_face = auto()
class MotionRecommended(StrEnum):
"""해당 이미지에 적용 가능한 카메라 모션 유형. 영상 제작 시 각 컷에 어울리는 움직임을 추천하는 데 사용."""
static = auto()
slow_pan = auto()
slow_zoom_in = auto()
slow_zoom_out = auto()
walkthrough = auto()
dolly = auto()
class NarrativePhase(StrEnum):
"""광고 영상 내러티브 단계. 이미지가 스토리 흐름에서 어느 위치에 배치되어야 하는지를 나타냄."""
intro = auto()
welcome = auto()
core = auto()
highlight = auto()
support = auto()
accent = auto()
cta = auto()
NARRATIVE_PHASE_LABEL: dict[NarrativePhase, str] = {
NarrativePhase.intro: "첫인상(intro)",
NarrativePhase.welcome: "진입(welcome)",
NarrativePhase.core: "핵심(core)",
NarrativePhase.highlight: "차별화(highlight)",
NarrativePhase.support: "보조(support)",
NarrativePhase.accent: "감성(accent)",
NarrativePhase.cta: "행동유도(cta)",
}
class NarrativePreference(BaseModel):
"""이미지가 각 내러티브 단계에 얼마나 적합한지를 나타내는 점수 모델 (0.0 ~ 100.0).
LLM이 이미지를 분석한 뒤 각 단계별 적합도를 수치로 반환하며,
영상 편집기가 이 점수를 기반으로 컷 배치 순서를 자동 결정하는 데 사용."""
intro: float = Field(..., description="첫인상 — 여기가 어디인가 | 장소의 정체성과 위치를 전달하는 이미지. 영상 첫 1~2초에 어떤 곳인지 즉시 인지시키는 역할. 건물 외관, 간판, 정원 등 **장소 자체를 보여주는** 컷")
welcome: float = Field(..., description="진입/환영 — 어떻게 들어가나 | 도착 후 내부로 들어가는 경험을 전달하는 이미지. 공간의 첫 분위기와 동선을 보여줘 들어가고 싶다는 기대감을 만드는 역할. **문을 열고 들어갔을 때 보이는** 컷.")
core: float = Field(..., description="핵심 가치 — 무엇을 경험하나 | **고객이 이 장소를 찾는 본질적 이유.** 이 이미지가 없으면 영상 자체가 성립하지 않음. 질문: 이 비즈니스에서 돈을 지불하는 대상이 뭔가? → 그 답이 core.")
highlight: float = Field(..., description="차별화 — 뭐가 특별한가 | **같은 카테고리의 경쟁사 대비 이곳을 선택하게 만드는 이유.** core가 왜 왔는가라면, highlight는 왜 **여기**인가에 대한 답.")
support: float = Field(..., description="보조/부대 — 그 외에 뭐가 있나 | 핵심은 아니지만 전체 경험을 풍성하게 하는 부가 요소. 없어도 영상은 성립하지만, 있으면 설득력이 올라감. **이것도 있어요** 라고 말하는 컷.")
accent: float = Field(..., description="감성/마무리 — 어떤 느낌인가 | 공간의 분위기와 톤을 전달하는 감성 디테일 컷. 직접적 정보 전달보다 **느낌과 무드**를 제공. 영상 사이사이에 삽입되어 완성도를 높이는 역할.")
cta: float = Field(..., description="행동 유도 — 지금 예약/방문하고 싶게 만드는 컷 | 가격·혜택·한정 오퍼 등 직접적 행동을 촉구하거나, 마지막 장면에서 강한 인상을 남기는 이미지.")
# Input 정의
class ImageTagPromptInput(BaseModel):
"""이미지 태깅 프롬프트에 전달되는 입력 스키마.
분석할 이미지 URL과 업종 정보, LLM이 선택 가능한 태그 후보 리스트를 포함."""
img_url : str = Field(..., description="이미지 URL")
industry : str = Field(default="", description = "업종 분류 (stay|restaurant|cafe|salon|clinic|fitness|academy|attraction|general). 통합 프롬프트가 이 값으로 내부 분기")
space_type: list[str] = Field(list(SpaceType), description="공간적 정보를 가지는 태그 리스트")
subject: list[str] = Field(list(Subject), description="피사체 정보를 가지는 태그 리스트")
camera: list[str] = Field(list(Camera), description="카메라 정보를 가지는 태그 리스트")
motion_recommended: list[str] = Field(list(MotionRecommended), description="가능한 카메라 모션 리스트")
class MarketingFilterOutput(BaseModel):
"""크롤링 단계에서 이미지의 마케팅(광고 영상) 사용 가능 여부만 판정하는 프롬프트의 출력 스키마.
ImageTagPromptOutput과 달리 태깅(공간/피사체/카메라/모션/내러티브)은 포함하지 않는다."""
marketing_acceptable: bool = Field(..., description="광고 영상 사용 가능 여부")
reject_reason: str = Field(default="", description="거부 사유 (marketing_acceptable=false 시). 예: 저화질, 인물 신원 노출, 업종 부적합, 민감/의료 장면, 미성년자 식별 노출")
# Output 정의
class ImageTagPromptOutput(BaseModel):
"""이미지 태깅 프롬프트의 LLM 출력 스키마.
공간·피사체·카메라·모션 태그와 내러티브 적합도 점수를 담아 반환.
마케팅 사용 가능 여부는 크롤링 단계(MarketingFilterOutput)에서 이미 판정되므로 포함하지 않는다."""
space_type: list[SpaceType] = Field(..., description="공간적 정보를 가지는 태그 리스트")
subject: list[Subject] = Field(..., description="피사체 정보를 가지는 태그 리스트")
camera: list[Camera] = Field(..., description="카메라 정보를 가지는 태그 리스트")
motion_recommended: list[MotionRecommended] = Field(..., description="가능한 카메라 모션 리스트")
narrative_preference: NarrativePreference = Field(..., description="이미지의 내러티브 상 점수")