import { auth } from "@/app/(auth)/auth"; import { NextResponse } from "next/server"; import { mkdtemp, readFile, rm } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { randomUUID } from "node:crypto"; import { uploadFile } from "@/lib/storage/service"; import { downloadCanvasMedia } from "@/lib/video-canvas/download-media"; import { processVideoFile } from "@/lib/video-canvas/media-processing"; import { videoProcessSchema } from "@/lib/video-canvas/media-contract"; export const runtime = "nodejs"; export const maxDuration = 300; const running = new Set(); export async function POST(request: Request) { const session = await auth(); if (!session?.user?.id) return NextResponse.json({ success: false, error: "未登录" }, { status: 401 }); const parsed = videoProcessSchema.safeParse(await request.json().catch(() => null)); if (!parsed.success) return NextResponse.json({ success: false, error: "视频处理参数无效" }, { status: 400 }); if (running.has(session.user.id) || running.size >= 2) return NextResponse.json( { success: false, error: "视频处理中,请稍后再提交" }, { status: 429 }, ); running.add(session.user.id); let dir = ""; try { dir = await mkdtemp(join(tmpdir(), "canvas-video-")); const input = join(dir, "input"); await downloadCanvasMedia(parsed.data.url, input); const save = async (name: string) => { const buffer = await readFile(join(dir, name)); return ( await uploadFile(`canvas-video-${randomUUID()}-${name}`, new Uint8Array(buffer).buffer) ).url; }; if (parsed.data.operation === "frame" || parsed.data.operation === "slow") { const name = parsed.data.operation === "frame" ? "frame.png" : "slow.mp4"; const metadata = await processVideoFile( input, join(dir, name), parsed.data.operation, undefined, undefined, parsed.data, ); return NextResponse.json({ success: true, data: { url: await save(name), ...metadata } }); } if (parsed.data.operation === "trim") { const metadata = await processVideoFile( input, join(dir, "clip.mp4"), "trim", undefined, parsed.data.range, ); return NextResponse.json({ success: true, data: { url: await save("clip.mp4"), ...metadata }, }); } if (parsed.data.operation === "crop") { const metadata = await processVideoFile( input, join(dir, "crop.mp4"), "crop", parsed.data.rect, ); return NextResponse.json({ success: true, data: { url: await save("crop.mp4"), ...metadata }, }); } const metadata = await processVideoFile(input, join(dir, "audio.m4a"), "extract-audio"); await processVideoFile(input, join(dir, "silent.mp4"), "silent-video"); return NextResponse.json({ success: true, data: { audioUrl: await save("audio.m4a"), url: await save("silent.mp4"), ...metadata }, }); } catch (error) { const message = error instanceof Error ? error.message : "视频处理失败"; return NextResponse.json( { success: false, error: message.startsWith("Command failed") ? "视频处理失败,请确认文件完整且格式受支持" : message, }, { status: 400 }, ); } finally { running.delete(session.user.id); if (dir) await rm(dir, { recursive: true, force: true }); } }