o2o-castad-backend/app/video/worker/video_task.py

243 lines
10 KiB
Python

"""
Video Background Tasks
영상 생성 관련 백그라운드 태스크를 정의합니다.
"""
from pathlib import Path
import aiofiles
import httpx
from sqlalchemy import select
from app.database.session import BackgroundSessionLocal
from app.video.models import Video
from app.utils.upload_blob_as_request import AzureBlobUploader
async def download_and_upload_video_to_blob(
task_id: str,
video_url: str,
store_name: str,
) -> None:
"""백그라운드에서 영상을 다운로드하고 Azure Blob Storage에 업로드한 뒤 Video 테이블을 업데이트합니다.
Args:
task_id: 프로젝트 task_id
video_url: 다운로드할 영상 URL
store_name: 저장할 파일명에 사용할 업체명
"""
print(f"[download_and_upload_video_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 "video"
file_name = f"{safe_store_name}.mp4"
# 임시 저장 경로 생성
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_video_to_blob] Temp directory created - path: {temp_file_path}")
# 영상 파일 다운로드
print(f"[download_and_upload_video_to_blob] Downloading video - task_id: {task_id}, url: {video_url}")
async with httpx.AsyncClient() as client:
response = await client.get(video_url, timeout=180.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_video_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_video(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_video_to_blob] Uploaded to Blob - task_id: {task_id}, url: {blob_url}")
# Video 테이블 업데이트 (새 세션 사용)
async with BackgroundSessionLocal() as session:
# 여러 개 있을 경우 가장 최근 것 선택
result = await session.execute(
select(Video)
.where(Video.task_id == task_id)
.order_by(Video.created_at.desc())
.limit(1)
)
video = result.scalar_one_or_none()
if video:
video.status = "completed"
video.result_movie_url = blob_url
await session.commit()
print(f"[download_and_upload_video_to_blob] SUCCESS - task_id: {task_id}, status: completed")
else:
print(f"[download_and_upload_video_to_blob] Video NOT FOUND in DB - task_id: {task_id}")
except Exception as e:
print(f"[download_and_upload_video_to_blob] EXCEPTION - task_id: {task_id}, error: {e}")
# 실패 시 Video 테이블 업데이트
async with BackgroundSessionLocal() as session:
result = await session.execute(
select(Video)
.where(Video.task_id == task_id)
.order_by(Video.created_at.desc())
.limit(1)
)
video = result.scalar_one_or_none()
if video:
video.status = "failed"
await session.commit()
print(f"[download_and_upload_video_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_video_to_blob] Temp file deleted - path: {temp_file_path}")
except Exception as e:
print(f"[download_and_upload_video_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 # 디렉토리가 비어있지 않으면 무시
async def download_and_upload_video_by_creatomate_render_id(
creatomate_render_id: str,
video_url: str,
store_name: str,
) -> None:
"""creatomate_render_id로 Video를 조회하여 영상을 다운로드하고 Azure Blob Storage에 업로드한 뒤 Video 테이블을 업데이트합니다.
Args:
creatomate_render_id: Creatomate API 렌더 ID
video_url: 다운로드할 영상 URL
store_name: 저장할 파일명에 사용할 업체명
"""
print(f"[download_and_upload_video_by_creatomate_render_id] START - creatomate_render_id: {creatomate_render_id}, store_name: {store_name}")
temp_file_path: Path | None = None
task_id: str | None = None
try:
# creatomate_render_id로 Video 조회하여 task_id 가져오기
async with BackgroundSessionLocal() as session:
result = await session.execute(
select(Video)
.where(Video.creatomate_render_id == creatomate_render_id)
.order_by(Video.created_at.desc())
.limit(1)
)
video = result.scalar_one_or_none()
if not video:
print(f"[download_and_upload_video_by_creatomate_render_id] Video NOT FOUND - creatomate_render_id: {creatomate_render_id}")
return
task_id = video.task_id
print(f"[download_and_upload_video_by_creatomate_render_id] Video found - creatomate_render_id: {creatomate_render_id}, task_id: {task_id}")
# 파일명에 사용할 수 없는 문자 제거
safe_store_name = "".join(
c for c in store_name if c.isalnum() or c in (" ", "_", "-")
).strip()
safe_store_name = safe_store_name or "video"
file_name = f"{safe_store_name}.mp4"
# 임시 저장 경로 생성
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_video_by_creatomate_render_id] Temp directory created - path: {temp_file_path}")
# 영상 파일 다운로드
print(f"[download_and_upload_video_by_creatomate_render_id] Downloading video - creatomate_render_id: {creatomate_render_id}, url: {video_url}")
async with httpx.AsyncClient() as client:
response = await client.get(video_url, timeout=180.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_video_by_creatomate_render_id] File downloaded - creatomate_render_id: {creatomate_render_id}, path: {temp_file_path}")
# Azure Blob Storage에 업로드
uploader = AzureBlobUploader(task_id=task_id)
upload_success = await uploader.upload_video(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_video_by_creatomate_render_id] Uploaded to Blob - creatomate_render_id: {creatomate_render_id}, url: {blob_url}")
# Video 테이블 업데이트 (새 세션 사용)
async with BackgroundSessionLocal() as session:
result = await session.execute(
select(Video)
.where(Video.creatomate_render_id == creatomate_render_id)
.order_by(Video.created_at.desc())
.limit(1)
)
video = result.scalar_one_or_none()
if video:
video.status = "completed"
video.result_movie_url = blob_url
await session.commit()
print(f"[download_and_upload_video_by_creatomate_render_id] SUCCESS - creatomate_render_id: {creatomate_render_id}, status: completed")
else:
print(f"[download_and_upload_video_by_creatomate_render_id] Video NOT FOUND in DB - creatomate_render_id: {creatomate_render_id}")
except Exception as e:
print(f"[download_and_upload_video_by_creatomate_render_id] EXCEPTION - creatomate_render_id: {creatomate_render_id}, error: {e}")
# 실패 시 Video 테이블 업데이트
if task_id:
async with BackgroundSessionLocal() as session:
result = await session.execute(
select(Video)
.where(Video.creatomate_render_id == creatomate_render_id)
.order_by(Video.created_at.desc())
.limit(1)
)
video = result.scalar_one_or_none()
if video:
video.status = "failed"
await session.commit()
print(f"[download_and_upload_video_by_creatomate_render_id] FAILED - creatomate_render_id: {creatomate_render_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_video_by_creatomate_render_id] Temp file deleted - path: {temp_file_path}")
except Exception as e:
print(f"[download_and_upload_video_by_creatomate_render_id] Failed to delete temp file: {e}")
# 임시 디렉토리 삭제 시도
if task_id:
temp_dir = Path("media") / "temp" / task_id
if temp_dir.exists():
try:
temp_dir.rmdir()
except Exception:
pass # 디렉토리가 비어있지 않으면 무시