From 12e6f7357c1626e798c7f1875e132b46e64ffff1 Mon Sep 17 00:00:00 2001 From: bluebamus Date: Fri, 26 Dec 2025 15:25:04 +0900 Subject: [PATCH] =?UTF-8?q?blob=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EC=97=85?= =?UTF-8?q?=EB=A1=9C=EB=93=9C=20=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/home/api/routers/v1/home.py | 457 ++++++++---------- app/home/schemas/home_schema.py | 5 +- app/home/worker/home_task.py | 62 +++ app/home/worker/main_task.py | 91 ---- app/song/worker/song_task.py | 106 ++++ app/utils/upload_blob_as_request.py | 419 ++++++++++++---- config.py | 16 + .../upload_blob_as_request.py | 47 +- 8 files changed, 756 insertions(+), 447 deletions(-) create mode 100644 app/home/worker/home_task.py delete mode 100644 app/home/worker/main_task.py diff --git a/app/home/api/routers/v1/home.py b/app/home/api/routers/v1/home.py index 6f4b0ce..4721ce7 100644 --- a/app/home/api/routers/v1/home.py +++ b/app/home/api/routers/v1/home.py @@ -20,6 +20,7 @@ from app.home.schemas.home_schema import ( MarketingAnalysis, ProcessedInfo, ) +from app.utils.upload_blob_as_request import AzureBlobUploader from app.utils.chatgpt_prompt import ChatgptService from app.utils.common import generate_task_id from app.utils.nvMapScraper import NvMapScraper @@ -178,10 +179,11 @@ IMAGES_JSON_EXAMPLE = """[ @router.post( - "/image/{task_id}/upload", + "/image/upload/server/{task_id}", + include_in_schema=False, summary="이미지 업로드", description=""" -task_id에 연결된 이미지를 업로드합니다. +task_id에 연결된 이미지를 서버에 업로드합니다. ## 요청 방식 multipart/form-data 형식으로 전송합니다. @@ -218,12 +220,12 @@ jpg, jpeg, png, webp, heic, heif ### 2. cURL로 테스트 ```bash # 바이너리 파일만 업로드 -curl -X POST "http://localhost:8000/image/test-task-001/upload" \\ +curl -X POST "http://localhost:8000/image/upload/server/test-task-001" \\ -F "files=@/path/to/image1.jpg" \\ -F "files=@/path/to/image2.png" # URL + 바이너리 파일 동시 업로드 -curl -X POST "http://localhost:8000/image/test-task-001/upload" \\ +curl -X POST "http://localhost:8000/image/upload/server/test-task-001" \\ -F 'images_json=[{"url":"https://example.com/image.jpg"}]' \\ -F "files=@/path/to/local_image.jpg" ``` @@ -232,7 +234,7 @@ curl -X POST "http://localhost:8000/image/test-task-001/upload" \\ ```python import requests -url = "http://localhost:8000/image/test-task-001/upload" +url = "http://localhost:8000/image/upload/server/test-task-001" files = [ ("files", ("image1.jpg", open("image1.jpg", "rb"), "image/jpeg")), ("files", ("image2.png", open("image2.png", "rb"), "image/png")), @@ -252,7 +254,7 @@ print(response.json()) - **images**: 업로드된 이미지 목록 ## 저장 경로 -- 바이너리 파일: /media/{날짜}/{uuid7}/{파일명} +- 바이너리 파일: /media/image/{날짜}/{uuid7}/{파일명} """, response_model=ImageUploadResponse, responses={ @@ -285,7 +287,9 @@ async def upload_images( 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}") + 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( @@ -312,16 +316,24 @@ async def upload_images( if has_files and files: for f in files: is_valid_ext = _is_valid_image_extension(f.filename) - is_not_empty = f.size is None or f.size > 0 # size가 None이면 아직 읽지 않은 것 - 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}") + is_not_empty = ( + f.size is None or f.size > 0 + ) # size가 None이면 아직 읽지 않은 것 + 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}") + print( + f"[upload_images] valid_files count: {len(valid_files)}, skipped: {skipped_files}" + ) # 유효한 데이터가 하나도 없으면 에러 if not url_images and not valid_files: @@ -358,7 +370,7 @@ async def upload_images( ) img_order += 1 - # 2. 바이너리 파일 저장 + # 2. 바이너리 파일을 media에 저장 if valid_files: today = date.today().strftime("%Y-%m-%d") # 한 번의 요청에서 업로드된 모든 이미지는 같은 폴더에 저장 @@ -371,22 +383,27 @@ async def upload_images( original_name = file.filename or "image" ext = _get_file_extension(file.filename) # type: ignore[arg-type] # 파일명에서 확장자 제거 후 순서 추가 - name_without_ext = original_name.rsplit(".", 1)[0] if "." in original_name else original_name + name_without_ext = ( + original_name.rsplit(".", 1)[0] + if "." in original_name + else original_name + ) filename = f"{name_without_ext}_{img_order:03d}{ext}" save_path = upload_dir / filename - # 파일 저장 + # media에 파일 저장 await _save_upload_file(file, save_path) - # 이미지 URL 생성 + # 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, img_name=img_name, - img_url=img_url, + img_url=img_url, # Media URL을 DB에 저장 img_order=img_order, ) session.add(image) @@ -403,7 +420,8 @@ async def upload_images( ) img_order += 1 - print(f"[upload_images] Committing {len(result_images)} images to database...") + saved_count = len(result_images) + print(f"[upload_images] Committing {saved_count} images to database...") await session.commit() print("[upload_images] Commit successful!") @@ -412,263 +430,216 @@ async def upload_images( total_count=len(result_images), url_count=len(url_images), file_count=len(valid_files), + saved_count=saved_count, images=result_images, ) -# @router.post( -# "/generate", -# summary="기본 영상 생성 요청", -# description=""" -# 고객 정보만 받아 영상 생성 작업을 시작합니다. (이미지 없음) +@router.post( + "/image/upload/blob/{task_id}", + summary="이미지 업로드 (Azure Blob)", + description=""" +task_id에 연결된 이미지를 Azure Blob Storage에 업로드합니다. -# ## 요청 필드 -# - **customer_name**: 고객명/가게명 (필수) -# - **region**: 지역명 (필수) -# - **detail_region_info**: 상세 지역 정보 (선택) -# - **attribute**: 음악 속성 정보 (genre, vocal, tempo, mood) +## 요청 방식 +multipart/form-data 형식으로 전송합니다. -# ## 반환 정보 -# - **task_id**: 작업 고유 식별자 (UUID7) -# - **status**: 작업 상태 -# - **message**: 응답 메시지 -# """, -# response_model=GenerateResponse, -# response_description="생성 작업 시작 결과", -# tags=["generate"], -# ) -# async def generate( -# request_body: GenerateRequest, -# background_tasks: BackgroundTasks, -# session: AsyncSession = Depends(get_session), -# ): -# """기본 영상 생성 요청 처리 (이미지 없음)""" -# # UUID7 생성 및 중복 검사 -# while True: -# task_id = str(uuid7()) -# existing = await session.execute( -# select(Project).where(Project.task_id == task_id) -# ) -# if existing.scalar_one_or_none() is None: -# break +## 요청 필드 +- **images_json**: 외부 이미지 URL 목록 (JSON 문자열, 선택) +- **files**: 이미지 바이너리 파일 목록 (선택) -# # Project 생성 (이미지 없음) -# project = Project( -# store_name=request_body.customer_name, -# region=request_body.region, -# task_id=task_id, -# detail_region_info=json.dumps( -# { -# "detail": request_body.detail_region_info, -# "attribute": request_body.attribute.model_dump(), -# }, -# ensure_ascii=False, -# ), -# ) -# session.add(project) -# await session.commit() -# await session.refresh(project) +**주의**: images_json 또는 files 중 최소 하나는 반드시 전달해야 합니다. -# background_tasks.add_task(task_process, request_body, task_id, project.id) +## 지원 이미지 확장자 +jpg, jpeg, png, webp, heic, heif -# return { -# "task_id": task_id, -# "status": "processing", -# "message": "생성 작업이 시작되었습니다.", -# } +## images_json 예시 +```json +[ + {"url": "https://example.com/image1.jpg"}, + {"url": "https://example.com/image2.jpg", "name": "외관"} +] +``` +## 바이너리 파일 업로드 테스트 방법 -# @router.post( -# "/generate/urls", -# summary="URL 기반 영상 생성 요청", -# description=""" -# 고객 정보와 이미지 URL을 받아 영상 생성 작업을 시작합니다. +### cURL로 테스트 +```bash +# 바이너리 파일만 업로드 +curl -X POST "http://localhost:8000/image/upload/blob/test-task-001" \\ + -F "files=@/path/to/image1.jpg" \\ + -F "files=@/path/to/image2.png" -# ## 요청 필드 -# - **customer_name**: 고객명/가게명 (필수) -# - **region**: 지역명 (필수) -# - **detail_region_info**: 상세 지역 정보 (선택) -# - **attribute**: 음악 속성 정보 (genre, vocal, tempo, mood) -# - **images**: 이미지 URL 목록 (필수) +# URL + 바이너리 파일 동시 업로드 +curl -X POST "http://localhost:8000/image/upload/blob/test-task-001" \\ + -F 'images_json=[{"url":"https://example.com/image.jpg"}]' \\ + -F "files=@/path/to/local_image.jpg" +``` -# ## 반환 정보 -# - **task_id**: 작업 고유 식별자 (UUID7) -# - **status**: 작업 상태 -# - **message**: 응답 메시지 -# """, -# response_model=GenerateResponse, -# response_description="생성 작업 시작 결과", -# tags=["generate"], -# ) -# async def generate_urls( -# request_body: GenerateUrlsRequest, -# session: AsyncSession = Depends(get_session), -# ): -# """URL 기반 영상 생성 요청 처리""" -# # UUID7 생성 및 중복 검사 -# while True: -# task_id = str(uuid7()) -# existing = await session.execute( -# select(Project).where(Project.task_id == task_id) -# ) -# if existing.scalar_one_or_none() is None: -# break +## 반환 정보 +- **task_id**: 작업 고유 식별자 +- **total_count**: 총 업로드된 이미지 개수 +- **url_count**: URL로 등록된 이미지 개수 +- **file_count**: 파일로 업로드된 이미지 개수 (Blob에 저장됨) +- **images**: 업로드된 이미지 목록 -# # Project 생성 (이미지 정보 제외) -# project = Project( -# store_name=request_body.customer_name, -# region=request_body.region, -# task_id=task_id, -# detail_region_info=json.dumps( -# { -# "detail": request_body.detail_region_info, -# "attribute": request_body.attribute.model_dump(), -# }, -# ensure_ascii=False, -# ), -# ) -# session.add(project) +## 저장 경로 +- 바이너리 파일: Azure Blob Storage ({task_id}/{파일명}) + """, + response_model=ImageUploadResponse, + responses={ + 200: {"description": "이미지 업로드 성공"}, + 400: {"description": "이미지가 제공되지 않음", "model": ErrorResponse}, + }, + tags=["image"], +) +async def upload_images_blob( + task_id: str, + images_json: Optional[str] = Form( + default=None, + description="외부 이미지 URL 목록 (JSON 문자열)", + example=IMAGES_JSON_EXAMPLE, + ), + files: Optional[list[UploadFile]] = File( + default=None, description="이미지 바이너리 파일 목록" + ), + session: AsyncSession = Depends(get_session), +) -> ImageUploadResponse: + """이미지 업로드 (URL + Azure Blob Storage)""" + print(f"[upload_images_blob] START - task_id: {task_id}") -# # Image 레코드 생성 (독립 테이블, task_id로 연결) -# for idx, img_item in enumerate(request_body.images): -# # name이 있으면 사용, 없으면 URL에서 추출 -# img_name = img_item.name or _extract_image_name(img_item.url, idx) -# image = Image( -# task_id=task_id, -# img_name=img_name, -# img_url=img_item.url, -# img_order=idx, -# ) -# session.add(image) + # 1. 진입 검증 + has_images_json = images_json is not None and images_json.strip() != "" + has_files = files is not None and len(files) > 0 -# await session.commit() + if not has_images_json and not has_files: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="images_json 또는 files 중 하나는 반드시 제공해야 합니다.", + ) -# return { -# "task_id": task_id, -# "status": "processing", -# "message": "생성 작업이 시작되었습니다.", -# } + # 2. images_json 파싱 + url_images: list[ImageUrlItem] = [] + if has_images_json: + try: + parsed = json.loads(images_json) + if isinstance(parsed, list): + url_images = [ImageUrlItem(**item) for item in parsed if item] + except (json.JSONDecodeError, TypeError, ValueError) as e: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail=f"images_json 파싱 오류: {str(e)}", + ) + # 3. 유효한 파일만 필터링 + valid_files: list[UploadFile] = [] + skipped_files: list[str] = [] + if has_files and files: + for f in files: + is_valid_ext = _is_valid_image_extension(f.filename) + is_not_empty = f.size is None or f.size > 0 + is_real_file = f.filename and f.filename != "filename" -# async def _save_upload_file(file: UploadFile, save_path: Path) -> None: -# """업로드 파일을 지정된 경로에 저장""" -# save_path.parent.mkdir(parents=True, exist_ok=True) -# async with aiofiles.open(save_path, "wb") as f: -# content = await file.read() -# await f.write(content) + 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_blob] valid_files: {len(valid_files)}, url_images: {len(url_images)}") -# def _get_file_extension(filename: str | None) -> str: -# """파일명에서 확장자 추출""" -# if not filename: -# return ".jpg" -# ext = Path(filename).suffix.lower() -# return ext if ext else ".jpg" + if not url_images and not valid_files: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail=f"유효한 이미지가 없습니다. 지원 확장자: {', '.join(ALLOWED_IMAGE_EXTENSIONS)}. 건너뛴 파일: {skipped_files}", + ) + result_images: list[ImageUploadResultItem] = [] + img_order = 0 -# @router.post( -# "/generate/upload", -# summary="파일 업로드 기반 영상 생성 요청", -# description=""" -# 고객 정보와 이미지 파일을 받아 영상 생성 작업을 시작합니다. + # 1. URL 이미지 저장 + for url_item in url_images: + img_name = url_item.name or _extract_image_name(url_item.url, img_order) -# ## 요청 필드 (multipart/form-data) -# - **customer_name**: 고객명/가게명 (필수) -# - **region**: 지역명 (필수) -# - **detail_region_info**: 상세 지역 정보 (선택) -# - **attribute**: 음악 속성 정보 JSON 문자열 (필수) -# - **images**: 이미지 파일 목록 (필수, 복수 파일) + image = Image( + task_id=task_id, + img_name=img_name, + img_url=url_item.url, + img_order=img_order, + ) + session.add(image) + await session.flush() + print(f"[upload_images_blob] URL saved - id: {image.id}, img_name: {img_name}") -# ## 반환 정보 -# - **task_id**: 작업 고유 식별자 (UUID7) -# - **status**: 작업 상태 -# - **message**: 응답 메시지 -# - **uploaded_count**: 업로드된 이미지 개수 -# """, -# response_model=GenerateUploadResponse, -# response_description="생성 작업 시작 결과", -# tags=["generate"], -# ) -# async def generate_upload( -# customer_name: str = Form(..., description="고객명/가게명"), -# region: str = Form(..., description="지역명"), -# attribute: str = Form(..., description="음악 속성 정보 (JSON 문자열)"), -# images: list[UploadFile] = File(..., description="이미지 파일 목록"), -# detail_region_info: str | None = Form(None, description="상세 지역 정보"), -# session: AsyncSession = Depends(get_session), -# ): -# """파일 업로드 기반 영상 생성 요청 처리""" -# # attribute JSON 파싱 및 검증 -# try: -# attribute_dict = json.loads(attribute) -# attribute_info = AttributeInfo(**attribute_dict) -# except json.JSONDecodeError: -# raise HTTPException( -# status_code=400, detail="attribute는 유효한 JSON 형식이어야 합니다." -# ) -# except Exception as e: -# raise HTTPException(status_code=400, detail=f"attribute 검증 실패: {e}") + result_images.append( + ImageUploadResultItem( + id=image.id, + img_name=img_name, + img_url=url_item.url, + img_order=img_order, + source="url", + ) + ) + img_order += 1 -# # 이미지 파일 검증 -# if not images: -# raise HTTPException( -# status_code=400, detail="최소 1개 이상의 이미지 파일이 필요합니다." -# ) + # 2. 바이너리 파일을 Azure Blob Storage에 직접 업로드 (media 저장 없음) + if valid_files: + uploader = AzureBlobUploader(task_id=task_id) -# # UUID7 생성 및 중복 검사 -# while True: -# task_id = str(uuid7()) -# existing = await session.execute( -# select(Project).where(Project.task_id == task_id) -# ) -# if existing.scalar_one_or_none() is None: -# break + for file in valid_files: + original_name = file.filename or "image" + ext = _get_file_extension(file.filename) # type: ignore[arg-type] + name_without_ext = ( + original_name.rsplit(".", 1)[0] + if "." in original_name + else original_name + ) + filename = f"{name_without_ext}_{img_order:03d}{ext}" -# # 저장 경로 생성: media/날짜/task_id/ -# today = date.today().strftime("%Y%m%d") -# upload_dir = MEDIA_ROOT / today / task_id + # 파일 내용 읽기 + file_content = await file.read() + print(f"[upload_images_blob] Uploading {filename} ({len(file_content)} bytes) to Blob...") -# # Project 생성 (이미지 정보 제외) -# project = Project( -# store_name=customer_name, -# region=region, -# task_id=task_id, -# detail_region_info=json.dumps( -# { -# "detail": detail_region_info, -# "attribute": attribute_info.model_dump(), -# }, -# ensure_ascii=False, -# ), -# ) -# session.add(project) + # Azure Blob Storage에 직접 업로드 + upload_success = await uploader.upload_image_bytes(file_content, filename) -# # 이미지 파일 저장 및 Image 레코드 생성 -# for idx, file in enumerate(images): -# # 각 이미지에 고유 UUID7 생성 -# img_uuid = str(uuid7()) -# ext = _get_file_extension(file.filename) -# filename = f"{img_uuid}{ext}" -# save_path = upload_dir / filename + if upload_success: + blob_url = uploader.public_url + img_name = file.filename or filename -# # 파일 저장 -# await _save_upload_file(file, save_path) + image = Image( + task_id=task_id, + img_name=img_name, + img_url=blob_url, + img_order=img_order, + ) + session.add(image) + await session.flush() + print(f"[upload_images_blob] Blob saved - id: {image.id}, blob_url: {blob_url}") -# # Image 레코드 생성 (독립 테이블, task_id로 연결) -# img_url = f"/media/{today}/{task_id}/{filename}" -# image = Image( -# task_id=task_id, -# img_name=file.filename or filename, -# img_url=img_url, -# img_order=idx, -# ) -# session.add(image) + result_images.append( + ImageUploadResultItem( + id=image.id, + img_name=img_name, + img_url=blob_url, + img_order=img_order, + source="blob", + ) + ) + img_order += 1 + else: + print(f"[upload_images_blob] Failed to upload {filename}") + skipped_files.append(filename) -# await session.commit() + 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 { -# "task_id": task_id, -# "status": "processing", -# "message": "생성 작업이 시작되었습니다.", -# "uploaded_count": len(images), -# } + return ImageUploadResponse( + task_id=task_id, + total_count=len(result_images), + url_count=len(url_images), + file_count=len(valid_files) - len(skipped_files), + saved_count=saved_count, + images=result_images, + ) diff --git a/app/home/schemas/home_schema.py b/app/home/schemas/home_schema.py index cf1a2a3..5b34872 100644 --- a/app/home/schemas/home_schema.py +++ b/app/home/schemas/home_schema.py @@ -203,7 +203,9 @@ class ImageUploadResultItem(BaseModel): img_name: str = Field(..., description="이미지명") img_url: str = Field(..., description="이미지 URL") img_order: int = Field(..., description="이미지 순서") - source: Literal["url", "file"] = Field(..., description="이미지 소스 (url 또는 file)") + source: Literal["url", "file", "blob"] = Field( + ..., description="이미지 소스 (url: 외부 URL, file: 로컬 서버, blob: Azure Blob)" + ) class ImageUploadResponse(BaseModel): @@ -213,4 +215,5 @@ class ImageUploadResponse(BaseModel): total_count: int = Field(..., description="총 업로드된 이미지 개수") url_count: int = Field(..., description="URL로 등록된 이미지 개수") file_count: int = Field(..., description="파일로 업로드된 이미지 개수") + saved_count: int = Field(..., description="Image 테이블에 저장된 row 수") images: list[ImageUploadResultItem] = Field(..., description="업로드된 이미지 목록") diff --git a/app/home/worker/home_task.py b/app/home/worker/home_task.py new file mode 100644 index 0000000..0ec38aa --- /dev/null +++ b/app/home/worker/home_task.py @@ -0,0 +1,62 @@ +""" +Home Worker 모듈 + +이미지 업로드 관련 백그라운드 작업을 처리합니다. +""" + +from pathlib import Path + +import aiofiles +from fastapi import UploadFile + +from app.utils.upload_blob_as_request import AzureBlobUploader + +MEDIA_ROOT = Path("media") + + +async def save_upload_file(file: UploadFile, save_path: Path) -> None: + """업로드 파일을 지정된 경로에 저장""" + save_path.parent.mkdir(parents=True, exist_ok=True) + async with aiofiles.open(save_path, "wb") as f: + content = await file.read() + await f.write(content) + + +async def upload_image_to_blob( + task_id: str, + file: UploadFile, + filename: str, + save_dir: Path, +) -> tuple[bool, str, str]: + """ + 이미지 파일을 media에 저장하고 Azure Blob Storage에 업로드합니다. + + Args: + task_id: 작업 고유 식별자 + file: 업로드할 파일 객체 + filename: 저장될 파일명 + save_dir: media 저장 디렉토리 경로 + + Returns: + tuple[bool, str, str]: (업로드 성공 여부, blob_url 또는 에러 메시지, media_path) + """ + save_path = save_dir / filename + media_path = str(save_path) + + 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) + upload_success = await uploader.upload_image(file_path=str(save_path)) + + if upload_success: + return True, uploader.public_url, media_path + else: + 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/home/worker/main_task.py b/app/home/worker/main_task.py deleted file mode 100644 index 3cebcb4..0000000 --- a/app/home/worker/main_task.py +++ /dev/null @@ -1,91 +0,0 @@ -import asyncio - -from sqlalchemy import select - -from app.database.session import get_worker_session -from app.home.schemas.home_schema import GenerateRequest -from app.lyric.models import Lyric -from app.utils.chatgpt_prompt import ChatgptService - - -async def _save_lyric(task_id: str, project_id: int, lyric_prompt: str) -> int: - """Lyric 레코드를 DB에 저장 (status=processing, lyric_result=null)""" - async with get_worker_session() as session: - lyric = Lyric( - task_id=task_id, - project_id=project_id, - status="processing", - lyric_prompt=lyric_prompt, - lyric_result=None, - ) - session.add(lyric) - await session.commit() - await session.refresh(lyric) - print(f"Lyric saved: id={lyric.id}, task_id={task_id}, status=processing") - return lyric.id - - -async def _update_lyric_status(lyric_id: int, status: str, lyric_result: str | None = None) -> None: - """Lyric 레코드의 status와 lyric_result를 업데이트""" - async with get_worker_session() as session: - result = await session.execute(select(Lyric).where(Lyric.id == lyric_id)) - lyric = result.scalar_one_or_none() - if lyric: - lyric.status = status - if lyric_result is not None: - lyric.lyric_result = lyric_result - await session.commit() - print(f"Lyric updated: id={lyric_id}, status={status}") - - -async def lyric_task( - task_id: str, - project_id: int, - customer_name: str, - region: str, - detail_region_info: str, - language: str = "Korean", -) -> None: - """가사 생성 작업: ChatGPT로 가사 생성 및 Lyric 테이블 저장/업데이트""" - service = ChatgptService( - customer_name=customer_name, - region=region, - detail_region_info=detail_region_info, - language=language, - ) - - # Lyric 레코드 저장 (status=processing, lyric_result=null) - lyric_prompt = service.build_lyrics_prompt() - lyric_id = await _save_lyric(task_id, project_id, lyric_prompt) - - # GPT 호출 - result = await service.generate(prompt=lyric_prompt) - - print(f"GPT Response:\n{result}") - - # 결과에 ERROR가 포함되어 있으면 status를 failed로 업데이트 - if "ERROR:" in result: - await _update_lyric_status(lyric_id, "failed", lyric_result=result) - else: - await _update_lyric_status(lyric_id, "completed", lyric_result=result) - - -async def _task_process_async(request_body: GenerateRequest, task_id: str, project_id: int) -> None: - """백그라운드 작업 처리 (async 버전)""" - customer_name = request_body.customer_name - region = request_body.region - detail_region_info = request_body.detail_region_info or "" - language = request_body.language - - print(f"customer_name: {customer_name}") - print(f"region: {region}") - print(f"detail_region_info: {detail_region_info}") - print(f"language: {language}") - - # 가사 생성 작업 - await lyric_task(task_id, project_id, customer_name, region, detail_region_info, language) - - -def task_process(request_body: GenerateRequest, task_id: str, project_id: int) -> None: - """백그라운드 작업 처리 함수 (sync wrapper)""" - asyncio.run(_task_process_async(request_body, task_id, project_id)) diff --git a/app/song/worker/song_task.py b/app/song/worker/song_task.py index c71ec32..3fd62bd 100644 --- a/app/song/worker/song_task.py +++ b/app/song/worker/song_task.py @@ -14,6 +14,7 @@ from sqlalchemy import select from app.database.session import AsyncSessionLocal from app.song.models import Song from app.utils.common import generate_task_id +from app.utils.upload_blob_as_request import AzureBlobUploader from config import prj_settings @@ -99,3 +100,108 @@ async def download_and_save_song( song.status = "failed" await session.commit() print(f"[download_and_save_song] FAILED - task_id: {task_id}, status updated to failed") + + +async def download_and_upload_song_to_blob( + task_id: str, + audio_url: str, + store_name: str, +) -> None: + """백그라운드에서 노래를 다운로드하고 Azure Blob Storage에 업로드한 뒤 Song 테이블을 업데이트합니다. + + Args: + task_id: 프로젝트 task_id + audio_url: 다운로드할 오디오 URL + store_name: 저장할 파일명에 사용할 업체명 + """ + print(f"[download_and_upload_song_to_blob] START - task_id: {task_id}, store_name: {store_name}") + temp_file_path: Path | None = None + + try: + # 파일명에 사용할 수 없는 문자 제거 + safe_store_name = "".join( + c for c in store_name if c.isalnum() or c in (" ", "_", "-") + ).strip() + safe_store_name = safe_store_name or "song" + file_name = f"{safe_store_name}.mp3" + + # 임시 저장 경로 생성 + temp_dir = Path("media") / "temp" / task_id + temp_dir.mkdir(parents=True, exist_ok=True) + temp_file_path = temp_dir / file_name + print(f"[download_and_upload_song_to_blob] Temp directory created - path: {temp_file_path}") + + # 오디오 파일 다운로드 + print(f"[download_and_upload_song_to_blob] Downloading audio - task_id: {task_id}, url: {audio_url}") + async with httpx.AsyncClient() as client: + response = await client.get(audio_url, timeout=60.0) + response.raise_for_status() + + async with aiofiles.open(str(temp_file_path), "wb") as f: + await f.write(response.content) + print(f"[download_and_upload_song_to_blob] File downloaded - task_id: {task_id}, path: {temp_file_path}") + + # Azure Blob Storage에 업로드 + uploader = AzureBlobUploader(task_id=task_id) + upload_success = await uploader.upload_music(file_path=str(temp_file_path)) + + if not upload_success: + raise Exception("Azure Blob Storage 업로드 실패") + + # SAS 토큰이 제외된 public_url 사용 + blob_url = uploader.public_url + print(f"[download_and_upload_song_to_blob] Uploaded to Blob - task_id: {task_id}, url: {blob_url}") + + # Song 테이블 업데이트 (새 세션 사용) + async with AsyncSessionLocal() as session: + # 여러 개 있을 경우 가장 최근 것 선택 + result = await session.execute( + select(Song) + .where(Song.task_id == task_id) + .order_by(Song.created_at.desc()) + .limit(1) + ) + song = result.scalar_one_or_none() + + if song: + song.status = "completed" + song.song_result_url = blob_url + await session.commit() + print(f"[download_and_upload_song_to_blob] SUCCESS - task_id: {task_id}, status: completed") + else: + print(f"[download_and_upload_song_to_blob] Song NOT FOUND in DB - task_id: {task_id}") + + except Exception as e: + print(f"[download_and_upload_song_to_blob] EXCEPTION - task_id: {task_id}, error: {e}") + # 실패 시 Song 테이블 업데이트 + async with AsyncSessionLocal() as session: + # 여러 개 있을 경우 가장 최근 것 선택 + result = await session.execute( + select(Song) + .where(Song.task_id == task_id) + .order_by(Song.created_at.desc()) + .limit(1) + ) + song = result.scalar_one_or_none() + + if song: + song.status = "failed" + await session.commit() + print(f"[download_and_upload_song_to_blob] FAILED - task_id: {task_id}, status updated to failed") + + finally: + # 임시 파일 삭제 + if temp_file_path and temp_file_path.exists(): + try: + temp_file_path.unlink() + print(f"[download_and_upload_song_to_blob] Temp file deleted - path: {temp_file_path}") + except Exception as e: + print(f"[download_and_upload_song_to_blob] Failed to delete temp file: {e}") + + # 임시 디렉토리 삭제 시도 + temp_dir = Path("media") / "temp" / task_id + if temp_dir.exists(): + try: + temp_dir.rmdir() + except Exception: + pass # 디렉토리가 비어있지 않으면 무시 diff --git a/app/utils/upload_blob_as_request.py b/app/utils/upload_blob_as_request.py index 412ea7e..5753aa8 100644 --- a/app/utils/upload_blob_as_request.py +++ b/app/utils/upload_blob_as_request.py @@ -1,7 +1,30 @@ """ Azure Blob Storage 업로드 유틸리티 -Azure Blob Storage에 파일을 업로드하는 비동기 함수들을 제공합니다. +Azure Blob Storage에 파일을 업로드하는 클래스를 제공합니다. +파일 경로 또는 바이트 데이터를 직접 업로드할 수 있습니다. + +URL 경로 형식: + - 음악: {BASE_URL}/{task_id}/song/{파일명} + - 영상: {BASE_URL}/{task_id}/video/{파일명} + - 이미지: {BASE_URL}/{task_id}/image/{파일명} + +사용 예시: + from app.utils.upload_blob_as_request import AzureBlobUploader + + uploader = AzureBlobUploader(task_id="task-123") + + # 파일 경로로 업로드 + success = await uploader.upload_music(file_path="my_song.mp3") + success = await uploader.upload_video(file_path="my_video.mp4") + success = await uploader.upload_image(file_path="my_image.png") + + # 바이트 데이터로 직접 업로드 (media 저장 없이) + success = await uploader.upload_music_bytes(audio_bytes, "my_song") # .mp3 자동 추가 + success = await uploader.upload_video_bytes(video_bytes, "my_video") # .mp4 자동 추가 + success = await uploader.upload_image_bytes(image_bytes, "my_image.png") + + print(uploader.public_url) # 마지막 업로드의 공개 URL """ from pathlib import Path @@ -9,87 +32,26 @@ from pathlib import Path import aiofiles import httpx -SAS_TOKEN = "sp=racwdl&st=2025-12-01T00:13:29Z&se=2026-07-31T08:28:29Z&spr=https&sv=2024-11-04&sr=c&sig=7fE2ozVBPu3Gq43%2FZDxEYdEcPLDXyNVfTf16IBasmVQ%3D" +from config import azure_blob_settings -async def upload_music_to_azure_blob( - file_path: str = "스테이 머뭄_1.mp3", - url: str = "https://ado2mediastoragepublic.blob.core.windows.net/ado2-media-public-access/ado2-media-original/dev-user-idx/dev-task-idx/test_my_mp3_should_now_exist.mp3", -) -> bool: - """음악 파일을 Azure Blob Storage에 업로드합니다. +class AzureBlobUploader: + """Azure Blob Storage 업로드 클래스 - Args: - file_path: 업로드할 파일 경로 - url: Azure Blob Storage URL + Azure Blob Storage에 음악, 영상, 이미지 파일을 업로드합니다. + URL 형식: {BASE_URL}/{task_id}/{category}/{file_name}?{SAS_TOKEN} - Returns: - bool: 업로드 성공 여부 + 카테고리별 경로: + - 음악: {task_id}/song/{file_name} + - 영상: {task_id}/video/{file_name} + - 이미지: {task_id}/image/{file_name} + + Attributes: + task_id: 작업 고유 식별자 """ - access_url = f"{url}?{SAS_TOKEN}" - headers = {"Content-Type": "audio/mpeg", "x-ms-blob-type": "BlockBlob"} - async with aiofiles.open(file_path, "rb") as file: - file_content = await file.read() - - async with httpx.AsyncClient() as client: - response = await client.put(access_url, content=file_content, headers=headers, timeout=120.0) - - if response.status_code in [200, 201]: - print(f"[upload_music_to_azure_blob] Success - Status Code: {response.status_code}") - return True - else: - print(f"[upload_music_to_azure_blob] Failed - Status Code: {response.status_code}") - print(f"[upload_music_to_azure_blob] Response: {response.text}") - return False - - -async def upload_video_to_azure_blob( - file_path: str = "스테이 머뭄.mp4", - url: str = "https://ado2mediastoragepublic.blob.core.windows.net/ado2-media-public-access/ado2-media-original/dev-user-idx/dev-task-idx/test_my_mp3_should_now_exist.mp4", -) -> bool: - """영상 파일을 Azure Blob Storage에 업로드합니다. - - Args: - file_path: 업로드할 파일 경로 - url: Azure Blob Storage URL - - Returns: - bool: 업로드 성공 여부 - """ - access_url = f"{url}?{SAS_TOKEN}" - headers = {"Content-Type": "video/mp4", "x-ms-blob-type": "BlockBlob"} - - async with aiofiles.open(file_path, "rb") as file: - file_content = await file.read() - - async with httpx.AsyncClient() as client: - response = await client.put(access_url, content=file_content, headers=headers, timeout=180.0) - - if response.status_code in [200, 201]: - print(f"[upload_video_to_azure_blob] Success - Status Code: {response.status_code}") - return True - else: - print(f"[upload_video_to_azure_blob] Failed - Status Code: {response.status_code}") - print(f"[upload_video_to_azure_blob] Response: {response.text}") - return False - - -async def upload_image_to_azure_blob( - file_path: str = "스테이 머뭄.png", - url: str = "https://ado2mediastoragepublic.blob.core.windows.net/ado2-media-public-access/ado2-media-original/dev-user-idx/dev-task-idx/test_my_mp3_should_now_exist.png", -) -> bool: - """이미지 파일을 Azure Blob Storage에 업로드합니다. - - Args: - file_path: 업로드할 파일 경로 - url: Azure Blob Storage URL - - Returns: - bool: 업로드 성공 여부 - """ - access_url = f"{url}?{SAS_TOKEN}" - extension = Path(file_path).suffix.lower() - content_types = { + # Content-Type 매핑 + IMAGE_CONTENT_TYPES = { ".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".png": "image/png", @@ -97,25 +59,300 @@ async def upload_image_to_azure_blob( ".webp": "image/webp", ".bmp": "image/bmp", } - content_type = content_types.get(extension, "image/jpeg") - headers = {"Content-Type": content_type, "x-ms-blob-type": "BlockBlob"} - async with aiofiles.open(file_path, "rb") as file: - file_content = await file.read() + def __init__(self, task_id: str): + """AzureBlobUploader 초기화 - async with httpx.AsyncClient() as client: - response = await client.put(access_url, content=file_content, headers=headers, timeout=60.0) + Args: + task_id: 작업 고유 식별자 + """ + self._task_id = task_id + self._base_url = azure_blob_settings.AZURE_BLOB_BASE_URL + self._sas_token = azure_blob_settings.AZURE_BLOB_SAS_TOKEN + self._last_public_url: str = "" - if response.status_code in [200, 201]: - print(f"[upload_image_to_azure_blob] Success - Status Code: {response.status_code}") - return True - else: - print(f"[upload_image_to_azure_blob] Failed - Status Code: {response.status_code}") - print(f"[upload_image_to_azure_blob] Response: {response.text}") - return False + @property + def task_id(self) -> str: + """작업 고유 식별자""" + return self._task_id + + @property + def public_url(self) -> str: + """마지막 업로드의 공개 URL (SAS 토큰 제외)""" + return self._last_public_url + + def _build_upload_url(self, category: str, file_name: str) -> str: + """업로드 URL 생성 (SAS 토큰 포함)""" + # SAS 토큰 앞뒤의 ?, ', " 제거 + sas_token = self._sas_token.strip("?'\"") + return f"{self._base_url}/{self._task_id}/{category}/{file_name}?{sas_token}" + + def _build_public_url(self, category: str, file_name: str) -> str: + """공개 URL 생성 (SAS 토큰 제외)""" + return f"{self._base_url}/{self._task_id}/{category}/{file_name}" + + async def _upload_file( + self, + file_path: str, + category: str, + content_type: str, + timeout: float, + log_prefix: str, + ) -> bool: + """파일을 Azure Blob Storage에 업로드하는 내부 메서드 + + Args: + file_path: 업로드할 파일 경로 + category: 카테고리 (song, video, image) + content_type: Content-Type 헤더 값 + timeout: 요청 타임아웃 (초) + log_prefix: 로그 접두사 + + Returns: + bool: 업로드 성공 여부 + """ + # 파일 경로에서 파일명 추출 + file_name = Path(file_path).name + + upload_url = self._build_upload_url(category, file_name) + self._last_public_url = self._build_public_url(category, file_name) + print(f"[{log_prefix}] Upload URL (without SAS): {self._last_public_url}") + + headers = {"Content-Type": content_type, "x-ms-blob-type": "BlockBlob"} + + async with aiofiles.open(file_path, "rb") as file: + file_content = await file.read() + + async with httpx.AsyncClient() as client: + response = await client.put( + upload_url, content=file_content, headers=headers, timeout=timeout + ) + + if response.status_code in [200, 201]: + print(f"[{log_prefix}] Success - Status Code: {response.status_code}") + print(f"[{log_prefix}] Public URL: {self._last_public_url}") + return True + else: + print(f"[{log_prefix}] Failed - Status Code: {response.status_code}") + print(f"[{log_prefix}] Response: {response.text}") + return False + + async def upload_music(self, file_path: str) -> bool: + """음악 파일을 Azure Blob Storage에 업로드합니다. + + URL 경로: {task_id}/song/{파일명} + + Args: + file_path: 업로드할 파일 경로 + + Returns: + bool: 업로드 성공 여부 + + Example: + uploader = AzureBlobUploader(task_id="task-123") + success = await uploader.upload_music(file_path="my_song.mp3") + print(uploader.public_url) # {BASE_URL}/task-123/song/my_song.mp3 + """ + return await self._upload_file( + file_path=file_path, + category="song", + content_type="audio/mpeg", + timeout=120.0, + log_prefix="upload_music", + ) + + async def upload_music_bytes(self, file_content: bytes, file_name: str) -> bool: + """음악 바이트 데이터를 Azure Blob Storage에 직접 업로드합니다. + + URL 경로: {task_id}/song/{파일명} + + Args: + file_content: 업로드할 파일 바이트 데이터 + file_name: 저장할 파일명 (확장자가 없으면 .mp3 추가) + + Returns: + bool: 업로드 성공 여부 + + Example: + uploader = AzureBlobUploader(task_id="task-123") + success = await uploader.upload_music_bytes(audio_bytes, "my_song") + print(uploader.public_url) # {BASE_URL}/task-123/song/my_song.mp3 + """ + # 확장자가 없으면 .mp3 추가 + if not Path(file_name).suffix: + file_name = f"{file_name}.mp3" + + upload_url = self._build_upload_url("song", file_name) + self._last_public_url = self._build_public_url("song", file_name) + print(f"[upload_music_bytes] Upload URL (without SAS): {self._last_public_url}") + + headers = {"Content-Type": "audio/mpeg", "x-ms-blob-type": "BlockBlob"} + + async with httpx.AsyncClient() as client: + response = await client.put( + upload_url, content=file_content, headers=headers, timeout=120.0 + ) + + if response.status_code in [200, 201]: + print(f"[upload_music_bytes] Success - Status Code: {response.status_code}") + print(f"[upload_music_bytes] Public URL: {self._last_public_url}") + return True + else: + print(f"[upload_music_bytes] Failed - Status Code: {response.status_code}") + print(f"[upload_music_bytes] Response: {response.text}") + return False + + async def upload_video(self, file_path: str) -> bool: + """영상 파일을 Azure Blob Storage에 업로드합니다. + + URL 경로: {task_id}/video/{파일명} + + Args: + file_path: 업로드할 파일 경로 + + Returns: + bool: 업로드 성공 여부 + + Example: + uploader = AzureBlobUploader(task_id="task-123") + success = await uploader.upload_video(file_path="my_video.mp4") + print(uploader.public_url) # {BASE_URL}/task-123/video/my_video.mp4 + """ + return await self._upload_file( + file_path=file_path, + category="video", + content_type="video/mp4", + timeout=180.0, + log_prefix="upload_video", + ) + + async def upload_video_bytes(self, file_content: bytes, file_name: str) -> bool: + """영상 바이트 데이터를 Azure Blob Storage에 직접 업로드합니다. + + URL 경로: {task_id}/video/{파일명} + + Args: + file_content: 업로드할 파일 바이트 데이터 + file_name: 저장할 파일명 (확장자가 없으면 .mp4 추가) + + Returns: + bool: 업로드 성공 여부 + + Example: + uploader = AzureBlobUploader(task_id="task-123") + success = await uploader.upload_video_bytes(video_bytes, "my_video") + print(uploader.public_url) # {BASE_URL}/task-123/video/my_video.mp4 + """ + # 확장자가 없으면 .mp4 추가 + if not Path(file_name).suffix: + file_name = f"{file_name}.mp4" + + upload_url = self._build_upload_url("video", file_name) + self._last_public_url = self._build_public_url("video", file_name) + print(f"[upload_video_bytes] Upload URL (without SAS): {self._last_public_url}") + + headers = {"Content-Type": "video/mp4", "x-ms-blob-type": "BlockBlob"} + + async with httpx.AsyncClient() as client: + response = await client.put( + upload_url, content=file_content, headers=headers, timeout=180.0 + ) + + if response.status_code in [200, 201]: + print(f"[upload_video_bytes] Success - Status Code: {response.status_code}") + print(f"[upload_video_bytes] Public URL: {self._last_public_url}") + return True + else: + print(f"[upload_video_bytes] Failed - Status Code: {response.status_code}") + print(f"[upload_video_bytes] Response: {response.text}") + return False + + async def upload_image(self, file_path: str) -> bool: + """이미지 파일을 Azure Blob Storage에 업로드합니다. + + URL 경로: {task_id}/image/{파일명} + + Args: + file_path: 업로드할 파일 경로 + + Returns: + bool: 업로드 성공 여부 + + Example: + uploader = AzureBlobUploader(task_id="task-123") + success = await uploader.upload_image(file_path="my_image.png") + print(uploader.public_url) # {BASE_URL}/task-123/image/my_image.png + """ + extension = Path(file_path).suffix.lower() + content_type = self.IMAGE_CONTENT_TYPES.get(extension, "image/jpeg") + + return await self._upload_file( + file_path=file_path, + category="image", + content_type=content_type, + timeout=60.0, + log_prefix="upload_image", + ) + + async def upload_image_bytes(self, file_content: bytes, file_name: str) -> bool: + """이미지 바이트 데이터를 Azure Blob Storage에 직접 업로드합니다. + + URL 경로: {task_id}/image/{파일명} + + Args: + file_content: 업로드할 파일 바이트 데이터 + file_name: 저장할 파일명 + + Returns: + bool: 업로드 성공 여부 + + Example: + uploader = AzureBlobUploader(task_id="task-123") + with open("my_image.png", "rb") as f: + content = f.read() + success = await uploader.upload_image_bytes(content, "my_image.png") + print(uploader.public_url) # {BASE_URL}/task-123/image/my_image.png + """ + extension = Path(file_name).suffix.lower() + content_type = self.IMAGE_CONTENT_TYPES.get(extension, "image/jpeg") + + upload_url = self._build_upload_url("image", file_name) + self._last_public_url = self._build_public_url("image", file_name) + print(f"[upload_image_bytes] Upload URL (without SAS): {self._last_public_url}") + + headers = {"Content-Type": content_type, "x-ms-blob-type": "BlockBlob"} + + async with httpx.AsyncClient() as client: + response = await client.put( + upload_url, content=file_content, headers=headers, timeout=60.0 + ) + + if response.status_code in [200, 201]: + print(f"[upload_image_bytes] Success - Status Code: {response.status_code}") + print(f"[upload_image_bytes] Public URL: {self._last_public_url}") + return True + else: + print(f"[upload_image_bytes] Failed - Status Code: {response.status_code}") + print(f"[upload_image_bytes] Response: {response.text}") + return False # 사용 예시: # import asyncio -# asyncio.run(upload_video_to_azure_blob()) -# asyncio.run(upload_image_to_azure_blob()) +# +# async def main(): +# uploader = AzureBlobUploader(task_id="task-123") +# +# # 음악 업로드 -> {BASE_URL}/task-123/song/my_song.mp3 +# await uploader.upload_music("my_song.mp3") +# print(uploader.public_url) +# +# # 영상 업로드 -> {BASE_URL}/task-123/video/my_video.mp4 +# await uploader.upload_video("my_video.mp4") +# print(uploader.public_url) +# +# # 이미지 업로드 -> {BASE_URL}/task-123/image/my_image.png +# await uploader.upload_image("my_image.png") +# print(uploader.public_url) +# +# asyncio.run(main()) diff --git a/config.py b/config.py index 92f73a2..f076efe 100644 --- a/config.py +++ b/config.py @@ -127,6 +127,21 @@ class CrawlerSettings(BaseSettings): model_config = _base_config +class AzureBlobSettings(BaseSettings): + """Azure Blob Storage 설정""" + + AZURE_BLOB_SAS_TOKEN: str = Field( + default="", + description="Azure Blob Storage SAS 토큰", + ) + AZURE_BLOB_BASE_URL: str = Field( + default="https://ado2mediastoragepublic.blob.core.windows.net/ado2-media-public-access/ado2-media-original", + description="Azure Blob Storage 기본 URL", + ) + + model_config = _base_config + + prj_settings = ProjectSettings() apikey_settings = APIKeySettings() db_settings = DatabaseSettings() @@ -134,3 +149,4 @@ security_settings = SecuritySettings() notification_settings = NotificationSettings() cors_settings = CORSSettings() crawler_settings = CrawlerSettings() +azure_blob_settings = AzureBlobSettings() diff --git a/poc/upload_blob_as_request/upload_blob_as_request.py b/poc/upload_blob_as_request/upload_blob_as_request.py index 789c05e..4ec3b3c 100644 --- a/poc/upload_blob_as_request/upload_blob_as_request.py +++ b/poc/upload_blob_as_request/upload_blob_as_request.py @@ -1,14 +1,18 @@ -import requests from pathlib import Path +import requests + SAS_TOKEN = "sp=racwdl&st=2025-12-01T00:13:29Z&se=2026-07-31T08:28:29Z&spr=https&sv=2024-11-04&sr=c&sig=7fE2ozVBPu3Gq43%2FZDxEYdEcPLDXyNVfTf16IBasmVQ%3D" -def upload_music_to_azure_blob(file_path = "스테이 머뭄_1.mp3", url = "https://ado2mediastoragepublic.blob.core.windows.net/ado2-media-public-access/ado2-media-original/dev-user-idx/dev-task-idx/test_my_mp3_should_now_exist.mp3"): +URL = "https://ado2mediastoragepublic.blob.core.windows.net/ado2-media-public-access/ado2-media-original/" + + +def upload_music_to_azure_blob( + file_path="스테이 머뭄_1.mp3", + url="https://ado2mediastoragepublic.blob.core.windows.net/ado2-media-public-access/ado2-media-original/dev-user-idx/dev-task-idx/test_my_mp3_should_now_exist.mp3", +): access_url = f"{url}?{SAS_TOKEN}" - headers = { - "Content-Type": "audio/mpeg", - "x-ms-blob-type": "BlockBlob" - } + headers = {"Content-Type": "audio/mpeg", "x-ms-blob-type": "BlockBlob"} with open(file_path, "rb") as file: response = requests.put(access_url, data=file, headers=headers) if response.status_code in [200, 201]: @@ -17,15 +21,16 @@ def upload_music_to_azure_blob(file_path = "스테이 머뭄_1.mp3", url = "http print(f"Failed Status Code: {response.status_code}") print(f"Response: {response.text}") -def upload_video_to_azure_blob(file_path = "스테이 머뭄.mp4", url = "https://ado2mediastoragepublic.blob.core.windows.net/ado2-media-public-access/ado2-media-original/dev-user-idx/dev-task-idx/test_my_mp3_should_now_exist.mp4"): + +def upload_video_to_azure_blob( + file_path="스테이 머뭄.mp4", + url="https://ado2mediastoragepublic.blob.core.windows.net/ado2-media-public-access/ado2-media-original/dev-user-idx/dev-task-idx/test_my_mp3_should_now_exist.mp4", +): access_url = f"{url}?{SAS_TOKEN}" - headers = { - "Content-Type": "video/mp4", - "x-ms-blob-type": "BlockBlob" - } + headers = {"Content-Type": "video/mp4", "x-ms-blob-type": "BlockBlob"} with open(file_path, "rb") as file: response = requests.put(access_url, data=file, headers=headers) - + if response.status_code in [200, 201]: print(f"Success Status Code: {response.status_code}") else: @@ -33,7 +38,10 @@ def upload_video_to_azure_blob(file_path = "스테이 머뭄.mp4", url = "https: print(f"Response: {response.text}") -def upload_image_to_azure_blob(file_path = "스테이 머뭄.png", url = "https://ado2mediastoragepublic.blob.core.windows.net/ado2-media-public-access/ado2-media-original/dev-user-idx/dev-task-idx/test_my_mp3_should_now_exist.png"): +def upload_image_to_azure_blob( + file_path="스테이 머뭄.png", + url="https://ado2mediastoragepublic.blob.core.windows.net/ado2-media-public-access/ado2-media-original/dev-user-idx/dev-task-idx/test_my_mp3_should_now_exist.png", +): access_url = f"{url}?{SAS_TOKEN}" extension = Path(file_path).suffix.lower() content_types = { @@ -42,23 +50,20 @@ def upload_image_to_azure_blob(file_path = "스테이 머뭄.png", url = "https: ".png": "image/png", ".gif": "image/gif", ".webp": "image/webp", - ".bmp": "image/bmp" + ".bmp": "image/bmp", } content_type = content_types.get(extension, "image/jpeg") - headers = { - "Content-Type": content_type, - "x-ms-blob-type": "BlockBlob" - } + headers = {"Content-Type": content_type, "x-ms-blob-type": "BlockBlob"} with open(file_path, "rb") as file: response = requests.put(access_url, data=file, headers=headers) - + if response.status_code in [200, 201]: print(f"Success Status Code: {response.status_code}") else: print(f"Failed Status Code: {response.status_code}") print(f"Response: {response.text}") - + upload_video_to_azure_blob() -upload_image_to_azure_blob() \ No newline at end of file +upload_image_to_azure_blob()