mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-31 16:49:07 +08:00
123
This commit is contained in:
parent
2a42bbe918
commit
4b652b418e
@ -10,11 +10,9 @@ import log from 'electron-log';
|
|||||||
*/
|
*/
|
||||||
class MsEdgeTTSService {
|
class MsEdgeTTSService {
|
||||||
private static instance: MsEdgeTTSService;
|
private static instance: MsEdgeTTSService;
|
||||||
private tts: EdgeTTS;
|
|
||||||
private tempDir: string;
|
private tempDir: string;
|
||||||
|
|
||||||
private constructor() {
|
private constructor() {
|
||||||
this.tts = new EdgeTTS();
|
|
||||||
this.tempDir = path.join(app.getPath('temp'), 'cherry-tts');
|
this.tempDir = path.join(app.getPath('temp'), 'cherry-tts');
|
||||||
|
|
||||||
// 确保临时目录存在
|
// 确保临时目录存在
|
||||||
@ -65,19 +63,64 @@ class MsEdgeTTSService {
|
|||||||
*/
|
*/
|
||||||
public async synthesize(text: string, voice: string, outputFormat: string): Promise<string> {
|
public async synthesize(text: string, voice: string, outputFormat: string): Promise<string> {
|
||||||
try {
|
try {
|
||||||
// 设置TTS参数
|
log.info(`Microsoft Edge TTS合成语音: 文本="${text.substring(0, 30)}...", 语音=${voice}, 格式=${outputFormat}`);
|
||||||
await this.tts.setMetadata(voice, outputFormat);
|
|
||||||
|
// 验证输入参数
|
||||||
|
if (!text || text.trim() === '') {
|
||||||
|
throw new Error('要合成的文本不能为空');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!voice || voice.trim() === '') {
|
||||||
|
throw new Error('语音名称不能为空');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建一个新的EdgeTTS实例,并设置参数
|
||||||
|
const tts = new EdgeTTS({
|
||||||
|
voice: voice,
|
||||||
|
outputFormat: outputFormat,
|
||||||
|
timeout: 30000, // 30秒超时
|
||||||
|
rate: '+0%', // 正常语速
|
||||||
|
pitch: '+0Hz', // 正常音调
|
||||||
|
volume: '+0%' // 正常音量
|
||||||
|
});
|
||||||
|
|
||||||
// 生成临时文件路径
|
// 生成临时文件路径
|
||||||
const timestamp = Date.now();
|
const timestamp = Date.now();
|
||||||
const outputPath = path.join(this.tempDir, `tts_${timestamp}.mp3`);
|
const fileExtension = outputFormat.includes('mp3') ? 'mp3' : outputFormat.split('-').pop() || 'audio';
|
||||||
|
const outputPath = path.join(this.tempDir, `tts_${timestamp}.${fileExtension}`);
|
||||||
|
|
||||||
// 合成语音
|
log.info(`开始生成语音文件: ${outputPath}`);
|
||||||
await this.tts.toFile(outputPath, text);
|
|
||||||
|
|
||||||
|
// 使用ttsPromise方法生成文件
|
||||||
|
await tts.ttsPromise(text, outputPath);
|
||||||
|
|
||||||
|
// 验证生成的文件是否存在且大小大于0
|
||||||
|
if (!fs.existsSync(outputPath)) {
|
||||||
|
throw new Error(`生成的语音文件不存在: ${outputPath}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const stats = fs.statSync(outputPath);
|
||||||
|
if (stats.size === 0) {
|
||||||
|
throw new Error(`生成的语音文件大小为0: ${outputPath}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info(`Microsoft Edge TTS合成成功: ${outputPath}, 文件大小: ${stats.size} 字节`);
|
||||||
return outputPath;
|
return outputPath;
|
||||||
} catch (error) {
|
} catch (error: any) {
|
||||||
log.error('Microsoft Edge TTS语音合成失败:', error);
|
// 记录详细的错误信息
|
||||||
|
log.error(`Microsoft Edge TTS语音合成失败 (语音=${voice}):`, error);
|
||||||
|
|
||||||
|
// 尝试提供更有用的错误信息
|
||||||
|
if (error.message && typeof error.message === 'string') {
|
||||||
|
if (error.message.includes('Timed out')) {
|
||||||
|
throw new Error(`语音合成超时,请检查网络连接或尝试其他语音`);
|
||||||
|
} else if (error.message.includes('ENOTFOUND')) {
|
||||||
|
throw new Error(`无法连接到Microsoft语音服务,请检查网络连接`);
|
||||||
|
} else if (error.message.includes('ECONNREFUSED')) {
|
||||||
|
throw new Error(`连接被拒绝,请检查网络设置或代理配置`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1405,7 +1405,15 @@
|
|||||||
"empty_text": "Text is empty",
|
"empty_text": "Text is empty",
|
||||||
"general": "An error occurred during speech synthesis",
|
"general": "An error occurred during speech synthesis",
|
||||||
"unsupported_service_type": "Unsupported service type: {{serviceType}}"
|
"unsupported_service_type": "Unsupported service type: {{serviceType}}"
|
||||||
}
|
},
|
||||||
|
"service_type.mstts": "[to be translated]:免费在线 TTS",
|
||||||
|
"edge_voice.available_count": "[to be translated]:可用语音: {{count}}个",
|
||||||
|
"edge_voice.refreshing": "[to be translated]:正在刷新语音列表...",
|
||||||
|
"edge_voice.refreshed": "[to be translated]:语音列表已刷新",
|
||||||
|
"mstts.voice": "[to be translated]:免费在线 TTS音色",
|
||||||
|
"mstts.output_format": "[to be translated]:输出格式",
|
||||||
|
"mstts.info": "[to be translated]:免费在线TTS服务不需要API密钥,完全免费使用。",
|
||||||
|
"error.no_mstts_voice": "[to be translated]:未设置免费在线 TTS音色"
|
||||||
},
|
},
|
||||||
"asr": {
|
"asr": {
|
||||||
"title": "Speech Recognition",
|
"title": "Speech Recognition",
|
||||||
|
|||||||
@ -1355,7 +1355,14 @@
|
|||||||
"not_enabled": "音声合成が有効になっていません",
|
"not_enabled": "音声合成が有効になっていません",
|
||||||
"no_edge_voice": "ブラウザ TTSの音声が選択されていません",
|
"no_edge_voice": "ブラウザ TTSの音声が選択されていません",
|
||||||
"no_api_key": "[to be translated]:未设置API密钥",
|
"no_api_key": "[to be translated]:未设置API密钥",
|
||||||
"browser_not_support": "[to be translated]:浏览器不支持语音合成"
|
"browser_not_support": "[to be translated]:浏览器不支持语音合成",
|
||||||
|
"no_voice": "[to be translated]:未选择音色",
|
||||||
|
"no_model": "[to be translated]:未选择模型",
|
||||||
|
"synthesis_failed": "[to be translated]:语音合成失败",
|
||||||
|
"play_failed": "[to be translated]:语音播放失败",
|
||||||
|
"empty_text": "[to be translated]:文本为空",
|
||||||
|
"general": "[to be translated]:语音合成出现错误",
|
||||||
|
"unsupported_service_type": "[to be translated]:不支持的服务类型: {{serviceType}}"
|
||||||
},
|
},
|
||||||
"help": "OpenAIのTTS APIを使用するには、APIキーが必要です。ブラウザ TTSはブラウザの機能を使用するため、APIキーは不要です。",
|
"help": "OpenAIのTTS APIを使用するには、APIキーが必要です。ブラウザ TTSはブラウザの機能を使用するため、APIキーは不要です。",
|
||||||
"learn_more": "詳細はこちら",
|
"learn_more": "詳細はこちら",
|
||||||
@ -1385,7 +1392,28 @@
|
|||||||
"filter.markdown": "[to be translated]:过滤Markdown标记",
|
"filter.markdown": "[to be translated]:过滤Markdown标记",
|
||||||
"filter.code_blocks": "[to be translated]:过滤代码块",
|
"filter.code_blocks": "[to be translated]:过滤代码块",
|
||||||
"filter.html_tags": "[to be translated]:过滤HTML标签",
|
"filter.html_tags": "[to be translated]:过滤HTML标签",
|
||||||
"max_text_length": "[to be translated]:最大文本长度"
|
"max_text_length": "[to be translated]:最大文本长度",
|
||||||
|
"service_type.siliconflow": "[to be translated]:硅基流动",
|
||||||
|
"service_type.mstts": "[to be translated]:免费在线 TTS",
|
||||||
|
"siliconflow_api_key": "[to be translated]:硅基流动API密钥",
|
||||||
|
"siliconflow_api_key.placeholder": "[to be translated]:请输入硅基流动API密钥",
|
||||||
|
"siliconflow_api_url": "[to be translated]:硅基流动API地址",
|
||||||
|
"siliconflow_api_url.placeholder": "[to be translated]:例如:https://api.siliconflow.cn/v1/audio/speech",
|
||||||
|
"siliconflow_voice": "[to be translated]:硅基流动音色",
|
||||||
|
"siliconflow_voice.placeholder": "[to be translated]:请选择音色",
|
||||||
|
"siliconflow_model": "[to be translated]:硅基流动模型",
|
||||||
|
"siliconflow_model.placeholder": "[to be translated]:请选择模型",
|
||||||
|
"siliconflow_response_format": "[to be translated]:响应格式",
|
||||||
|
"siliconflow_response_format.placeholder": "[to be translated]:默认为mp3",
|
||||||
|
"siliconflow_speed": "[to be translated]:语速",
|
||||||
|
"siliconflow_speed.placeholder": "[to be translated]:默认为1.0",
|
||||||
|
"edge_voice.available_count": "[to be translated]:可用语音: {{count}}个",
|
||||||
|
"edge_voice.refreshing": "[to be translated]:正在刷新语音列表...",
|
||||||
|
"edge_voice.refreshed": "[to be translated]:语音列表已刷新",
|
||||||
|
"mstts.voice": "[to be translated]:免费在线 TTS音色",
|
||||||
|
"mstts.output_format": "[to be translated]:输出格式",
|
||||||
|
"mstts.info": "[to be translated]:免费在线TTS服务不需要API密钥,完全免费使用。",
|
||||||
|
"error.no_mstts_voice": "[to be translated]:未设置免费在线 TTS音色"
|
||||||
},
|
},
|
||||||
"asr": {
|
"asr": {
|
||||||
"title": "音声認識",
|
"title": "音声認識",
|
||||||
|
|||||||
@ -1355,7 +1355,14 @@
|
|||||||
"not_enabled": "Преобразование текста в речь не включено",
|
"not_enabled": "Преобразование текста в речь не включено",
|
||||||
"no_edge_voice": "Голос Edge TTS не выбран",
|
"no_edge_voice": "Голос Edge TTS не выбран",
|
||||||
"no_api_key": "[to be translated]:未设置API密钥",
|
"no_api_key": "[to be translated]:未设置API密钥",
|
||||||
"browser_not_support": "[to be translated]:浏览器不支持语音合成"
|
"browser_not_support": "[to be translated]:浏览器不支持语音合成",
|
||||||
|
"no_voice": "[to be translated]:未选择音色",
|
||||||
|
"no_model": "[to be translated]:未选择模型",
|
||||||
|
"synthesis_failed": "[to be translated]:语音合成失败",
|
||||||
|
"play_failed": "[to be translated]:语音播放失败",
|
||||||
|
"empty_text": "[to be translated]:文本为空",
|
||||||
|
"general": "[to be translated]:语音合成出现错误",
|
||||||
|
"unsupported_service_type": "[to be translated]:不支持的服务类型: {{serviceType}}"
|
||||||
},
|
},
|
||||||
"help": "Для использования API TTS OpenAI требуется ключ API. Edge TTS использует функции браузера и не требует ключа API.",
|
"help": "Для использования API TTS OpenAI требуется ключ API. Edge TTS использует функции браузера и не требует ключа API.",
|
||||||
"learn_more": "Узнать больше",
|
"learn_more": "Узнать больше",
|
||||||
@ -1385,7 +1392,28 @@
|
|||||||
"filter.markdown": "[to be translated]:过滤Markdown标记",
|
"filter.markdown": "[to be translated]:过滤Markdown标记",
|
||||||
"filter.code_blocks": "[to be translated]:过滤代码块",
|
"filter.code_blocks": "[to be translated]:过滤代码块",
|
||||||
"filter.html_tags": "[to be translated]:过滤HTML标签",
|
"filter.html_tags": "[to be translated]:过滤HTML标签",
|
||||||
"max_text_length": "[to be translated]:最大文本长度"
|
"max_text_length": "[to be translated]:最大文本长度",
|
||||||
|
"service_type.siliconflow": "[to be translated]:硅基流动",
|
||||||
|
"service_type.mstts": "[to be translated]:免费在线 TTS",
|
||||||
|
"siliconflow_api_key": "[to be translated]:硅基流动API密钥",
|
||||||
|
"siliconflow_api_key.placeholder": "[to be translated]:请输入硅基流动API密钥",
|
||||||
|
"siliconflow_api_url": "[to be translated]:硅基流动API地址",
|
||||||
|
"siliconflow_api_url.placeholder": "[to be translated]:例如:https://api.siliconflow.cn/v1/audio/speech",
|
||||||
|
"siliconflow_voice": "[to be translated]:硅基流动音色",
|
||||||
|
"siliconflow_voice.placeholder": "[to be translated]:请选择音色",
|
||||||
|
"siliconflow_model": "[to be translated]:硅基流动模型",
|
||||||
|
"siliconflow_model.placeholder": "[to be translated]:请选择模型",
|
||||||
|
"siliconflow_response_format": "[to be translated]:响应格式",
|
||||||
|
"siliconflow_response_format.placeholder": "[to be translated]:默认为mp3",
|
||||||
|
"siliconflow_speed": "[to be translated]:语速",
|
||||||
|
"siliconflow_speed.placeholder": "[to be translated]:默认为1.0",
|
||||||
|
"edge_voice.available_count": "[to be translated]:可用语音: {{count}}个",
|
||||||
|
"edge_voice.refreshing": "[to be translated]:正在刷新语音列表...",
|
||||||
|
"edge_voice.refreshed": "[to be translated]:语音列表已刷新",
|
||||||
|
"mstts.voice": "[to be translated]:免费在线 TTS音色",
|
||||||
|
"mstts.output_format": "[to be translated]:输出格式",
|
||||||
|
"mstts.info": "[to be translated]:免费在线TTS服务不需要API密钥,完全免费使用。",
|
||||||
|
"error.no_mstts_voice": "[to be translated]:未设置免费在线 TTS音色"
|
||||||
},
|
},
|
||||||
"voice": {
|
"voice": {
|
||||||
"title": "[to be translated]:语音功能",
|
"title": "[to be translated]:语音功能",
|
||||||
|
|||||||
@ -1355,7 +1355,14 @@
|
|||||||
"not_enabled": "語音合成未啟用",
|
"not_enabled": "語音合成未啟用",
|
||||||
"no_edge_voice": "未選擇Edge TTS音色",
|
"no_edge_voice": "未選擇Edge TTS音色",
|
||||||
"no_api_key": "[to be translated]:未设置API密钥",
|
"no_api_key": "[to be translated]:未设置API密钥",
|
||||||
"browser_not_support": "[to be translated]:浏览器不支持语音合成"
|
"browser_not_support": "[to be translated]:浏览器不支持语音合成",
|
||||||
|
"no_voice": "[to be translated]:未选择音色",
|
||||||
|
"no_model": "[to be translated]:未选择模型",
|
||||||
|
"synthesis_failed": "[to be translated]:语音合成失败",
|
||||||
|
"play_failed": "[to be translated]:语音播放失败",
|
||||||
|
"empty_text": "[to be translated]:文本为空",
|
||||||
|
"general": "[to be translated]:语音合成出现错误",
|
||||||
|
"unsupported_service_type": "[to be translated]:不支持的服务类型: {{serviceType}}"
|
||||||
},
|
},
|
||||||
"help": "使用OpenAI的TTS API需要API金鑰。Edge TTS使用瀏覽器功能,不需要API金鑰。",
|
"help": "使用OpenAI的TTS API需要API金鑰。Edge TTS使用瀏覽器功能,不需要API金鑰。",
|
||||||
"learn_more": "了解更多",
|
"learn_more": "了解更多",
|
||||||
@ -1385,7 +1392,28 @@
|
|||||||
"filter.markdown": "[to be translated]:过滤Markdown标记",
|
"filter.markdown": "[to be translated]:过滤Markdown标记",
|
||||||
"filter.code_blocks": "[to be translated]:过滤代码块",
|
"filter.code_blocks": "[to be translated]:过滤代码块",
|
||||||
"filter.html_tags": "[to be translated]:过滤HTML标签",
|
"filter.html_tags": "[to be translated]:过滤HTML标签",
|
||||||
"max_text_length": "[to be translated]:最大文本长度"
|
"max_text_length": "[to be translated]:最大文本长度",
|
||||||
|
"service_type.siliconflow": "[to be translated]:硅基流动",
|
||||||
|
"service_type.mstts": "[to be translated]:免费在线 TTS",
|
||||||
|
"siliconflow_api_key": "[to be translated]:硅基流动API密钥",
|
||||||
|
"siliconflow_api_key.placeholder": "[to be translated]:请输入硅基流动API密钥",
|
||||||
|
"siliconflow_api_url": "[to be translated]:硅基流动API地址",
|
||||||
|
"siliconflow_api_url.placeholder": "[to be translated]:例如:https://api.siliconflow.cn/v1/audio/speech",
|
||||||
|
"siliconflow_voice": "[to be translated]:硅基流动音色",
|
||||||
|
"siliconflow_voice.placeholder": "[to be translated]:请选择音色",
|
||||||
|
"siliconflow_model": "[to be translated]:硅基流动模型",
|
||||||
|
"siliconflow_model.placeholder": "[to be translated]:请选择模型",
|
||||||
|
"siliconflow_response_format": "[to be translated]:响应格式",
|
||||||
|
"siliconflow_response_format.placeholder": "[to be translated]:默认为mp3",
|
||||||
|
"siliconflow_speed": "[to be translated]:语速",
|
||||||
|
"siliconflow_speed.placeholder": "[to be translated]:默认为1.0",
|
||||||
|
"edge_voice.available_count": "[to be translated]:可用语音: {{count}}个",
|
||||||
|
"edge_voice.refreshing": "[to be translated]:正在刷新语音列表...",
|
||||||
|
"edge_voice.refreshed": "[to be translated]:语音列表已刷新",
|
||||||
|
"mstts.voice": "[to be translated]:免费在线 TTS音色",
|
||||||
|
"mstts.output_format": "[to be translated]:输出格式",
|
||||||
|
"mstts.info": "[to be translated]:免费在线TTS服务不需要API密钥,完全免费使用。",
|
||||||
|
"error.no_mstts_voice": "[to be translated]:未设置免费在线 TTS音色"
|
||||||
},
|
},
|
||||||
"voice": {
|
"voice": {
|
||||||
"title": "[to be translated]:语音功能",
|
"title": "[to be translated]:语音功能",
|
||||||
|
|||||||
@ -255,12 +255,12 @@ export class EdgeTTSService implements TTSServiceInterface {
|
|||||||
mediaRecorder.stop();
|
mediaRecorder.stop();
|
||||||
}
|
}
|
||||||
}, 10000); // 10秒超时
|
}, 10000); // 10秒超时
|
||||||
} catch (error) {
|
} catch (error: any) {
|
||||||
console.error('浏览器TTS语音合成失败:', error);
|
console.error('浏览器TTS语音合成失败:', error);
|
||||||
reject(new Error(`浏览器TTS语音合成失败: ${error.message}`));
|
reject(new Error(`浏览器TTS语音合成失败: ${error?.message || '未知错误'}`));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error: any) {
|
||||||
console.error('浏览器TTS语音合成失败:', error);
|
console.error('浏览器TTS语音合成失败:', error);
|
||||||
// 即使失败也返回一个空的Blob,而不是抛出异常
|
// 即使失败也返回一个空的Blob,而不是抛出异常
|
||||||
// 这样可以避免在UI上显示错误消息
|
// 这样可以避免在UI上显示错误消息
|
||||||
|
|||||||
@ -50,9 +50,9 @@ export class MsTTSService implements TTSServiceInterface {
|
|||||||
|
|
||||||
// 将Buffer转换为Blob
|
// 将Buffer转换为Blob
|
||||||
return new Blob([audioData], { type: 'audio/mp3' });
|
return new Blob([audioData], { type: 'audio/mp3' });
|
||||||
} catch (error) {
|
} catch (error: any) {
|
||||||
console.error('免费在线TTS语音合成失败:', error);
|
console.error('免费在线TTS语音合成失败:', error);
|
||||||
throw new Error(`免费在线TTS语音合成失败: ${error.message}`);
|
throw new Error(`免费在线TTS语音合成失败: ${error?.message || '未知错误'}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -69,8 +69,8 @@ export class SiliconflowTTSService implements TTSServiceInterface {
|
|||||||
model: this.model,
|
model: this.model,
|
||||||
input: text,
|
input: text,
|
||||||
voice: this.voice,
|
voice: this.voice,
|
||||||
// 强制使用mp3格式,因为浏览器支持性最好
|
// 使用配置的响应格式,默认为mp3
|
||||||
response_format: 'mp3',
|
response_format: this.responseFormat,
|
||||||
stream: false,
|
stream: false,
|
||||||
speed: this.speed
|
speed: this.speed
|
||||||
};
|
};
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user