import path from "node:path"; import { labelFor, resolveInRoots } from "./paths"; import { probeMedia } from "./media"; import { projectRef } from "./projects"; // --------------------------------------------------------------------------- // A deep link into /mix. // // Resolved SERVER-SIDE, in the page, which is why this feature needs no // useSearchParams and no Suspense boundary -- app/mix/page.tsx is a server // component that simply did not read searchParams before. // // REFUSE, NEVER CLAMP. A body outside the roots produces a bench with no preset // and a visible reason. Silently opening a DIFFERENT file than the link named is // the one outcome worse than an error, and lib/mix.ts already takes this line // for the same reason. // --------------------------------------------------------------------------- export type MixPreset = { body: string; label: string; start: number; end: number; duration: number; hasAudio: boolean; /** Where the link came from, for the back-crumb. Never used to resolve media. */ from: string | null; fromHref: string | null; clip: string | null; /** The file's own span, so "there is more material here" is visible. */ fileFrom: number; fileTo: number; }; export type MixRefusal = { reason: string }; export async function resolvePreset(params: { body?: string; start?: string; end?: string; from?: string; clip?: string; }): Promise { if (!params.body) return null; const abs = resolveInRoots(params.body); if (!abs) return { reason: `that file is not inside any known root: ${params.body}` }; let info; try { info = await probeMedia(abs); } catch (e) { return { reason: `could not read it: ${e instanceof Error ? e.message : String(e)}` }; } // A body with no audio fails at render time anyway (lib/mix.ts throws), so // say it now rather than after the numbers have been set. if (!info.hasAudio) return { reason: `${labelFor(abs)} has no audio track` }; const num = (v: string | undefined) => { const n = Number(v); return Number.isFinite(n) && n >= 0 ? n : 0; }; // `from` and `clip` are PROVENANCE, for the crumb and the header line. They // are deliberately not used to resolve the media -- that would be a second, // divergent resolver for the thing this one already did. const project = params.from ? await projectRef(params.from) : null; // The file's own span, when its name carries one. A raw clip is //