63 lines
2.5 KiB
Python
63 lines
2.5 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""Azure Blob Storage 업로더 (ADO2 방식 슬림 포팅).
|
|
|
|
SDK 없이 SAS 토큰을 붙여 REST PUT 한다(aiohttp). 컨테이너가 공개 읽기이므로
|
|
재생 URL엔 SAS가 없고, 업로드에만 write SAS가 필요하다. SAS 미설정 시 비활성.
|
|
|
|
blob 경로: {BASE_URL}/{PREFIX}/{owner}/{job_id}/video/{파일명}
|
|
DB(Content.video)엔 SAS 없는 공개 URL을 저장한다.
|
|
"""
|
|
import re
|
|
from pathlib import Path
|
|
|
|
import aiohttp
|
|
|
|
from .config import settings
|
|
|
|
_UPLOAD_TIMEOUT = aiohttp.ClientTimeout(total=180, connect=10)
|
|
_SANITIZE_RE = re.compile(r"[^A-Za-z0-9._-]+")
|
|
|
|
|
|
def enabled() -> bool:
|
|
return settings.azure_blob_enabled
|
|
|
|
|
|
def sanitize_filename(name: str) -> str:
|
|
"""URL·blob 안전 파일명(한글/공백/특수문자 → '_'). 확장자 유지."""
|
|
stem = Path(name).stem
|
|
ext = Path(name).suffix.lower()
|
|
stem = _SANITIZE_RE.sub("_", stem).strip("_") or "video"
|
|
return f"{stem}{ext}"
|
|
|
|
|
|
def _blob_path(owner_uuid: str | None, job_id: str, filename: str) -> str:
|
|
owner = owner_uuid or "public"
|
|
fn = sanitize_filename(filename)
|
|
return f"{settings.AZURE_BLOB_PREFIX}/{owner}/{job_id}/video/{fn}"
|
|
|
|
|
|
def public_url(owner_uuid: str | None, job_id: str, filename: str) -> str:
|
|
return f"{settings.AZURE_BLOB_BASE_URL.rstrip('/')}/{_blob_path(owner_uuid, job_id, filename)}"
|
|
|
|
|
|
async def upload_video(local_path: Path, owner_uuid: str | None, job_id: str) -> str | None:
|
|
"""mp4를 블롭에 업로드하고 공개 URL 반환. 실패/비활성 시 None."""
|
|
if not enabled() or not local_path.is_file():
|
|
return None
|
|
blob = _blob_path(owner_uuid, job_id, local_path.name)
|
|
sas = (settings.AZURE_BLOB_SAS_TOKEN or "").strip("?'\"")
|
|
upload_url = f"{settings.AZURE_BLOB_BASE_URL.rstrip('/')}/{blob}?{sas}"
|
|
headers = {"Content-Type": "video/mp4", "x-ms-blob-type": "BlockBlob"}
|
|
try:
|
|
data = local_path.read_bytes()
|
|
async with aiohttp.ClientSession(timeout=_UPLOAD_TIMEOUT) as s:
|
|
async with s.put(upload_url, data=data, headers=headers) as r:
|
|
if r.status in (200, 201):
|
|
return f"{settings.AZURE_BLOB_BASE_URL.rstrip('/')}/{blob}"
|
|
body = (await r.text())[:300]
|
|
print(f"[blob] upload failed {r.status}: {body}")
|
|
return None
|
|
except Exception as e: # 네트워크/타임아웃 — fallback
|
|
print(f"[blob] upload error: {type(e).__name__}: {e}")
|
|
return None
|