import { FaceError, detectFace } from "@/lib/faces"; export const dynamic = "force-dynamic"; // WHERE THE DETECTOR LOOKED. // // Its own route, and not folded into the frame route, because the two cost // wildly different amounts: a still is ~80ms of ffmpeg and this is ~0.94s of // python decoding five frames and running YuNet on each. Bundling them would // make every arrow key wait on the detector for a picture that is already // cached, and the deck would feel broken while doing exactly what it should. // // So the deck fetches the frame immediately and the detection separately, and // nothing is detected until a candidate is actually focused. Two of the corners // on record find NO FACE AT ALL at their recorded time, which is a real answer // this route has to be able to give: `face: null` with the frame size still // filled in, so the deck can draw the picture and let a person crop it by hand. export async function GET(request: Request) { const url = new URL(request.url); const video = url.searchParams.get("video") ?? ""; const at = Number(url.searchParams.get("at")); if (!/^[A-Za-z0-9_-]+$/.test(video)) { return Response.json({ error: "bad video" }, { status: 400 }); } if (!Number.isFinite(at) || at < 0) { return Response.json({ error: "bad time" }, { status: 400 }); } try { return Response.json(await detectFace(video, at), { headers: { "cache-control": "no-store" }, }); } catch (e) { if (e instanceof FaceError) return Response.json({ error: e.message }, { status: e.status }); return Response.json({ error: "could not run the detector" }, { status: 500 }); } }