ai-chatbot/lib/video-canvas/archive-video.ts

36 lines
1.7 KiB
TypeScript

import { randomUUID } from "node:crypto";
import { execFile } from "node:child_process";
import { mkdtemp, readFile, rm } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { promisify } from "node:util";
import { uploadFile } from "@/lib/storage/service";
import { downloadCanvasMedia } from "./download-media";
const probe = promisify(execFile);
const pending = new Map<string, Promise<string>>();
export async function archiveCanvasVideo(url: string): Promise<string> {
const existing = pending.get(url);
if (existing) return existing;
if (pending.size >= 2) throw new Error("视频保存繁忙,请稍后重试");
const job = (async () => {
const dir = await mkdtemp(join(tmpdir(), "canvas-video-archive-"));
try {
const input = join(dir, "video");
await downloadCanvasMedia(url, input);
const { stdout } = await probe("ffprobe", ["-v", "error", "-protocol_whitelist", "file", "-format_whitelist", "mov,matroska,webm", "-show_streams", "-show_format", "-of", "json", input], { timeout: 15000, maxBuffer: 1024 * 1024 });
const media = JSON.parse(stdout);
if (!media.streams?.some((stream: { codec_type: string }) => stream.codec_type === "video") || !(Number(media.format?.duration) > 0)) throw new Error("视频文件不完整");
const extension = String(media.format.format_name).includes("mov") ? "mp4" : "webm";
const data = await readFile(input);
const uploaded = await uploadFile(`canvas-generated-${randomUUID()}.${extension}`, new Uint8Array(data).buffer);
return uploaded.url;
} finally {
await rm(dir, { recursive: true, force: true });
}
})();
pending.set(url, job);
try { return await job; } finally { pending.delete(url); }
}