ow
This commit is contained in:
parent
77a62b8d88
commit
2c6475b2d5
@ -5,6 +5,8 @@ VisualStudioVersion = 17.8.34316.72
|
|||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CloudBuilder.Topshelf", "CloudBuilder.Topshelf.csproj", "{63EB47F6-A616-4C64-B850-85ED294E2524}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CloudBuilder.Topshelf", "CloudBuilder.Topshelf.csproj", "{63EB47F6-A616-4C64-B850-85ED294E2524}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CloudBuilder.AI", "..\CloudBuilder\CloudBuilder.AI\CloudBuilder.AI.csproj", "{FD89E87E-BDEB-4040-A380-A5218B2BDAB1}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@ -15,6 +17,10 @@ Global
|
|||||||
{63EB47F6-A616-4C64-B850-85ED294E2524}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{63EB47F6-A616-4C64-B850-85ED294E2524}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{63EB47F6-A616-4C64-B850-85ED294E2524}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{63EB47F6-A616-4C64-B850-85ED294E2524}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{63EB47F6-A616-4C64-B850-85ED294E2524}.Release|Any CPU.Build.0 = Release|Any CPU
|
{63EB47F6-A616-4C64-B850-85ED294E2524}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{FD89E87E-BDEB-4040-A380-A5218B2BDAB1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{FD89E87E-BDEB-4040-A380-A5218B2BDAB1}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{FD89E87E-BDEB-4040-A380-A5218B2BDAB1}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{FD89E87E-BDEB-4040-A380-A5218B2BDAB1}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@ -2,7 +2,7 @@
|
|||||||
"profiles": {
|
"profiles": {
|
||||||
"CloudBuilder.Topshelf": {
|
"CloudBuilder.Topshelf": {
|
||||||
"commandName": "Project",
|
"commandName": "Project",
|
||||||
"commandLineArgs": "task:PythonRunTask book_id:B000010 from_chapter_id:181 to_chapter_id:200 speed:1 voice:Sherpa2 actor:韩立 actor_voice:Sherpa1 save_path:D:\\\\Net8\\\\FileServer\\\\Backup\\\\DmsFile\\\\voice"
|
"commandLineArgs": "task:SoVITSTtsTask book_id:B000011 chapter_id:3 voice:MIDDLE_AGED_FEMALE5 actor:韩立 actor_voice:YOUNG_MALE2 save_path:D:\\\\Net8\\\\FileServer\\\\Backup\\\\DmsFile\\\\voice"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,4 +1,5 @@
|
|||||||
using Python.Runtime;
|
using CloudBuilder.AI.Policy;
|
||||||
|
using Python.Runtime;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@ -303,101 +304,6 @@ namespace CloudBuilder.Topshelf.Python
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public enum SoVITSTtsVoice
|
|
||||||
{
|
|
||||||
[System.ComponentModel.Description("老年人(男性)-1")]
|
|
||||||
OLD_MAN1,
|
|
||||||
[System.ComponentModel.Description("老年人(男性)-2")]
|
|
||||||
OLD_MAN2,
|
|
||||||
[System.ComponentModel.Description("老年人(男性)-3")]
|
|
||||||
OLD_MAN3,
|
|
||||||
[System.ComponentModel.Description("老年人(男性)-4")]
|
|
||||||
OLD_MAN4,
|
|
||||||
[System.ComponentModel.Description("老年人(男性)-5")]
|
|
||||||
OLD_MAN5,
|
|
||||||
[System.ComponentModel.Description("老年人(男性)-6")]
|
|
||||||
OLD_MAN6,
|
|
||||||
[System.ComponentModel.Description("老年人(男性)-7")]
|
|
||||||
OLD_MAN7,
|
|
||||||
[System.ComponentModel.Description("男青年(男性)-1")]
|
|
||||||
MIDDLE_AGED_MAN1,
|
|
||||||
[System.ComponentModel.Description("男中年(男性)-2")]
|
|
||||||
MIDDLE_AGED_MAN2,
|
|
||||||
[System.ComponentModel.Description("男中年(男性)-3")]
|
|
||||||
MIDDLE_AGED_MAN3,
|
|
||||||
[System.ComponentModel.Description("男中年(男性)-4")]
|
|
||||||
MIDDLE_AGED_MAN4,
|
|
||||||
[System.ComponentModel.Description("男中年(男性)-5")]
|
|
||||||
MIDDLE_AGED_MAN5,
|
|
||||||
[System.ComponentModel.Description("男中年(男性)-6")]
|
|
||||||
MIDDLE_AGED_MAN6,
|
|
||||||
[System.ComponentModel.Description("男中年(男性)-7")]
|
|
||||||
MIDDLE_AGED_MAN7,
|
|
||||||
[System.ComponentModel.Description("男青年(男性)-1")]
|
|
||||||
YOUNG_MALE1,
|
|
||||||
[System.ComponentModel.Description("男青年(男性)-2")]
|
|
||||||
YOUNG_MALE2,
|
|
||||||
[System.ComponentModel.Description("男青年(男性)-3")]
|
|
||||||
YOUNG_MALE3,
|
|
||||||
[System.ComponentModel.Description("男青年(男性)-4")]
|
|
||||||
YOUNG_MALE4,
|
|
||||||
[System.ComponentModel.Description("男青年(男性)-5")]
|
|
||||||
YOUNG_MALE5,
|
|
||||||
[System.ComponentModel.Description("男青年(男性)-6")]
|
|
||||||
YOUNG_MALE6,
|
|
||||||
[System.ComponentModel.Description("男青年(男性)-7")]
|
|
||||||
YOUNG_MALE7,
|
|
||||||
[System.ComponentModel.Description("男儿童(男性)-1")]
|
|
||||||
MALE_CHILD1,
|
|
||||||
[System.ComponentModel.Description("男儿童(男性)-2")]
|
|
||||||
MALE_CHILD2,
|
|
||||||
[System.ComponentModel.Description("男儿童(男性)-3")]
|
|
||||||
MALE_CHILD3,
|
|
||||||
[System.ComponentModel.Description("男儿童(男性)-4")]
|
|
||||||
MALE_CHILD4,
|
|
||||||
[System.ComponentModel.Description("老年人(女性)-1")]
|
|
||||||
ELDERLY_FEMALE1,
|
|
||||||
[System.ComponentModel.Description("老年人(女性)-2")]
|
|
||||||
ELDERLY_FEMALE2,
|
|
||||||
[System.ComponentModel.Description("老年人(女性)-3")]
|
|
||||||
ELDERLY_FEMALE3,
|
|
||||||
[System.ComponentModel.Description("女中年(女性)-1")]
|
|
||||||
MIDDLE_AGED_FEMALE1,
|
|
||||||
[System.ComponentModel.Description("女中年(女性)-2")]
|
|
||||||
MIDDLE_AGED_FEMALE2,
|
|
||||||
[System.ComponentModel.Description("女中年(女性)-3")]
|
|
||||||
MIDDLE_AGED_FEMALE3,
|
|
||||||
[System.ComponentModel.Description("女中年(女性)-4")]
|
|
||||||
MIDDLE_AGED_FEMALE4,
|
|
||||||
[System.ComponentModel.Description("女中年(女性)-5")]
|
|
||||||
MIDDLE_AGED_FEMALE5,
|
|
||||||
[System.ComponentModel.Description("女中年(女性)-6")]
|
|
||||||
MIDDLE_AGED_FEMALE6,
|
|
||||||
[System.ComponentModel.Description("女中年(女性)-7")]
|
|
||||||
MIDDLE_AGED_FEMALE7,
|
|
||||||
[System.ComponentModel.Description("女青年(女性)-1")]
|
|
||||||
YOUNG_FEMALE1,
|
|
||||||
[System.ComponentModel.Description("女青年(女性)-2")]
|
|
||||||
YOUNG_FEMALE2,
|
|
||||||
[System.ComponentModel.Description("女青年(女性)-3")]
|
|
||||||
YOUNG_FEMALE3,
|
|
||||||
[System.ComponentModel.Description("女青年(女性)-4")]
|
|
||||||
YOUNG_FEMALE4,
|
|
||||||
[System.ComponentModel.Description("女青年(女性)-5")]
|
|
||||||
YOUNG_FEMALE5,
|
|
||||||
[System.ComponentModel.Description("女青年(女性)-6")]
|
|
||||||
YOUNG_FEMALE6,
|
|
||||||
[System.ComponentModel.Description("女青年(女性)-7")]
|
|
||||||
YOUNG_FEMALE7,
|
|
||||||
[System.ComponentModel.Description("女儿童(女性)-1")]
|
|
||||||
FEMALE_CHILD1,
|
|
||||||
[System.ComponentModel.Description("女儿童(女性)-2")]
|
|
||||||
FEMALE_CHILD2,
|
|
||||||
[System.ComponentModel.Description("女儿童(女性)-3")]
|
|
||||||
FEMALE_CHILD3,
|
|
||||||
[System.ComponentModel.Description("女儿童(女性)-4")]
|
|
||||||
FEMALE_CHILD4,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
/*
|
/*
|
||||||
import os
|
import os
|
||||||
|
|||||||
@ -9,10 +9,11 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using static System.Runtime.InteropServices.JavaScript.JSType;
|
||||||
|
|
||||||
namespace CloudBuilder.Topshelf.Task.AI
|
namespace CloudBuilder.Topshelf.Task.AI
|
||||||
{
|
{
|
||||||
//task:NameGenderPredictorTask book_id:B000010 from_chapter_id:177 to_chapter_id:200
|
//task:NameGenderPredictorTask book_id:B000011 from_chapter_id:177 to_chapter_id:200
|
||||||
public class NameGenderPredictorTask : IScheduleTask
|
public class NameGenderPredictorTask : IScheduleTask
|
||||||
{
|
{
|
||||||
private readonly IApplicationService service;
|
private readonly IApplicationService service;
|
||||||
@ -59,6 +60,41 @@ namespace CloudBuilder.Topshelf.Task.AI
|
|||||||
ConsoleOutput.UpdateProgress(processedLines, totalLines);
|
ConsoleOutput.UpdateProgress(processedLines, totalLines);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
string[] personNames = aiSentences.Select(x => x.PersonName).Distinct().ToArray();
|
||||||
|
if (personNames != null && personNames.Length > 0)
|
||||||
|
{
|
||||||
|
AiBookPersonEntity aiBookPersonEntity;
|
||||||
|
IRepository<AiBookPersonEntity> repositoryAiBookPersonEntity = service.GetRepository<IRepository<AiBookPersonEntity>>();
|
||||||
|
|
||||||
|
AiBookPersonEntity[] bps = repositoryAiBookPersonEntity.DetachedEntities.Where(x => x.BookId == aiSentences[0].BookId).ToArray();
|
||||||
|
|
||||||
|
if (bps != null && bps.Length > 0)
|
||||||
|
{
|
||||||
|
repositoryAiBookPersonEntity.DeleteNow(bps);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<AiBookPersonEntity> ps = new List<AiBookPersonEntity>();
|
||||||
|
//旁白
|
||||||
|
aiBookPersonEntity = new AiBookPersonEntity();
|
||||||
|
aiBookPersonEntity.BookId = aiSentences[0].BookId;
|
||||||
|
aiBookPersonEntity.PersonName = null;
|
||||||
|
aiBookPersonEntity.ActorName = SoVITSTtsVoiceRandom.GetRandomByGender("女性").ToString();
|
||||||
|
ps.Add(aiBookPersonEntity);
|
||||||
|
|
||||||
|
foreach (var personName in personNames)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(personName)) continue;
|
||||||
|
aiBookPersonEntity = new AiBookPersonEntity();
|
||||||
|
aiBookPersonEntity.BookId = aiSentences[0].BookId;
|
||||||
|
aiBookPersonEntity.PersonName = personName;
|
||||||
|
aiBookPersonEntity.ActorName = SoVITSTtsVoiceRandom.GetRandomByGender(NameGenderPredictor.IsMale(personName) == true ? "男性" : "女性").ToString();
|
||||||
|
|
||||||
|
ps.Add(aiBookPersonEntity);
|
||||||
|
}
|
||||||
|
|
||||||
|
repositoryAiBookPersonEntity.InsertNow(ps);
|
||||||
|
}
|
||||||
|
|
||||||
repositoryAiSentenceEntity.UpdateNow(aiSentences);
|
repositoryAiSentenceEntity.UpdateNow(aiSentences);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
using CloudBuilder.Core.DatabaseAccessor.Entity;
|
using CloudBuilder.AI.Policy;
|
||||||
|
using CloudBuilder.Core.DatabaseAccessor.Entity;
|
||||||
using CloudBuilder.Core.DependencyInjection.Task;
|
using CloudBuilder.Core.DependencyInjection.Task;
|
||||||
using CloudBuilder.Topshelf.Python;
|
using CloudBuilder.Topshelf.Python;
|
||||||
using CloudBuilder.Topshelf.Utility;
|
using CloudBuilder.Topshelf.Utility;
|
||||||
@ -17,18 +18,23 @@ namespace CloudBuilder.Topshelf.Task
|
|||||||
|
|
||||||
public void Run(Dictionary<string, string> bodyDict)
|
public void Run(Dictionary<string, string> bodyDict)
|
||||||
{
|
{
|
||||||
Python39Command python39Command = new Python39Command(service);
|
//Python39Command python39Command = new Python39Command(service);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
SoVITS soVITS = new SoVITS(python39Command);
|
//SoVITS soVITS = new SoVITS(python39Command);
|
||||||
Array voiceArray = Enum.GetValues(typeof(SoVITSTtsVoice));
|
//Array voiceArray = Enum.GetValues(typeof(SoVITSTtsVoice));
|
||||||
|
|
||||||
foreach (SoVITSTtsVoice voice in voiceArray)
|
//foreach (SoVITSTtsVoice voice in voiceArray)
|
||||||
|
//{
|
||||||
|
// soVITS.Generate("三更时分,青龙城毫无征兆地被血色浓雾笼罩,雾气带着诡异的幻术气息,城中低阶修士尽数陷入昏睡.", soVITS.Voices[voice], $"F:\\GPT-SoVITS\\gen\\{(int)voice}.mp3");
|
||||||
|
//}
|
||||||
|
|
||||||
|
Array voices = Enum.GetValues(typeof(ChineseTtsVoice));
|
||||||
|
|
||||||
|
foreach (ChineseTtsVoice voice in voices)
|
||||||
{
|
{
|
||||||
soVITS.Generate("三更时分,青龙城毫无征兆地被血色浓雾笼罩,雾气带着诡异的幻术气息,城中低阶修士尽数陷入昏睡.", soVITS.Voices[voice], $"F:\\GPT-SoVITS\\gen{(int)voice}.mp3");
|
SaveAudioToFileHelper.AudioToFile(voice, 1.0f, "三更时分,青龙城毫无征兆地被血色浓雾笼罩,雾气带着诡异的幻术气息,城中低阶修士尽数陷入昏睡.", $"F:\\GPT-SoVITS\\gen\\{(int)voice}.mp3");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@ -36,7 +42,7 @@ namespace CloudBuilder.Topshelf.Task
|
|||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
python39Command.Dispose();
|
//python39Command.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
|||||||
277
Task/TTS/SoVITSTtsTask.cs
Normal file
277
Task/TTS/SoVITSTtsTask.cs
Normal file
@ -0,0 +1,277 @@
|
|||||||
|
using CloudBuilder.AI.Entity;
|
||||||
|
using CloudBuilder.AI.Policy;
|
||||||
|
using CloudBuilder.AI.Utility;
|
||||||
|
using CloudBuilder.Core.DatabaseAccessor.Entity;
|
||||||
|
using CloudBuilder.Core.DependencyInjection.Task;
|
||||||
|
using CloudBuilder.Topshelf.Python;
|
||||||
|
using CloudBuilder.Topshelf.Utility;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata.Internal;
|
||||||
|
using Microsoft.IdentityModel.Tokens;
|
||||||
|
using NAudio.Lame;
|
||||||
|
using NAudio.Wave;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Numerics;
|
||||||
|
using System.Security.Cryptography.X509Certificates;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using static System.Net.Mime.MediaTypeNames;
|
||||||
|
using static System.Runtime.InteropServices.JavaScript.JSType;
|
||||||
|
|
||||||
|
namespace CloudBuilder.Topshelf.Task.TTS
|
||||||
|
{
|
||||||
|
//task:SoVITSTtsTask book_id:B000011 chapter_id:3 voice:MIDDLE_AGED_FEMALE5 actor:韩立 actor_voice:YOUNG_MALE2 save_path:D:\\Net8\\FileServer\\Backup\\DmsFile\\voice
|
||||||
|
public class SoVITSTtsTask : IScheduleTask
|
||||||
|
{
|
||||||
|
private readonly IApplicationService service;
|
||||||
|
|
||||||
|
public SoVITSTtsTask(IApplicationService service)
|
||||||
|
{
|
||||||
|
this.service = service;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Run(Dictionary<string, string> bodyDict)
|
||||||
|
{
|
||||||
|
ConsoleOutput.HideStdError();
|
||||||
|
|
||||||
|
IRepository<AiSentenceViewEntity> repository = service.GetRepository<IRepository<AiSentenceViewEntity>>();
|
||||||
|
|
||||||
|
string bookId = string.Empty;
|
||||||
|
if (bodyDict.ContainsKey("book_id"))
|
||||||
|
bookId = bodyDict["book_id"];
|
||||||
|
|
||||||
|
string chapterId = string.Empty;
|
||||||
|
if (bodyDict.ContainsKey("chapter_id"))
|
||||||
|
chapterId = bodyDict["chapter_id"];
|
||||||
|
|
||||||
|
string from_chapter_id = string.Empty;
|
||||||
|
if (bodyDict.ContainsKey("from_chapter_id"))
|
||||||
|
from_chapter_id = bodyDict["from_chapter_id"];
|
||||||
|
|
||||||
|
string to_chapter_id = string.Empty;
|
||||||
|
if (bodyDict.ContainsKey("to_chapter_id"))
|
||||||
|
to_chapter_id = bodyDict["to_chapter_id"];
|
||||||
|
|
||||||
|
AiSentenceViewEntity[] ents = repository.DetachedEntities.Where(x => x.BookId == bookId
|
||||||
|
&& (string.IsNullOrEmpty(chapterId) || x.ChapterId == Convert.ToInt32(chapterId))
|
||||||
|
&& (string.IsNullOrEmpty(from_chapter_id) || x.ChapterId >= Convert.ToInt32(from_chapter_id))
|
||||||
|
&& (string.IsNullOrEmpty(to_chapter_id) || x.ChapterId <= Convert.ToInt32(to_chapter_id)
|
||||||
|
)).OrderBy(x => x.ChapterId).ThenBy(x => x.ParagraphId).ThenBy(x => x.SentenceIndex).ToArray();
|
||||||
|
|
||||||
|
if (ents == null || ents.Length == 0) return;
|
||||||
|
|
||||||
|
string save_path = string.Empty, text = string.Empty, voice = SoVITSTtsVoiceRandom.GetRandomYoungFemale().ToString(), actor = string.Empty, actor_voice = SoVITSTtsVoiceRandom.GetRandomYoungMale().ToString();
|
||||||
|
if (bodyDict.ContainsKey("actor"))
|
||||||
|
actor = bodyDict["actor"];
|
||||||
|
|
||||||
|
if (bodyDict.ContainsKey("voice"))
|
||||||
|
voice = bodyDict["voice"];
|
||||||
|
|
||||||
|
if (bodyDict.ContainsKey("actor_voice"))
|
||||||
|
actor_voice = bodyDict["actor_voice"];
|
||||||
|
|
||||||
|
if (bodyDict.ContainsKey("save_path"))
|
||||||
|
save_path = bodyDict["save_path"];
|
||||||
|
|
||||||
|
float speed = 1.0f;
|
||||||
|
|
||||||
|
if (bodyDict.ContainsKey("speed"))
|
||||||
|
speed = Convert.ToSingle(bodyDict["speed"]);
|
||||||
|
|
||||||
|
GenerateAndMergeChapterAudio(ents, speed, actor, actor_voice, voice, save_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void GenerateAndMergeChapterAudio(AiSentenceViewEntity[] ents, float speed, string actor, string actor_voice, string mainVoice, string save_path)
|
||||||
|
{
|
||||||
|
IRepository<AiBookPersonEntity> repositoryAiBookPersonEntity = service.GetRepository<IRepository<AiBookPersonEntity>>();
|
||||||
|
AiBookPersonEntity[] bps = repositoryAiBookPersonEntity.DetachedEntities.Where(x => x.BookId == ents[0].BookId).ToArray();
|
||||||
|
|
||||||
|
|
||||||
|
Python39Command python39Command = new Python39Command(service);
|
||||||
|
SoVITS soVITS = new SoVITS(python39Command);
|
||||||
|
// 1. 按 BookId + ChapterId 分组(关键!)
|
||||||
|
var chapterGroups = ents
|
||||||
|
.Where(ent => ent.Content.Any(c => c >= 0x4E00 && c <= 0x9FFF)) // 只保留含中文的句子
|
||||||
|
.GroupBy(ent => new { ent.BookId, ent.ChapterId })
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
int totalLines = ents.Count();
|
||||||
|
int processedLines = 0; // 已处理行数计数器
|
||||||
|
UpdateProgress(processedLines, totalLines);
|
||||||
|
Dictionary<string, string> personMapping = new Dictionary<string, string>();
|
||||||
|
foreach (var chapter in chapterGroups)
|
||||||
|
{
|
||||||
|
string bookId = chapter.Key.BookId;
|
||||||
|
int chapterId = chapter.Key.ChapterId;
|
||||||
|
List<string> tempMp3List = new List<string>(); // 本章所有碎片音频
|
||||||
|
|
||||||
|
// 2. 生成本章所有句子音频
|
||||||
|
foreach (AiSentenceViewEntity ent in chapter)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
processedLines++;
|
||||||
|
|
||||||
|
string voice = SoVITSTtsVoiceRandom.GetRandomYoungFemale().ToString();
|
||||||
|
if (ent.DialogueIndc == YesNoPolicy.YES)
|
||||||
|
{
|
||||||
|
string key = string.IsNullOrEmpty(ent.PersonName) ? "NULL" : ent.PersonName;
|
||||||
|
if (key == actor)
|
||||||
|
{
|
||||||
|
if (personMapping.ContainsKey(key))
|
||||||
|
voice = personMapping[key];
|
||||||
|
else
|
||||||
|
{
|
||||||
|
voice = actor_voice;
|
||||||
|
personMapping.Add(key, voice);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(key) && personMapping.ContainsKey(key))
|
||||||
|
{
|
||||||
|
voice = personMapping[key];
|
||||||
|
}
|
||||||
|
else if (!string.IsNullOrEmpty(key) && bps != null && bps.Length > 0 && bps.Where(x => x.PersonName == key).Any())
|
||||||
|
{
|
||||||
|
voice = bps.Where(x => x.PersonName == key).FirstOrDefault().ActorName;
|
||||||
|
personMapping.Add(key, voice);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (ent.Gender == YesNoPolicy.YES)
|
||||||
|
{
|
||||||
|
voice = SoVITSTtsVoiceRandom.GetRandomFemaleExclude(EnumHelper.ToEnum<SoVITSTtsVoice>(actor_voice)).ToString();
|
||||||
|
personMapping.Add(key, voice);
|
||||||
|
}
|
||||||
|
else if (ent.Gender == YesNoPolicy.NO)
|
||||||
|
{
|
||||||
|
voice = SoVITSTtsVoiceRandom.GetRandomFemale().ToString();
|
||||||
|
personMapping.Add(key, voice);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
voice = SoVITSTtsVoiceRandom.GetRandom().ToString();
|
||||||
|
personMapping.Add(key, voice);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
voice = mainVoice;
|
||||||
|
if (personMapping.ContainsKey("mainVoice"))
|
||||||
|
voice = personMapping["mainVoice"];
|
||||||
|
else if (bps != null && bps.Length > 0 && bps.Where(x => string.IsNullOrEmpty(x.PersonName)).Any())
|
||||||
|
{
|
||||||
|
voice = bps.Where(x => string.IsNullOrEmpty(x.PersonName)).FirstOrDefault().ActorName;
|
||||||
|
personMapping.Add("mainVoice", voice);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(voice) || string.IsNullOrEmpty(ent.Content))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// 碎片文件名:BookId_ChapterId_ParagraphId_SentenceId.mp3
|
||||||
|
string fileName = string.Format("{0}_{1}_{2}_{3}.mp3", ent.BookId, ent.ChapterId, ent.ParagraphId, ent.SentenceId);
|
||||||
|
string filePath = Path.Combine(save_path, fileName);
|
||||||
|
|
||||||
|
// 生成音频
|
||||||
|
soVITS.Generate(ent.Content, soVITS.Voices[EnumHelper.ToEnum<SoVITSTtsVoice>(voice)], filePath);
|
||||||
|
|
||||||
|
tempMp3List.Add(filePath); // 加入合并列表
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"❌ 失败:{ex.Message}");
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateProgress(processedLines, totalLines);
|
||||||
|
}
|
||||||
|
|
||||||
|
Thread.Sleep(100);
|
||||||
|
|
||||||
|
// 3. 合并为:BookId_ChapterId.mp3
|
||||||
|
string outputFileName = string.Format("{0}_{1}.mp3", bookId, chapterId);
|
||||||
|
string outputFilePath = Path.Combine(save_path, outputFileName);
|
||||||
|
|
||||||
|
MergeChapterMp3Files(tempMp3List, outputFilePath);
|
||||||
|
|
||||||
|
// 4. 可选:合并后删除碎片文件(节省空间)
|
||||||
|
foreach (var chunk in tempMp3List)
|
||||||
|
{
|
||||||
|
try { File.Delete(chunk); } catch { }
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateProgress(processedLines, totalLines);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// 最终修复:完全解决 No fmt chunk / 采样率不兼容 / 只有一截声音
|
||||||
|
/// </summary>
|
||||||
|
public void MergeChapterMp3Files(List<string> chunkFiles, string outputFilePath)
|
||||||
|
{
|
||||||
|
var validFiles = chunkFiles.Where(File.Exists).ToList();
|
||||||
|
if (validFiles.Count == 0) return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// 目标格式:32000Hz 单声道 16bit
|
||||||
|
WaveFormat targetFormat = new WaveFormat(32000, 16, 1);
|
||||||
|
|
||||||
|
// 直接生成 MP3,不经过 WAV 文件,彻底避免 fmt 错误
|
||||||
|
using (var mp3Writer = new LameMP3FileWriter(outputFilePath, targetFormat, 128))
|
||||||
|
{
|
||||||
|
foreach (var file in validFiles)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// 释放文件占用
|
||||||
|
GC.Collect();
|
||||||
|
GC.WaitForPendingFinalizers();
|
||||||
|
|
||||||
|
// 用Windows系统解码器读取任何MP3
|
||||||
|
using (var reader = new MediaFoundationReader(file))
|
||||||
|
using (var resampler = new MediaFoundationResampler(reader, targetFormat))
|
||||||
|
{
|
||||||
|
byte[] buffer = new byte[8192];
|
||||||
|
int read;
|
||||||
|
while ((read = resampler.Read(buffer, 0, buffer.Length)) > 0)
|
||||||
|
{
|
||||||
|
mp3Writer.Write(buffer, 0, read);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"跳过文件:{file},错误:{ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Console.WriteLine($"✅ 合并成功:{outputFilePath},大小:{new FileInfo(outputFilePath).Length} 字节");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
//Console.WriteLine($"❌ 合并失败:{ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void UpdateProgress(int processed, int total)
|
||||||
|
{
|
||||||
|
double progressPercent = (double)processed / total * 100;
|
||||||
|
int progressBarLength = 50; // 进度条总长度
|
||||||
|
int filledLength = (int)(progressPercent / 100 * progressBarLength);
|
||||||
|
|
||||||
|
// 构建进度条(如:[██████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░])
|
||||||
|
string progressBar = "[" + new string('█', filledLength) + new string('░', progressBarLength - filledLength) + "]";
|
||||||
|
|
||||||
|
string progressInfo = $"{progressBar} {progressPercent:F2}% | 已处理:{processed}/{total} ";
|
||||||
|
Console.Write($"\r{progressInfo}");
|
||||||
|
Console.Out.Flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,4 +1,5 @@
|
|||||||
using CloudBuilder.AI.Entity;
|
using CloudBuilder.AI.Entity;
|
||||||
|
using CloudBuilder.AI.Policy;
|
||||||
using CloudBuilder.Core.DatabaseAccessor.Entity;
|
using CloudBuilder.Core.DatabaseAccessor.Entity;
|
||||||
using CloudBuilder.Core.DependencyInjection.Task;
|
using CloudBuilder.Core.DependencyInjection.Task;
|
||||||
using CloudBuilder.Topshelf.Utility;
|
using CloudBuilder.Topshelf.Utility;
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
using SherpaOnnx;
|
using CloudBuilder.AI.Policy;
|
||||||
|
using SherpaOnnx;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
@ -156,132 +157,6 @@ namespace CloudBuilder.Topshelf.Utility
|
|||||||
audio.SaveToWaveFile(saveFilePath);
|
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
|
public static class ChineseTtsVoiceExtensions
|
||||||
{
|
{
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user