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>
This commit is contained in:
jungwoo choi
2026-01-26 11:39:38 +09:00
commit cc547372c0
70 changed files with 18399 additions and 0 deletions

View File

@ -0,0 +1,165 @@
"""Freesound API 클라이언트
효과음 검색 및 다운로드
https://freesound.org/docs/api/
"""
import os
import logging
from typing import Optional, List, Dict
import httpx
logger = logging.getLogger(__name__)
class FreesoundClient:
"""Freesound API 클라이언트"""
BASE_URL = "https://freesound.org/apiv2"
def __init__(self):
self.api_key = os.getenv("FREESOUND_API_KEY", "")
self.timeout = httpx.Timeout(30.0, connect=10.0)
def _get_headers(self) -> dict:
"""인증 헤더 반환"""
return {"Authorization": f"Token {self.api_key}"}
async def search(
self,
query: str,
page: int = 1,
page_size: int = 20,
filter_fields: Optional[str] = None,
sort: str = "score",
min_duration: Optional[float] = None,
max_duration: Optional[float] = None,
) -> Dict:
"""효과음 검색
Args:
query: 검색어
page: 페이지 번호
page_size: 페이지당 결과 수
filter_fields: 필터 (예: "duration:[1 TO 5]")
sort: 정렬 (score, duration_asc, duration_desc, created_desc 등)
min_duration: 최소 길이 (초)
max_duration: 최대 길이 (초)
Returns:
검색 결과 딕셔너리
"""
if not self.api_key:
logger.warning("Freesound API 키가 설정되지 않음")
return {"count": 0, "results": []}
# 필터 구성
filters = []
if min_duration is not None or max_duration is not None:
min_d = min_duration if min_duration is not None else 0
max_d = max_duration if max_duration is not None else "*"
filters.append(f"duration:[{min_d} TO {max_d}]")
if filter_fields:
filters.append(filter_fields)
params = {
"query": query,
"page": page,
"page_size": min(page_size, 150), # Freesound 최대 150
"sort": sort,
"fields": "id,name,description,duration,tags,previews,license,username",
}
if filters:
params["filter"] = " ".join(filters)
async with httpx.AsyncClient(timeout=self.timeout) as client:
response = await client.get(
f"{self.BASE_URL}/search/text/",
params=params,
headers=self._get_headers(),
)
response.raise_for_status()
data = response.json()
# 결과 정리
results = []
for sound in data.get("results", []):
results.append({
"freesound_id": sound["id"],
"name": sound.get("name", ""),
"description": sound.get("description", ""),
"duration": sound.get("duration", 0),
"tags": sound.get("tags", []),
"preview_url": sound.get("previews", {}).get("preview-hq-mp3", ""),
"license": sound.get("license", ""),
"username": sound.get("username", ""),
})
return {
"count": data.get("count", 0),
"page": page,
"page_size": page_size,
"results": results,
}
async def get_sound(self, sound_id: int) -> Dict:
"""사운드 상세 정보 조회"""
if not self.api_key:
raise ValueError("Freesound API 키 필요")
async with httpx.AsyncClient(timeout=self.timeout) as client:
response = await client.get(
f"{self.BASE_URL}/sounds/{sound_id}/",
headers=self._get_headers(),
)
response.raise_for_status()
return response.json()
async def download_preview(self, preview_url: str) -> bytes:
"""프리뷰 오디오 다운로드 (인증 불필요)"""
async with httpx.AsyncClient(timeout=self.timeout) as client:
response = await client.get(preview_url)
response.raise_for_status()
return response.content
async def get_similar_sounds(
self,
sound_id: int,
page_size: int = 10,
) -> List[Dict]:
"""유사한 사운드 검색"""
if not self.api_key:
return []
async with httpx.AsyncClient(timeout=self.timeout) as client:
response = await client.get(
f"{self.BASE_URL}/sounds/{sound_id}/similar/",
params={
"page_size": page_size,
"fields": "id,name,description,duration,tags,previews,license",
},
headers=self._get_headers(),
)
response.raise_for_status()
data = response.json()
results = []
for sound in data.get("results", []):
results.append({
"freesound_id": sound["id"],
"name": sound.get("name", ""),
"description": sound.get("description", ""),
"duration": sound.get("duration", 0),
"tags": sound.get("tags", []),
"preview_url": sound.get("previews", {}).get("preview-hq-mp3", ""),
"license": sound.get("license", ""),
})
return results
# 싱글톤 인스턴스
freesound_client = FreesoundClient()