CloudBuilder.Topshelf/Utility/SaveAudioToFileHelper.cs
owenchen 527205520b ow
2026-05-29 13:03:00 +08:00

320 lines
14 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 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, 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, 1.0f, sid);
break;
case ChineseTtsVoice.Sherpa0:
case ChineseTtsVoice.Sherpa1:
case ChineseTtsVoice.Sherpa2:
case ChineseTtsVoice.Sherpa3:
case ChineseTtsVoice.Sherpa4:
sid = actorName.GetSidId();
audio = sherpaTTs.Generate(text, 1.0f, sid);
break;
case ChineseTtsVoice.Melo:
sid = actorName.GetSidId();
audio = meloTTs.Generate(text, 1.0f, sid);
break;
case ChineseTtsVoice.Eula:
sid = actorName.GetSidId();
audio = eulaTTs.Generate(text, 1.0f, sid);
break;
case ChineseTtsVoice.Theresa:
sid = actorName.GetSidId();
audio = theresaTTs.Generate(text, 1.0f, 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, 1.0f, sid);
break;
default:
sid = actorName.GetSidId();
audio = matchaTTs.Generate(text, 1.0f, sid);
break;
}
string directoryName = Path.GetDirectoryName(saveFilePath);
if (!Directory.Exists(directoryName)) Directory.CreateDirectory(directoryName);
// --- 4. 保存音频 ---
audio.SaveToWaveFile(saveFilePath);
}
}
/// <summary>
/// 微软Edge TTS中文语音音色枚举
/// 对应官方Neural语音标识覆盖港澳台及大陆各地区音色
/// </summary>
public enum ChineseTtsVoice
{
/// <summary>
/// 普通话(中国大陆) 娇柔(女性)
/// </summary>
[System.ComponentModel.Description("普通话(中国大陆) 喜佳(女性)-0")]
Sherpa0,
/// <summary>
/// 普通话(中国大陆)- 柔美(女性)
/// </summary>
[System.ComponentModel.Description("普通话(中国大陆) 柔美(女性)-2")]
Sherpa2,
/// <summary>
/// 普通话(中国大陆)- 知性(男性)
/// </summary>
[System.ComponentModel.Description("普通话(中国大陆) 知性(男性)-4")]
Sherpa4,
/// <summary>
/// 普通话(中国大陆)- 知青(男性)
/// </summary>
[System.ComponentModel.Description("普通话(中国大陆) 知青(男性)-3")]
Sherpa3,
/// <summary>
/// 普通话(中国大陆)- 中青(男性)
/// </summary>
[System.ComponentModel.Description("普通话(中国大陆) 中青(男性)-1")]
Sherpa1,
/// <summary>
/// 普通话(中国大陆)- 爱莎(女性)- 文件小速度快
/// </summary>
[System.ComponentModel.Description("普通话(中国大陆) 爱莎(女性)-6")]
Aaishe6,
/// <summary>
/// 普通话(中国大陆)- 爱莎(女性)- 文件小速度快
/// </summary>
[System.ComponentModel.Description("普通话(中国大陆) 爱莎(女性)-14")]
Aaishe14,
/// <summary>
/// 普通话(中国大陆)- 爱莎(女性)- 文件小速度快
/// </summary>
[System.ComponentModel.Description("普通话(中国大陆) 爱莎(女性)-18")]
Aaishe18,
/// <summary>
/// 普通话(中国大陆)- 云健(男性)- 文件小速度快
/// </summary>
[System.ComponentModel.Description("普通话(中国大陆) 云健(男性)-10")]
Aaishe10,
/// <summary>
/// 普通话(中国大陆)- 爱莎(女性)- 文件小速度快
/// </summary>
[System.ComponentModel.Description("普通话(中国大陆) 爱莎(女性)-1")]
Aaishe1,
/// <summary>
/// 普通话(中国大陆)- 云健(男性)- 文件小速度快
/// </summary>
[System.ComponentModel.Description("普通话(中国大陆) 云健(男性)-15")]
Aaishe15,
/// 普通话(中国大陆) 云阳(男性)
/// </summary>
[System.ComponentModel.Description("普通话(中国大陆) 云阳(男性)-10")]
Fanchen10,
/// <summary>
/// 普通话(中国大陆) 云阳(男性)
/// </summary>
[System.ComponentModel.Description("普通话(中国大陆) 云阳(男性)-110")]
Fanchen110,
/// 普通话(中国大陆) 喜佳(女性)
/// </summary>
[System.ComponentModel.Description("普通话(中国大陆) 云夏(女性)-6")]
Fanchen6,
/// <summary>
/// 普通话(中国大陆) 云阳(男性)
/// </summary>
[System.ComponentModel.Description("普通话(中国大陆) 云阳(男性)-15")]
Fanchen15,
/// <summary>
/// 普通话(中国大陆) 喜佳(女性)
/// </summary>
[System.ComponentModel.Description("普通话(中国大陆) 喜佳(女性)-2")]
Fanchen2,
/// <summary>
/// 普通话(中国大陆) 喜佳(女性)
/// </summary>
[System.ComponentModel.Description("普通话(中国大陆) 喜佳(女性)-14")]
Fanchen14,
/// <summary>
/// 普通话(中国大陆) 喜佳(女性)
/// </summary>
[System.ComponentModel.Description("普通话(中国大陆) 喜佳(女性)-20")]
Fanchen20,
/// <summary>
/// 普通话(中国大陆) 喜佳(女性)
/// </summary>
[System.ComponentModel.Description("普通话(中国大陆) 喜佳(女性)-36")]
Fanchen36,
/// <summary>
/// 粤语(香港)- 女性(男性)- 文件大
/// </summary>
[System.ComponentModel.Description("粤语(香港) 梅隆 (女性)-0")]
Melo,
/// <summary>
/// 普通话(中国大陆)- 神精女(女性)
/// </summary>
[System.ComponentModel.Description("普通话(中国大陆) 神精女(女性)-0")]
Eula,
/// <summary>
/// 普通话(中国大陆)- 小神精女(女性)
/// </summary>
[System.ComponentModel.Description("普通话(中国大陆) 小神精女(女性)-27")]
Theresa,
}
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;
}
}
}