89 lines
2.5 KiB
TypeScript

import { spawn, execFile } from "node:child_process";
import { promisify } from "node:util";
import { readFile } from "node:fs/promises";
import path from "node:path";
import {
audioJobStatusSchema,
type AudioJobInput,
} from "../../lib/video-canvas/smart-edit/audio-contract";
const python = () => process.env.AUDIO_PYTHON || "python3";
export async function audioHealth() {
const { stdout } = await promisify(execFile)(
python(),
[path.join(__dirname, "run.py"), "--check"],
{ timeout: 30000, maxBuffer: 65536 }
);
const value = JSON.parse(stdout);
if (value.asr !== true || value.separation !== true)
throw Error("音频模型未准备就绪");
return { asr: true, separation: true };
}
export async function audioJob(
plan: AudioJobInput,
_tenant: string,
dir: string,
progress: (stage: string) => Promise<void>
) {
await progress(
plan.operation === "transcribe"
? "算力服务器正在自动转写"
: "算力服务器正在分离人声与背景声"
);
await new Promise<void>((resolve, reject) => {
const child = spawn(
python(),
[
path.join(__dirname, "run.py"),
plan.operation,
path.join(dir, "input.wav"),
dir,
],
{
stdio: ["ignore", "ignore", "pipe"],
detached: true,
env: { ...process.env, CUDA_VISIBLE_DEVICES: "", OMP_NUM_THREADS: "2" },
}
);
let tail = "",
timedOut = false;
const timer = setTimeout(() => {
timedOut = true;
try {
process.kill(-child.pid!, "SIGKILL");
} catch {}
}, 600000);
child.stderr.on("data", (b) => {
tail = (tail + b.toString()).slice(-2000);
});
child.once("error", () => {
clearTimeout(timer);
reject(Error("音频处理器无法启动"));
});
child.once("close", (code) => {
clearTimeout(timer);
if (code === 0) resolve();
else {
console.error(
JSON.stringify({
event: "audio-inference-failed",
code,
timedOut,
detail: tail.replace(/https?:\S+/g, "[url]"),
})
);
reject(
Error(
timedOut
? "音频处理超时,请缩短素材后重试"
: "音频模型处理失败,请检查素材或服务日志"
)
);
}
});
});
const result = JSON.parse(
await readFile(path.join(dir, "result.json"), "utf8")
);
return audioJobStatusSchema.shape.result.parse(result)!;
}