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>
164 lines
4.9 KiB
Python
164 lines
4.9 KiB
Python
"""
|
|
Fonts Router - Korean font management for subtitles.
|
|
|
|
Provides font listing and recommendations for YouTube Shorts subtitles.
|
|
"""
|
|
|
|
from fastapi import APIRouter, HTTPException
|
|
from app.models.schemas import FontInfo, KOREAN_FONTS, FONT_RECOMMENDATIONS
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
@router.get("/")
|
|
async def list_fonts():
|
|
"""
|
|
List all available Korean fonts for subtitles.
|
|
|
|
Returns font information including:
|
|
- id: System font name to use in subtitle_style.font_name
|
|
- name: Display name in Korean
|
|
- style: Font style description
|
|
- recommended_for: Content types this font works well with
|
|
- download_url: Where to download the font
|
|
- license: Font license information
|
|
"""
|
|
fonts = []
|
|
for font_id, font_info in KOREAN_FONTS.items():
|
|
fonts.append({
|
|
"id": font_info.id,
|
|
"name": font_info.name,
|
|
"style": font_info.style,
|
|
"recommended_for": font_info.recommended_for,
|
|
"download_url": font_info.download_url,
|
|
"license": font_info.license,
|
|
})
|
|
|
|
return {
|
|
"fonts": fonts,
|
|
"total": len(fonts),
|
|
"default": "NanumGothic",
|
|
"usage": "Set subtitle_style.font_name to the font id",
|
|
}
|
|
|
|
|
|
@router.get("/recommend/{content_type}")
|
|
async def recommend_fonts(content_type: str):
|
|
"""
|
|
Get font recommendations for a specific content type.
|
|
|
|
Available content types:
|
|
- tutorial: 튜토리얼, 강의
|
|
- gaming: 게임, 리액션
|
|
- cooking: 요리, 먹방
|
|
- comedy: 코미디, 유머
|
|
- travel: 여행, 브이로그
|
|
- news: 뉴스, 정보
|
|
- asmr: ASMR, 릴렉스
|
|
- fitness: 운동, 피트니스
|
|
- tech: 기술, IT
|
|
- lifestyle: 라이프스타일, 일상
|
|
"""
|
|
content_type_lower = content_type.lower()
|
|
|
|
if content_type_lower not in FONT_RECOMMENDATIONS:
|
|
available_types = list(FONT_RECOMMENDATIONS.keys())
|
|
raise HTTPException(
|
|
status_code=404,
|
|
detail=f"Unknown content type. Available: {', '.join(available_types)}"
|
|
)
|
|
|
|
recommended_ids = FONT_RECOMMENDATIONS[content_type_lower]
|
|
recommendations = []
|
|
|
|
for font_id in recommended_ids:
|
|
if font_id in KOREAN_FONTS:
|
|
font = KOREAN_FONTS[font_id]
|
|
recommendations.append({
|
|
"id": font.id,
|
|
"name": font.name,
|
|
"style": font.style,
|
|
"download_url": font.download_url,
|
|
})
|
|
|
|
return {
|
|
"content_type": content_type_lower,
|
|
"recommendations": recommendations,
|
|
"primary": recommended_ids[0] if recommended_ids else "NanumGothic",
|
|
}
|
|
|
|
|
|
@router.get("/categories")
|
|
async def list_font_categories():
|
|
"""
|
|
List fonts grouped by style category.
|
|
"""
|
|
categories = {
|
|
"clean": {
|
|
"name": "깔끔/모던",
|
|
"description": "정보성 콘텐츠, 튜토리얼에 적합",
|
|
"fonts": ["Pretendard", "SpoqaHanSansNeo", "NanumGothic"],
|
|
},
|
|
"friendly": {
|
|
"name": "친근/둥글",
|
|
"description": "일상, 라이프스타일 콘텐츠에 적합",
|
|
"fonts": ["GmarketSans", "NanumSquareRound", "Cafe24SsurroundAir"],
|
|
},
|
|
"handwriting": {
|
|
"name": "손글씨/캐주얼",
|
|
"description": "먹방, 요리, 유머 콘텐츠에 적합",
|
|
"fonts": ["BMDoHyeon", "BMJua", "DoHyeon"],
|
|
},
|
|
"impact": {
|
|
"name": "강조/임팩트",
|
|
"description": "게임, 하이라이트, 리액션에 적합",
|
|
"fonts": ["Cafe24Ssurround", "BlackHanSans"],
|
|
},
|
|
}
|
|
|
|
# Add font details to each category
|
|
for category_id, category_info in categories.items():
|
|
font_details = []
|
|
for font_id in category_info["fonts"]:
|
|
if font_id in KOREAN_FONTS:
|
|
font = KOREAN_FONTS[font_id]
|
|
font_details.append({
|
|
"id": font.id,
|
|
"name": font.name,
|
|
})
|
|
category_info["font_details"] = font_details
|
|
|
|
return {
|
|
"categories": categories,
|
|
}
|
|
|
|
|
|
@router.get("/{font_id}")
|
|
async def get_font(font_id: str):
|
|
"""
|
|
Get detailed information about a specific font.
|
|
"""
|
|
if font_id not in KOREAN_FONTS:
|
|
available_fonts = list(KOREAN_FONTS.keys())
|
|
raise HTTPException(
|
|
status_code=404,
|
|
detail=f"Font not found. Available fonts: {', '.join(available_fonts)}"
|
|
)
|
|
|
|
font = KOREAN_FONTS[font_id]
|
|
return {
|
|
"id": font.id,
|
|
"name": font.name,
|
|
"style": font.style,
|
|
"recommended_for": font.recommended_for,
|
|
"download_url": font.download_url,
|
|
"license": font.license,
|
|
"usage_example": {
|
|
"subtitle_style": {
|
|
"font_name": font.id,
|
|
"font_size": 36,
|
|
"position": "center",
|
|
}
|
|
},
|
|
}
|