""" 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), )