Initial commit
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
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<typeof serializeVideoMetadata>;
|
||||
}
|
||||
|
||||
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<ProbeResponse>({ codec: null, bFrames: null, cachedMode: null, metadata: null });
|
||||
}
|
||||
|
||||
try {
|
||||
const meta = await probeVideoMetadata(file, req.signal);
|
||||
return NextResponse.json<ProbeResponse>({
|
||||
codec: meta.videoCodec,
|
||||
bFrames: meta.videoBFrames,
|
||||
cachedMode: meta.playbackMode,
|
||||
metadata: serializeVideoMetadata(meta),
|
||||
});
|
||||
} catch (e) {
|
||||
console.error("[video-probe] failed:", e);
|
||||
return NextResponse.json<ProbeResponse>(
|
||||
{ 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 });
|
||||
}
|
||||
Reference in New Issue
Block a user