using CloudBuilder.AI.Entity; using CloudBuilder.Core.DatabaseAccessor.Entity; using CloudBuilder.Topshelf.Python; namespace CloudBuilder.Topshelf.Utility { public class SpeakerAnalysisHelper { public string GetPerson(Hanlp2 hanlp, string text) { string depRoot = hanlp.GetDepRoot(text); List ners = hanlp.GetHanlpNer(text); List> srls = hanlp.GetHanlpSrl(text); List cons = hanlp.GetHanlpCon(text); SpeakerAnalysisHelper speakerFeatureLearner = new SpeakerAnalysisHelper(); List ps = new List(); string arg1 = string.Empty; string person = GetArg0(hanlp, depRoot, ners, srls, cons, ref arg1); //同边同时有的,放在最前面 if (!string.IsNullOrEmpty(person) && ners != null && ners.Where(x => x.Type.Contains("PERSON")).Count() > 0) { if (ners.Where(x => x.Text.Contains(person)).Any()) ps.Add(person); } //如果前面的不一样,那么加入人名 if (ners != null && ners.Where(x => x.Type.Contains("PERSON")).Count() > 0) { foreach (string namedEntity in ners.Where(x => x.Type.Contains("PERSON")).Select(x => x.Text).Distinct().ToArray()) { if (namedEntity.Length > 1) { if (!ps.Contains(namedEntity)) ps.Add(namedEntity); } else { HanlpConstituencyNode[] nodes; HanlpConstituencyNode nodef; string feature = null; nodes = HanlpResultHelper.GetNodeByLabel(cons, namedEntity); if (nodes == null || nodes.Length == 0) continue; string[] nps = new string[] { "NP", "DNP" }; //如果存在,找到父节点上的NP foreach (HanlpConstituencyNode node in nodes) { nodef = HanlpResultHelper.GetNodeTopLabelById(cons, node.FatherId, nps); if (nodef != null) { feature = HanlpResultHelper.GetNodeBottomLabelById(cons, nodef.ItemId, nps); if (string.IsNullOrEmpty(feature)) continue; } else if (string.IsNullOrEmpty(feature)) { feature = node.Label; } //如果下一个是“NP、DNP”,可以串起来,跟上面的GetNodeBottomLabelById类似 if (feature.Length == 1) { HanlpConstituencyNode[] nodesSon; nodef = HanlpResultHelper.GetNodeById(cons, node.ItemId + 1); if (nodef != null) { if (nps.Contains(nodef.Label)) { nodesSon = HanlpResultHelper.GetNodeSonById(cons, nodef.ItemId); if (nodesSon != null && nodesSon.Where(x => x.Children == 0).Any()) { feature += string.Concat(nodesSon.Select(x => x.Label)); } } } } if (feature.Length == 1) { var bs = HanlpResultHelper.GetNodeBrothersById(cons, node.ItemId); if (bs != null && bs.Where(x => x.Children == 0).Any()) { feature += string.Concat(bs.Select(x => x.Label)); } } if (feature.Contains(namedEntity) && !ps.Contains(feature)) ps.Add(feature); } } } } //如果主语==动作发出者 if (!string.IsNullOrEmpty(person) && !ps.Contains(person) && !ps.Contains(string.Format("[{0}]", person))) ps.Add(string.Format("【{0}】", person)); //动作接受者 //if (!string.IsNullOrEmpty(arg1) && !ps.Contains(arg1)) ps.Add(arg1); person = QuotedString.ToDelimiteredList(ps.ToArray()); return person; } public string GetAgr0(Hanlp2 hanlp, string text) { string depRoot = hanlp.GetDepRoot(text); List ners = hanlp.GetHanlpNer(text); List> srls = hanlp.GetHanlpSrl(text); List cons = hanlp.GetHanlpCon(text); SpeakerAnalysisHelper speakerFeatureLearner = new SpeakerAnalysisHelper(); List ps = new List(); string arg1 = string.Empty; return GetArg0(hanlp, depRoot, ners, srls, cons, ref arg1); } public string GetArg0(Hanlp2 hanlp, string depRoot, List ners, List> srls, List cons, ref string arg1) { string arg0 = string.Empty; if (!string.IsNullOrEmpty(depRoot) && srls != null && srls.Count() > 0) { foreach (var srl in srls) { if (srl.Where(x => x.Text == depRoot).Any()) { foreach (var mrp in srl) { if (mrp.Type.ToUpper() != "ARG0") continue; if (ners != null && ners.Where(x => mrp.Text.Contains(x.Text)).Any() && ners.Where(x => mrp.Text.Contains(x.Text)).FirstOrDefault().Text.Length > 1) return ners.Where(x => mrp.Text.Contains(x.Text)).FirstOrDefault().Text; if (mrp.Text.Length > 4) { arg0 = GetAgr0(hanlp, mrp.Text); } else if (mrp.Text.Length > 1) { if (ners == null || ners.Where(x => x.Type.Contains("PERSON")).Count() == 0) return mrp.Text; } } } /*暂不处理arg1 foreach (var mrp in srl) { if (mrp.Type.ToUpper() != "ARG0") continue; if (mrp.Text.Length > 4) { var temp = GetAgr0(hanlp, mrp.Text); if (!string.IsNullOrEmpty(temp)) { arg1 = temp; return arg0; } } else { arg1 = mrp.Text; } } */ } } return arg0; } public void MatchPerson(AiSentenceViewEntity[] aiSentences, IRepository repositoryAiSentenceEntity) { if (AiChapterPersons == null || AiChapterPersons.Length == 0) return; AiSentenceEntity aiSentence; string dialogueName = string.Empty; int totalLines = aiSentences.Length; int processedLines = 0; // 已处理行数计数器 Console.WriteLine($"\r"); foreach (AiSentenceViewEntity aiSentenceView in aiSentences) { aiSentenceView.PersonName = null; processedLines++; ConsoleOutput.UpdateProgress(processedLines, totalLines); if (aiSentenceView.DialogueIndc != YesNoPolicy.YES) continue; //A说:“B,你好!”/“A,你好!”B对A说。 if (aiSentences.Where(x => x.ChapterId == aiSentenceView.ChapterId && x.ParagraphId == aiSentenceView.ParagraphId).Count() > 1) { dialogueName = string.Empty; //提取对话中的人物 if (AiChapterPersons.Where(x => x.ChapterId == aiSentenceView.ChapterId && x.ParagraphIndex == aiSentenceView.ParagraphId && aiSentenceView.SentenceIndex == x.SentenceIndex).Any()) { dialogueName = AiChapterPersons.Where(x => x.ChapterId == aiSentenceView.ChapterId && x.ParagraphIndex == aiSentenceView.ParagraphId && aiSentenceView.SentenceIndex == x.SentenceIndex).FirstOrDefault().PersonName; } //提取对话段落中,非对话内容中的人物 if (AiChapterPersons.Where(x => x.ChapterId == aiSentenceView.ChapterId && x.ParagraphIndex == aiSentenceView.ParagraphId && aiSentenceView.SentenceIndex != x.SentenceIndex && x.PersonName != dialogueName).Any()) { aiSentenceView.PersonName = AiChapterPersons.Where(x => x.ChapterId == aiSentenceView.ChapterId && x.ParagraphIndex == aiSentenceView.ParagraphId && aiSentenceView.SentenceIndex != x.SentenceIndex && x.PersonName != dialogueName).OrderBy(x => x.OrderIndex).FirstOrDefault().PersonName; if (aiSentenceView.PersonName.Contains("【")) { if (AiChapterPersons.Where(x => !x.PersonName.Contains("【") && x.PersonName.Contains(aiSentenceView.PersonName.Replace("【", "").Replace("】", ""))).Any()) aiSentenceView.PersonName = AiChapterPersons.Where(x => !x.PersonName.Contains("【") && x.PersonName.Contains(aiSentenceView.PersonName.Replace("【", "").Replace("】", ""))).FirstOrDefault()!.PersonName; } } //说话者在前面 if (aiSentenceView.SentenceIndex == 2) { //如果没有提取到人物,优先向前,再向后 if (string.IsNullOrEmpty(aiSentenceView.PersonName)) { aiSentenceView.PersonName = GetForwardName(dialogueName, aiSentenceView, aiSentences); } //如果没有提取到人物,优先向前,再向后 if (string.IsNullOrEmpty(aiSentenceView.PersonName)) { aiSentenceView.PersonName = GetBackwardName(dialogueName, aiSentenceView, aiSentences); } } else { if (string.IsNullOrEmpty(aiSentenceView.PersonName)) { aiSentenceView.PersonName = GetBackwardName(dialogueName, aiSentenceView, aiSentences); } if (string.IsNullOrEmpty(aiSentenceView.PersonName)) { aiSentenceView.PersonName = GetForwardName(dialogueName, aiSentenceView, aiSentences); } } //如果“你好”,前后面有說話者的,单独内容,下下文不能是同一個人 dialogueName = aiSentenceView.PersonName; //dialogueName = string.Empty; } else { if (AiChapterPersons.Where(x => x.ChapterId == aiSentenceView.ChapterId && x.ParagraphIndex == aiSentenceView.ParagraphId && aiSentenceView.SentenceIndex == x.SentenceIndex).Any()) { dialogueName = AiChapterPersons.Where(x => x.ChapterId == aiSentenceView.ChapterId && x.ParagraphIndex == aiSentenceView.ParagraphId && aiSentenceView.SentenceIndex == x.SentenceIndex).FirstOrDefault().PersonName; } //"你好,"張三說道 //"你好" if (aiSentences.Where(x => x.ChapterId == aiSentenceView.ChapterId && x.ParagraphId == aiSentenceView.ParagraphId - 1 && x.DialogueIndc == YesNoPolicy.NO).Count() > 0 && aiSentences.Where(x => x.ChapterId == aiSentenceView.ChapterId && x.ParagraphId == aiSentenceView.ParagraphId + 1 && x.DialogueIndc == YesNoPolicy.YES).Count() == 1) dialogueName = string.Empty; //如果没有提取到人物,优先向前,再向后 aiSentenceView.PersonName = GetForwardName(dialogueName, aiSentenceView, aiSentences, 3); //如果没有提取到人物,优先向前,再向后 if (string.IsNullOrEmpty(aiSentenceView.PersonName)) { aiSentenceView.PersonName = GetBackwardName(dialogueName, aiSentenceView, aiSentences, 5); } //如果“你好”,前后面有說話者的,单独内容,下下文不能是同一個人 dialogueName = aiSentenceView.PersonName; } aiSentence = new AiSentenceEntity(); ObjectCopyHelper.Copy(aiSentenceView, aiSentence); repositoryAiSentenceEntity.UpdateNow(aiSentence); } } public string GetForwardName(string excludeName, AiSentenceViewEntity aiSentenceView, AiSentenceViewEntity[] aiSentences, int maxLength = 10) { foreach (var cp in AiChapterPersons.Where(x => x.DialogueIndc != YesNoPolicy.YES && x.ChapterId == aiSentenceView.ChapterId && x.ParagraphIndex < aiSentenceView.ParagraphId && x.OrderIndex == 1).OrderByDescending(x => x.ParagraphIndex).ToArray()) { if (aiSentences.Where(x => x.ChapterId == cp.ChapterId && x.ParagraphId == cp.ParagraphIndex && x.DialogueIndc == YesNoPolicy.YES).Any()) { if (aiSentences.Where(x => x.ChapterId == cp.ChapterId && x.ParagraphId == cp.ParagraphIndex && x.DialogueIndc == YesNoPolicy.NO).Any()) { if (string.IsNullOrEmpty(cp.PersonName) || cp.PersonName.Contains("【")) continue; if (!string.IsNullOrEmpty(excludeName) && excludeName == cp.PersonName) continue; if (!string.IsNullOrEmpty(excludeName) && AreSamePerson(excludeName, cp.PersonName)) continue; if (aiSentenceView.ParagraphId - cp.ParagraphIndex > maxLength) break; return cp.PersonName; } break; } if (string.IsNullOrEmpty(cp.PersonName) || cp.PersonName.Contains("【")) continue; if (!string.IsNullOrEmpty(excludeName) && excludeName == cp.PersonName) continue; if (!string.IsNullOrEmpty(excludeName) && AreSamePerson(excludeName, cp.PersonName)) continue; if (aiSentenceView.ParagraphId - cp.ParagraphIndex > maxLength) break; return cp.PersonName; } return null; } public string GetBackwardName(string excludeName, AiSentenceViewEntity aiSentenceView, AiSentenceViewEntity[] aiSentences, int maxLength = 10) { foreach (var cp in AiChapterPersons.Where(x => x.ChapterId == aiSentenceView.ChapterId && x.ParagraphIndex > aiSentenceView.ParagraphId).OrderBy(x => x.ParagraphIndex).ThenBy(x => x.OrderIndex).ToArray()) { if (aiSentences.Where(x => x.ChapterId == cp.ChapterId && x.ParagraphId == cp.ParagraphIndex && x.DialogueIndc == YesNoPolicy.YES).Any()) break; if (string.IsNullOrEmpty(cp.PersonName) || cp.PersonName.Contains("【")) continue; if (!string.IsNullOrEmpty(excludeName) && aiSentenceView.PersonName == cp.PersonName) continue; if (!string.IsNullOrEmpty(excludeName) && AreSamePerson(excludeName, cp.PersonName)) continue; if (cp.ParagraphIndex - aiSentenceView.ParagraphId > maxLength) break; return cp.PersonName; } return null; } public bool AreSamePerson(string nameA, string nameB) { if (string.IsNullOrWhiteSpace(nameA) || string.IsNullOrWhiteSpace(nameB)) return false; if (nameA.Substring(0, 1) == nameB.Substring(0, 1) && nameA != nameB) return false; string surnameA = ExtractSurname(nameA); string surnameB = ExtractSurname(nameB); return !string.IsNullOrEmpty(surnameA) && surnameA == surnameB; } private string ExtractSurname(string name) { // 优先提取第一个字符(常见“姓+后缀”模式) if (name.Length > 0 && CommonSurnames.Contains(name[0].ToString())) return name[0].ToString(); // 对于“韩胖子”这类姓不在首位的情况,扫描整个字符串 foreach (char c in name) { if (CommonSurnames.Contains(c.ToString())) return c.ToString(); } return null; } public AiSentenceEntity[] AiSentences { get; set; } public AiChapterPersonEntity[] AiChapterPersons { get; set; } public List CommonSurnames { get; set; } } } /* “文本解析→人物提取→上下文匹配→结果输出” graph TD A[对话行] --> B{规则1:文本特征匹配}; B -->|匹配成功| C[直接赋值说话者]; B -->|匹配失败| D{规则2:紧邻旁白匹配}; D -->|匹配成功| C; D -->|匹配失败| E{规则3:段落内最近人物}; E -->|匹配成功| C; E -->|匹配失败| F[赋值默认值+标记异常]; */