diff --git a/Lib/CloudBuilder.AI.dll b/Lib/CloudBuilder.AI.dll index a19d798..891f626 100644 Binary files a/Lib/CloudBuilder.AI.dll and b/Lib/CloudBuilder.AI.dll differ diff --git a/Properties/launchSettings.json b/Properties/launchSettings.json index aa79893..c1cd06f 100644 --- a/Properties/launchSettings.json +++ b/Properties/launchSettings.json @@ -2,7 +2,7 @@ "profiles": { "CloudBuilder.Topshelf": { "commandName": "Project", - "commandLineArgs": "task:ChineseNameExtractorTask book_id:B000010 chapter_id:73" + "commandLineArgs": "task:TtsTask 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" } } } \ No newline at end of file diff --git a/Task/AI/ChineseNameExtractorTask.cs b/Task/AI/ChineseNameExtractorTask.cs index 476487b..96004e4 100644 --- a/Task/AI/ChineseNameExtractorTask.cs +++ b/Task/AI/ChineseNameExtractorTask.cs @@ -9,7 +9,7 @@ using static System.Net.Mime.MediaTypeNames; namespace CloudBuilder.Topshelf.Task { //task:ChineseNameExtractorTask book_id:B000010 chapter_id:74 from_chapter_id:74 - //task:ChineseNameExtractorTask book_id:B000010 from_chapter_id:500 + //task:ChineseNameExtractorTask book_id:B000010 from_chapter_id:177 to_chapter_id:200 public class ChineseNameExtractorTask : IScheduleTask { private readonly IApplicationService service; @@ -32,6 +32,10 @@ namespace CloudBuilder.Topshelf.Task 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"]; + PythonCommand python = new PythonCommand(service); try { @@ -43,7 +47,7 @@ namespace CloudBuilder.Topshelf.Task IRepository repositoryAiChapterPersonEntity = service.GetRepository>(); - AiChapterPersonEntity[] dels = repositoryAiChapterPersonEntity.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))).ToArray(); + AiChapterPersonEntity[] dels = repositoryAiChapterPersonEntity.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))).ToArray(); if (dels != null && dels.Length > 0) repositoryAiChapterPersonEntity.DeleteNow(dels); string depRoot; @@ -56,7 +60,7 @@ namespace CloudBuilder.Topshelf.Task SpeakerAnalysisHelper speakerAnalysisHelper = new SpeakerAnalysisHelper(); - 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))).OrderBy(x => x.ChapterId).ThenBy(x => x.ParagraphId).ThenBy(x => x.SentenceIndex).ToArray(); + 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; AiSentenceEntity aiSentenceEntity; diff --git a/Task/AI/NameGenderPredictorTask.cs b/Task/AI/NameGenderPredictorTask.cs new file mode 100644 index 0000000..bf4d36d --- /dev/null +++ b/Task/AI/NameGenderPredictorTask.cs @@ -0,0 +1,70 @@ +using CloudBuilder.AI.Entity; +using CloudBuilder.AI.Utility; +using CloudBuilder.Core.DatabaseAccessor.Entity; +using CloudBuilder.Core.DependencyInjection.Task; +using CloudBuilder.Topshelf.Python; +using CloudBuilder.Topshelf.Utility; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CloudBuilder.Topshelf.Task.AI +{ + //task:NameGenderPredictorTask book_id:B000010 from_chapter_id:177 to_chapter_id:200 + public class NameGenderPredictorTask : IScheduleTask + { + private readonly IApplicationService service; + + public NameGenderPredictorTask(IApplicationService service) + { + this.service = service; + } + + public void Run(Dictionary bodyDict) + { + try + { + if (!bodyDict.ContainsKey("book_id")) return; + + string 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"]; + + IRepository repositoryAiSentenceEntity = service.GetRepository>(); + + AiSentenceEntity[] aiSentences = repositoryAiSentenceEntity.DetachedEntities.Where(x => !string.IsNullOrEmpty(x.PersonName) && 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 (aiSentences == null || aiSentences.Length == 0) return; + + int totalLines = aiSentences.Length; + int processedLines = 0; // 已处理行数计数器 + + foreach (AiSentenceEntity aiSentence in aiSentences) + { + processedLines++; + + aiSentence.Gender = NameGenderPredictor.PredictGender(aiSentence.PersonName); + + ConsoleOutput.UpdateProgress(processedLines, totalLines); + } + + repositoryAiSentenceEntity.UpdateNow(aiSentences); + } + catch (Exception ex) + { + Console.WriteLine(ex.Message); + } + } + } +} diff --git a/Task/TTS/TtsTask.cs b/Task/TTS/TtsTask.cs index 0c99e7f..a8bfea2 100644 --- a/Task/TTS/TtsTask.cs +++ b/Task/TTS/TtsTask.cs @@ -18,6 +18,7 @@ using static System.Runtime.InteropServices.JavaScript.JSType; namespace CloudBuilder.Topshelf.Task.TTS { //task:TtsTask book_id:B000008 chapter_id:3 speed:1 voice:Sherpa2 actor:韩立 actor_voice:Sherpa1 save_path:D:\\Net8\\FileServer\\Backup\\DmsFile\\voice + //task:TtsTask book_id:B000010 from_chapter_id:177 to_chapter_id:200 speed:1 voice:Sherpa2 actor:韩立 actor_voice:Sherpa1 save_path:D:\\Net8\\FileServer\\Backup\\DmsFile\\voice //task:TtsTask voice:Sherpa2 save_path:D:\\Net8\\FileServer\\Backup\\DmsFile\\voice\\20260528112739.mp3 text:二愣子睁大着双眼,直直望着茅草和烂泥糊成的黑屋顶,身上盖着的旧棉被,已呈深黄色,看不出原来的本来面目,还若有若无的散发着淡淡的霉味。 public class TtsTask : IScheduleTask { @@ -42,7 +43,19 @@ namespace CloudBuilder.Topshelf.Task.TTS if (bodyDict.ContainsKey("chapter_id")) chapterId = bodyDict["chapter_id"]; - AiSentenceViewEntity[] ents = repository.DetachedEntities.Where(x => x.BookId == bookId && (string.IsNullOrEmpty(chapterId) || x.ChapterId == Convert.ToInt32(chapterId))).OrderBy(x => x.ChapterId).ThenBy(x => x.ParagraphId).ThenBy(x => x.SentenceIndex).ToArray(); + 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; @@ -88,51 +101,72 @@ namespace CloudBuilder.Topshelf.Task.TTS // 2. 生成本章所有句子音频 foreach (AiSentenceViewEntity ent in chapter) { - processedLines++; - - string voice = ChineseTtsVoice.Sherpa2.ToString(); - if (ent.DialogueIndc == YesNoPolicy.YES) + try { - if (ent.PersonName == actor) + processedLines++; + + string voice = ChineseTtsVoice.Sherpa2.ToString(); + if (ent.DialogueIndc == YesNoPolicy.YES) { - if (personMapping.ContainsKey(ent.PersonName)) - voice = personMapping[ent.PersonName]; + 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 { - voice = actor_voice; - personMapping.Add(ent.PersonName, voice); - } + if (!string.IsNullOrEmpty(key) && personMapping.ContainsKey(key)) + voice = personMapping[key]; + else + { + if (ent.Gender == YesNoPolicy.YES) + { + voice = CloudBuilder.AI.Policy.ChineseTtsVoiceRandom.GetRandomFemaleExclude(80, EnumHelper.ToEnum(actor_voice)).ToString(); + personMapping.Add(key, voice); + } + else if (ent.Gender == YesNoPolicy.NO) + { + voice = CloudBuilder.AI.Policy.ChineseTtsVoiceRandom.GetRandomFemale(80).ToString(); + personMapping.Add(key, voice); + } + else + { + voice = CloudBuilder.AI.Policy.ChineseTtsVoiceRandom.GetRandom(50).ToString(); + personMapping.Add(key, voice); + } + } + } } else { - if (!string.IsNullOrEmpty(ent.PersonName) && personMapping.ContainsKey(ent.PersonName)) - voice = personMapping[ent.PersonName]; - else - { - voice = CloudBuilder.AI.Policy.ChineseTtsVoiceRandom.GetRandom(80).ToString(); - personMapping.Add(ent.PersonName, voice); - } - + voice = mainVoice; } + + 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); + + // 生成音频 + SaveAudioToFileHelper.AudioToFile(EnumHelper.ToEnum(voice), speed, ent.Content, filePath); + + tempMp3List.Add(filePath); // 加入合并列表 } - else + catch (Exception ex) { - voice = mainVoice; + Console.WriteLine($"❌ 失败:{ex.Message}"); } - 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); - - // 生成音频 - SaveAudioToFileHelper.AudioToFile(EnumHelper.ToEnum(voice), speed, ent.Content, filePath); - - tempMp3List.Add(filePath); // 加入合并列表 - UpdateProgress(processedLines, totalLines); }