import { NextRequest, NextResponse } from "next/server"; import { findVideosForCode, getVideoIndex, rescanVideoIndex } from "@/lib/video"; import { getAppSetting } from "@/lib/db/appSettings"; import { assertLocalRequest } from "@/lib/api/localOnly"; import { probeVideoMetadata, serializeVideoMetadata, setVideoPlaybackMode } from "@/lib/video/metadata"; export const runtime = "nodejs"; export const dynamic = "force-dynamic"; interface ProbeResponse { codec: string | null; bFrames: number | null; cachedMode: string | null; metadata: ReturnType; } async function resolveFile(decoded: string, partIdx: number) { let files = findVideosForCode(decoded); if (files.length === 0) { const main = (getAppSetting("videoLibraryPath") || "").trim(); const extras = getAppSetting("videoExtraPaths") ?? []; const expected = [main, ...extras].filter(Boolean); const idx = getVideoIndex(); const haveAll = expected.length === idx.rootsScanned.length && expected.every((r, i) => r === idx.rootsScanned[i]); if (expected.length > 0 && !haveAll) { await rescanVideoIndex(); files = findVideosForCode(decoded); } } if (files.length === 0) return null; return files[Math.min(Math.max(0, partIdx), files.length - 1)]; } export async function GET(req: NextRequest, ctx: { params: Promise<{ code: string }> }) { const blocked = assertLocalRequest(req); if (blocked) return blocked; const { code } = await ctx.params; const decoded = decodeURIComponent(code); const url = new URL(req.url); const partRaw = url.searchParams.get("part"); const partIdx = partRaw == null ? 0 : Math.max(0, parseInt(partRaw, 10) || 0); const file = await resolveFile(decoded, partIdx); if (!file) { return NextResponse.json({ codec: null, bFrames: null, cachedMode: null, metadata: null }); } try { const meta = await probeVideoMetadata(file, req.signal); return NextResponse.json({ codec: meta.videoCodec, bFrames: meta.videoBFrames, cachedMode: meta.playbackMode, metadata: serializeVideoMetadata(meta), }); } catch (e) { console.error("[video-probe] failed:", e); return NextResponse.json( { codec: null, bFrames: null, cachedMode: null, metadata: null }, { status: 200 }, ); } } export async function POST(req: NextRequest, ctx: { params: Promise<{ code: string }> }) { const blocked = assertLocalRequest(req); if (blocked) return blocked; const { code } = await ctx.params; const decoded = decodeURIComponent(code); const url = new URL(req.url); const partRaw = url.searchParams.get("part"); const partIdx = partRaw == null ? 0 : Math.max(0, parseInt(partRaw, 10) || 0); const body = await req.json().catch(() => ({})) as { mode?: string | null }; const mode = body.mode; if (mode !== "direct" && mode !== "transcode" && mode !== null && mode !== undefined) { return NextResponse.json({ error: "invalid mode" }, { status: 400 }); } const file = await resolveFile(decoded, partIdx); if (!file) return NextResponse.json({ updated: 0 }); setVideoPlaybackMode(file, mode ?? null); return NextResponse.json({ updated: 1 }); }