// app/api/admin/upload/finalize/route.ts import { NextResponse } from "next/server"; import fs from "fs"; import path from "path"; import { spawn } from "child_process"; import { prisma } from "@/lib/prisma"; import { getServerSession } from "next-auth"; import { authOptions } from "@/lib/auth-options"; const UPLOADS_DIR = process.env.UPLOADS_DIR || "/uploads"; async function getVideoDurationFromFile(filePath: string): Promise { return new Promise((resolve) => { try { const ffprobe = spawn("ffprobe", [ "-v", "error", "-show_entries", "format=duration", "-of", "default=noprint_wrappers=1:nokey=1:nokey=1", filePath, ]); let output = ""; let timedOut = false; const timeoutId = setTimeout(() => { timedOut = true; ffprobe.kill(); resolve(null); }, 30000); ffprobe.stdout.on("data", (data) => { output += data.toString(); }); ffprobe.on("close", (code) => { clearTimeout(timeoutId); if (!timedOut && code === 0) { const duration = parseFloat(output.trim()); if (!isNaN(duration) && isFinite(duration) && duration > 0) { resolve(Math.round(duration)); } else { resolve(null); } } else { resolve(null); } }); ffprobe.on("error", () => { clearTimeout(timeoutId); resolve(null); }); } catch (err) { resolve(null); } }); } async function checkAdmin() { const session = await getServerSession(authOptions); if (!session?.user?.email) throw { status: 401, message: "Unauthorized" }; const allowed = (process.env.ALLOWED_ADMINS || "") .split(",") .map((s) => s.trim()) .filter(Boolean); if (allowed.length && !allowed.includes(session.user.email)) throw { status: 403, message: "Forbidden" }; return session; } export async function POST(req: Request) { try { console.log('[Upload/Finalize] Request received'); await checkAdmin(); const body = await req.json(); const { videoId } = body; if (!videoId) { return NextResponse.json({ error: "videoId required" }, { status: 400 }); } console.log('[Upload/Finalize] Checking for video:', videoId); // Check if video exists in DB const video = await prisma.video.findUnique({ where: { id: videoId }, }); if (!video) { return NextResponse.json({ error: "video not found" }, { status: 404 }); } // Check if file exists at the expected path const videoPath = path.join(UPLOADS_DIR, "videos", `${videoId}.mp4`); console.log('[Upload/Finalize] Checking file at:', videoPath); try { await fs.promises.access(videoPath, fs.constants.F_OK); } catch { return NextResponse.json( { error: `File not found at ${videoPath}. Please copy the file there first.` }, { status: 404 } ); } console.log('[Upload/Finalize] File found, extracting duration'); // Extract duration if not already set let durationSec = video.durationSec; if (!durationSec) { try { const extractedDuration = await getVideoDurationFromFile(videoPath); if (extractedDuration !== null) { durationSec = extractedDuration; console.log('[Upload/Finalize] Extracted duration:', durationSec); } else { console.warn('[Upload/Finalize] Could not extract duration'); } } catch (err) { console.warn('[Upload/Finalize] Duration extraction error:', err); } } // Update video: set URL, duration, and transcoding status to trigger processing const videoUrl = `/uploads/videos/${videoId}.mp4`; const updatedVideo = await prisma.video.update({ where: { id: videoId }, data: { url: videoUrl, transcodingStatus: 'uploaded', // Mark as ready for transcoding ...(durationSec !== null && { durationSec }), }, }); console.log('[Upload/Finalize] Video updated successfully with URL:', videoUrl); return NextResponse.json({ success: true, video: updatedVideo, message: `Video finalized${durationSec ? ` (${durationSec}s)` : ''}. HLS transcoding will start shortly.`, }, { status: 200 }); } catch (err: any) { console.error("[Upload/Finalize] Error:", err); const status = err?.status ?? 500; const message = err?.message ?? "server error"; return NextResponse.json({ error: message }, { status }); } }