From 4b652b418ef93affee8a03c4e89f3713be219d14 Mon Sep 17 00:00:00 2001 From: 1600822305 <1600822305@qq.com> Date: Fri, 11 Apr 2025 00:53:50 +0800 Subject: [PATCH] 123 --- src/main/services/MsEdgeTTSService.ts | 69 +++++++++++++++---- src/renderer/src/i18n/locales/en-us.json | 10 ++- src/renderer/src/i18n/locales/ja-jp.json | 32 ++++++++- src/renderer/src/i18n/locales/ru-ru.json | 32 ++++++++- src/renderer/src/i18n/locales/zh-tw.json | 32 ++++++++- .../src/services/tts/EdgeTTSService.ts | 6 +- src/renderer/src/services/tts/MsTTSService.ts | 8 +-- .../src/services/tts/SiliconflowTTSService.ts | 4 +- 8 files changed, 164 insertions(+), 29 deletions(-) diff --git a/src/main/services/MsEdgeTTSService.ts b/src/main/services/MsEdgeTTSService.ts index fdf31e2edb..1dfc62d9c1 100644 --- a/src/main/services/MsEdgeTTSService.ts +++ b/src/main/services/MsEdgeTTSService.ts @@ -10,13 +10,11 @@ import log from 'electron-log'; */ class MsEdgeTTSService { private static instance: MsEdgeTTSService; - private tts: EdgeTTS; private tempDir: string; private constructor() { - this.tts = new EdgeTTS(); this.tempDir = path.join(app.getPath('temp'), 'cherry-tts'); - + // 确保临时目录存在 if (!fs.existsSync(this.tempDir)) { fs.mkdirSync(this.tempDir, { recursive: true }); @@ -65,19 +63,64 @@ class MsEdgeTTSService { */ public async synthesize(text: string, voice: string, outputFormat: string): Promise { try { - // 设置TTS参数 - await this.tts.setMetadata(voice, outputFormat); - + log.info(`Microsoft Edge TTS合成语音: 文本="${text.substring(0, 30)}...", 语音=${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 outputPath = path.join(this.tempDir, `tts_${timestamp}.mp3`); - - // 合成语音 - await this.tts.toFile(outputPath, text); - + const fileExtension = outputFormat.includes('mp3') ? 'mp3' : outputFormat.split('-').pop() || 'audio'; + const outputPath = path.join(this.tempDir, `tts_${timestamp}.${fileExtension}`); + + log.info(`开始生成语音文件: ${outputPath}`); + + // 使用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; - } catch (error) { - log.error('Microsoft Edge TTS语音合成失败:', error); + } catch (error: any) { + // 记录详细的错误信息 + 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; } } diff --git a/src/renderer/src/i18n/locales/en-us.json b/src/renderer/src/i18n/locales/en-us.json index aa2686a43a..173deedbd1 100644 --- a/src/renderer/src/i18n/locales/en-us.json +++ b/src/renderer/src/i18n/locales/en-us.json @@ -1405,7 +1405,15 @@ "empty_text": "Text is empty", "general": "An error occurred during speech synthesis", "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": { "title": "Speech Recognition", diff --git a/src/renderer/src/i18n/locales/ja-jp.json b/src/renderer/src/i18n/locales/ja-jp.json index d4af289fe5..16f380f5ab 100644 --- a/src/renderer/src/i18n/locales/ja-jp.json +++ b/src/renderer/src/i18n/locales/ja-jp.json @@ -1355,7 +1355,14 @@ "not_enabled": "音声合成が有効になっていません", "no_edge_voice": "ブラウザ TTSの音声が選択されていません", "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キーは不要です。", "learn_more": "詳細はこちら", @@ -1385,7 +1392,28 @@ "filter.markdown": "[to be translated]:过滤Markdown标记", "filter.code_blocks": "[to be translated]:过滤代码块", "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": { "title": "音声認識", diff --git a/src/renderer/src/i18n/locales/ru-ru.json b/src/renderer/src/i18n/locales/ru-ru.json index 4a9e285e10..113b8a1864 100644 --- a/src/renderer/src/i18n/locales/ru-ru.json +++ b/src/renderer/src/i18n/locales/ru-ru.json @@ -1355,7 +1355,14 @@ "not_enabled": "Преобразование текста в речь не включено", "no_edge_voice": "Голос Edge TTS не выбран", "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.", "learn_more": "Узнать больше", @@ -1385,7 +1392,28 @@ "filter.markdown": "[to be translated]:过滤Markdown标记", "filter.code_blocks": "[to be translated]:过滤代码块", "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": { "title": "[to be translated]:语音功能", diff --git a/src/renderer/src/i18n/locales/zh-tw.json b/src/renderer/src/i18n/locales/zh-tw.json index 047a9a3ba9..78808393a7 100644 --- a/src/renderer/src/i18n/locales/zh-tw.json +++ b/src/renderer/src/i18n/locales/zh-tw.json @@ -1355,7 +1355,14 @@ "not_enabled": "語音合成未啟用", "no_edge_voice": "未選擇Edge TTS音色", "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金鑰。", "learn_more": "了解更多", @@ -1385,7 +1392,28 @@ "filter.markdown": "[to be translated]:过滤Markdown标记", "filter.code_blocks": "[to be translated]:过滤代码块", "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": { "title": "[to be translated]:语音功能", diff --git a/src/renderer/src/services/tts/EdgeTTSService.ts b/src/renderer/src/services/tts/EdgeTTSService.ts index 964216fcbc..027d72237e 100644 --- a/src/renderer/src/services/tts/EdgeTTSService.ts +++ b/src/renderer/src/services/tts/EdgeTTSService.ts @@ -255,12 +255,12 @@ export class EdgeTTSService implements TTSServiceInterface { mediaRecorder.stop(); } }, 10000); // 10秒超时 - } catch (error) { + } catch (error: any) { 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); // 即使失败也返回一个空的Blob,而不是抛出异常 // 这样可以避免在UI上显示错误消息 diff --git a/src/renderer/src/services/tts/MsTTSService.ts b/src/renderer/src/services/tts/MsTTSService.ts index 455fe20b0e..0905b3268d 100644 --- a/src/renderer/src/services/tts/MsTTSService.ts +++ b/src/renderer/src/services/tts/MsTTSService.ts @@ -44,15 +44,15 @@ export class MsTTSService implements TTSServiceInterface { // 通过IPC调用主进程的MsTTSService const outputPath = await window.api.msTTS.synthesize(text, this.voice, this.outputFormat); - + // 读取生成的音频文件 const audioData = await window.api.fs.read(outputPath); - + // 将Buffer转换为Blob return new Blob([audioData], { type: 'audio/mp3' }); - } catch (error) { + } catch (error: any) { console.error('免费在线TTS语音合成失败:', error); - throw new Error(`免费在线TTS语音合成失败: ${error.message}`); + throw new Error(`免费在线TTS语音合成失败: ${error?.message || '未知错误'}`); } } } diff --git a/src/renderer/src/services/tts/SiliconflowTTSService.ts b/src/renderer/src/services/tts/SiliconflowTTSService.ts index 6f0d7bb772..3eae5e552d 100644 --- a/src/renderer/src/services/tts/SiliconflowTTSService.ts +++ b/src/renderer/src/services/tts/SiliconflowTTSService.ts @@ -69,8 +69,8 @@ export class SiliconflowTTSService implements TTSServiceInterface { model: this.model, input: text, voice: this.voice, - // 强制使用mp3格式,因为浏览器支持性最好 - response_format: 'mp3', + // 使用配置的响应格式,默认为mp3 + response_format: this.responseFormat, stream: false, speed: this.speed };