Files
jungwoo choi cc547372c0 feat: Drama Studio 프로젝트 초기 구조 설정
- FastAPI 백엔드 (audio-studio-api)
- Next.js 프론트엔드 (audio-studio-ui)
- Qwen3-TTS 엔진 (audio-studio-tts)
- MusicGen 서비스 (audio-studio-musicgen)
- Docker Compose 개발/운영 환경

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-26 11:39:38 +09:00

206 lines
6.2 KiB
Python

"""Audio Studio MusicGen API
AI 음악 생성 API 서버
"""
import logging
from contextlib import asynccontextmanager
from typing import Optional
from fastapi import FastAPI, HTTPException, UploadFile, File, Form
from fastapi.responses import Response
from pydantic import BaseModel, Field
from app.services.musicgen_service import musicgen_service
# 로깅 설정
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
)
logger = logging.getLogger(__name__)
# ========================================
# Pydantic 모델
# ========================================
class GenerateRequest(BaseModel):
"""음악 생성 요청"""
prompt: str = Field(..., min_length=5, max_length=500, description="음악 설명")
duration: int = Field(default=30, ge=5, le=30, description="생성 길이 (초)")
top_k: int = Field(default=250, ge=50, le=500, description="top-k 샘플링")
temperature: float = Field(default=1.0, ge=0.5, le=2.0, description="생성 다양성")
class HealthResponse(BaseModel):
"""헬스체크 응답"""
status: str
model_info: dict
# ========================================
# 앱 생명주기
# ========================================
@asynccontextmanager
async def lifespan(app: FastAPI):
"""앱 시작/종료 시 실행"""
logger.info("MusicGen 서비스 시작...")
try:
await musicgen_service.initialize()
logger.info("MusicGen 서비스 준비 완료")
except Exception as e:
logger.error(f"MusicGen 초기화 실패: {e}")
# 초기화 실패해도 서버는 시작 (lazy loading 시도)
yield
logger.info("MusicGen 서비스 종료")
# ========================================
# FastAPI 앱
# ========================================
app = FastAPI(
title="Audio Studio MusicGen",
description="AI 음악 생성 API (Meta AudioCraft)",
version="0.1.0",
lifespan=lifespan,
)
# ========================================
# API 엔드포인트
# ========================================
@app.get("/health", response_model=HealthResponse)
async def health_check():
"""헬스체크 엔드포인트"""
return HealthResponse(
status="healthy" if musicgen_service.is_initialized() else "initializing",
model_info=musicgen_service.get_model_info(),
)
@app.post("/generate")
async def generate_music(request: GenerateRequest):
"""텍스트 프롬프트로 음악 생성
예시 프롬프트:
- "upbeat electronic music for gaming"
- "calm piano music, peaceful, ambient"
- "energetic rock music with drums"
- "lo-fi hip hop beats, relaxing"
"""
try:
audio_bytes = await musicgen_service.generate(
prompt=request.prompt,
duration=request.duration,
top_k=request.top_k,
temperature=request.temperature,
)
return Response(
content=audio_bytes,
media_type="audio/wav",
headers={
"X-Sample-Rate": "32000",
"X-Duration": str(request.duration),
"Content-Disposition": 'attachment; filename="generated_music.wav"',
},
)
except Exception as e:
logger.error(f"음악 생성 실패: {e}")
raise HTTPException(status_code=500, detail=f"Music generation failed: {str(e)}")
@app.post("/generate-with-melody")
async def generate_with_melody(
prompt: str = Form(..., min_length=5, description="음악 설명"),
duration: int = Form(default=30, ge=5, le=30, description="생성 길이"),
melody_audio: UploadFile = File(..., description="참조 멜로디 오디오"),
):
"""멜로디 조건부 음악 생성
참조 멜로디의 멜로디/하모니를 유지하면서 새로운 음악 생성
"""
try:
melody_bytes = await melody_audio.read()
if len(melody_bytes) < 1000:
raise HTTPException(status_code=400, detail="Melody audio is too small")
audio_bytes = await musicgen_service.generate_with_melody(
prompt=prompt,
melody_audio=melody_bytes,
duration=duration,
)
return Response(
content=audio_bytes,
media_type="audio/wav",
headers={
"X-Sample-Rate": "32000",
"X-Duration": str(duration),
"Content-Disposition": 'attachment; filename="melody_based_music.wav"',
},
)
except HTTPException:
raise
except Exception as e:
logger.error(f"멜로디 기반 생성 실패: {e}")
raise HTTPException(status_code=500, detail=f"Generation failed: {str(e)}")
@app.get("/prompts")
async def get_example_prompts():
"""예시 프롬프트 목록"""
return {
"examples": [
{
"category": "Electronic",
"prompts": [
"upbeat electronic dance music with synthesizers",
"chill electronic ambient music",
"retro synthwave 80s style music",
],
},
{
"category": "Classical",
"prompts": [
"calm piano solo, classical style",
"orchestral epic cinematic music",
"gentle string quartet, romantic",
],
},
{
"category": "Pop/Rock",
"prompts": [
"energetic rock music with electric guitar",
"upbeat pop song with catchy melody",
"acoustic guitar folk music",
],
},
{
"category": "Ambient/Lo-fi",
"prompts": [
"lo-fi hip hop beats, relaxing, study music",
"peaceful ambient nature sounds music",
"meditation music, calm, zen",
],
},
{
"category": "Game/Film",
"prompts": [
"epic adventure game soundtrack",
"tense suspenseful thriller music",
"cheerful happy video game background",
],
},
]
}