diff --git a/CloudBuilder.AI.Service/CloudBuilder.AI.Service.csproj b/CloudBuilder.AI.Service/CloudBuilder.AI.Service.csproj
index 27bbe56..d8a3b42 100644
--- a/CloudBuilder.AI.Service/CloudBuilder.AI.Service.csproj
+++ b/CloudBuilder.AI.Service/CloudBuilder.AI.Service.csproj
@@ -24,6 +24,9 @@
+
+
+
diff --git a/CloudBuilder.AI.Service/Utility/SaveAudioToFileHelper.cs b/CloudBuilder.AI.Service/Utility/SaveAudioToFileHelper.cs
new file mode 100644
index 0000000..4b3e2fe
--- /dev/null
+++ b/CloudBuilder.AI.Service/Utility/SaveAudioToFileHelper.cs
@@ -0,0 +1,225 @@
+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();
+ }
+ }
+}
diff --git a/CloudBuilder.AI/Policy/DeclaimerPolicy.cs b/CloudBuilder.AI/Policy/DeclaimerPolicy.cs
new file mode 100644
index 0000000..5dc1949
--- /dev/null
+++ b/CloudBuilder.AI/Policy/DeclaimerPolicy.cs
@@ -0,0 +1,44 @@
+using CloudBuilder.Core.DependencyInjection.Policy;
+using CloudBuilder.Core.DependencyInjection.Request;
+using CloudBuilder.Core.Policy;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace CloudBuilder.AI.Policy
+{
+ public class DeclaimerPolicy : LocalePolicy, IPolicy
+ {
+ public const string ZhHkHiuGaaiNeural = "ZhHkHiuGaaiNeural";
+ public const string ZhHkHiuMaanNeural = "ZhHkHiuMaanNeural";
+ public const string ZhHkWanLungNeural = "ZhHkWanLungNeural";
+ public const string ZhCnXiaoxiaoNeural = "ZhCnXiaoxiaoNeural";
+ public const string ZhCnXiaoyiNeural = "ZhCnXiaoyiNeural";
+ public const string ZhCnYunjianNeural = "ZhCnYunjianNeural";
+ public const string ZhCnYunxiNeural = "ZhCnYunxiNeural";
+ public const string ZhCnYunxiaNeural = "ZhCnYunxiaNeural";
+ public const string ZhCnYunyangNeural = "ZhCnYunyangNeural";
+ public const string ZhCnLiaoningXiaobeiNeural = "ZhCnLiaoningXiaobeiNeural";
+ public const string ZhTwHsiaoChenNeural = "ZhTwHsiaoChenNeural";
+ public const string ZhTwYunJheNeural = "ZhTwYunJheNeural";
+ public const string ZhTwHsiaoYuNeural = "ZhTwHsiaoYuNeural";
+ public const string ZhCnShaanxiXiaoniNeural = "ZhCnShaanxiXiaoniNeural";
+ public const string VITS_MELO_TTS_ZH_EN = "vits-melo-tts-zh_en";
+ public const string VITS_ZH_HF_THERESA = "vits-zh-hf-theresa";
+ public const string VITS_ZH_HF_FANCHEN_C = "vits-zh-hf-fanchen-C";
+ public const string SHERPA_ONNX_VITS_ZH_LL = "sherpa-onnx-vits-zh-ll";
+
+ private readonly IContext context;
+ private readonly ILocaleMessageManager localeMessageManager;
+
+ public DeclaimerPolicy(IContext context, ILocaleMessageManager localeMessageManager) : base(context, localeMessageManager)
+ {
+ this.context = context;
+ this.localeMessageManager = localeMessageManager;
+
+ this.Name = typeof(DeclaimerPolicy).Name;
+ }
+ }
+}
diff --git a/CloudBuilder.Web.Entry/CloudBuilder.Web.Entry.csproj b/CloudBuilder.Web.Entry/CloudBuilder.Web.Entry.csproj
index 3682b51..8a85c29 100644
--- a/CloudBuilder.Web.Entry/CloudBuilder.Web.Entry.csproj
+++ b/CloudBuilder.Web.Entry/CloudBuilder.Web.Entry.csproj
@@ -28,6 +28,9 @@
+
+
+