- 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>
290 lines
10 KiB
Python
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"],
|
|
}
|