# -*- coding: utf-8 -*- """generator/output 스캔 → content 테이블 upsert. 디스크의 완성 mp4 와 DB 를 잇는다. 시작 시 + 잡 완료 시 호출. """ import re import shutil from pathlib import Path from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from . import blob_client from .config import OUTPUT_DIR, ENGINE_SCENARIO from .database import SessionLocal from .models import Content _TITLE_RE = re.compile(r"\[제목\]\s*(.+)") def _cleanup_dir(job_dir: Path) -> None: """블롭 업로드·DB 커밋 성공한 job 폴더의 로컬 원본 제거(디스크 절약). 안전장치: 반드시 OUTPUT_DIR 하위 경로일 때만 삭제. """ try: job_dir = job_dir.resolve() if OUTPUT_DIR.resolve() in job_dir.parents and job_dir.is_dir(): shutil.rmtree(job_dir, ignore_errors=True) except Exception as e: print(f"[cleanup] skip {job_dir}: {type(e).__name__}: {e}") def _title(job_dir) -> str: txt = job_dir / "narration.txt" if txt.exists(): for line in txt.read_text(encoding="utf-8", errors="ignore").splitlines(): m = _TITLE_RE.match(line.strip()) if m: return m.group(1).strip() return job_dir.name def _scan() -> list[dict]: rows = [] if not OUTPUT_DIR.is_dir(): return rows for engine, sc in ENGINE_SCENARIO.items(): d = OUTPUT_DIR / engine if not d.is_dir(): continue for job in d.iterdir(): if not job.is_dir(): continue mp4s = sorted(job.glob("*.mp4")) if not mp4s: continue rel = mp4s[0].relative_to(OUTPUT_DIR).as_posix() rows.append({ "id": job.name, "sc": sc, "title": _title(job), "mp4": mp4s[0], "local_url": f"/videos/{rel}", }) return rows async def sync_output(session: AsyncSession | None = None, owner_uuid: str | None = None) -> int: """새 output 폴더를 content 로 삽입. 삽입 개수 반환.""" own = session is None session = session or SessionLocal() try: existing = set((await session.execute(select(Content.id))).scalars().all()) added = 0 uploaded_dirs: list = [] # 블롭 업로드 성공한 job 폴더 → 커밋 후 정리 for r in _scan(): if r["id"] in existing: continue # Azure Blob 활성 시 mp4 업로드 → 공개 URL 저장, 실패/비활성 시 로컬 /videos/ URL video = r["local_url"] if blob_client.enabled(): url = await blob_client.upload_video(r["mp4"], owner_uuid, r["id"]) if url: video = url uploaded_dirs.append(r["mp4"].parent) session.add(Content( id=r["id"], scenario=r["sc"], title=r["title"], video=video, owner_uuid=owner_uuid, )) added += 1 if added: await session.commit() # 커밋 성공 후에만 로컬 원본 삭제(블롭이 진실의 원천). 실패분은 /videos fallback 위해 유지. for d in uploaded_dirs: _cleanup_dir(d) return added finally: if own: await session.close()