o2o-castad-backend/app/video/schemas/video_schema.py
김성경 a2d84421d2 feat(video): 제목·설명·해시태그를 영상 완료 시 DB에 저장
업로드 시점 Redis SEO 캐시 대신 video 행을 기준으로 두고, 게시 시에만 변경분을 갱신한다.
2026-08-19 10:17:22 +09:00

220 lines
8.9 KiB
Python

"""
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="댓글 수 (대댓글 포함)")
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="지역명")
created_at: datetime = Field(..., description="생성 일시")
like_count: int = Field(..., description="좋아요 수")
is_liked_by_me: bool = Field(..., description="현재 로그인 사용자가 좋아요를 눌렀는지 (비로그인은 항상 false)")
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="토글 후 전체 좋아요 수")