from pydantic import BaseModel, HttpUrl from typing import Optional, List from enum import Enum from datetime import datetime class JobStatus(str, Enum): PENDING = "pending" DOWNLOADING = "downloading" READY_FOR_TRIM = "ready_for_trim" # Download complete, ready for trimming TRIMMING = "trimming" # Video trimming in progress EXTRACTING_AUDIO = "extracting_audio" # Step 2: FFmpeg audio extraction NOISE_REDUCTION = "noise_reduction" # Step 3: Noise reduction TRANSCRIBING = "transcribing" # Step 4: Whisper STT TRANSLATING = "translating" # Step 5: GPT translation AWAITING_REVIEW = "awaiting_review" # Script ready, waiting for user review before rendering PROCESSING = "processing" # Step 6: Video composition + BGM COMPLETED = "completed" FAILED = "failed" AWAITING_SUBTITLE = "awaiting_subtitle" # No audio - waiting for manual subtitle input class DownloadRequest(BaseModel): url: str platform: Optional[str] = None # auto-detect if not provided class DownloadResponse(BaseModel): job_id: str status: JobStatus message: str class SubtitleStyle(BaseModel): font_size: int = 28 font_color: str = "white" outline_color: str = "black" outline_width: int = 2 position: str = "bottom" # top, center, bottom font_name: str = "Pretendard" # Enhanced styling options bold: bool = True # 굵은 글씨 (가독성 향상) shadow: int = 1 # 그림자 깊이 (0=없음, 1-4) background_box: bool = True # 불투명 배경 박스로 원본 자막 덮기 background_opacity: str = "E0" # 배경 불투명도 (00=투명, FF=완전불투명, E0=권장) animation: str = "none" # none, fade, pop (자막 애니메이션) class TranslationModeEnum(str, Enum): DIRECT = "direct" # 직접 번역 (원본 구조 유지) SUMMARIZE = "summarize" # 요약 후 번역 REWRITE = "rewrite" # 완전 재구성 (권장) class ProcessRequest(BaseModel): job_id: str bgm_id: Optional[str] = None bgm_volume: float = 0.3 subtitle_style: Optional[SubtitleStyle] = None keep_original_audio: bool = False translation_mode: Optional[str] = None # direct, summarize, rewrite (default from settings) use_vocal_separation: bool = False # Separate vocals from BGM before transcription class ProcessResponse(BaseModel): job_id: str status: JobStatus message: str class TrimRequest(BaseModel): """Request to trim a video to a specific time range.""" start_time: float # Start time in seconds end_time: float # End time in seconds reprocess: bool = False # Whether to automatically reprocess after trimming (default: False for manual workflow) class TranscribeRequest(BaseModel): """Request to start transcription (audio extraction + STT + translation).""" translation_mode: Optional[str] = "rewrite" # direct, summarize, rewrite use_vocal_separation: bool = False # Separate vocals from BGM before transcription class RenderRequest(BaseModel): """Request to render final video with subtitles and BGM.""" bgm_id: Optional[str] = None bgm_volume: float = 0.3 subtitle_style: Optional[SubtitleStyle] = None keep_original_audio: bool = False # Intro text overlay (shown at beginning of video for YouTube Shorts thumbnail) intro_text: Optional[str] = None # Max 20 characters recommended intro_duration: float = 0.7 # Duration of frozen frame with intro text (seconds) intro_font_size: int = 100 # Font size class TrimResponse(BaseModel): """Response after trimming a video.""" job_id: str success: bool message: str new_duration: Optional[float] = None class VideoInfoResponse(BaseModel): """Video information for trimming UI.""" duration: float width: Optional[int] = None height: Optional[int] = None thumbnail_url: Optional[str] = None class TranscriptSegment(BaseModel): start: float end: float text: str translated: Optional[str] = None class JobInfo(BaseModel): job_id: str status: JobStatus created_at: datetime updated_at: datetime original_url: Optional[str] = None video_path: Optional[str] = None output_path: Optional[str] = None transcript: Optional[List[TranscriptSegment]] = None error: Optional[str] = None progress: int = 0 has_audio: Optional[bool] = None # None = not checked, True = has audio, False = no audio audio_status: Optional[str] = None # "ok", "no_audio_stream", "audio_silent" detected_language: Optional[str] = None # Whisper detected language (e.g., "zh", "en", "ko") class BGMInfo(BaseModel): id: str name: str duration: float path: str class BGMUploadResponse(BaseModel): id: str name: str message: str # 한글 폰트 정의 class FontInfo(BaseModel): """Font information for subtitle styling.""" id: str # 폰트 ID (시스템 폰트명) name: str # 표시 이름 style: str # 스타일 분류 recommended_for: List[str] # 추천 콘텐츠 유형 download_url: Optional[str] = None # 다운로드 링크 license: str = "Free for commercial use" # 쇼츠에서 인기있는 무료 상업용 한글 폰트 KOREAN_FONTS = { # 기본 시스템 폰트 (대부분의 시스템에 설치됨) "NanumGothic": FontInfo( id="NanumGothic", name="나눔고딕", style="깔끔, 기본", recommended_for=["tutorial", "news", "general"], download_url="https://hangeul.naver.com/font", license="OFL (Open Font License)", ), "NanumGothicBold": FontInfo( id="NanumGothicBold", name="나눔고딕 Bold", style="깔끔, 강조", recommended_for=["tutorial", "news", "general"], download_url="https://hangeul.naver.com/font", license="OFL (Open Font License)", ), "NanumSquareRound": FontInfo( id="NanumSquareRound", name="나눔스퀘어라운드", style="둥글, 친근", recommended_for=["travel", "lifestyle", "vlog"], download_url="https://hangeul.naver.com/font", license="OFL (Open Font License)", ), # 인기 무료 폰트 (별도 설치 필요) "Pretendard": FontInfo( id="Pretendard", name="프리텐다드", style="현대적, 깔끔", recommended_for=["tutorial", "tech", "business"], download_url="https://github.com/orioncactus/pretendard", license="OFL (Open Font License)", ), "SpoqaHanSansNeo": FontInfo( id="SpoqaHanSansNeo", name="스포카 한 산스 Neo", style="깔끔, 가독성", recommended_for=["tutorial", "tech", "presentation"], download_url="https://github.com/spoqa/spoqa-han-sans", license="OFL (Open Font License)", ), "GmarketSans": FontInfo( id="GmarketSans", name="G마켓 산스", style="둥글, 친근", recommended_for=["shopping", "review", "lifestyle"], download_url="https://corp.gmarket.com/fonts", license="Free for commercial use", ), # 개성있는 폰트 "BMDoHyeon": FontInfo( id="BMDoHyeon", name="배민 도현체", style="손글씨, 유머", recommended_for=["comedy", "mukbang", "cooking"], download_url="https://www.woowahan.com/fonts", license="OFL (Open Font License)", ), "BMJua": FontInfo( id="BMJua", name="배민 주아체", style="귀여움, 캐주얼", recommended_for=["cooking", "lifestyle", "kids"], download_url="https://www.woowahan.com/fonts", license="OFL (Open Font License)", ), "Cafe24Ssurround": FontInfo( id="Cafe24Ssurround", name="카페24 써라운드", style="강조, 임팩트", recommended_for=["gaming", "reaction", "highlight"], download_url="https://fonts.cafe24.com/", license="Free for commercial use", ), "Cafe24SsurroundAir": FontInfo( id="Cafe24SsurroundAir", name="카페24 써라운드 에어", style="가벼움, 깔끔", recommended_for=["vlog", "daily", "lifestyle"], download_url="https://fonts.cafe24.com/", license="Free for commercial use", ), # 제목/강조용 폰트 "BlackHanSans": FontInfo( id="BlackHanSans", name="검은고딕", style="굵음, 강렬", recommended_for=["gaming", "sports", "action"], download_url="https://fonts.google.com/specimen/Black+Han+Sans", license="OFL (Open Font License)", ), "DoHyeon": FontInfo( id="DoHyeon", name="도현", style="손글씨, 자연스러움", recommended_for=["vlog", "cooking", "asmr"], download_url="https://fonts.google.com/specimen/Do+Hyeon", license="OFL (Open Font License)", ), } # 콘텐츠 유형별 추천 폰트 FONT_RECOMMENDATIONS = { "tutorial": ["Pretendard", "SpoqaHanSansNeo", "NanumGothic"], "gaming": ["Cafe24Ssurround", "BlackHanSans", "GmarketSans"], "cooking": ["BMDoHyeon", "BMJua", "DoHyeon"], "comedy": ["BMDoHyeon", "Cafe24Ssurround", "GmarketSans"], "travel": ["NanumSquareRound", "Cafe24SsurroundAir", "GmarketSans"], "news": ["Pretendard", "NanumGothic", "SpoqaHanSansNeo"], "asmr": ["DoHyeon", "NanumSquareRound", "Cafe24SsurroundAir"], "fitness": ["BlackHanSans", "Cafe24Ssurround", "GmarketSans"], "tech": ["Pretendard", "SpoqaHanSansNeo", "NanumGothic"], "lifestyle": ["GmarketSans", "NanumSquareRound", "Cafe24SsurroundAir"], }