Initial commit
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
// app/api/transcoder/download/[videoId]/route.ts
|
||||
// Streams the original MP4 directly to the remote transcoder worker.
|
||||
// Never buffers the file in memory.
|
||||
|
||||
import { NextResponse } from "next/server";
|
||||
import * as fssync from "fs";
|
||||
import * as fs from "fs/promises";
|
||||
import * as path from "path";
|
||||
import { Readable } from "stream";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { verifyTranscoderToken } from "@/lib/transcoder-auth";
|
||||
|
||||
// Allow up to 45 minutes for large file transfers.
|
||||
export const maxDuration = 2700;
|
||||
|
||||
const UPLOADS_DIR = process.env.UPLOADS_DIR ?? "/uploads";
|
||||
const ORIGINALS_DIR = path.join(UPLOADS_DIR, "videos");
|
||||
|
||||
// Narrow character set – CUIDs are alphanumeric plus underscore/dash.
|
||||
const SAFE_ID = /^[a-zA-Z0-9_-]{1,64}$/;
|
||||
|
||||
export async function GET(
|
||||
request: Request,
|
||||
context: { params: Promise<{ videoId: string }> }
|
||||
) {
|
||||
if (!verifyTranscoderToken(request)) {
|
||||
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
||||
}
|
||||
|
||||
const { videoId } = await context.params;
|
||||
|
||||
if (!SAFE_ID.test(videoId)) {
|
||||
return NextResponse.json({ error: "Invalid video ID" }, { status: 400 });
|
||||
}
|
||||
|
||||
const video = await prisma.video.findUnique({ where: { id: videoId } });
|
||||
if (!video) {
|
||||
return NextResponse.json({ error: "Not found" }, { status: 404 });
|
||||
}
|
||||
|
||||
// Only allow download while the job is actively claimed.
|
||||
if (video.transcodingStatus !== "processing") {
|
||||
return NextResponse.json(
|
||||
{ error: "Video is not in processing state" },
|
||||
{ status: 409 }
|
||||
);
|
||||
}
|
||||
|
||||
const filePath = path.join(ORIGINALS_DIR, `${videoId}.mp4`);
|
||||
|
||||
// Security: ensure the resolved path stays within ORIGINALS_DIR.
|
||||
const resolved = path.resolve(filePath);
|
||||
if (!resolved.startsWith(path.resolve(ORIGINALS_DIR))) {
|
||||
return NextResponse.json({ error: "Forbidden" }, { status: 403 });
|
||||
}
|
||||
|
||||
try {
|
||||
const stat = await fs.stat(resolved);
|
||||
const nodeStream = fssync.createReadStream(resolved);
|
||||
const webStream = Readable.toWeb(nodeStream) as ReadableStream;
|
||||
|
||||
return new Response(webStream, {
|
||||
headers: {
|
||||
"Content-Type": "video/mp4",
|
||||
"Content-Length": stat.size.toString(),
|
||||
"Content-Disposition": `attachment; filename="${videoId}.mp4"`,
|
||||
},
|
||||
});
|
||||
} catch {
|
||||
return NextResponse.json({ error: "File not found on disk" }, { status: 404 });
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user