clip-forge/backend/app/api/routes/renders.py

127 lines
3.7 KiB
Python

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",
)