diff --git a/app/song/api/routers/v1/song.py b/app/song/api/routers/v1/song.py index 13f3901..e9a9738 100644 --- a/app/song/api/routers/v1/song.py +++ b/app/song/api/routers/v1/song.py @@ -13,7 +13,7 @@ Song API Router app.include_router(router, prefix="/api/v1") """ -from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException +from fastapi import APIRouter, Depends, HTTPException from sqlalchemy import func, select from sqlalchemy.ext.asyncio import AsyncSession @@ -32,7 +32,6 @@ from app.song.schemas.song_schema import ( PollingSongResponse, SongListItem, ) -from app.song.worker.song_task import download_and_upload_song_by_suno_task_id from app.utils.pagination import PaginatedResponse from app.utils.suno import SunoService @@ -220,7 +219,6 @@ GET /song/status/abc123... ) async def get_song_status( suno_task_id: str, - background_tasks: BackgroundTasks, session: AsyncSession = Depends(get_session), ) -> PollingSongResponse: """suno_task_id로 노래 생성 작업의 상태를 조회합니다. @@ -236,7 +234,7 @@ async def get_song_status( parsed_response = suno_service.parse_status_response(result) print(f"[get_song_status] Suno API response - suno_task_id: {suno_task_id}, status: {parsed_response.status}") - # SUCCESS 상태인 경우 백그라운드 태스크 실행 + # SUCCESS 상태인 경우 첫 번째 클립 정보를 DB에 직접 저장 if parsed_response.status == "SUCCESS" and parsed_response.clips: # 첫 번째 클립(clips[0])의 audioUrl과 duration 사용 first_clip = parsed_response.clips[0] @@ -245,7 +243,7 @@ async def get_song_status( print(f"[get_song_status] Using first clip - id: {first_clip.id}, audio_url: {audio_url}, duration: {clip_duration}") if audio_url: - # suno_task_id로 Song 조회하여 store_name 가져오기 + # suno_task_id로 Song 조회 song_result = await session.execute( select(Song) .where(Song.suno_task_id == suno_task_id) @@ -255,32 +253,15 @@ async def get_song_status( song = song_result.scalar_one_or_none() if song and song.status != "completed": - # 이미 완료된 경우 백그라운드 작업 중복 실행 방지 - # project_id로 Project 조회하여 store_name 가져오기 - project_result = await session.execute( - select(Project).where(Project.id == song.project_id) - ) - project = project_result.scalar_one_or_none() - - store_name = project.store_name if project else "song" - - # 백그라운드 태스크로 MP3 다운로드 및 Blob 업로드, DB 업데이트 (suno_task_id 사용) - print(f"[get_song_status] Background task args - suno_task_id: {suno_task_id}, audio_url: {audio_url}, store_name: {store_name}, duration: {clip_duration}") - background_tasks.add_task( - download_and_upload_song_by_suno_task_id, - suno_task_id=suno_task_id, - audio_url=audio_url, - store_name=store_name, - duration=clip_duration, - ) - elif song and song.status == "completed": - # 이미 완료된 경우에도 duration이 다르면 업데이트 - if clip_duration is not None and song.duration != clip_duration: - print(f"[get_song_status] Updating duration - suno_task_id: {suno_task_id}, old: {song.duration}, new: {clip_duration}") + # 첫 번째 클립의 audio_url과 duration을 직접 DB에 저장 + song.status = "completed" + song.song_result_url = audio_url + if clip_duration is not None: song.duration = clip_duration - await session.commit() - else: - print(f"[get_song_status] SKIPPED - Song already completed, suno_task_id: {suno_task_id}") + await session.commit() + print(f"[get_song_status] Song updated - suno_task_id: {suno_task_id}, status: completed, song_result_url: {audio_url}, duration: {clip_duration}") + elif song and song.status == "completed": + print(f"[get_song_status] SKIPPED - Song already completed, suno_task_id: {suno_task_id}") print(f"[get_song_status] SUCCESS - suno_task_id: {suno_task_id}") return parsed_response @@ -459,23 +440,36 @@ async def get_songs( try: offset = (pagination.page - 1) * pagination.page_size - # 서브쿼리: task_id별 최신 Song의 id 조회 (completed 상태만) - subquery = ( - select(func.max(Song.id).label("max_id")) + # 서브쿼리: task_id별 최신 Song의 id 조회 (completed 상태, created_at 기준) + from sqlalchemy import and_ + + # task_id별 최신 created_at 조회 + latest_subquery = ( + select( + Song.task_id, + func.max(Song.created_at).label("max_created_at") + ) .where(Song.status == "completed") .group_by(Song.task_id) .subquery() ) # 전체 개수 조회 (task_id별 최신 1개만) - count_query = select(func.count()).select_from(subquery) + count_query = select(func.count()).select_from(latest_subquery) total_result = await session.execute(count_query) total = total_result.scalar() or 0 - # 데이터 조회 (completed 상태, task_id별 최신 1개만, 최신순) + # 데이터 조회 (completed 상태, task_id별 created_at 기준 최신 1개만, 최신순) query = ( select(Song) - .where(Song.id.in_(select(subquery.c.max_id))) + .join( + latest_subquery, + and_( + Song.task_id == latest_subquery.c.task_id, + Song.created_at == latest_subquery.c.max_created_at + ) + ) + .where(Song.status == "completed") .order_by(Song.created_at.desc()) .offset(offset) .limit(pagination.page_size) diff --git a/app/video/api/routers/v1/video.py b/app/video/api/routers/v1/video.py index 21c1022..74e8225 100644 --- a/app/video/api/routers/v1/video.py +++ b/app/video/api/routers/v1/video.py @@ -165,6 +165,7 @@ async def generate_video( detail=f"task_id '{task_id}'에 해당하는 Song을 찾을 수 없습니다.", ) print(f"[generate_video] Song found - song_id: {song.id}, task_id: {task_id}, duration: {song.duration}") + print(f"[generate_video] Music URL: {request_body.music_url}, Song duration: {song.duration}") # 4. Video 테이블에 초기 데이터 저장 video = Video(