CloudBuilder/CloudBuilder.AI.Service/Utility/SaveAudioToFileHelper.cs
owenchen 4b9af1cf4f ow
2026-05-22 15:07:47 +08:00

226 lines
8.6 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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(); // 同步等待保存完成
}
}
/// <summary>
/// 微软Edge TTS中文语音音色枚举
/// 对应官方Neural语音标识覆盖港澳台及大陆各地区音色
/// </summary>
public enum ChineseTtsVoice
{
/// <summary>
/// 粤语(香港)- 喜佳(女性)
/// </summary>
[System.ComponentModel.Description("zh-HK-HiuGaaiNeural")]
ZhHkHiuGaaiNeural,
/// <summary>
/// 粤语(香港)- 喜文(女性)
/// </summary>
[System.ComponentModel.Description("zh-HK-HiuMaanNeural")]
ZhHkHiuMaanNeural,
/// <summary>
/// 粤语(香港)- 云龙(男性)
/// </summary>
[System.ComponentModel.Description("zh-HK-WanLungNeural")]
ZhHkWanLungNeural,
/// <summary>
/// 普通话(中国大陆)- 晓晓(女性)
/// </summary>
[System.ComponentModel.Description("zh-CN-XiaoxiaoNeural")]
ZhCnXiaoxiaoNeural,
/// <summary>
/// 普通话(中国大陆)- 小艺(女性)
/// </summary>
[System.ComponentModel.Description("zh-CN-XiaoyiNeural")]
ZhCnXiaoyiNeural,
/// <summary>
/// 普通话(中国大陆)- 云健(男性)
/// </summary>
[System.ComponentModel.Description("zh-CN-YunjianNeural")]
ZhCnYunjianNeural,
/// <summary>
/// 普通话(中国大陆)- 云希(男性)
/// </summary>
[System.ComponentModel.Description("zh-CN-YunxiNeural")]
ZhCnYunxiNeural,
/// <summary>
/// 普通话(中国大陆)- 云夏(女性)
/// </summary>
[System.ComponentModel.Description("zh-CN-YunxiaNeural")]
ZhCnYunxiaNeural,
/// <summary>
/// 普通话(中国大陆)- 云阳(男性)
/// </summary>
[System.ComponentModel.Description("zh-CN-YunyangNeural")]
ZhCnYunyangNeural,
/// <summary>
/// 普通话(中国大陆-辽宁)- 小北(女性)
/// </summary>
[System.ComponentModel.Description("zh-CN-liaoning-XiaobeiNeural")]
ZhCnLiaoningXiaobeiNeural,
/// <summary>
/// 普通话(台湾)- 小珍(女性)
/// </summary>
[System.ComponentModel.Description("zh-TW-HsiaoChenNeural")]
ZhTwHsiaoChenNeural,
/// <summary>
/// 普通话(台湾)- 云哲(男性)
/// </summary>
[System.ComponentModel.Description("zh-TW-YunJheNeural")]
ZhTwYunJheNeural,
/// <summary>
/// 普通话(台湾)- 小瑜(女性)
/// </summary>
[System.ComponentModel.Description("zh-TW-HsiaoYuNeural")]
ZhTwHsiaoYuNeural,
/// <summary>
/// 普通话(中国大陆-陕西)- 小妮(女性)
/// </summary>
[System.ComponentModel.Description("zh-CN-shaanxi-XiaoniNeural")]
ZhCnShaanxiXiaoniNeural
}
public static class ChineseTtsVoiceExtensions
{
/// <summary>
/// 获取枚举值对应的Description特性值
/// </summary>
/// <param name="voice">语音枚举值</param>
/// <returns>描述字符串(无特性时返回枚举名称)</returns>
public static string GetVoiceId(this ChineseTtsVoice voice)
{
// 获取枚举成员的特性
var field = voice.GetType().GetField(voice.ToString());
var attribute = field?.GetCustomAttribute<DescriptionAttribute>();
// 有特性返回描述,无则返回枚举名
return attribute?.Description ?? voice.ToString();
}
}
}