using SherpaOnnx; using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Reflection; using System.Security.Cryptography; using System.Text; using System.Threading.Tasks; using static System.Net.Mime.MediaTypeNames; namespace CloudBuilder.Topshelf.Utility { public static class SaveAudioToFileHelper { private static OfflineTts fanchenTTs = null; private static OfflineTts sherpaTTs = null; private static OfflineTts theresaTTs = null; private static OfflineTts meloTTs = null; private static OfflineTts icefallTTs = null; private static OfflineTts matchaTTs = null; private static OfflineTts eulaTTs = null; public static void InitConfig() { OfflineTtsConfig config; OfflineTtsGeneratedAudio audio; int numThreads = 4; if (fanchenTTs == null) { config = new OfflineTtsConfig(); 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/number.fst,tts-models/vits-zh-hf-fanchen-C/phone.fst"; config.Model.NumThreads = numThreads; //187 fanchenTTs = new OfflineTts(config); config = new OfflineTtsConfig(); 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/number.fst,tts-models/sherpa-onnx-vits-zh-ll/phone.fst"; config.Model.NumThreads = numThreads; //多人音质最好(0 - 4) sherpaTTs = new OfflineTts(config); config = new OfflineTtsConfig(); 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"; config.RuleFars = "tts-models/vits-zh-hf-theresa/number.fst,tts-models/vits-zh-hf-theresa/phone.fst"; config.Model.NumThreads = numThreads; //804 theresaTTs = new OfflineTts(config); config = new OfflineTtsConfig(); 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"; config.RuleFars = "tts-models/vits-melo-tts-zh_en/number.fst,tts-models/vits-melo-tts-zh_en/phone.fst"; config.Model.NumThreads = numThreads; //1 meloTTs = new OfflineTts(config); config = new OfflineTtsConfig(); config.Model.Vits.Model = "tts-models/vits-icefall-zh-aishell3/model.onnx"; config.Model.Vits.Tokens = "tts-models/vits-icefall-zh-aishell3/tokens.txt"; config.Model.Vits.Lexicon = "tts-models/vits-icefall-zh-aishell3/lexicon.txt"; config.RuleFars = "tts-models/vits-icefall-zh-aishell3/number.fst,tts-models/vits-icefall-zh-aishell3/phone.fst"; config.Model.NumThreads = numThreads; //中文、32 个不同说话人,最快 icefallTTs = new OfflineTts(config); config = new OfflineTtsConfig(); config.Model.Vits.Model = "tts-models/vits-zh-hf-eula/eula.onnx"; config.Model.Vits.Tokens = "tts-models/vits-zh-hf-eula/tokens.txt"; config.Model.Vits.Lexicon = "tts-models/vits-zh-hf-eula/lexicon.txt"; config.RuleFars = "tts-models/vits-zh-hf-eula/number.fst,tts-models/vits-zh-hf-eula/phone.fst"; config.Model.NumThreads = numThreads; eulaTTs = new OfflineTts(config); config = new OfflineTtsConfig(); config.Model.Matcha.AcousticModel = "tts-models/matcha-icefall-zh-baker/model-steps-3.onnx"; config.Model.Matcha.Vocoder = "tts-models/matcha-icefall-zh-baker/vocos-22khz-univ.onnx"; config.Model.Matcha.Tokens = "tts-models/matcha-icefall-zh-baker/tokens.txt"; config.Model.Matcha.Lexicon = "tts-models/matcha-icefall-zh-baker/lexicon.txt"; config.RuleFars = "tts-models/matcha-icefall-zh-baker/number.fst,tts-models/matcha-icefall-zh-baker/phone.fst"; config.Model.NumThreads = numThreads; //1 matchaTTs = new OfflineTts(config); } } public static void AudioToFile(ChineseTtsVoice actorName, float speed, string text, string saveFilePath) { InitConfig(); int sid = 0; OfflineTtsGeneratedAudio audio; switch (actorName) { case ChineseTtsVoice.Fanchen10: case ChineseTtsVoice.Fanchen110: case ChineseTtsVoice.Fanchen6: case ChineseTtsVoice.Fanchen15: case ChineseTtsVoice.Fanchen2: case ChineseTtsVoice.Fanchen20: case ChineseTtsVoice.Fanchen14: case ChineseTtsVoice.Fanchen36: sid = actorName.GetSidId(); audio = fanchenTTs.Generate(text, speed, sid); break; case ChineseTtsVoice.Sherpa0: case ChineseTtsVoice.Sherpa1: case ChineseTtsVoice.Sherpa2: case ChineseTtsVoice.Sherpa3: case ChineseTtsVoice.Sherpa4: sid = actorName.GetSidId(); audio = sherpaTTs.Generate(text, speed, sid); break; case ChineseTtsVoice.Melo: sid = actorName.GetSidId(); audio = meloTTs.Generate(text, speed, sid); break; case ChineseTtsVoice.Eula: sid = actorName.GetSidId(); audio = eulaTTs.Generate(text, speed, sid); break; case ChineseTtsVoice.Theresa: sid = actorName.GetSidId(); audio = theresaTTs.Generate(text, speed, sid); break; case ChineseTtsVoice.Aaishe1: case ChineseTtsVoice.Aaishe6: case ChineseTtsVoice.Aaishe14: case ChineseTtsVoice.Aaishe15: case ChineseTtsVoice.Aaishe18: case ChineseTtsVoice.Aaishe10: sid = actorName.GetSidId(); audio = icefallTTs.Generate(text, speed, sid); break; default: sid = actorName.GetSidId(); audio = matchaTTs.Generate(text, speed, sid); break; } string directoryName = Path.GetDirectoryName(saveFilePath); if (!Directory.Exists(directoryName)) Directory.CreateDirectory(directoryName); // --- 4. 保存音频 --- audio.SaveToWaveFile(saveFilePath); } } /// /// 微软Edge TTS中文语音音色枚举 /// 对应官方Neural语音标识,覆盖港澳台及大陆各地区音色 /// public enum ChineseTtsVoice { /// /// 普通话(中国大陆) 娇柔(女性) /// [System.ComponentModel.Description("普通话(中国大陆) 喜佳(女性)-0")] Sherpa0, /// /// 普通话(中国大陆)- 柔美(女性) /// [System.ComponentModel.Description("普通话(中国大陆) 柔美(女性)-2")] Sherpa2, /// /// 普通话(中国大陆)- 知性(男性) /// [System.ComponentModel.Description("普通话(中国大陆) 知性(男性)-4")] Sherpa4, /// /// 普通话(中国大陆)- 知青(男性) /// [System.ComponentModel.Description("普通话(中国大陆) 知青(男性)-3")] Sherpa3, /// /// 普通话(中国大陆)- 中青(男性) /// [System.ComponentModel.Description("普通话(中国大陆) 中青(男性)-1")] Sherpa1, /// /// 普通话(中国大陆)- 爱莎(女性)- 文件小速度快 /// [System.ComponentModel.Description("普通话(中国大陆) 爱莎(女性)-6")] Aaishe6, /// /// 普通话(中国大陆)- 爱莎(女性)- 文件小速度快 /// [System.ComponentModel.Description("普通话(中国大陆) 爱莎(女性)-14")] Aaishe14, /// /// 普通话(中国大陆)- 爱莎(女性)- 文件小速度快 /// [System.ComponentModel.Description("普通话(中国大陆) 爱莎(女性)-18")] Aaishe18, /// /// 普通话(中国大陆)- 云健(男性)- 文件小速度快 /// [System.ComponentModel.Description("普通话(中国大陆) 云健(男性)-10")] Aaishe10, /// /// 普通话(中国大陆)- 爱莎(女性)- 文件小速度快 /// [System.ComponentModel.Description("普通话(中国大陆) 爱莎(女性)-1")] Aaishe1, /// /// 普通话(中国大陆)- 云健(男性)- 文件小速度快 /// [System.ComponentModel.Description("普通话(中国大陆) 云健(男性)-15")] Aaishe15, /// 普通话(中国大陆) 云阳(男性) /// [System.ComponentModel.Description("普通话(中国大陆) 云阳(男性)-10")] Fanchen10, /// /// 普通话(中国大陆) 云阳(男性) /// [System.ComponentModel.Description("普通话(中国大陆) 云阳(男性)-110")] Fanchen110, /// 普通话(中国大陆) 喜佳(女性) /// [System.ComponentModel.Description("普通话(中国大陆) 云夏(女性)-6")] Fanchen6, /// /// 普通话(中国大陆) 云阳(男性) /// [System.ComponentModel.Description("普通话(中国大陆) 云阳(男性)-15")] Fanchen15, /// /// 普通话(中国大陆) 喜佳(女性) /// [System.ComponentModel.Description("普通话(中国大陆) 喜佳(女性)-2")] Fanchen2, /// /// 普通话(中国大陆) 喜佳(女性) /// [System.ComponentModel.Description("普通话(中国大陆) 喜佳(女性)-14")] Fanchen14, /// /// 普通话(中国大陆) 喜佳(女性) /// [System.ComponentModel.Description("普通话(中国大陆) 喜佳(女性)-20")] Fanchen20, /// /// 普通话(中国大陆) 喜佳(女性) /// [System.ComponentModel.Description("普通话(中国大陆) 喜佳(女性)-36")] Fanchen36, /// /// 粤语(香港)- 女性(男性)- 文件大 /// [System.ComponentModel.Description("粤语(香港) 梅隆 (女性)-0")] Melo, /// /// 普通话(中国大陆)- 神精女(女性) /// [System.ComponentModel.Description("普通话(中国大陆) 神精女(女性)-0")] Eula, /// /// 普通话(中国大陆)- 小神精女(女性) /// [System.ComponentModel.Description("普通话(中国大陆) 小神精女(女性)-27")] Theresa, } public static class ChineseTtsVoiceExtensions { /// /// 获取枚举值对应的Description特性值 /// /// 语音枚举值 /// 描述字符串(无特性时返回枚举名称) public static int GetSidId(this ChineseTtsVoice voice) { // 获取枚举成员的特性 var field = voice.GetType().GetField(voice.ToString()); var attribute = field?.GetCustomAttribute(); // 有特性返回描述,无则返回枚举名 return Convert.ToInt32(attribute?.Description.Split('-')[1] ?? "0"); } } public static class EnumHelper { // 枚举转字符串 public static string ToEnumString(T value) where T : Enum { return value.ToString(); } // 字符串转枚举 public static T ToEnum(string str) where T : struct, Enum { return Enum.TryParse(str, out var result) ? result : default; } } }