CloudBuilder.Topshelf/Utility/SaveAudioToFileHelper.cs
owenchen 2c6475b2d5 ow
2026-06-22 17:02:26 +08:00

195 lines
8.7 KiB
C#

using CloudBuilder.AI.Policy;
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);
}
}
public static class ChineseTtsVoiceExtensions
{
/// <summary>
/// 获取枚举值对应的Description特性值
/// </summary>
/// <param name="voice">语音枚举值</param>
/// <returns>描述字符串(无特性时返回枚举名称)</returns>
public static int GetSidId(this ChineseTtsVoice voice)
{
// 获取枚举成员的特性
var field = voice.GetType().GetField(voice.ToString());
var attribute = field?.GetCustomAttribute<DescriptionAttribute>();
// 有特性返回描述,无则返回枚举名
return Convert.ToInt32(attribute?.Description.Split('-')[1] ?? "0");
}
}
public static class EnumHelper
{
// 枚举转字符串
public static string ToEnumString<T>(T value) where T : Enum
{
return value.ToString();
}
// 字符串转枚举
public static T ToEnum<T>(string str) where T : struct, Enum
{
return Enum.TryParse<T>(str, out var result) ? result : default;
}
}
}