36 lines
1.7 KiB
TypeScript
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); }
|
|
}
|