Files
yaoyaoai/scripts/nodejs/digital_human_generate.js
fengchuanhn@gmail.com 1325ff92e5 1
2026-05-18 18:40:03 +08:00

156 lines
4.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use strict";
/**
* 口播视频生成:调用 Gradio 数字人 API对齐 Electron compshare:digitalHumanProcess
*/
const fs = require("fs");
const path = require("path");
const os = require("os");
const { spawn } = require("child_process");
const desktopConfig = require("./desktop_config.js");
const TEMP_DIR = path.join(os.tmpdir(), "aiclient-digital-human");
function downloadFile(url, destPath) {
return new Promise((resolve, reject) => {
const client = url.startsWith("https") ? require("https") : require("http");
const file = fs.createWriteStream(destPath);
client
.get(url, (res) => {
if (res.statusCode && res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
file.close();
return downloadFile(res.headers.location, destPath).then(resolve).catch(reject);
}
if (res.statusCode !== 200) {
reject(new Error(`下载失败 HTTP ${res.statusCode}`));
return;
}
res.pipe(file);
file.on("finish", () => file.close(() => resolve(destPath)));
})
.on("error", reject);
});
}
async function ensureLocalVideo(videoPath) {
const raw = String(videoPath || "").trim();
if (!raw) throw new Error("视频路径为空");
if (/^https?:\/\//i.test(raw)) {
fs.mkdirSync(TEMP_DIR, { recursive: true });
const ext = raw.includes(".mp4") ? ".mp4" : ".mp4";
const dest = path.join(TEMP_DIR, `remote_${Date.now()}${ext}`);
await downloadFile(raw, dest);
return dest;
}
if (raw.startsWith("file://")) {
return raw.replace(/^file:\/\//, "");
}
return raw;
}
function runPythonDigitalHuman(apiUrl, audioFile, videoFile) {
const scriptPath = path.join(__dirname, "digital_human_process.py");
const py = process.env.AICLIENT_PYTHON || (process.platform === "win32" ? "python" : "python3");
return new Promise((resolve) => {
const child = spawn(py, [scriptPath, apiUrl, audioFile, videoFile], {
cwd: __dirname,
env: process.env,
windowsHide: true,
});
let stdout = "";
let stderr = "";
child.stdout.on("data", (d) => {
stdout += d.toString();
});
child.stderr.on("data", (d) => {
stderr += d.toString();
});
child.on("error", (err) => {
resolve({ success: false, error: err.message });
});
const timer = setTimeout(() => {
child.kill();
resolve({ success: false, error: "数字人处理超时10 分钟)" });
}, 600000);
child.on("close", (code) => {
clearTimeout(timer);
if (code !== 0 || !stdout.trim()) {
resolve({
success: false,
error: stderr.trim() || `数字人脚本退出码 ${code}`,
});
return;
}
try {
const lines = stdout
.split(/\r?\n/)
.map((l) => l.trim())
.filter(Boolean);
const jsonLine = [...lines]
.reverse()
.find((l) => l.startsWith("{") && l.endsWith("}"));
if (!jsonLine) {
resolve({
success: false,
error: `解析结果失败: ${stdout.slice(0, 200)}`,
});
return;
}
resolve(JSON.parse(jsonLine));
} catch (e) {
resolve({
success: false,
error: `解析 JSON 失败: ${e.message}`,
});
}
});
});
}
globalThis.__nodejsMain = async function (params) {
const p = params || {};
const apiUrl =
desktopConfig.get("DIGITAL_HUMAN_API_URL") ||
desktopConfig.get("VIDEO_GEN_API_URL") ||
desktopConfig.get("DIGITAL_HUMAN_GRADIO_URL");
if (!apiUrl) {
return {
success: false,
error:
"缺少 DIGITAL_HUMAN_API_URL或 VIDEO_GEN_API_URL请在配置管理中设置数字人 Gradio 服务地址",
};
}
const audioFile = String(p.audioPath || "").trim();
const videoFile = String(p.avatarVideoPath || "").trim();
if (!audioFile) {
return { success: false, error: "缺少音频文件,请先生成语音" };
}
if (!videoFile) {
return { success: false, error: "缺少形象视频,请先选择形象" };
}
if (!fs.existsSync(audioFile)) {
return { success: false, error: `音频文件不存在: ${audioFile}` };
}
if (!fs.existsSync(videoFile)) {
return { success: false, error: `形象视频不存在: ${videoFile}` };
}
const result = await runPythonDigitalHuman(apiUrl, audioFile, videoFile);
if (!result?.success || !result?.videoPath) {
return {
success: false,
error: String(result?.error || "口播视频生成失败"),
};
}
try {
const localPath = await ensureLocalVideo(result.videoPath);
return { success: true, videoPath: localPath };
} catch (e) {
return { success: false, error: e.message || String(e) };
}
};