using CloudBuilder.AI.Policy; using CloudBuilder.Core.DatabaseAccessor.Entity; using DocumentFormat.OpenXml.Spreadsheet; using EdgeTtsSharp; using Google.Protobuf.WellKnownTypes; using SherpaOnnx; using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Reflection; using System.Text; using System.Threading.Tasks; namespace CloudBuilder.AI.Service.Utility { public class SaveAudioToFileHelper { private IApplicationService service; private OfflineTts offlineTts1; private OfflineTts offlineTts2; private OfflineTts offlineTts3; private OfflineTts offlineTts4; public SaveAudioToFileHelper(IApplicationService service) { this.service = service; InitOfflineTtsConfig(DeclaimerPolicy.VITS_ZH_HF_THERESA); InitOfflineTtsConfig(DeclaimerPolicy.VITS_ZH_HF_FANCHEN_C); InitOfflineTtsConfig(DeclaimerPolicy.SHERPA_ONNX_VITS_ZH_LL); InitOfflineTtsConfig(DeclaimerPolicy.VITS_MELO_TTS_ZH_EN); } private void InitOfflineTtsConfig(string ttsId) { var config = new OfflineTtsConfig(); config.Model.Provider = "cpu"; config.Model.NumThreads = 2; config.MaxNumSentences = 1; switch (ttsId) { case DeclaimerPolicy.VITS_ZH_HF_THERESA: config.Model.Vits.Model = "tts-models/vits-zh-hf-theresa/theresa.onnx"; config.Model.Vits.Tokens = "tts-models/vits-zh-hf-theresa/tokens.txt"; config.Model.Vits.Lexicon = "tts-models/vits-zh-hf-theresa/lexicon.txt"; offlineTts1 = new OfflineTts(config); break; case DeclaimerPolicy.VITS_ZH_HF_FANCHEN_C: config.Model.Vits.Model = "tts-models/vits-zh-hf-fanchen-C/vits-zh-hf-fanchen-C.onnx"; config.Model.Vits.Tokens = "tts-models/vits-zh-hf-fanchen-C/tokens.txt"; config.Model.Vits.Lexicon = "tts-models/vits-zh-hf-fanchen-C/lexicon.txt"; config.RuleFars = "tts-models/vits-zh-hf-fanchen-C/phone.fst,tts-models/vits-zh-hf-fanchen-C/number.fst"; offlineTts2 = new OfflineTts(config); break; case DeclaimerPolicy.SHERPA_ONNX_VITS_ZH_LL: config.Model.Vits.Model = "tts-models/sherpa-onnx-vits-zh-ll/model.onnx"; config.Model.Vits.Tokens = "tts-models/sherpa-onnx-vits-zh-ll/tokens.txt"; config.Model.Vits.Lexicon = "tts-models/sherpa-onnx-vits-zh-ll/lexicon.txt"; config.RuleFars = "tts-models/sherpa-onnx-vits-zh-ll/phone.fst,tts-models/sherpa-onnx-vits-zh-ll/number.fst"; offlineTts3 = new OfflineTts(config); break; default://DeclaimerPolicy.VITS_MELO_TTS_ZH_EN config.Model.Vits.Model = "tts-models/vits-melo-tts-zh_en/model.onnx"; config.Model.Vits.Tokens = "tts-models/vits-melo-tts-zh_en/tokens.txt"; config.Model.Vits.Lexicon = "tts-models/vits-melo-tts-zh_en/lexicon.txt"; offlineTts4 = new OfflineTts(config); break; } // --- 2. 初始化引擎 --- offlineTts1 = new OfflineTts(config); } public void AudioToFile(string text, string saveFilePath) { AudioToFile(DeclaimerPolicy.VITS_MELO_TTS_ZH_EN, 0, text, saveFilePath); } public void AudioToFile(string ttsId, int sid, string text, string saveFilePath) { float speed = 1.0f; OfflineTtsGeneratedAudio audio; switch (ttsId) { case DeclaimerPolicy.VITS_ZH_HF_THERESA: audio = offlineTts1.Generate(text, speed, sid); break; case DeclaimerPolicy.VITS_ZH_HF_FANCHEN_C: audio = offlineTts2.Generate(text, speed, sid); break; case DeclaimerPolicy.SHERPA_ONNX_VITS_ZH_LL: audio = offlineTts3.Generate(text, speed, sid); break; default://DeclaimerPolicy.VITS_MELO_TTS_ZH_EN audio = offlineTts4.Generate(text, speed, sid); break; } audio.SaveToWaveFile(saveFilePath); } public static void AudioToFile(ChineseTtsVoice actorName, string text, string saveFilePath) { // 步骤1:同步获取异步语音对象 var voiceTask = EdgeTts.GetVoice(actorName.GetVoiceId()); var voice = voiceTask.GetAwaiter().GetResult(); // 替代await,同步阻塞 // 步骤2:同步保存音频文件 var saveTask = voice.SaveAudioToFile(text, saveFilePath); saveTask.GetAwaiter().GetResult(); // 同步等待保存完成 } } /// /// 微软Edge TTS中文语音音色枚举 /// 对应官方Neural语音标识,覆盖港澳台及大陆各地区音色 /// public enum ChineseTtsVoice { /// /// 粤语(香港)- 喜佳(女性) /// [System.ComponentModel.Description("zh-HK-HiuGaaiNeural")] ZhHkHiuGaaiNeural, /// /// 粤语(香港)- 喜文(女性) /// [System.ComponentModel.Description("zh-HK-HiuMaanNeural")] ZhHkHiuMaanNeural, /// /// 粤语(香港)- 云龙(男性) /// [System.ComponentModel.Description("zh-HK-WanLungNeural")] ZhHkWanLungNeural, /// /// 普通话(中国大陆)- 晓晓(女性) /// [System.ComponentModel.Description("zh-CN-XiaoxiaoNeural")] ZhCnXiaoxiaoNeural, /// /// 普通话(中国大陆)- 小艺(女性) /// [System.ComponentModel.Description("zh-CN-XiaoyiNeural")] ZhCnXiaoyiNeural, /// /// 普通话(中国大陆)- 云健(男性) /// [System.ComponentModel.Description("zh-CN-YunjianNeural")] ZhCnYunjianNeural, /// /// 普通话(中国大陆)- 云希(男性) /// [System.ComponentModel.Description("zh-CN-YunxiNeural")] ZhCnYunxiNeural, /// /// 普通话(中国大陆)- 云夏(女性) /// [System.ComponentModel.Description("zh-CN-YunxiaNeural")] ZhCnYunxiaNeural, /// /// 普通话(中国大陆)- 云阳(男性) /// [System.ComponentModel.Description("zh-CN-YunyangNeural")] ZhCnYunyangNeural, /// /// 普通话(中国大陆-辽宁)- 小北(女性) /// [System.ComponentModel.Description("zh-CN-liaoning-XiaobeiNeural")] ZhCnLiaoningXiaobeiNeural, /// /// 普通话(台湾)- 小珍(女性) /// [System.ComponentModel.Description("zh-TW-HsiaoChenNeural")] ZhTwHsiaoChenNeural, /// /// 普通话(台湾)- 云哲(男性) /// [System.ComponentModel.Description("zh-TW-YunJheNeural")] ZhTwYunJheNeural, /// /// 普通话(台湾)- 小瑜(女性) /// [System.ComponentModel.Description("zh-TW-HsiaoYuNeural")] ZhTwHsiaoYuNeural, /// /// 普通话(中国大陆-陕西)- 小妮(女性) /// [System.ComponentModel.Description("zh-CN-shaanxi-XiaoniNeural")] ZhCnShaanxiXiaoniNeural } public static class ChineseTtsVoiceExtensions { /// /// 获取枚举值对应的Description特性值 /// /// 语音枚举值 /// 描述字符串(无特性时返回枚举名称) public static string GetVoiceId(this ChineseTtsVoice voice) { // 获取枚举成员的特性 var field = voice.GetType().GetField(voice.ToString()); var attribute = field?.GetCustomAttribute(); // 有特性返回描述,无则返回枚举名 return attribute?.Description ?? voice.ToString(); } } }