from uuid import UUID from arq import create_pool from arq.connections import RedisSettings from fastapi import APIRouter, Depends, HTTPException from fastapi.responses import FileResponse from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from app.config import settings from app.database import get_db from app.models import Clip, RenderRequest from app.schemas import RenderCreate, RenderResponse, BulkRenderCreate router = APIRouter() def _redis_settings() -> RedisSettings: from urllib.parse import urlparse parsed = urlparse(settings.redis_url) return RedisSettings( host=parsed.hostname or "redis", port=parsed.port or 6379, database=int(parsed.path.lstrip("/") or "0"), ) @router.post("/clips/{clip_id}/render", response_model=RenderResponse, status_code=201) async def render_clip( clip_id: UUID, render_in: RenderCreate, db: AsyncSession = Depends(get_db), ): clip = await db.get(Clip, clip_id) if not clip: raise HTTPException(404, "Clip not found") if not clip.raw_clip_path: raise HTTPException(400, "Clip not yet extracted") render = RenderRequest( clip_id=clip_id, aspect_ratio=render_in.aspect_ratio, subtitle_style=render_in.subtitle_style, status="pending", ) db.add(render) await db.commit() await db.refresh(render) pool = await create_pool(_redis_settings()) await pool.enqueue_job("render_clip", str(render.id)) await pool.close() return render @router.post("/jobs/{job_id}/render-all", response_model=list[RenderResponse], status_code=201) async def render_all_clips( job_id: UUID, bulk_in: BulkRenderCreate, db: AsyncSession = Depends(get_db), ): renders = [] pool = await create_pool(_redis_settings()) for clip_id in bulk_in.clip_ids: clip = await db.get(Clip, clip_id) if not clip or not clip.raw_clip_path: continue render = RenderRequest( clip_id=clip_id, aspect_ratio=bulk_in.aspect_ratio, subtitle_style=bulk_in.subtitle_style, status="pending", ) db.add(render) await db.commit() await db.refresh(render) await pool.enqueue_job("render_clip", str(render.id)) renders.append(render) await pool.close() return renders @router.get("/renders/{render_id}", response_model=RenderResponse) async def get_render(render_id: UUID, db: AsyncSession = Depends(get_db)): render = await db.get(RenderRequest, render_id) if not render: raise HTTPException(404, "Render not found") return render @router.get("/renders/{render_id}/download") async def download_render(render_id: UUID, db: AsyncSession = Depends(get_db)): render = await db.get(RenderRequest, render_id) if not render: raise HTTPException(404, "Render not found") if render.status != "complete" or not render.output_path: raise HTTPException(400, "Render not complete") clip = await db.get(Clip, render.clip_id) filename = f"{clip.title}_{render.aspect_ratio.replace(':', 'x')}.mp4" if clip else "clip.mp4" return FileResponse( render.output_path, media_type="video/mp4", filename=filename, ) @router.get("/renders/{render_id}/preview") async def preview_render(render_id: UUID, db: AsyncSession = Depends(get_db)): render = await db.get(RenderRequest, render_id) if not render: raise HTTPException(404, "Render not found") if render.status != "complete" or not render.output_path: raise HTTPException(400, "Render not complete") return FileResponse( render.output_path, media_type="video/mp4", content_disposition_type="inline", )