97 lines
3.5 KiB
TypeScript
97 lines
3.5 KiB
TypeScript
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<string>();
|
|
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 });
|
|
}
|
|
}
|