From 62dd681b8359790dd84b3ce8c488b6b6eb390e5f Mon Sep 17 00:00:00 2001 From: bluebamus Date: Fri, 26 Dec 2025 15:45:32 +0900 Subject: [PATCH] =?UTF-8?q?=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EC=97=85?= =?UTF-8?q?=EB=A1=9C=EB=93=9C=20=EA=B4=80=EB=A0=A8=20=EB=94=94=EB=B2=84?= =?UTF-8?q?=EA=B7=B8=20=ED=94=84=EB=A6=B0=ED=8A=B8=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/home/api/routers/v1/home.py | 56 +++++++++------------------------ app/home/worker/home_task.py | 2 -- app/song/api/routers/v1/song.py | 54 ++++++++++++++++--------------- 3 files changed, 44 insertions(+), 68 deletions(-) diff --git a/app/home/api/routers/v1/home.py b/app/home/api/routers/v1/home.py index 4721ce7..d7a9ffb 100644 --- a/app/home/api/routers/v1/home.py +++ b/app/home/api/routers/v1/home.py @@ -181,9 +181,9 @@ IMAGES_JSON_EXAMPLE = """[ @router.post( "/image/upload/server/{task_id}", include_in_schema=False, - summary="이미지 업로드", + summary="이미지 업로드 (로컬 서버)", description=""" -task_id에 연결된 이미지를 서버에 업로드합니다. +task_id에 연결된 이미지를 로컬 서버(media 폴더)에 업로드합니다. ## 요청 방식 multipart/form-data 형식으로 전송합니다. @@ -249,12 +249,15 @@ print(response.json()) ## 반환 정보 - **task_id**: 작업 고유 식별자 - **total_count**: 총 업로드된 이미지 개수 -- **url_count**: URL로 등록된 이미지 개수 -- **file_count**: 파일로 업로드된 이미지 개수 +- **url_count**: URL로 등록된 이미지 개수 (Image 테이블에 외부 URL 그대로 저장) +- **file_count**: 파일로 업로드된 이미지 개수 (media 폴더에 저장) +- **saved_count**: Image 테이블에 저장된 row 수 - **images**: 업로드된 이미지 목록 + - **source**: "url" (외부 URL) 또는 "file" (로컬 서버 저장) ## 저장 경로 - 바이너리 파일: /media/image/{날짜}/{uuid7}/{파일명} +- URL 이미지: 외부 URL 그대로 Image 테이블에 저장 """, response_model=ImageUploadResponse, responses={ @@ -276,20 +279,9 @@ async def upload_images( session: AsyncSession = Depends(get_session), ) -> ImageUploadResponse: """이미지 업로드 (URL + 바이너리 파일)""" - print(f"[upload_images] START - task_id: {task_id}") - print(f"[upload_images] images_json: {images_json}") - print(f"[upload_images] files: {files}") - # 1. 진입 검증: images_json 또는 files 중 하나는 반드시 있어야 함 has_images_json = images_json is not None and images_json.strip() != "" has_files = files is not None and len(files) > 0 - print(f"[upload_images] has_images_json: {has_images_json}, has_files: {has_files}") - - if has_files and files: - for idx, f in enumerate(files): - print( - f"[upload_images] file[{idx}]: filename={f.filename}, size={f.size}, content_type={f.content_type}" - ) if not has_images_json and not has_files: raise HTTPException( @@ -322,19 +314,11 @@ async def upload_images( is_real_file = ( f.filename and f.filename != "filename" ) # Swagger 빈 파일 체크 - print( - f"[upload_images] Checking file: {f.filename}, size={f.size}, is_valid_ext={is_valid_ext}, is_real_file={is_real_file}" - ) - if f and is_real_file and is_valid_ext and is_not_empty: valid_files.append(f) else: skipped_files.append(f.filename or "unknown") - print( - f"[upload_images] valid_files count: {len(valid_files)}, skipped: {skipped_files}" - ) - # 유효한 데이터가 하나도 없으면 에러 if not url_images and not valid_files: raise HTTPException( @@ -357,7 +341,6 @@ async def upload_images( ) session.add(image) await session.flush() # ID 생성을 위해 flush - print(f"[upload_images] URL image saved - id: {image.id}, img_name: {img_name}") result_images.append( ImageUploadResultItem( @@ -398,7 +381,6 @@ async def upload_images( # media 기준 URL 생성 img_url = f"/media/image/{today}/{batch_uuid}/{filename}" img_name = file.filename or filename - print(f"[upload_images] File saved to media - path: {save_path}, url: {img_url}") image = Image( task_id=task_id, @@ -421,9 +403,7 @@ async def upload_images( img_order += 1 saved_count = len(result_images) - print(f"[upload_images] Committing {saved_count} images to database...") await session.commit() - print("[upload_images] Commit successful!") return ImageUploadResponse( task_id=task_id, @@ -437,9 +417,10 @@ async def upload_images( @router.post( "/image/upload/blob/{task_id}", - summary="이미지 업로드 (Azure Blob)", + summary="이미지 업로드 (Azure Blob Storage)", description=""" task_id에 연결된 이미지를 Azure Blob Storage에 업로드합니다. +바이너리 파일은 로컬 서버에 저장하지 않고 Azure Blob에 직접 업로드됩니다. ## 요청 방식 multipart/form-data 형식으로 전송합니다. @@ -479,12 +460,15 @@ curl -X POST "http://localhost:8000/image/upload/blob/test-task-001" \\ ## 반환 정보 - **task_id**: 작업 고유 식별자 - **total_count**: 총 업로드된 이미지 개수 -- **url_count**: URL로 등록된 이미지 개수 -- **file_count**: 파일로 업로드된 이미지 개수 (Blob에 저장됨) +- **url_count**: URL로 등록된 이미지 개수 (Image 테이블에 외부 URL 그대로 저장) +- **file_count**: 파일로 업로드된 이미지 개수 (Azure Blob Storage에 저장) +- **saved_count**: Image 테이블에 저장된 row 수 - **images**: 업로드된 이미지 목록 + - **source**: "url" (외부 URL) 또는 "blob" (Azure Blob Storage) ## 저장 경로 -- 바이너리 파일: Azure Blob Storage ({task_id}/{파일명}) +- 바이너리 파일: Azure Blob Storage ({BASE_URL}/{task_id}/image/{파일명}) +- URL 이미지: 외부 URL 그대로 Image 테이블에 저장 """, response_model=ImageUploadResponse, responses={ @@ -506,8 +490,6 @@ async def upload_images_blob( session: AsyncSession = Depends(get_session), ) -> ImageUploadResponse: """이미지 업로드 (URL + Azure Blob Storage)""" - print(f"[upload_images_blob] START - task_id: {task_id}") - # 1. 진입 검증 has_images_json = images_json is not None and images_json.strip() != "" has_files = files is not None and len(files) > 0 @@ -545,8 +527,6 @@ async def upload_images_blob( else: skipped_files.append(f.filename or "unknown") - print(f"[upload_images_blob] valid_files: {len(valid_files)}, url_images: {len(url_images)}") - if not url_images and not valid_files: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, @@ -568,7 +548,6 @@ async def upload_images_blob( ) session.add(image) await session.flush() - print(f"[upload_images_blob] URL saved - id: {image.id}, img_name: {img_name}") result_images.append( ImageUploadResultItem( @@ -597,7 +576,6 @@ async def upload_images_blob( # 파일 내용 읽기 file_content = await file.read() - print(f"[upload_images_blob] Uploading {filename} ({len(file_content)} bytes) to Blob...") # Azure Blob Storage에 직접 업로드 upload_success = await uploader.upload_image_bytes(file_content, filename) @@ -614,7 +592,6 @@ async def upload_images_blob( ) session.add(image) await session.flush() - print(f"[upload_images_blob] Blob saved - id: {image.id}, blob_url: {blob_url}") result_images.append( ImageUploadResultItem( @@ -627,13 +604,10 @@ async def upload_images_blob( ) img_order += 1 else: - print(f"[upload_images_blob] Failed to upload {filename}") skipped_files.append(filename) saved_count = len(result_images) - print(f"[upload_images_blob] Committing {saved_count} images...") await session.commit() - print(f"[upload_images_blob] Done! saved_count: {saved_count}") return ImageUploadResponse( task_id=task_id, diff --git a/app/home/worker/home_task.py b/app/home/worker/home_task.py index 0ec38aa..69adfca 100644 --- a/app/home/worker/home_task.py +++ b/app/home/worker/home_task.py @@ -46,7 +46,6 @@ async def upload_image_to_blob( try: # 1. media에 파일 저장 await save_upload_file(file, save_path) - print(f"[upload_image_to_blob] File saved to media: {save_path}") # 2. Azure Blob Storage에 업로드 uploader = AzureBlobUploader(task_id=task_id) @@ -58,5 +57,4 @@ async def upload_image_to_blob( return False, f"Failed to upload {filename} to Blob", media_path except Exception as e: - print(f"[upload_image_to_blob] Error: {e}") return False, str(e), media_path diff --git a/app/song/api/routers/v1/song.py b/app/song/api/routers/v1/song.py index 12f13e7..fb458da 100644 --- a/app/song/api/routers/v1/song.py +++ b/app/song/api/routers/v1/song.py @@ -182,7 +182,7 @@ async def generate_song( summary="노래 생성 상태 조회", description=""" Suno API를 통해 노래 생성 작업의 상태를 조회합니다. -SUCCESS 상태인 경우 백그라운드에서 MP3 파일을 다운로드하고 Song 테이블을 업데이트합니다. +SUCCESS 상태인 경우 백그라운드에서 MP3 파일을 다운로드하고 Azure Blob Storage에 업로드한 뒤 Song 테이블을 업데이트합니다. ## 경로 파라미터 - **suno_task_id**: 노래 생성 시 반환된 Suno API 작업 ID (필수) @@ -208,7 +208,9 @@ GET /song/status/abc123... ## 참고 - 스트림 URL: 30-40초 내 생성 - 다운로드 URL: 2-3분 내 생성 -- SUCCESS 시 백그라운드에서 MP3 다운로드 및 DB 업데이트 진행 +- SUCCESS 시 백그라운드에서 MP3 다운로드 → Azure Blob Storage 업로드 → Song 테이블 업데이트 진행 +- 저장 경로: Azure Blob Storage ({BASE_URL}/{task_id}/song/{store_name}.mp3) +- Song 테이블의 song_result_url에 Blob URL이 저장됩니다 """, response_model=PollingSongResponse, responses={ @@ -224,7 +226,8 @@ async def get_song_status( """suno_task_id로 노래 생성 작업의 상태를 조회합니다. SUCCESS 상태인 경우 백그라운드에서 MP3 파일을 다운로드하고 - Song 테이블의 status를 completed로, song_result_url을 업데이트합니다. + Azure Blob Storage에 업로드한 뒤 Song 테이블의 status를 completed로, + song_result_url을 Blob URL로 업데이트합니다. """ print(f"[get_song_status] START - suno_task_id: {suno_task_id}") try: @@ -288,32 +291,33 @@ async def get_song_status( "/download/{task_id}", summary="노래 다운로드 상태 조회", description=""" - task_id를 기반으로 Song 테이블의 상태를 polling하고, - completed인 경우 Project 정보와 노래 URL을 반환합니다. +task_id를 기반으로 Song 테이블의 상태를 polling하고, +completed인 경우 Project 정보와 노래 URL을 반환합니다. - ## 경로 파라미터 - - **task_id**: 프로젝트 task_id (필수) +## 경로 파라미터 +- **task_id**: 프로젝트 task_id (필수) - ## 반환 정보 - - **success**: 조회 성공 여부 - - **status**: 처리 상태 (processing, completed, failed) - - **message**: 응답 메시지 - - **store_name**: 업체명 - - **region**: 지역명 - - **detail_region_info**: 상세 지역 정보 - - **task_id**: 작업 고유 식별자 - - **language**: 언어 - - **song_result_url**: 노래 결과 URL (completed 시) - - **created_at**: 생성 일시 +## 반환 정보 +- **success**: 조회 성공 여부 +- **status**: 처리 상태 (processing, completed, failed, not_found) +- **message**: 응답 메시지 +- **store_name**: 업체명 +- **region**: 지역명 +- **detail_region_info**: 상세 지역 정보 +- **task_id**: 작업 고유 식별자 +- **language**: 언어 +- **song_result_url**: 노래 결과 URL (completed 시, Azure Blob Storage URL) +- **created_at**: 생성 일시 - ## 사용 예시 - ``` - GET /song/download/019123ab-cdef-7890-abcd-ef1234567890 - ``` +## 사용 예시 +``` +GET /song/download/019123ab-cdef-7890-abcd-ef1234567890 +``` - ## 참고 - - processing 상태인 경우 song_result_url은 null입니다. - - completed 상태인 경우 Project 정보와 함께 song_result_url을 반환합니다. +## 참고 +- processing 상태인 경우 song_result_url은 null입니다. +- completed 상태인 경우 Project 정보와 함께 song_result_url (Azure Blob URL)을 반환합니다. +- song_result_url 형식: {AZURE_BLOB_BASE_URL}/{task_id}/song/{store_name}.mp3 """, response_model=DownloadSongResponse, responses={