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:
kihong.kim
2026-01-03 21:38:34 +09:00
commit c3795138da
64 changed files with 13059 additions and 0 deletions

64
backend/app/main.py Normal file
View File

@@ -0,0 +1,64 @@
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles
from contextlib import asynccontextmanager
import os
from app.routers import download, process, bgm, jobs, fonts
from app.config import settings
@asynccontextmanager
async def lifespan(app: FastAPI):
# Startup
os.makedirs(settings.DOWNLOAD_DIR, exist_ok=True)
os.makedirs(settings.PROCESSED_DIR, exist_ok=True)
os.makedirs(settings.BGM_DIR, exist_ok=True)
# Check BGM status on startup
bgm_files = []
if os.path.exists(settings.BGM_DIR):
bgm_files = [f for f in os.listdir(settings.BGM_DIR) if f.endswith(('.mp3', '.wav', '.m4a', '.ogg'))]
if len(bgm_files) == 0:
print("[Startup] No BGM files found. Upload BGM via /api/bgm/upload or download from Pixabay/Mixkit")
else:
names = ', '.join(bgm_files[:3])
suffix = f'... (+{len(bgm_files) - 3} more)' if len(bgm_files) > 3 else ''
print(f"[Startup] Found {len(bgm_files)} BGM files: {names}{suffix}")
yield
# Shutdown
app = FastAPI(
title="Shorts Maker API",
description="중국 쇼츠 영상을 한글 자막으로 변환하는 서비스",
version="1.0.0",
lifespan=lifespan,
)
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Static files for processed videos
app.mount("/static/downloads", StaticFiles(directory="data/downloads"), name="downloads")
app.mount("/static/processed", StaticFiles(directory="data/processed"), name="processed")
app.mount("/static/bgm", StaticFiles(directory="data/bgm"), name="bgm")
# Routers
app.include_router(download.router, prefix="/api/download", tags=["Download"])
app.include_router(process.router, prefix="/api/process", tags=["Process"])
app.include_router(bgm.router, prefix="/api/bgm", tags=["BGM"])
app.include_router(jobs.router, prefix="/api/jobs", tags=["Jobs"])
app.include_router(fonts.router, prefix="/api/fonts", tags=["Fonts"])
@app.get("/api/health")
async def health_check():
return {"status": "healthy", "service": "shorts-maker"}