@webgal-tools/voice
Version:
WebGAL GPT-SoVITS语音合成应用
363 lines (362 loc) • 17.3 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ParallelProcessor = void 0;
const path_1 = require("path");
const index_js_1 = require("./translate/index.js");
const config_1 = require("@webgal-tools/config");
const logger_1 = require("@webgal-tools/logger");
const model_scanner_js_1 = require("./model-scanner.js");
/**
* 并发控制器 - 基于Promise而非多进程
*/
class ParallelProcessor {
constructor(api, audioOutputDir, gptSovitsPath) {
// 状态跟踪
this.totalTasks = 0;
this.completedTranslateCount = 0;
this.completedVoiceCount = 0;
this.completedVoiceTasks = [];
// 语音合成队列和状态
this.voiceQueue = [];
this.isVoiceSynthesizing = false;
this.currentCharacter = null;
this.activeTranslations = 0;
this.api = api;
this.audioOutputDir = audioOutputDir;
this.gptSovitsPath = gptSovitsPath || '';
this.translateService = new index_js_1.TranslateService();
// 从配置获取最大并发数
this.maxConcurrency = (0, config_1.getMaxTranslator)();
logger_1.logger.info(`🔧 最大并发数: ${this.maxConcurrency}`);
}
/**
* 设置进度回调函数
*/
setCallbacks(callbacks) {
this.onTranslateProgress = callbacks.onTranslateProgress;
this.onVoiceProgress = callbacks.onVoiceProgress;
this.onError = callbacks.onError;
}
/**
* 限制并发执行的Promise工具函数
*/
async limitConcurrency(tasks, limit, onProgress) {
const results = [];
const executing = [];
let completed = 0;
for (const task of tasks) {
const promise = task().then((result) => {
completed++;
results.push(result);
if (onProgress) {
onProgress(completed, tasks.length, result);
}
});
executing.push(promise);
if (executing.length >= limit) {
await Promise.race(executing);
executing.splice(executing.findIndex(p => p === promise || p._isCompleted), 1);
}
}
await Promise.all(executing);
return results;
}
/**
* 执行单个翻译任务
*/
async translateSingleTask(task, config) {
try {
logger_1.logger.info(`🔄 开始翻译: ${task.character} - ${task.originalText.substring(0, 20)}...`);
let translatedText;
let emotionResult;
if (task.isAutoMode && task.characterConfig && task.gptSovitsPath) {
// 自动模式:执行情绪识别和模型选择
logger_1.logger.info(`🤖 [${task.character}] 自动模式 - 执行情绪识别和模型选择...`);
// 构建文件夹路径
const gptDir = path_1.default.resolve(task.gptSovitsPath, task.characterConfig.gpt);
const sovitsDir = path_1.default.resolve(task.gptSovitsPath, task.characterConfig.sovits);
const refAudioDir = task.characterConfig.ref_audio;
// 扫描模型文件
const scannedFiles = model_scanner_js_1.ModelScanner.scanModelFiles(task.gptSovitsPath, gptDir, sovitsDir, refAudioDir);
// 检查是否有足够的文件
if (scannedFiles.gpt_files.length === 0) {
throw new Error(`未找到GPT模型文件在: ${gptDir}`);
}
if (scannedFiles.sovits_files.length === 0) {
throw new Error(`未找到SoVITS模型文件在: ${sovitsDir}`);
}
if (scannedFiles.ref_audio_files.length === 0) {
throw new Error(`未找到参考音频文件在: ${refAudioDir}`);
}
// 执行情绪识别和模型选择
emotionResult = await this.translateService.selectModelAndTranslate(task.character, task.originalText, task.targetLanguage, scannedFiles, config, task.characterConfig, task.context);
translatedText = emotionResult.translated_text;
logger_1.logger.info(`✅ [${task.character}] 自动模式翻译完成 - 情绪: ${emotionResult.emotion}`);
}
else {
// 正常模式:常规翻译
translatedText = await this.translateService.translate(task.character, task.originalText, task.targetLanguage, config, task.characterConfig, task.context);
}
const result = {
id: task.id,
character: task.character,
originalText: task.originalText,
translatedText,
audioFileName: task.audioFileName,
success: true,
isAutoMode: task.isAutoMode,
emotionResult
};
logger_1.logger.info(`✅ 翻译完成: ${task.character} - ${translatedText.substring(0, 20)}...`);
return result;
}
catch (error) {
logger_1.logger.error(`❌ 翻译失败 ${task.character}:`, error);
const result = {
id: task.id,
character: task.character,
originalText: task.originalText,
translatedText: task.originalText, // 失败时使用原文
audioFileName: task.audioFileName,
success: false,
error: error instanceof Error ? error.message : String(error),
isAutoMode: task.isAutoMode
};
if (this.onError) {
this.onError(error instanceof Error ? error : new Error(String(error)), task);
}
return result;
}
}
/**
* 并发执行翻译任务
*/
async translateTasks(tasks, config) {
if (tasks.length === 0) {
return [];
}
logger_1.logger.info(`🚀 开始并发翻译 ${tasks.length} 个任务,最大并发数: ${this.maxConcurrency}`);
const taskFunctions = tasks.map(task => () => this.translateSingleTask(task, config));
return this.limitConcurrency(taskFunctions, this.maxConcurrency, (completed, total, result) => {
this.completedTranslateCount = completed;
if (this.onTranslateProgress) {
this.onTranslateProgress(completed, total, result);
}
// 将翻译完成的任务加入语音合成队列
this.enqueueVoiceSynthesis(result);
});
}
/**
* 将翻译结果加入语音合成队列
*/
enqueueVoiceSynthesis(translateResult) {
this.voiceQueue.push(translateResult);
logger_1.logger.info(`📋 加入语音合成队列: ${translateResult.character} (队列长度: ${this.voiceQueue.length})`);
// 触发语音合成处理
this.processVoiceQueue();
}
/**
* 处理语音合成队列(保持单线程以避免模型切换冲突)
*/
async processVoiceQueue() {
if (this.isVoiceSynthesizing || this.voiceQueue.length === 0) {
return;
}
this.isVoiceSynthesizing = true;
while (this.voiceQueue.length > 0) {
const translateResult = this.voiceQueue.shift();
// 优先处理同一角色的任务
if (this.currentCharacter && this.currentCharacter !== translateResult.character) {
const sameCharacterIndex = this.voiceQueue.findIndex(task => task.character === this.currentCharacter);
if (sameCharacterIndex !== -1) {
this.voiceQueue.unshift(translateResult);
const sameCharacterTask = this.voiceQueue.splice(sameCharacterIndex + 1, 1)[0];
await this.synthesizeVoice(sameCharacterTask);
continue;
}
}
await this.synthesizeVoice(translateResult);
}
this.isVoiceSynthesizing = false;
}
/**
* 执行语音合成
*/
async synthesizeVoice(translateResult) {
try {
logger_1.logger.info(`🎵 开始语音合成: ${translateResult.character} (队列剩余: ${this.voiceQueue.length})`);
// 找到对应的角色配置
const characterConfig = this.characterConfigs?.get(translateResult.character);
if (!characterConfig) {
throw new Error(`未找到角色配置: ${translateResult.character}`);
}
let finalCharacterConfig;
let refAudioPath;
let refText;
if (translateResult.isAutoMode && translateResult.emotionResult) {
// 自动模式:使用情绪识别结果
const emotionResult = translateResult.emotionResult;
// 创建临时的角色配置
finalCharacterConfig = {
...characterConfig,
gpt: path_1.default.relative(this.gptSovitsPath, emotionResult.gpt),
sovits: path_1.default.relative(this.gptSovitsPath, emotionResult.sovits),
ref_audio: emotionResult.ref_audio,
ref_text: model_scanner_js_1.ModelScanner.extractRefTextFromAudioFileName(emotionResult.ref_audio)
};
refAudioPath = emotionResult.ref_audio;
refText = model_scanner_js_1.ModelScanner.extractRefTextFromAudioFileName(emotionResult.ref_audio);
logger_1.logger.info(`🤖 [${translateResult.character}] 自动模式 - 使用情绪: ${emotionResult.emotion}`);
}
else {
// 正常模式:使用原配置
finalCharacterConfig = characterConfig;
refAudioPath = characterConfig.ref_audio;
refText = characterConfig.ref_text;
}
// 检查是否需要切换角色模型
const modelKey = `${finalCharacterConfig.gpt}_${finalCharacterConfig.sovits}`;
const currentModelKey = this.currentCharacter ? `${this.characterConfigs?.get(this.currentCharacter)?.gpt}_${this.characterConfigs?.get(this.currentCharacter)?.sovits}` : null;
if (currentModelKey !== modelKey) {
logger_1.logger.info(`🔄 切换到角色模型: ${translateResult.character} (${finalCharacterConfig.gpt}/${finalCharacterConfig.sovits})`);
await this.api.setGptModel(finalCharacterConfig.gpt);
await this.api.setSovitsModel(finalCharacterConfig.sovits, finalCharacterConfig.inferrence_config?.prompt_language || '中文', finalCharacterConfig.inferrence_config?.text_language || '中文');
this.currentCharacter = translateResult.character;
}
// 生成语音
const outputPath = await this.api.generateVoice(refAudioPath, refText, translateResult.translatedText, finalCharacterConfig.inferrence_config || {});
// 下载音频文件
const finalAudioPath = path_1.default.join(this.audioOutputDir, translateResult.audioFileName);
await this.api.downloadAudio(outputPath, finalAudioPath);
// 创建完成的任务
const completedTask = {
character: translateResult.character,
originalText: translateResult.originalText,
targetText: translateResult.translatedText,
audioFileName: translateResult.audioFileName
};
this.completedVoiceTasks.push(completedTask);
this.completedVoiceCount++;
logger_1.logger.info(`✅ 语音合成完成: ${translateResult.character} - ${translateResult.audioFileName}`);
if (this.onVoiceProgress) {
this.onVoiceProgress(this.completedVoiceCount, this.totalTasks, completedTask);
}
}
catch (error) {
logger_1.logger.error(`❌ 语音合成失败 ${translateResult.character}:`, error);
this.completedVoiceCount++;
if (this.onError) {
const voiceTask = {
character: translateResult.character,
originalText: translateResult.originalText,
targetText: translateResult.translatedText,
audioFileName: translateResult.audioFileName
};
this.onError(error instanceof Error ? error : new Error(String(error)), voiceTask);
}
}
}
/**
* 处理翻译和语音合成任务(新的基于Promise的实现)
*/
async processTasksParallel(voiceTasks, characterConfigs, translateConfig, contextMap, gptSovitsPath // 新增:GPT-SoVITS路径参数
) {
this.characterConfigs = characterConfigs;
this.totalTasks = voiceTasks.length;
this.completedTranslateCount = 0;
this.completedVoiceCount = 0;
this.completedVoiceTasks = [];
this.voiceQueue = [];
// 更新 gptSovitsPath 如果提供了
if (gptSovitsPath) {
this.gptSovitsPath = gptSovitsPath;
}
if (this.totalTasks === 0) {
return [];
}
logger_1.logger.info(`🚀 开始并行处理 ${this.totalTasks} 个任务`);
// 准备翻译任务
const translateTasks = [];
const noTranslateTasks = [];
for (const task of voiceTasks) {
const characterConfig = characterConfigs.get(task.character);
if (!characterConfig) {
logger_1.logger.error(`❌ 角色 ${task.character} 未在配置中找到`);
continue;
}
const taskId = `${task.character}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
const isAutoMode = characterConfig.auto === true;
const translateTarget = characterConfig.translate_to;
if (translateTarget || isAutoMode) {
// 需要翻译或者是自动模式
const taskKey = `${task.character}:${task.originalText}`;
const context = contextMap.get(taskKey);
const translateTask = {
id: taskId,
character: task.character,
originalText: task.originalText,
targetLanguage: translateTarget || '中文', // 自动模式默认中文
audioFileName: task.audioFileName,
context,
characterConfig,
isAutoMode, // 标识是否为自动模式
gptSovitsPath: this.gptSovitsPath
};
translateTasks.push(translateTask);
if (isAutoMode) {
logger_1.logger.info(`🤖 创建自动模式任务: ${task.character} - ${task.originalText.substring(0, 20)}...`);
}
else {
logger_1.logger.info(`📝 创建翻译任务: ${task.character} - ${task.originalText.substring(0, 20)}...`);
}
}
else {
// 不需要翻译,直接使用原文
const result = {
id: taskId,
character: task.character,
originalText: task.originalText,
translatedText: task.originalText,
audioFileName: task.audioFileName,
success: true,
isAutoMode: false
};
noTranslateTasks.push(result);
logger_1.logger.info(`✅ 无需翻译任务: ${task.character} - ${task.originalText.substring(0, 20)}...`);
}
}
// 先将不需要翻译的任务加入语音合成队列
for (const result of noTranslateTasks) {
this.enqueueVoiceSynthesis(result);
}
// 并发执行翻译任务(包括自动模式和普通模式)
if (translateTasks.length > 0) {
const autoModeCount = translateTasks.filter(t => t.isAutoMode).length;
const normalModeCount = translateTasks.length - autoModeCount;
logger_1.logger.info(`📊 开始并行处理翻译任务: 自动模式 ${autoModeCount} 个, 普通模式 ${normalModeCount} 个`);
await this.translateTasks(translateTasks, translateConfig);
}
// 等待所有任务完成
return new Promise((resolve) => {
const checkCompletion = () => {
if (this.completedVoiceCount >= this.totalTasks) {
logger_1.logger.info(`🎉 并行处理完成!成功处理 ${this.completedVoiceTasks.length}/${this.totalTasks} 个任务`);
resolve(this.completedVoiceTasks);
}
else {
setTimeout(checkCompletion, 100);
}
};
checkCompletion();
});
}
/**
* 清理资源
*/
cleanup() {
// 清理翻译服务缓存
this.translateService.clearCache();
logger_1.logger.info('🛑 并行处理器资源已清理');
}
}
exports.ParallelProcessor = ParallelProcessor;