"""비디오 썸네일 서빙 API — /api/video ffmpeg 썸네일 생성은 thumbnail_worker 에서 수행. 본 라우터는 저장된 파일만 서빙. """ from pathlib import Path from typing import Annotated from fastapi import APIRouter, Depends, HTTPException, Query from fastapi.responses import FileResponse from sqlalchemy.ext.asyncio import AsyncSession from core.auth import decode_token, get_current_user from core.database import get_session from models.document import Document from models.user import User router = APIRouter() @router.get("/{doc_id}/thumbnail") async def get_video_thumbnail( doc_id: int, session: Annotated[AsyncSession, Depends(get_session)], token: str | None = Query(None, description="Bearer token (img src 용)"), user: User | None = Depends(lambda: None), ): """비디오 썸네일 jpg 서빙. `` 바인딩 가능. 쿼리 토큰 또는 Authorization 헤더 중 하나로 인증. /file 엔드포인트와 동일 정책. """ # 쿼리 토큰 검증 (img src 용) — /file 과 동일 패턴 if not token: raise HTTPException(status_code=401, detail="토큰이 필요합니다") payload = decode_token(token) if not payload or payload.get("type") != "access": raise HTTPException(status_code=401, detail="유효하지 않은 토큰") doc = await session.get(Document, doc_id) if not doc or doc.deleted_at is not None: raise HTTPException(status_code=404, detail="문서를 찾을 수 없습니다") thumb = getattr(doc, "thumbnail_path", None) if not thumb: raise HTTPException(status_code=404, detail="썸네일이 아직 생성되지 않았습니다") path = Path(thumb) if not path.exists(): raise HTTPException(status_code=404, detail="썸네일 파일이 없습니다") return FileResponse( path=str(path), media_type="image/jpeg", headers={"Content-Disposition": "inline"}, )