o2o-castad-backend/app/song/api/routers/v1/song.py

197 lines
5.5 KiB
Python

"""
Song API Router
이 모듈은 Suno API를 통한 노래 생성 관련 API 엔드포인트를 정의합니다.
엔드포인트 목록:
- POST /song/generate: 노래 생성 요청
- GET /song/status/{task_id}: 노래 생성 상태 조회
사용 예시:
from app.song.api.routers.v1.song import router
app.include_router(router, prefix="/api/v1")
"""
from fastapi import APIRouter
from app.song.schemas.song_schema import (
GenerateSongRequest,
GenerateSongResponse,
PollingSongResponse,
SongClipData,
)
from app.utils.suno import SunoService
def _parse_suno_status_response(result: dict) -> PollingSongResponse:
"""Suno API 상태 응답을 파싱하여 PollingSongResponse로 변환합니다."""
code = result.get("code", 0)
data = result.get("data", {})
if code != 200:
return PollingSongResponse(
success=False,
status="failed",
message="Suno API 응답 오류",
clips=None,
raw_response=result,
error_message=result.get("msg", "Unknown error"),
)
status = data.get("status", "unknown")
clips_data = data.get("data", [])
# 상태별 메시지
status_messages = {
"pending": "노래 생성 대기 중입니다.",
"processing": "노래를 생성하고 있습니다.",
"complete": "노래 생성이 완료되었습니다.",
"failed": "노래 생성에 실패했습니다.",
}
# 클립 데이터 파싱
clips = None
if clips_data:
clips = [
SongClipData(
id=clip.get("id"),
audio_url=clip.get("audio_url"),
stream_audio_url=clip.get("stream_audio_url"),
image_url=clip.get("image_url"),
title=clip.get("title"),
status=clip.get("status"),
duration=clip.get("duration"),
)
for clip in clips_data
]
return PollingSongResponse(
success=True,
status=status,
message=status_messages.get(status, f"상태: {status}"),
clips=clips,
raw_response=result,
error_message=None,
)
router = APIRouter(prefix="/song", tags=["song"])
@router.post(
"/generate",
summary="노래 생성 요청",
description="""
Suno API를 통해 노래 생성을 요청합니다.
## 요청 필드
- **lyrics**: 노래에 사용할 가사 (필수)
- **genre**: 음악 장르 (필수) - K-Pop, Pop, R&B, Hip-Hop, Ballad, EDM, Rock, Jazz 등
- **language**: 노래 언어 (선택, 기본값: Korean)
## 반환 정보
- **success**: 요청 성공 여부
- **task_id**: Suno 작업 ID (폴링에 사용)
- **message**: 응답 메시지
## 사용 예시
```
POST /song/generate
{
"lyrics": "여기 군산에서 만나요\\n아름다운 하루를 함께",
"genre": "K-Pop",
"language": "Korean"
}
```
## 참고
- 생성되는 노래는 약 1분 이내 길이입니다.
- task_id를 사용하여 /status/{task_id} 엔드포인트에서 생성 상태를 확인할 수 있습니다.
""",
response_model=GenerateSongResponse,
responses={
200: {"description": "노래 생성 요청 성공"},
500: {"description": "노래 생성 요청 실패"},
},
)
async def generate_song(
request_body: GenerateSongRequest,
) -> GenerateSongResponse:
"""가사와 장르를 기반으로 Suno API를 통해 노래를 생성합니다."""
try:
suno_service = SunoService()
task_id = await suno_service.generate(
prompt=request_body.lyrics,
genre=request_body.genre,
)
return GenerateSongResponse(
success=True,
task_id=task_id,
message="노래 생성 요청이 접수되었습니다. task_id로 상태를 조회하세요.",
error_message=None,
)
except Exception as e:
return GenerateSongResponse(
success=False,
task_id=None,
message="노래 생성 요청에 실패했습니다.",
error_message=str(e),
)
@router.get(
"/status/{task_id}",
summary="노래 생성 상태 조회",
description="""
Suno API를 통해 노래 생성 작업의 상태를 조회합니다.
## 경로 파라미터
- **task_id**: 노래 생성 시 반환된 작업 ID (필수)
## 반환 정보
- **success**: 조회 성공 여부
- **status**: 작업 상태 (pending, processing, complete, failed)
- **message**: 상태 메시지
- **clips**: 생성된 노래 클립 목록 (완료 시)
- **raw_response**: Suno API 원본 응답
## 사용 예시
```
GET /song/status/abc123...
```
## 상태 값
- **pending**: 대기 중
- **processing**: 생성 중
- **complete**: 생성 완료
- **failed**: 생성 실패
## 참고
- 스트림 URL: 30-40초 내 생성
- 다운로드 URL: 2-3분 내 생성
""",
response_model=PollingSongResponse,
responses={
200: {"description": "상태 조회 성공"},
500: {"description": "상태 조회 실패"},
},
)
async def get_song_status(
task_id: str,
) -> PollingSongResponse:
"""task_id로 노래 생성 작업의 상태를 조회합니다."""
try:
suno_service = SunoService()
result = await suno_service.get_task_status(task_id)
return _parse_suno_status_response(result)
except Exception as e:
return PollingSongResponse(
success=False,
status="error",
message="상태 조회에 실패했습니다.",
clips=None,
raw_response=None,
error_message=str(e),
)