import { getJob, recentJobs, resolveMix, startRender, type MixSpec } from "@/lib/mix"; export const dynamic = "force-dynamic"; // POST starts a render and returns immediately; GET polls it. // // A stream-copy remux (no head trim) is a couple of seconds, but trimming the // head forces a re-encode and that can run for minutes. Holding the request // open for the second case is the difference between a UI that looks busy and // one that looks hung. // // `?dry=1` resolves the spec and hands back the exact ffmpeg argv WITHOUT // running it -- so the command that will run is inspectable before it does, and // a spec that would write outside the roots is refused at the same place either // way. export async function POST(request: Request) { let body: Partial & { dry?: boolean }; try { body = (await request.json()) as Partial & { dry?: boolean }; } catch { return Response.json({ error: "bad json" }, { status: 400 }); } const url = new URL(request.url); const dry = body.dry || url.searchParams.get("dry") === "1"; try { if (dry) { const r = await resolveMix(body); return Response.json({ ok: true, dry: true, out: r.outPath, duration: r.duration, reencode: r.reencode, cmd: `ffmpeg ${r.args.join(" ")}`, }); } const job = await startRender(body); return Response.json({ ok: true, job }); } catch (err) { return Response.json({ error: String((err as Error)?.message ?? err) }, { status: 400 }); } } export async function GET(request: Request) { const id = new URL(request.url).searchParams.get("job"); if (!id) return Response.json({ jobs: recentJobs() }, { headers: { "cache-control": "no-store" } }); const job = getJob(id); if (!job) return Response.json({ error: "no such job" }, { status: 404 }); return Response.json({ job }, { headers: { "cache-control": "no-store" } }); }