From a8789b2e42d25dd6c67fdd0e8335c66735887272 Mon Sep 17 00:00:00 2001 From: owenchen Date: Fri, 5 Jun 2026 08:12:54 +0800 Subject: [PATCH] ow --- Lib/CloudBuilder.AI.dll | Bin 97792 -> 97792 bytes Properties/launchSettings.json | 2 +- Task/AI/ChineseNameExtractorTask.cs | 10 ++- Task/AI/NameGenderPredictorTask.cs | 70 +++++++++++++++++++ Task/TTS/TtsTask.cs | 100 +++++++++++++++++++--------- 5 files changed, 145 insertions(+), 37 deletions(-) create mode 100644 Task/AI/NameGenderPredictorTask.cs diff --git a/Lib/CloudBuilder.AI.dll b/Lib/CloudBuilder.AI.dll index a19d798239299bfd3578713a63451fa8969eade4..891f62618bc02a0131dcb6d5120ed776bcbae9be 100644 GIT binary patch delta 5653 zcmb7|4RBOf702JbyZbiTyzFQ8?MK2AHoKcN31N4$$tG<;f)V*lBLxa65*otR(u}l^ znm}5T6d5Rr(BvRdh7YG&l#WE}I%Z@#RdMQwj#EEzU^>`lP;5n@3}a_#O+9b(v1gs9 zeMx5DJNKM(|M&jRxo_VjgCCj(KQz7YywTbH$Pb1Nl`uYZ_F*;(jFp>y3rtuG^LRCE zWaTUmo?`>P@uN&of0p5g3nt;lfFN9RF@cT5dRa2v#wSF6PJp)j->^OiSY%7TL3pyD zDo;5lq!(FA8S96Rf>w-;7Wm55n9EwVKr~Nqm&4(x&R6;-T_t7_T~3D+UHx#gpvkHR zD%u6XGQU(rSA+Dx=Dq0J@mX|5!peSPe~H+Ki9Jm0W5hmA>{By#`YGv2ZW+uM1mn(4 zxCp#$-I88uJ7s{4#S*;Zj6$KSiT}+6587mY#Rw0(g6sl(-sQ3W%aq;MYbN-FU51mc zXe3VD7BQRBNBmY1K5_-2u_($bMfhS-4P10c@Ji8^!gexaJp|lkm?S~7+Y1N15^Qu! z@MD|Ahm9~dAj3hY1P9Tz2S1lx5`5L|LfM;1dKB*k zb0t110?13@ud(V2#@2U@7N^BcSwcrl8tw&e z^FHV=_CT}K3;%R@xnhJjWEmPAUjP4n8f5suDix;4ZnneKVh_KH=ebx82S^dyiLHqU47ZA8UN1s=&i$E3ug>j_(nmT% z7C$pHbI>yV{Sj8y8{vaFHP9UJg3m5N;~Xi>Ndp(Cc_ZY!TMK7*q5ht-#z#Y5LktslicXF5k z%|=yZ63qZ-qbG8smFiM8c$t)ntHZjPF)m%NZeusDI=@g8LxNsrA%Mt8r%9AL+C^>R___28F|^gv{!}(Xb$09jju`lf67M7Tn{~hP?Zp(WexHP&)Gzqh zb~~IJ*t2Z6s-aDt6VXq7l?qN#ryUL3)mA^-IN`sGaV_L;7U^nr#I3 z{m{E*B1l@DE^6vP@buFQW#&JLNm}GxUR~txdo{?{_-A*>RS|9SoQQt%r~O2bl7|*& z$oDlP`FAi$i~OMyUE~)_G{{4N*^#Rv+T=MA{p3vnB1p*-EgABopF;Arn50FXU#g4z zV5tWAYzkM(dN#H z=;z)aB7%fFy>I1p^(pUOonrnMF-fcApDfo!{>yR=^4WG=711WoiRdRUs3d}f9JVFASw74(C zb#X7M)!;TIX2-3HXmjU8^mA7wh#=uk@7w%cgKt$r5X#m)BpCl92>3UbQ1&D;tv0!} zzBb-fTie-{h^FGncruzy)W%}bWVCZzL%g9gmPjR1W3N2HR$APz_YS88wz`euQd{v8 z!vBbdMjSoNV>*E5fsy$=tjvC_W9@kAsUJ2UdspoK^_o8%9NE&t*2ky3?@fDAt9I_U zZhysVi@dn_t-3E?n)>f5$_lqtyL4!t%*T)ZsFzvU$l_jBF=pMx?1t6bgjykq%h%&Q zE_C6wR_MfgLWl|}^dvK%DBcqoiQ%tmM=@uc(15W9v|<=bsh^Q2o?$1sv2M)qEW5>q HqUQeqi`{ma delta 5626 zcmb`L4Qx}_702&=wx46?@nXl$v;E>Y!Ab0pgy1-z2AaZ@0U-ose23AbNlaQk7SMwQ zNP%Qk)DMJ!Z~{>ggSC|=(NSpUZrP>^s;x{b6LjmeZe>+BrBy3B1cOQK=z#8AJBhEi zC*eqo^xix7oOA#8{?2(Hat8022Je}UJZH?g+kIsCpqG{opIJ-a2TFsce*q<{V5U$G zZ8S(j@I5-#CG(Qj=cDudYq8&)Myk-$y+4gL7jlj*!`7#TX ztS7`eyFf%&y)-ZO2dP(p=B&r9gBC)JJ2qf2_M@y*23YL~!LTDFUoevG+a5!Apji9V z1b=b(!Qzzga6`ly0*_6C4-`qYipeyYv&T(v*y)Ee4xD0_;B{w6U13V5mU5{lOz^R@ z0`hVmhv%~;*pw&1u^c!2JjX5fa%q|fS&AP#iUf}+ZegwnYZbrTz=<9bf#xd^$&=)x z+{9m-;I!fwR1=KhMMf69pz~`*5gJU8=~~2Mi(P(*;7-qYBzVE)7Osg<=AIiE=k)oK z+~gMMHNg{Zzo6kdHU-|XOEO=Tr&%QwVW|n;RTT9rZeFT3WO)=pFvB*F0>d7+oNZ2) zOEJS@yB{{G611@r;S%kf&}AcZ zHF+htZ*wP;VP3ve+{>w@PEJ-@gNi&C%*vI7J~JG)D@;!KN`#AfO3EX#KOR+2aeFwy z*-x9{%WOZqn(c<`x!4EX&t1D){-4|4FPt#KtvtW*o)HGzif{-yauiQLSKFi9uJ^-7 z*)G;#>@(bTNwCltl82t%%d^ep74T?*BhSTI9#YG}?<~!v< z>*RZ&SnD&wp8N{=eNOnA8Qx`gkt%uo+?(MJzF8*U5(nUBo@?MEZqhhsf6CcDPT_OT zf0MKCaCX;}J(!u?-8rr54=day*sn!qu!| z0-FJD+l`KR0ph%gVH`9WWyoAK4xEg>of=)imRf_Sgw%RAteG0)((P;;dvMj+Gem9k zS^G@%4S20$Tph=NwTy_R#1RsR9V?1$v=c2W6W*$=sNI)| z^JbwYlQEZ@wP(>`Gnz^wY#p0+7>A~kLpV?#P|T4sFlQ&&LS8xq3+sZ-ha?n8w8Svk z5<{Y56W+MSL|Y8n54il)$OSkZ$_dA7Juy=@PEEVVPg7^$nu+ckv{E=#t}j@}Y3Ng@MAD}g9h~3<2p-${h(gs%n50L3(47`}zDI|=U!C4f z&P4RdQzGe;pHw+Pp1f#5oP1~@lK&2q^vIv`GIIaJ9`D;;u+=%KZ+fV=z1+@Hp68TE z`lwMKC&;5#H^xy%zKN)NF-Z@#vLLM{zh3ZvXfhMgZ*oc`eR9dq33BAnxa2c@tjn7& zs1=x^$Gx&JE$%CYI^3s%5huuT$A+3eV~_RLmL95J#w0!REy1+N z{}I$7pYCJLMD)p1BI%P?m2!d{Ic#k`N!wzUZ*8Y|>0j}VfMsZ0jb(TYXXusTm*r`d zAv{~B4AZ>p=Ayy$d{pU-JkHr_qLr=J1|9$yEl{;_jpK$`{l~%-S|vI zpF1UzKKEE9C&+V0zsyNJnvR0E`vIUQAWEY-^rG<2otz;u1 zGZx|bJBiU)k&WIkiU#~0W@sCebLe=PZHG6ob~Nk51*e@Yc>-*@M@Pzm8 zOD4Qb{BPvrC%c39hhAFM`;!MKF!|?Z|57a&o~a1dV`G2rqcR 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); }