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>
298 lines
9.3 KiB
Python
298 lines
9.3 KiB
Python
"""
|
|
Default BGM Initializer
|
|
|
|
Downloads pre-selected royalty-free BGM tracks on first startup.
|
|
Tracks are from Kevin MacLeod (incompetech.com) - CC-BY 4.0 License.
|
|
Free for commercial use with attribution: "Kevin MacLeod (incompetech.com)"
|
|
"""
|
|
|
|
import os
|
|
import httpx
|
|
import aiofiles
|
|
import asyncio
|
|
from typing import List, Tuple, Optional
|
|
from pydantic import BaseModel
|
|
|
|
|
|
class DefaultBGM(BaseModel):
|
|
"""Default BGM track info."""
|
|
id: str
|
|
name: str
|
|
url: str
|
|
category: str
|
|
description: str
|
|
|
|
|
|
# Curated list of royalty-free BGM from Kevin MacLeod (incompetech.com)
|
|
# CC-BY 4.0 License - Free for commercial use with attribution
|
|
# Attribution: "Kevin MacLeod (incompetech.com)"
|
|
DEFAULT_BGM_TRACKS: List[DefaultBGM] = [
|
|
# === 활기찬/에너지 (Upbeat/Energetic) ===
|
|
DefaultBGM(
|
|
id="upbeat_energetic",
|
|
name="Upbeat Energetic",
|
|
url="https://incompetech.com/music/royalty-free/mp3-royaltyfree/Vivacity.mp3",
|
|
category="upbeat",
|
|
description="활기차고 에너지 넘치는 BGM - 피트니스, 챌린지 영상",
|
|
),
|
|
DefaultBGM(
|
|
id="happy_pop",
|
|
name="Happy Pop",
|
|
url="https://incompetech.com/music/royalty-free/mp3-royaltyfree/Carefree.mp3",
|
|
category="upbeat",
|
|
description="밝고 경쾌한 팝 BGM - 제품 소개, 언박싱",
|
|
),
|
|
DefaultBGM(
|
|
id="upbeat_fun",
|
|
name="Upbeat Fun",
|
|
url="https://incompetech.com/music/royalty-free/mp3-royaltyfree/Happy%20Happy%20Game%20Show.mp3",
|
|
category="upbeat",
|
|
description="신나는 게임쇼 비트 - 트렌디한 쇼츠",
|
|
),
|
|
|
|
# === 차분한/편안한 (Chill/Relaxing) ===
|
|
DefaultBGM(
|
|
id="chill_lofi",
|
|
name="Chill Lo-Fi",
|
|
url="https://incompetech.com/music/royalty-free/mp3-royaltyfree/Gymnopedie%20No%201.mp3",
|
|
category="chill",
|
|
description="차분하고 편안한 피아노 BGM - 일상, 브이로그",
|
|
),
|
|
DefaultBGM(
|
|
id="calm_piano",
|
|
name="Calm Piano",
|
|
url="https://incompetech.com/music/royalty-free/mp3-royaltyfree/Prelude%20No.%201.mp3",
|
|
category="chill",
|
|
description="잔잔한 피아노 BGM - 감성적인 콘텐츠",
|
|
),
|
|
DefaultBGM(
|
|
id="soft_ambient",
|
|
name="Soft Ambient",
|
|
url="https://incompetech.com/music/royalty-free/mp3-royaltyfree/Dreamlike.mp3",
|
|
category="chill",
|
|
description="부드러운 앰비언트 - ASMR, 수면 콘텐츠",
|
|
),
|
|
|
|
# === 유머/코미디 (Funny/Comedy) ===
|
|
DefaultBGM(
|
|
id="funny_comedy",
|
|
name="Funny Comedy",
|
|
url="https://incompetech.com/music/royalty-free/mp3-royaltyfree/Sneaky%20Snitch.mp3",
|
|
category="funny",
|
|
description="유쾌한 코미디 BGM - 코미디, 밈 영상",
|
|
),
|
|
DefaultBGM(
|
|
id="quirky_playful",
|
|
name="Quirky Playful",
|
|
url="https://incompetech.com/music/royalty-free/mp3-royaltyfree/Monkeys%20Spinning%20Monkeys.mp3",
|
|
category="funny",
|
|
description="장난스럽고 귀여운 BGM - 펫, 키즈 콘텐츠",
|
|
),
|
|
|
|
# === 드라마틱/시네마틱 (Cinematic) ===
|
|
DefaultBGM(
|
|
id="cinematic_epic",
|
|
name="Cinematic Epic",
|
|
url="https://incompetech.com/music/royalty-free/mp3-royaltyfree/Epic%20Unease.mp3",
|
|
category="cinematic",
|
|
description="웅장한 시네마틱 BGM - 리뷰, 소개 영상",
|
|
),
|
|
DefaultBGM(
|
|
id="inspirational",
|
|
name="Inspirational",
|
|
url="https://incompetech.com/music/royalty-free/mp3-royaltyfree/Hero%20Theme.mp3",
|
|
category="cinematic",
|
|
description="영감을 주는 BGM - 동기부여, 성장 콘텐츠",
|
|
),
|
|
|
|
# === 생활용품/제품 리뷰 (Lifestyle/Product) ===
|
|
DefaultBGM(
|
|
id="lifestyle_modern",
|
|
name="Lifestyle Modern",
|
|
url="https://incompetech.com/music/royalty-free/mp3-royaltyfree/Acoustic%20Breeze.mp3",
|
|
category="lifestyle",
|
|
description="모던한 라이프스타일 BGM - 제품 리뷰",
|
|
),
|
|
DefaultBGM(
|
|
id="shopping_bright",
|
|
name="Shopping Bright",
|
|
url="https://incompetech.com/music/royalty-free/mp3-royaltyfree/Pleasant%20Porridge.mp3",
|
|
category="lifestyle",
|
|
description="밝은 쇼핑 BGM - 하울, 추천 영상",
|
|
),
|
|
DefaultBGM(
|
|
id="soft_corporate",
|
|
name="Soft Corporate",
|
|
url="https://incompetech.com/music/royalty-free/mp3-royaltyfree/Laid%20Back%20Guitars.mp3",
|
|
category="lifestyle",
|
|
description="부드러운 기업형 BGM - 정보성 콘텐츠",
|
|
),
|
|
|
|
# === 어쿠스틱/감성 (Acoustic/Emotional) ===
|
|
DefaultBGM(
|
|
id="soft_acoustic",
|
|
name="Soft Acoustic",
|
|
url="https://incompetech.com/music/royalty-free/mp3-royaltyfree/Peaceful.mp3",
|
|
category="acoustic",
|
|
description="따뜻한 어쿠스틱 BGM - 요리, 일상 브이로그",
|
|
),
|
|
DefaultBGM(
|
|
id="gentle_guitar",
|
|
name="Gentle Guitar",
|
|
url="https://incompetech.com/music/royalty-free/mp3-royaltyfree/Sunflower%20Slow%20Drag.mp3",
|
|
category="acoustic",
|
|
description="잔잔한 기타 BGM - 여행, 풍경 영상",
|
|
),
|
|
|
|
# === 트렌디/일렉트로닉 (Trendy/Electronic) ===
|
|
DefaultBGM(
|
|
id="electronic_chill",
|
|
name="Electronic Chill",
|
|
url="https://incompetech.com/music/royalty-free/mp3-royaltyfree/Digital%20Lemonade.mp3",
|
|
category="electronic",
|
|
description="일렉트로닉 칠아웃 - 테크, 게임 콘텐츠",
|
|
),
|
|
DefaultBGM(
|
|
id="driving_beat",
|
|
name="Driving Beat",
|
|
url="https://incompetech.com/music/royalty-free/mp3-royaltyfree/Cipher.mp3",
|
|
category="electronic",
|
|
description="드라이빙 비트 - 스포츠, 액션 영상",
|
|
),
|
|
]
|
|
|
|
|
|
async def download_bgm_file(
|
|
url: str,
|
|
output_path: str,
|
|
timeout: int = 60,
|
|
) -> Tuple[bool, str]:
|
|
"""
|
|
Download a single BGM file.
|
|
|
|
Args:
|
|
url: Download URL
|
|
output_path: Full path to save the file
|
|
timeout: Download timeout in seconds
|
|
|
|
Returns:
|
|
Tuple of (success, message)
|
|
"""
|
|
headers = {
|
|
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
|
"Accept": "audio/mpeg,audio/*;q=0.9,*/*;q=0.8",
|
|
"Accept-Language": "en-US,en;q=0.9",
|
|
}
|
|
|
|
try:
|
|
async with httpx.AsyncClient(follow_redirects=True, headers=headers) as client:
|
|
response = await client.get(url, timeout=timeout)
|
|
|
|
if response.status_code != 200:
|
|
return False, f"HTTP {response.status_code}"
|
|
|
|
# Ensure directory exists
|
|
os.makedirs(os.path.dirname(output_path), exist_ok=True)
|
|
|
|
# Save file
|
|
async with aiofiles.open(output_path, 'wb') as f:
|
|
await f.write(response.content)
|
|
|
|
return True, "Downloaded successfully"
|
|
|
|
except httpx.TimeoutException:
|
|
return False, "Download timeout"
|
|
except Exception as e:
|
|
return False, str(e)
|
|
|
|
|
|
async def initialize_default_bgm(
|
|
bgm_dir: str,
|
|
force: bool = False,
|
|
) -> Tuple[int, int, List[str]]:
|
|
"""
|
|
Initialize default BGM tracks.
|
|
|
|
Downloads default BGM tracks if not already present.
|
|
|
|
Args:
|
|
bgm_dir: Directory to save BGM files
|
|
force: Force re-download even if files exist
|
|
|
|
Returns:
|
|
Tuple of (downloaded_count, skipped_count, error_messages)
|
|
"""
|
|
os.makedirs(bgm_dir, exist_ok=True)
|
|
|
|
downloaded = 0
|
|
skipped = 0
|
|
errors = []
|
|
|
|
for track in DEFAULT_BGM_TRACKS:
|
|
output_path = os.path.join(bgm_dir, f"{track.id}.mp3")
|
|
|
|
# Skip if already exists (unless force=True)
|
|
if os.path.exists(output_path) and not force:
|
|
skipped += 1
|
|
print(f"[BGM] Skipping {track.name} (already exists)")
|
|
continue
|
|
|
|
print(f"[BGM] Downloading {track.name}...")
|
|
success, message = await download_bgm_file(track.url, output_path)
|
|
|
|
if success:
|
|
downloaded += 1
|
|
print(f"[BGM] Downloaded {track.name}")
|
|
else:
|
|
errors.append(f"{track.name}: {message}")
|
|
print(f"[BGM] Failed to download {track.name}: {message}")
|
|
|
|
return downloaded, skipped, errors
|
|
|
|
|
|
async def get_default_bgm_list() -> List[dict]:
|
|
"""
|
|
Get list of default BGM tracks with metadata.
|
|
|
|
Returns:
|
|
List of BGM info dictionaries
|
|
"""
|
|
return [
|
|
{
|
|
"id": track.id,
|
|
"name": track.name,
|
|
"category": track.category,
|
|
"description": track.description,
|
|
}
|
|
for track in DEFAULT_BGM_TRACKS
|
|
]
|
|
|
|
|
|
def check_default_bgm_status(bgm_dir: str) -> dict:
|
|
"""
|
|
Check which default BGM tracks are installed.
|
|
|
|
Args:
|
|
bgm_dir: BGM directory path
|
|
|
|
Returns:
|
|
Status dictionary with installed/missing tracks
|
|
"""
|
|
installed = []
|
|
missing = []
|
|
|
|
for track in DEFAULT_BGM_TRACKS:
|
|
file_path = os.path.join(bgm_dir, f"{track.id}.mp3")
|
|
if os.path.exists(file_path):
|
|
installed.append(track.id)
|
|
else:
|
|
missing.append(track.id)
|
|
|
|
return {
|
|
"total": len(DEFAULT_BGM_TRACKS),
|
|
"installed": len(installed),
|
|
"missing": len(missing),
|
|
"installed_ids": installed,
|
|
"missing_ids": missing,
|
|
}
|