import { absOf, pickWindow, resolveClip, windowsFor } from "@/lib/report/serve.mjs"; import { ENV_RATE, analyseMedia } from "@/lib/media"; export const dynamic = "force-dynamic"; // The envelope of a cached source window, in ABSOLUTE SOURCE SECONDS. // // Everything in the bench is in source seconds -- the manifest's, the cues', // the QR's. The file's own start is the only relative number, and it is added // here so nothing downstream has to remember to. // // analyseMedia decodes once and caches to MIX_CACHE keyed by mtime and size. A // raw clip's window is IN ITS NAME, so the file is immutable and that cache can // never miss twice for the same window. export async function GET(request: Request) { const url = new URL(request.url); const r = await resolveClip(url.searchParams.get("project") ?? "", url.searchParams.get("clip") ?? ""); if ("error" in r) return Response.json({ error: r.error }, { status: r.status }); const windows = await windowsFor(r.project, r.clip); const win = pickWindow(windows, url.searchParams.get("file")); if (!win) return Response.json({ error: "no cached window for this clip" }, { status: 404 }); const abs = absOf(win); if (!abs) return Response.json({ error: "outside the roots" }, { status: 400 }); const buckets = Math.max(64, Math.min(4000, Number(url.searchParams.get("n") ?? 1200))); let analysis; try { analysis = await analyseMedia(abs); } catch (e) { return Response.json({ error: e instanceof Error ? e.message : String(e) }, { status: 500 }); } const env = analysis.peak; const n = env.length; const min: number[] = []; const max: number[] = []; for (let b = 0; b < buckets; b += 1) { const i0 = Math.floor((b / buckets) * n); const i1 = Math.max(i0 + 1, Math.floor(((b + 1) / buckets) * n)); let p = 0; for (let i = i0; i < i1 && i < n; i += 1) if (env[i] > p) p = env[i]; max.push(p); // analyseMedia's envelope is positive-only. Mirroring it is what makes the // canvas draw the symmetric shape a waveform is expected to be, rather than // a row of upward spikes. min.push(-p); } return Response.json( { from: win.from, to: win.from + (n / ENV_RATE), duration: analysis.duration, fetchStart: win.from, window: win.name, min, max, }, { headers: { "cache-control": "private, max-age=3600" } }, ); }