""" Video API Schemas 영상 생성 관련 Pydantic 스키마를 정의합니다. """ from datetime import datetime from typing import Any, Dict, List, Optional from pydantic import BaseModel, ConfigDict, Field # ============================================================================= # Response Schemas # ============================================================================= class GenerateVideoResponse(BaseModel): """영상 생성 응답 스키마 Usage: GET /video/generate/{task_id} Returns the task IDs for tracking video generation. """ model_config = ConfigDict( json_schema_extra={ "example": { "success": True, "task_id": "0694b716-dbff-7219-8000-d08cb5fce431", "creatomate_render_id": "render-id-123456", "message": "영상 생성 요청이 접수되었습니다. creatomate_render_id로 상태를 조회하세요.", "error_message": None, } } ) success: bool = Field(..., description="요청 성공 여부") status: Optional[str] = Field(None, description="처리 상태 (subtitle_pending: 자막 미완료, completed: 정상 접수)") task_id: Optional[str] = Field(None, description="내부 작업 ID (Project task_id)") creatomate_render_id: Optional[str] = Field(None, description="Creatomate 렌더 ID") message: str = Field(..., description="응답 메시지") error_message: Optional[str] = Field(None, description="에러 메시지 (실패 시)") class VideoRenderData(BaseModel): """Creatomate 렌더링 결과 데이터""" id: Optional[str] = Field(None, description="렌더 ID") status: Optional[str] = Field(None, description="렌더 상태") url: Optional[str] = Field(None, description="영상 URL") snapshot_url: Optional[str] = Field(None, description="스냅샷 URL") video_id: Optional[int] = Field(None, description="Video id(DB)") class PollingVideoResponse(BaseModel): """영상 생성 상태 조회 응답 스키마 Usage: GET /video/status/{creatomate_render_id} Creatomate API 작업 상태를 조회합니다. Note: 상태 값: - planned: 예약됨 - waiting: 대기 중 - transcribing: 트랜스크립션 중 - rendering: 렌더링 중 - succeeded: 성공 - failed: 실패 Example Response (Success): { "success": true, "status": "succeeded", "message": "영상 생성이 완료되었습니다.", "render_data": { "id": "render-id", "status": "succeeded", "url": "https://...", "snapshot_url": "https://..." }, "raw_response": {...}, "error_message": null } """ success: bool = Field(..., description="조회 성공 여부") status: Optional[str] = Field( None, description="작업 상태 (planned, waiting, rendering, succeeded, failed)" ) message: str = Field(..., description="상태 메시지") render_data: Optional[VideoRenderData] = Field(None, description="렌더링 결과 데이터") raw_response: Optional[Dict[str, Any]] = Field(None, description="Creatomate API 원본 응답") error_message: Optional[str] = Field(None, description="에러 메시지 (실패 시)") class DownloadVideoResponse(BaseModel): """영상 다운로드 응답 스키마 Usage: GET /video/download/{task_id} Polls for video completion and returns project info with video URL. Note: 상태 값: - processing: 영상 생성 진행 중 (result_movie_url은 null) - completed: 영상 생성 완료 (result_movie_url 포함) - failed: 영상 생성 실패 - not_found: task_id에 해당하는 Video 없음 - error: 조회 중 오류 발생 Example Response (Completed): { "success": true, "status": "completed", "message": "영상 다운로드가 완료되었습니다.", "store_name": "스테이 머뭄", "region": "군산", "task_id": "019123ab-cdef-7890-abcd-ef1234567890", "result_movie_url": "http://localhost:8000/media/2025-01-15/video.mp4", "created_at": "2025-01-15T12:00:00", "error_message": null } """ success: bool = Field(..., description="다운로드 성공 여부") status: str = Field(..., description="처리 상태 (processing, completed, failed, not_found, error)") message: str = Field(..., description="응답 메시지") store_name: Optional[str] = Field(None, description="업체명") region: Optional[str] = Field(None, description="지역명") task_id: Optional[str] = Field(None, description="작업 고유 식별자") result_movie_url: Optional[str] = Field(None, description="영상 결과 URL") created_at: Optional[datetime] = Field(None, description="생성 일시") error_message: Optional[str] = Field(None, description="에러 메시지 (실패 시)") class VideoListItem(BaseModel): """영상 목록 아이템 스키마 Usage: GET /videos 응답의 개별 영상 정보 Example: { "video_id": 1, "store_name": "스테이 머뭄", "region": "군산", "task_id": "019123ab-cdef-7890-abcd-ef1234567890", "result_movie_url": "http://localhost:8000/media/2025-01-15/video.mp4", "poster_url": "http://localhost:8000/media/2025-01-15/video.jpg", "created_at": "2025-01-15T12:00:00" } """ video_id: int = Field(..., description="영상 고유 ID") store_name: Optional[str] = Field(None, description="업체명") region: Optional[str] = Field(None, description="지역명") task_id: str = Field(..., description="작업 고유 식별자") result_movie_url: Optional[str] = Field(None, description="영상 결과 URL") poster_url: Optional[str] = Field(None, description="영상 첫 프레임 포스터 이미지 URL") title: Optional[str] = Field(None, description="SNS 업로드 제목") description: Optional[str] = Field(None, description="SNS 업로드 설명") hashtags: Optional[List[str]] = Field(None, description="SNS 해시태그 목록") created_at: Optional[datetime] = Field(None, description="생성 일시") like_count: int = Field(0, description="좋아요 수") comment_count: int = Field(0, description="댓글 수 (대댓글 포함)") is_liked_by_me: bool = Field( False, description="현재 로그인 사용자가 좋아요를 눌렀는지", ) class VideoThumbnailItem(BaseModel): """ADO2 콘텐츠 갤러리용 최소 영상 정보 (썸네일 표시 + 상세 페이지 이동용) Usage: GET /video/all 응답의 개별 영상 정보 """ video_id: int = Field(..., description="영상 고유 ID (상세 페이지 라우팅 키)") store_name: str = Field(..., description="업체명") result_movie_url: str = Field(..., description="영상 URL") poster_url: Optional[str] = Field(None, description="영상 첫 프레임 포스터 이미지 URL (썸네일 표시용)") created_at: datetime = Field(..., description="생성 일시") like_count: int = Field(..., description="좋아요 수") is_liked_by_me: bool = Field(..., description="현재 로그인 사용자가 좋아요를 눌렀는지 (비로그인은 항상 false)") comment_count: int = Field(..., description="댓글 수 (대댓글 포함)") official_site_url: Optional[str] = Field( None, description="업체 공식 링크 (플레이스 홈페이지 항목 우선, 없으면 크롤링 소스 URL; 직접 입력 생성 영상만 null)", ) class VideoDetailResponse(BaseModel): """단일 영상 상세 응답 Usage: GET /video/{video_id} """ video_id: int = Field(..., description="영상 고유 ID") result_movie_url: str = Field(..., description="영상 URL") poster_url: Optional[str] = Field(None, description="영상 첫 프레임 포스터 이미지 URL") store_name: Optional[str] = Field(None, description="업체명") region: Optional[str] = Field(None, description="지역명") title: Optional[str] = Field(None, description="SNS 업로드 제목 (공유 시 og:title 및 공유 제목으로 사용)") description: Optional[str] = Field(None, description="SNS 업로드 설명") created_at: datetime = Field(..., description="생성 일시") like_count: int = Field(..., description="좋아요 수") is_liked_by_me: bool = Field(..., description="현재 로그인 사용자가 좋아요를 눌렀는지 (비로그인은 항상 false)") official_site_url: Optional[str] = Field( None, description="업체 공식 링크 (플레이스 홈페이지 항목 우선, 없으면 크롤링 소스 URL; 직접 입력 생성 영상만 null)", ) class LikeToggleResponse(BaseModel): """좋아요 토글 응답 Usage: POST /video/{video_id}/like """ video_id: int = Field(..., description="영상 고유 ID") is_liked: bool = Field(..., description="토글 후 상태 (true=좋아요 누름, false=취소됨)") like_count: int = Field(..., description="토글 후 전체 좋아요 수")