Initial commit: YouTube Shorts maker application
Features: - Video download from TikTok/Douyin using yt-dlp - Audio transcription with OpenAI Whisper - GPT-4 translation (direct/summarize/rewrite modes) - Subtitle generation with ASS format - Video trimming with frame-accurate preview - BGM integration with volume control - Intro text overlay support - Thumbnail generation with text overlay Tech stack: - Backend: FastAPI, Python 3.11+ - Frontend: React, Vite, TailwindCSS - Video processing: FFmpeg - AI: OpenAI Whisper, GPT-4 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
279
backend/app/models/schemas.py
Normal file
279
backend/app/models/schemas.py
Normal file
@@ -0,0 +1,279 @@
|
||||
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 10 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"],
|
||||
}
|
||||
Reference in New Issue
Block a user