Initial clean project import
This commit is contained in:
@@ -0,0 +1,349 @@
|
||||
import { ipcMain } from 'electron';
|
||||
import logger from '../log/main';
|
||||
import axios from 'axios';
|
||||
import crypto from 'crypto';
|
||||
import FormData from 'form-data';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { spawn } from 'child_process';
|
||||
|
||||
import { getPythonExecutablePath, resourceExists } from '../../lib/resource-path';
|
||||
import { buildRuntimeProcessEnv } from '../shell';
|
||||
|
||||
const COMPSHARE_API_BASE = 'https://api.compshare.cn';
|
||||
|
||||
let autoShutdownTimer: NodeJS.Timeout | null = null;
|
||||
let lastTaskTime: number = 0;
|
||||
const AUTO_SHUTDOWN_DELAY = 10 * 60 * 1000;
|
||||
|
||||
interface SelfServerConfig {
|
||||
region: string;
|
||||
zone: string;
|
||||
projectId: string;
|
||||
uhostId: string;
|
||||
apiUrl: string;
|
||||
}
|
||||
|
||||
let currentServerConfig: SelfServerConfig | null = null;
|
||||
let apiKey: { publicKey: string; privateKey: string } | null = null;
|
||||
|
||||
export function setCompShareApiKey(publicKey: string, privateKey: string) {
|
||||
apiKey = { publicKey, privateKey };
|
||||
}
|
||||
|
||||
function generateSignature(params: Record<string, string>): string {
|
||||
if (!apiKey) {
|
||||
throw new Error('API密钥未配置');
|
||||
}
|
||||
|
||||
const sortedKeys = Object.keys(params).sort();
|
||||
const paramString = sortedKeys.map(key => `${key}${params[key]}`).join('');
|
||||
const stringToSign = paramString + apiKey.privateKey;
|
||||
|
||||
return crypto.createHash('sha1').update(stringToSign).digest('hex');
|
||||
}
|
||||
|
||||
function buildSignedParams(action: string, extraParams: Record<string, string> = {}): URLSearchParams {
|
||||
if (!apiKey) {
|
||||
throw new Error('API密钥未配置');
|
||||
}
|
||||
|
||||
const timestamp = Math.floor(Date.now() / 1000).toString();
|
||||
const nonce = Math.random().toString(36).substring(2);
|
||||
|
||||
const params: Record<string, string> = {
|
||||
Action: action,
|
||||
PublicKey: apiKey.publicKey,
|
||||
Timestamp: timestamp,
|
||||
Nonce: nonce,
|
||||
...extraParams
|
||||
};
|
||||
|
||||
const signature = generateSignature(params);
|
||||
params.Signature = signature;
|
||||
|
||||
const urlParams = new URLSearchParams();
|
||||
for (const [key, value] of Object.entries(params)) {
|
||||
urlParams.append(key, value);
|
||||
}
|
||||
|
||||
return urlParams;
|
||||
}
|
||||
|
||||
export function getSelfServerConfig(): SelfServerConfig | null {
|
||||
return currentServerConfig;
|
||||
}
|
||||
|
||||
export function setSelfServerConfig(config: SelfServerConfig) {
|
||||
currentServerConfig = config;
|
||||
}
|
||||
|
||||
export async function startCompShareInstance(config: {
|
||||
region: string;
|
||||
zone: string;
|
||||
projectId: string;
|
||||
uhostId: string;
|
||||
}): Promise<{ success: boolean; message?: string }> {
|
||||
try {
|
||||
const extraParams: Record<string, string> = {
|
||||
Region: config.region,
|
||||
Zone: config.zone,
|
||||
UHostId: config.uhostId,
|
||||
WithoutGpu: 'false'
|
||||
};
|
||||
if (config.projectId) {
|
||||
extraParams.ProjectId = config.projectId;
|
||||
}
|
||||
|
||||
const params = buildSignedParams('StartCompShareInstance', extraParams);
|
||||
const response = await axios.get(`${COMPSHARE_API_BASE}/?${params.toString()}`);
|
||||
const data = response.data;
|
||||
|
||||
if (data.RetCode === 0) {
|
||||
logger.info('[CompShare] 实例启动成功', { uhostId: config.uhostId });
|
||||
return { success: true, message: '实例启动成功' };
|
||||
} else {
|
||||
logger.error('[CompShare] 实例启动失败', data);
|
||||
return { success: false, message: `启动失败: ${data.Message || '未知错误'}` };
|
||||
}
|
||||
} catch (error: any) {
|
||||
logger.error('[CompShare] 启动实例异常', error);
|
||||
return { success: false, message: error.message };
|
||||
}
|
||||
}
|
||||
|
||||
export async function stopCompShareInstance(config: {
|
||||
region: string;
|
||||
zone: string;
|
||||
projectId: string;
|
||||
uhostId: string;
|
||||
}): Promise<{ success: boolean; message?: string }> {
|
||||
try {
|
||||
const extraParams: Record<string, string> = {
|
||||
Region: config.region,
|
||||
Zone: config.zone,
|
||||
UHostId: config.uhostId
|
||||
};
|
||||
if (config.projectId) {
|
||||
extraParams.ProjectId = config.projectId;
|
||||
}
|
||||
|
||||
const params = buildSignedParams('StopCompShareInstance', extraParams);
|
||||
const response = await axios.get(`${COMPSHARE_API_BASE}/?${params.toString()}`);
|
||||
const data = response.data;
|
||||
|
||||
if (data.RetCode === 0) {
|
||||
logger.info('[CompShare] 实例关闭成功', { uhostId: config.uhostId });
|
||||
return { success: true, message: '实例关闭成功' };
|
||||
} else {
|
||||
logger.error('[CompShare] 实例关闭失败', data);
|
||||
return { success: false, message: `关闭失败: ${data.Message || '未知错误'}` };
|
||||
}
|
||||
} catch (error: any) {
|
||||
logger.error('[CompShare] 关闭实例异常', error);
|
||||
return { success: false, message: error.message };
|
||||
}
|
||||
}
|
||||
|
||||
export async function describeCompShareInstance(config: {
|
||||
region?: string;
|
||||
zone?: string;
|
||||
projectId?: string;
|
||||
uhostIds?: string[];
|
||||
}): Promise<{ success: boolean; instances?: any[]; message?: string }> {
|
||||
try {
|
||||
const extraParams: Record<string, string> = {};
|
||||
if (config.region) extraParams.Region = config.region;
|
||||
if (config.zone) extraParams.Zone = config.zone;
|
||||
if (config.projectId) extraParams.ProjectId = config.projectId;
|
||||
if (config.uhostIds && config.uhostIds.length > 0) {
|
||||
config.uhostIds.forEach((id, index) => {
|
||||
extraParams[`UHostIds.${index}`] = id;
|
||||
});
|
||||
}
|
||||
|
||||
const params = buildSignedParams('DescribeCompShareInstance', extraParams);
|
||||
const response = await axios.get(`${COMPSHARE_API_BASE}/?${params.toString()}`);
|
||||
const data = response.data;
|
||||
|
||||
if (data.RetCode === 0) {
|
||||
return { success: true, instances: data.UHostSet || [] };
|
||||
} else {
|
||||
return { success: false, message: `查询失败: ${data.Message || '未知错误'}` };
|
||||
}
|
||||
} catch (error: any) {
|
||||
logger.error('[CompShare] 查询实例异常', error);
|
||||
return { success: false, message: error.message };
|
||||
}
|
||||
}
|
||||
|
||||
export async function processDigitalHuman(params: {
|
||||
apiUrl: string;
|
||||
audioFile: string;
|
||||
videoFile: string;
|
||||
}): Promise<{ success: boolean; videoPath?: string; message?: string }> {
|
||||
const { apiUrl, audioFile, videoFile } = params;
|
||||
logger.info('[CompShare] 开始数字人处理', { apiUrl, audioFile, videoFile });
|
||||
|
||||
try {
|
||||
// 使用 Python 脚本调用 Gradio API
|
||||
let scriptPath: string = '';
|
||||
|
||||
// 尝试多个可能的路径
|
||||
const possiblePaths = [
|
||||
path.join(process.resourcesPath || '', 'scripts', 'digital_human_process.py'),
|
||||
path.join(process.env.APP_ROOT || '', 'scripts', 'digital_human_process.py'),
|
||||
path.join(process.cwd(), 'scripts', 'digital_human_process.py'),
|
||||
path.join(process.cwd(), '..', 'scripts', 'digital_human_process.py'),
|
||||
];
|
||||
|
||||
for (const p of possiblePaths) {
|
||||
if (fs.existsSync(p)) {
|
||||
scriptPath = p;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!scriptPath) {
|
||||
// 如果都找不到,使用 cwd 版本
|
||||
scriptPath = path.join(process.cwd(), 'scripts', 'digital_human_process.py');
|
||||
}
|
||||
|
||||
logger.info('[CompShare] Python 脚本路径', { scriptPath });
|
||||
|
||||
// ✅ 关键修复:使用内嵌的 Python,避免依赖系统安装
|
||||
let pythonExe = getPythonExecutablePath();
|
||||
if (!resourceExists(pythonExe)) {
|
||||
logger.warn(`[CompShare] 内嵌Python不存在: ${pythonExe},尝试系统python`);
|
||||
pythonExe = 'python'; // 回退到系统 python
|
||||
}
|
||||
logger.info('[CompShare] 使用Python路径', { pythonExe });
|
||||
|
||||
return new Promise((resolve) => {
|
||||
const pythonProcess = spawn(pythonExe, [scriptPath, apiUrl, audioFile, videoFile], {
|
||||
cwd: process.cwd(),
|
||||
env: buildRuntimeProcessEnv(),
|
||||
windowsHide: true
|
||||
});
|
||||
|
||||
let stdout = '';
|
||||
let stderr = '';
|
||||
|
||||
pythonProcess.stdout.on('data', (data: Buffer) => {
|
||||
stdout += data.toString();
|
||||
});
|
||||
|
||||
pythonProcess.stderr.on('data', (data: Buffer) => {
|
||||
stderr += data.toString();
|
||||
});
|
||||
|
||||
pythonProcess.on('close', (code: number) => {
|
||||
logger.info('[CompShare] Python 脚本执行完成', { code, stdout: stdout.substring(0, 500) });
|
||||
|
||||
if (code === 0 && stdout.trim()) {
|
||||
try {
|
||||
const stdoutLines = stdout
|
||||
.split(/\r?\n/)
|
||||
.map(line => line.trim())
|
||||
.filter(Boolean);
|
||||
const jsonLine = [...stdoutLines].reverse().find(line => line.startsWith('{') && line.endsWith('}'));
|
||||
|
||||
if (!jsonLine) {
|
||||
resolve({ success: false, message: `解析结果失败: ${stdout.substring(0, 200)}` });
|
||||
return;
|
||||
}
|
||||
|
||||
const result = JSON.parse(jsonLine);
|
||||
if (result.success) {
|
||||
resolve({ success: true, videoPath: result.videoPath });
|
||||
} else {
|
||||
resolve({ success: false, message: result.error || '处理失败' });
|
||||
}
|
||||
} catch (e) {
|
||||
resolve({ success: false, message: `解析结果失败: ${stdout.substring(0, 200)}` });
|
||||
}
|
||||
} else {
|
||||
resolve({ success: false, message: stderr || `进程退出码: ${code}` });
|
||||
}
|
||||
});
|
||||
|
||||
pythonProcess.on('error', (err: Error) => {
|
||||
logger.error('[CompShare] Python 进程错误', err);
|
||||
resolve({ success: false, message: err.message });
|
||||
});
|
||||
|
||||
// 10分钟超时
|
||||
setTimeout(() => {
|
||||
pythonProcess.kill();
|
||||
resolve({ success: false, message: '处理超时' });
|
||||
}, 600000);
|
||||
});
|
||||
|
||||
} catch (error: any) {
|
||||
const errorDetail = error.message || '未知错误';
|
||||
logger.error('[CompShare] 数字人处理失败', errorDetail);
|
||||
return { success: false, message: errorDetail };
|
||||
}
|
||||
}
|
||||
|
||||
export function updateLastTaskTime() {
|
||||
lastTaskTime = Date.now();
|
||||
|
||||
if (autoShutdownTimer) {
|
||||
clearTimeout(autoShutdownTimer);
|
||||
}
|
||||
|
||||
autoShutdownTimer = setTimeout(async () => {
|
||||
const now = Date.now();
|
||||
if (now - lastTaskTime >= AUTO_SHUTDOWN_DELAY && currentServerConfig) {
|
||||
logger.info('[CompShare] 10分钟无任务,自动关闭实例');
|
||||
await stopCompShareInstance(currentServerConfig);
|
||||
autoShutdownTimer = null;
|
||||
}
|
||||
}, AUTO_SHUTDOWN_DELAY);
|
||||
}
|
||||
|
||||
export function registerCompShareHandlers() {
|
||||
ipcMain.handle('compshare:setApiKey', async (event, params: { publicKey: string; privateKey: string }) => {
|
||||
logger.info('[CompShare:IPC] 设置API密钥');
|
||||
setCompShareApiKey(params.publicKey, params.privateKey);
|
||||
return { success: true };
|
||||
});
|
||||
|
||||
ipcMain.handle('compshare:startInstance', async (event, params) => {
|
||||
logger.info('[CompShare:IPC] 启动实例', params);
|
||||
const result = await startCompShareInstance(params);
|
||||
if (result.success) {
|
||||
setSelfServerConfig({
|
||||
region: params.region,
|
||||
zone: params.zone,
|
||||
projectId: params.projectId,
|
||||
uhostId: params.uhostId,
|
||||
apiUrl: ''
|
||||
});
|
||||
updateLastTaskTime();
|
||||
}
|
||||
return result;
|
||||
});
|
||||
|
||||
ipcMain.handle('compshare:stopInstance', async (event, params) => {
|
||||
logger.info('[CompShare:IPC] 关闭实例', params);
|
||||
return await stopCompShareInstance(params);
|
||||
});
|
||||
|
||||
ipcMain.handle('compshare:describeInstance', async (event, params) => {
|
||||
logger.info('[CompShare:IPC] 查询实例', params);
|
||||
return await describeCompShareInstance(params);
|
||||
});
|
||||
|
||||
ipcMain.handle('compshare:digitalHumanProcess', async (event, params) => {
|
||||
logger.info('[CompShare:IPC] 数字人处理', { apiUrl: params.apiUrl });
|
||||
const result = await processDigitalHuman(params);
|
||||
if (result.success) {
|
||||
updateLastTaskTime();
|
||||
}
|
||||
return result;
|
||||
});
|
||||
|
||||
logger.info('[CompShare:IPC] Handlers已注册');
|
||||
}
|
||||
Reference in New Issue
Block a user