insta
bluebamus 2025-12-26 18:45:06 +09:00
parent 5dddbaeda2
commit d4bce083ab
2 changed files with 31 additions and 36 deletions

View File

@ -13,7 +13,7 @@ Song API Router
app.include_router(router, prefix="/api/v1") 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 import func, select
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
@ -32,7 +32,6 @@ from app.song.schemas.song_schema import (
PollingSongResponse, PollingSongResponse,
SongListItem, 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.pagination import PaginatedResponse
from app.utils.suno import SunoService from app.utils.suno import SunoService
@ -220,7 +219,6 @@ GET /song/status/abc123...
) )
async def get_song_status( async def get_song_status(
suno_task_id: str, suno_task_id: str,
background_tasks: BackgroundTasks,
session: AsyncSession = Depends(get_session), session: AsyncSession = Depends(get_session),
) -> PollingSongResponse: ) -> PollingSongResponse:
"""suno_task_id로 노래 생성 작업의 상태를 조회합니다. """suno_task_id로 노래 생성 작업의 상태를 조회합니다.
@ -236,7 +234,7 @@ async def get_song_status(
parsed_response = suno_service.parse_status_response(result) 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}") 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: if parsed_response.status == "SUCCESS" and parsed_response.clips:
# 첫 번째 클립(clips[0])의 audioUrl과 duration 사용 # 첫 번째 클립(clips[0])의 audioUrl과 duration 사용
first_clip = parsed_response.clips[0] 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}") print(f"[get_song_status] Using first clip - id: {first_clip.id}, audio_url: {audio_url}, duration: {clip_duration}")
if audio_url: if audio_url:
# suno_task_id로 Song 조회하여 store_name 가져오기 # suno_task_id로 Song 조회
song_result = await session.execute( song_result = await session.execute(
select(Song) select(Song)
.where(Song.suno_task_id == suno_task_id) .where(Song.suno_task_id == suno_task_id)
@ -255,31 +253,14 @@ async def get_song_status(
song = song_result.scalar_one_or_none() song = song_result.scalar_one_or_none()
if song and song.status != "completed": if song and song.status != "completed":
# 이미 완료된 경우 백그라운드 작업 중복 실행 방지 # 첫 번째 클립의 audio_url과 duration을 직접 DB에 저장
# project_id로 Project 조회하여 store_name 가져오기 song.status = "completed"
project_result = await session.execute( song.song_result_url = audio_url
select(Project).where(Project.id == song.project_id) if clip_duration is not None:
)
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}")
song.duration = clip_duration song.duration = clip_duration
await session.commit() await session.commit()
else: 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] SKIPPED - Song already completed, suno_task_id: {suno_task_id}")
print(f"[get_song_status] SUCCESS - suno_task_id: {suno_task_id}") print(f"[get_song_status] SUCCESS - suno_task_id: {suno_task_id}")
@ -459,23 +440,36 @@ async def get_songs(
try: try:
offset = (pagination.page - 1) * pagination.page_size offset = (pagination.page - 1) * pagination.page_size
# 서브쿼리: task_id별 최신 Song의 id 조회 (completed 상태만) # 서브쿼리: task_id별 최신 Song의 id 조회 (completed 상태, created_at 기준)
subquery = ( from sqlalchemy import and_
select(func.max(Song.id).label("max_id"))
# task_id별 최신 created_at 조회
latest_subquery = (
select(
Song.task_id,
func.max(Song.created_at).label("max_created_at")
)
.where(Song.status == "completed") .where(Song.status == "completed")
.group_by(Song.task_id) .group_by(Song.task_id)
.subquery() .subquery()
) )
# 전체 개수 조회 (task_id별 최신 1개만) # 전체 개수 조회 (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_result = await session.execute(count_query)
total = total_result.scalar() or 0 total = total_result.scalar() or 0
# 데이터 조회 (completed 상태, task_id별 최신 1개만, 최신순) # 데이터 조회 (completed 상태, task_id별 created_at 기준 최신 1개만, 최신순)
query = ( query = (
select(Song) 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()) .order_by(Song.created_at.desc())
.offset(offset) .offset(offset)
.limit(pagination.page_size) .limit(pagination.page_size)

View File

@ -165,6 +165,7 @@ async def generate_video(
detail=f"task_id '{task_id}'에 해당하는 Song을 찾을 수 없습니다.", 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] 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 테이블에 초기 데이터 저장 # 4. Video 테이블에 초기 데이터 저장
video = Video( video = Video(