- 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>
166 lines
5.3 KiB
Python
166 lines
5.3 KiB
Python
"""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()
|