This commit is contained in:
owenchen committed 2026-05-28 15:05:46 +08:00
1 parent e60118eb53
commit 2e990e687f
38 files changed
+441 -237

No files matched your search

+76 -2
View File
@@ -1,4 +1,5 @@
using CloudBuilder.AI.Data;
using CliWrap;
using CloudBuilder.AI.Data;
using CloudBuilder.AI.Entity;
using CloudBuilder.AI.Policy;
using CloudBuilder.AI.SearchCriteria;
@@ -14,8 +15,10 @@ using CloudBuilder.Security.Utility;
using DocumentFormat.OpenXml.ExtendedProperties;
using DocumentFormat.OpenXml.Presentation;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using System.Diagnostics.Contracts;
using System.IO;
using System.Text;
using System.Transactions;
namespace CloudBuilder.AI.Service
@@ -291,7 +294,7 @@ namespace CloudBuilder.AI.Service
foreach (var name in names)
{
entity = new AiChapterPersonEntity();
entity.Guid= Guid.NewGuid().ToString();
entity.Guid = Guid.NewGuid().ToString();
entity.ChapterGuid = paragraph.ChapterGuid;
entity.ParagraphGuid = paragraph.Guid;
entity.ParagraphIndex = paragraph.ParagraphIndex;
@@ -304,5 +307,76 @@ namespace CloudBuilder.AI.Service
if (aiChapterPersonEntities != null && aiChapterPersonEntities.Count() > 0)
repositoryAiChapterPersonEntity.InsertNow(aiChapterPersonEntities);
}
[PermissionOperate(name: AIPermissionPolicy.AiBook, operate: Operate.Find)]
public IActionResult GetTtsStream(string text)
{
string fileDownloadName = Path.Combine(service.Configuration["FileServiceSettings:BackupDmsFileDirectory"], @"voice", DateTime.Now.ToString("yyyyMMddHHmmss") + ".mp3");
string param = PackParameters(ChineseTtsVoice.Sherpa2.ToString(), fileDownloadName, text);
RunMyProcess(service.Configuration["CloudBuilder.Topshelf:Path"], param);
var stream = System.IO.File.OpenRead(fileDownloadName);
string contentType = "application/octet-stream";
return new FileStreamResult(stream, contentType) { FileDownloadName = fileDownloadName };
}
private string PackParameters(string voice, string save_path, string text)
{
List<string> para = new List<string>();
para.Add("task:" + "TtsTask");
para.Add("voice:" + voice);
para.Add("save_path:" + save_path);
para.Add("text:" + text.Replace(":", ":"));
return para.ToArray().ToDelimiteredList(' ');
}
/// <summary>
/// 执行cmd指令(同步等待,await要求等待返回,转码太耗时)
/// </summary>
/// <param name="Parameters"></param>
public void RunMyProcess(string exePath, string Parameters)
{
try
{
if (string.IsNullOrEmpty(exePath)) return;
var result = Cli.Wrap(exePath)
.WithWorkingDirectory(Path.GetDirectoryName(exePath))
.WithArguments(Parameters)
.ExecuteAsync().GetAwaiter().GetResult();
Thread.Sleep(300);
}
catch (Exception ex)
{
throw new ValidatedException(ex.Message);
}
}
/// <summary>
/// 执行cmd指令(异步不等待,await要求等待返回,转码太耗时)
/// </summary>
/// <param name="Parameters"></param>
public void RunMyProcessAsync(string exePath, string Parameters)
{
try
{
if (string.IsNullOrEmpty(exePath)) return;
Cli.Wrap(exePath)
.WithWorkingDirectory(Path.GetDirectoryName(exePath))
.WithArguments(Parameters)
.ExecuteAsync();
Thread.Sleep(300);
}
catch (Exception ex)
{
throw new ValidatedException(ex.Message);
}
}
}
}
@@ -7,6 +7,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CliWrap" Version="3.6.6" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Authorization" Version="8.0.0" />
<PackageReference Include="ClosedXML" Version="0.102.1" />
@@ -24,9 +25,7 @@
<PackageReference Include="Microsoft.ML" Version="5.0.0" />
<PackageReference Include="Microsoft.ML.OnnxRuntime" Version="1.26.0" />
<PackageReference Include="Microsoft.ML.OnnxTransformer" Version="5.0.0" />
<PackageReference Include="EdgeTtsSharp" Version="2.0.6" />
<PackageReference Include="EdgeTtsSharp.NAudio" Version="2.0.6" />
<PackageReference Include="org.k2fsa.sherpa.onnx" Version="1.13.2" />
<PackageReference Include="NAudio.Core" Version="2.3.0" />
</ItemGroup>
<ItemGroup>
@@ -1,225 +0,0 @@
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();
}
}
}