import { spawn } from "node:child_process"; import path from "node:path"; import { downloadCanvasMedia } from "../../lib/video-canvas/download-media"; import { videoUpscaleSchema, type VideoUpscaleInput, } from "../../lib/video-canvas/video-upscale-contract"; export async function upscaleJob( value: VideoUpscaleInput, _tenant: string, dir: string, progress: (stage: string) => Promise ) { const input = videoUpscaleSchema.parse(value); const file = path.join(dir, "input.bin"); await downloadCanvasMedia(input.url, file); await progress("GPU 正在进行 AI 超分"); return new Promise((resolve, reject) => { const child = spawn( process.env.UPSCALE_PYTHON || "/opt/video-upscale/venv/bin/python", [ process.env.UPSCALE_SCRIPT || path.join(__dirname, "upscale.py"), "--input", file, "--output", path.join(dir, "result.mp4"), "--resolution", input.resolution, "--model", process.env.UPSCALE_MODEL || "/opt/video-upscale/models/realesr-general-x4v3.pth", ], { stdio: ["ignore", "pipe", "pipe"], detached: true } ); let buffer = "", result: any, error = "", stages = Promise.resolve(); const timer = setTimeout(() => { try { process.kill(-child.pid!, "SIGKILL"); } catch {} }, 2 * 3600000); child.stdout.on("data", (chunk) => { buffer += chunk; const lines = buffer.split("\n"); buffer = lines.pop()!; for (const line of lines) try { const value = JSON.parse(line); if (value.result) result = value.result; if (value.stage) stages = stages .then(() => progress(value.stage)) .catch(() => { try { process.kill(-child.pid!, "SIGKILL"); } catch {} }); } catch {} }); child.stderr.on("data", (chunk) => { error = (error + chunk).slice(-4000); }); child.on("error", (e) => { clearTimeout(timer); reject(e); }); child.on("close", async (code) => { clearTimeout(timer); await stages.catch(() => {}); if (code !== 0 || !result) { console.error( "Upscale inference failed", error.replace(/https?:\/\/\S+/g, "[url]") ); reject( Error( "AI 超分失败,请检查视频是否为恒定帧率、120 秒以内,且放大倍率为 1–4 倍" ) ); } else resolve({ ...result, duration: result.durationSeconds }); }); }); }