Files
bini-shorts-maker/backend/app/models/schemas.py
kihong.kim 5c57f33903 feat: 타임라인 에디터 및 비디오 스튜디오 컴포넌트 추가
- TimelineEditor, VideoStudio 컴포넌트 신규 추가
- 백엔드 transcriber, video_processor 서비스 개선
- 프론트엔드 HomePage 리팩토링 및 스타일 업데이트

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-06 21:21:58 +09:00

290 lines
10 KiB
Python

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 = 70
font_color: str = "FFFFFF" # hex color code (white)
outline_color: str = "000000" # hex color code (black)
outline_width: int = 4 # 아웃라인 두께 (가독성을 위해 4로 증가)
position: str = "center" # top, center, bottom
margin_v: int = 50 # 수직 위치 (0=가장자리, 100=화면 중심쪽) - 화면 높이의 %
font_name: str = "Pretendard"
# Enhanced styling options
bold: bool = True # 굵은 글씨 (가독성 향상)
shadow: int = 2 # 그림자 깊이 (0=없음, 1-4)
background_box: bool = False # False=아웃라인 스타일 (깔끔함), True=배경 박스
background_opacity: str = "80" # 배경 불투명도 (00=투명, FF=완전불투명, 80=반투명)
animation: str = "fade" # none, fade, pop (자막 애니메이션)
max_chars_per_line: int = 0 # 줄당 최대 글자 수 (0=비활성화, 15~20 권장)
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 ExcludeRegion(BaseModel):
"""A region to exclude (cut out) from the video."""
start: float # Start time in seconds
end: float # End time in seconds
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)
exclude_regions: List[ExcludeRegion] = [] # Regions to cut out from the middle of the video
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
intro_position: str = "center" # top, center, bottom
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"],
}