320 lines
14 KiB
C#
320 lines
14 KiB
C#
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;
|
||
}
|
||
}
|
||
}
|
||
|