diff --git a/CloudBuilder.AI.Service/AiBookService.auto.cs b/CloudBuilder.AI.Service/AiBookService.auto.cs index cd6228e..5180f40 100644 --- a/CloudBuilder.AI.Service/AiBookService.auto.cs +++ b/CloudBuilder.AI.Service/AiBookService.auto.cs @@ -240,7 +240,7 @@ namespace CloudBuilder.AI.Service repositoryAiChapterEntity.InsertNow(data.AiChapters); if (data.AiSentences == null || data.AiSentences.Count() == 0) return data; - repositoryAiSentenceEntity.InsertNow(data.AiSentences); + repositoryAiSentenceEntity.InsertNow(data.AiSentences); return FindByKey(key); } @@ -337,9 +337,13 @@ namespace CloudBuilder.AI.Service private string PackTtsParameters(string voice, string save_path, string bookId, string chapterId) { + //速度快,音质差 //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:SoVITSTtsTask book_id:B000011 chapter_id:3 save_path:D:\\Net8\\FileServer\\Backup\\DmsFile\\voice + //非必要参数 voice:MIDDLE_AGED_FEMALE5 actor:韩立 actor_voice:YOUNG_MALE2 List para = new List(); - para.Add("task:" + "TtsTask"); + para.Add("task:" + "SoVITSTtsTask"); para.Add("book_id:" + bookId); para.Add("chapter_id:" + chapterId); para.Add("voice:" + voice); diff --git a/CloudBuilder.AI/Entity/AiBookPersonEntity.auto.cs b/CloudBuilder.AI/Entity/AiBookPersonEntity.auto.cs index dcf36d3..18db6ed 100644 --- a/CloudBuilder.AI/Entity/AiBookPersonEntity.auto.cs +++ b/CloudBuilder.AI/Entity/AiBookPersonEntity.auto.cs @@ -31,14 +31,6 @@ namespace CloudBuilder.AI.Entity public string PersonName { get; set; } [Column("actor_name")] public string? ActorName { get; set; } - [Column("gender")] - public string? Gender { get; set; } - [Column("sentiment")] - public string? Sentiment { get; set; } - [Column("sound_file_path")] - public string? SoundFilePath { get; set; } - [Column("status")] - public string? Status { get; set; } public const string CREATED_DATETIME = "CreatedDatetime"; public const string CREATED_BY = "CreatedBy"; @@ -48,13 +40,9 @@ namespace CloudBuilder.AI.Entity public const string BOOK_ID = "BookId"; public const string PERSON_NAME = "PersonName"; public const string ACTOR_NAME = "ActorName"; - public const string GENDER = "Gender"; - public const string SENTIMENT = "Sentiment"; - public const string SOUND_FILE_PATH = "SoundFilePath"; - public const string STATUS = "Status"; public const string DB_NAME_AI_BOOK_PERSON = "ai_book_person"; - public const string DB_NAME_FIELDS="created_datetime,created_by,updated_datetime,updated_by,person_id,book_id,person_name,actor_name,gender,sentiment,sound_file_path,status"; + public const string DB_NAME_FIELDS="created_datetime,created_by,updated_datetime,updated_by,person_id,book_id,person_name,actor_name"; public const string DB_CREATED_DATETIME = "created_datetime"; public const string DB_CREATED_BY = "created_by"; @@ -64,10 +52,6 @@ namespace CloudBuilder.AI.Entity public const string DB_BOOK_ID = "book_id"; public const string DB_PERSON_NAME = "person_name"; public const string DB_ACTOR_NAME = "actor_name"; - public const string DB_GENDER = "gender"; - public const string DB_SENTIMENT = "sentiment"; - public const string DB_SOUND_FILE_PATH = "sound_file_path"; - public const string DB_STATUS = "status"; public static AiBookPersonEntity GetInstance() @@ -95,10 +79,6 @@ namespace CloudBuilder.AI.Entity this.BookId = source.BookId; this.PersonName = source.PersonName; this.ActorName = source.ActorName; - this.Gender = source.Gender; - this.Sentiment = source.Sentiment; - this.SoundFilePath = source.SoundFilePath; - this.Status = source.Status; if (includeSystemFields) { diff --git a/CloudBuilder.AI/Utility/NameGenderPredictor.cs b/CloudBuilder.AI/Utility/NameGenderPredictor.cs index dfc7c35..e24a820 100644 --- a/CloudBuilder.AI/Utility/NameGenderPredictor.cs +++ b/CloudBuilder.AI/Utility/NameGenderPredictor.cs @@ -97,9 +97,11 @@ namespace CloudBuilder.AI.Utility // 复姓列表 private static readonly HashSet CompoundSurnames = new HashSet { - "欧阳","太史","端木","上官","司马","东方","独孤","南宫", - "万俟","闻人","夏侯","诸葛","尉迟","公羊","赫连","澹台", - "皇甫","宗政","濮阳","公冶","太叔","申屠","公孙","慕容" + "游竺", "权逯", "盖益", "桓公", "万俟", "司马", "上官", "欧阳", + "夏侯", "诸葛", "闻人", "东方", "赫连", "皇甫", "尉迟", "公羊", + "澹台", "公冶", "宗政", "濮阳", "淳于", "单于", "太叔", "申屠", + "公孙", "仲孙", "轩辕", "令狐", "钟离", "宇文", "长孙", "慕容", + "司徒", "司空" }; /// diff --git a/CloudBuilder.AI/Utility/SentenceSplitter.cs b/CloudBuilder.AI/Utility/SentenceSplitter.cs index c6694eb..7650612 100644 --- a/CloudBuilder.AI/Utility/SentenceSplitter.cs +++ b/CloudBuilder.AI/Utility/SentenceSplitter.cs @@ -18,7 +18,7 @@ namespace CloudBuilder.AI.Utility // 动态生成的正则表达式(匹配所有配置的引号类型) private Regex _dynamicQuotePattern; // 引号内必须包含的结尾标点集合 - private readonly string _endPunctuations = "?!。,;:、……~·"; + private readonly string _endPunctuations = "?!.,;:?!。,;:、……~·"; /// /// 默认构造函数:初始化常用成对引号(中文、英文、直角引号) @@ -48,47 +48,31 @@ namespace CloudBuilder.AI.Utility /// /// 初始化动态正则:根据配置的成对引号生成匹配规则 /// - //private void InitDynamicRegex() - //{ - // if (_quotePairs.Count == 0) - // { - // // 空配置时生成空正则,避免匹配任何内容 - // _dynamicQuotePattern = new Regex(@"^$", RegexOptions.Compiled); - // return; - // } - - // // 步骤1:转义所有引号(避免正则特殊字符冲突),拼接左/右引号匹配组 - // string escapedLeftQuotes = string.Join("|", _quotePairs.Select(p => Regex.Escape(p.LeftQuote))); - // string escapedRightQuotes = string.Join("|", _quotePairs.Select(p => Regex.Escape(p.RightQuote))); - // // 步骤2:转义标点集合,避免正则解析错误 - // string escapedPunctuations = Regex.Escape(_endPunctuations); - - // // 步骤3:构建动态正则表达式 - // // 分组说明: - // // ?:匹配配置的任意左引号 - // // ?:匹配引号内的内容(必须以指定标点结尾) - // // ?:匹配配置的任意右引号 - // string regexPattern = $@"(?{escapedLeftQuotes})(?.+?[{escapedPunctuations}])(?{escapedRightQuotes})"; - // _dynamicQuotePattern = new Regex(regexPattern, RegexOptions.Compiled | RegexOptions.Singleline); - //} - private void InitDynamicRegex() { if (_quotePairs.Count == 0) { + // 空配置时生成空正则,避免匹配任何内容 _dynamicQuotePattern = new Regex(@"^$", RegexOptions.Compiled); return; } + // 步骤1:转义所有引号(避免正则特殊字符冲突),拼接左/右引号匹配组 string escapedLeftQuotes = string.Join("|", _quotePairs.Select(p => Regex.Escape(p.LeftQuote))); string escapedRightQuotes = string.Join("|", _quotePairs.Select(p => Regex.Escape(p.RightQuote))); + // 步骤2:转义标点集合,避免正则解析错误 + string escapedPunctuations = Regex.Escape(_endPunctuations); - // ✅ 修复:移除必须以标点结尾的限制,匹配任意引号内容 - string regexPattern = $@"(?{escapedLeftQuotes})(?.+?)(?{escapedRightQuotes})"; - + // 步骤3:构建动态正则表达式 + // 分组说明: + // ?:匹配配置的任意左引号 + // ?:匹配引号内的内容(必须以指定标点结尾) + // ?:匹配配置的任意右引号 + string regexPattern = $@"(?{escapedLeftQuotes})(?.+?[{escapedPunctuations}])(?{escapedRightQuotes})"; _dynamicQuotePattern = new Regex(regexPattern, RegexOptions.Compiled | RegexOptions.Singleline); } + /// /// 核心分割方法:按配置的成对引号分割文本,结果无引号字符 /// @@ -182,15 +166,12 @@ namespace CloudBuilder.AI.Utility /// true=包含对话,false=纯旁白/空文本 public bool IsDialogue(string text) { - // 1. 处理空文本/NULL,直接返回false if (string.IsNullOrWhiteSpace(text)) return false; - // 2. 文本预处理:去空格、统一全角/半角引号 string cleanedText = text.Trim(); - // 替换全角空格为半角,避免干扰判断 cleanedText = Regex.Replace(cleanedText, @" ", " "); - // 统一引号格式(中文弯引号、直引号 → 标准双引号) + // 统一引号 cleanedText = cleanedText.Replace("“", "\"") .Replace("”", "\"") .Replace("〝", "\"") @@ -200,17 +181,34 @@ namespace CloudBuilder.AI.Utility .Replace("‘", "'") .Replace("’", "'"); - // 3. 核心判断规则: - var hasQuote = Regex.IsMatch(cleanedText, @""".+?""") || - Regex.IsMatch(cleanedText, @"[::,,]\s*"""); // 冒号/逗号+引号(说话标记) + // 1. 存在说话引导标记(冒号/逗号 + 引号) + bool hasDialogueTag = Regex.IsMatch(cleanedText, @"[::,,]\s*"""); - var hasSingleQuote = Regex.IsMatch(cleanedText, @"'[^']+'"); + // 2. 提取所有双引号内容,逐个判断是否为有效对话 + var quotedMatches = Regex.Matches(cleanedText, @"""(?.*?)"""); + bool hasValidDialogue = false; + foreach (Match m in quotedMatches) + { + string content = m.Groups["content"].Value.Trim(); + if (string.IsNullOrEmpty(content)) continue; - // 4. 排除仅含引号符号的无效情况(修复:只匹配替换后的符号) - var onlyQuote = Regex.IsMatch(cleanedText, @"^\s*[""']{1,2}\s*$"); + // 规则A:包含典型对话结尾标点 + bool endsWithDialogPunct = Regex.IsMatch(content, @"[。!??!…\.~~、,,]$"); + // 规则B:长度至少4个字符(或按中文字数算) + bool isLongEnough = content.Length >= 4; - // 最终判断 - return (hasQuote || hasSingleQuote) && !onlyQuote; + if (endsWithDialogPunct || isLongEnough) + { + hasValidDialogue = true; + break; + } + } + + // 3. 单引号内容同理(可省略或同逻辑) + + // 最终:有说话标记 或 存在有效对话内容,且不是“仅由引号符号组成” + bool onlyQuote = Regex.IsMatch(cleanedText, @"^\s*[""']{1,2}\s*$"); + return (hasDialogueTag || hasValidDialogue) && !onlyQuote; } } } diff --git a/CloudBuilder.AI/Utility/SoVITSTtsVoiceRandom.cs b/CloudBuilder.AI/Utility/SoVITSTtsVoiceRandom.cs index e7c3762..daa251d 100644 --- a/CloudBuilder.AI/Utility/SoVITSTtsVoiceRandom.cs +++ b/CloudBuilder.AI/Utility/SoVITSTtsVoiceRandom.cs @@ -183,12 +183,12 @@ namespace CloudBuilder.AI.Utility // -------------------- 常用组合快捷方法 -------------------- public static SoVITSTtsVoice GetRandomElderlyMale() => GetRandomByGenderAndAge("男性", "老年人"); public static SoVITSTtsVoice GetRandomElderlyFemale() => GetRandomByGenderAndAge("女性", "老年人"); - public static SoVITSTtsVoice GetRandomMiddleAgedMale() => GetRandomByGenderAndAge("男性", "中年"); - public static SoVITSTtsVoice GetRandomMiddleAgedFemale() => GetRandomByGenderAndAge("女性", "中年"); - public static SoVITSTtsVoice GetRandomYoungMale() => GetRandomByGenderAndAge("男性", "青年"); - public static SoVITSTtsVoice GetRandomYoungFemale() => GetRandomByGenderAndAge("女性", "青年"); - public static SoVITSTtsVoice GetRandomChildMale() => GetRandomByGenderAndAge("男性", "儿童"); - public static SoVITSTtsVoice GetRandomChildFemale() => GetRandomByGenderAndAge("女性", "儿童"); + public static SoVITSTtsVoice GetRandomMiddleAgedMale() => GetRandomByGenderAndAge("男性", "男中年"); + public static SoVITSTtsVoice GetRandomMiddleAgedFemale() => GetRandomByGenderAndAge("女性", "女中年"); + public static SoVITSTtsVoice GetRandomYoungMale() => GetRandomByGenderAndAge("男性", "男青年"); + public static SoVITSTtsVoice GetRandomYoungFemale() => GetRandomByGenderAndAge("女性", "女青年"); + public static SoVITSTtsVoice GetRandomChildMale() => GetRandomByGenderAndAge("男性", "男儿童"); + public static SoVITSTtsVoice GetRandomChildFemale() => GetRandomByGenderAndAge("女性", "女儿童"); // -------------------- 排除指定音色的扩展(可选)--------------------