CloudBuilder.Topshelf/Utility/SpeakerAnalysisHelper.cs
owenchen dbfa9bd1e8 ow
2026-06-04 16:02:14 +08:00

369 lines
18 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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<NamedEntity> ners = hanlp.GetHanlpNer(text);
List<List<MeaningRepresentationParsingEntity>> srls = hanlp.GetHanlpSrl(text);
List<HanlpConstituencyNode> cons = hanlp.GetHanlpCon(text);
SpeakerAnalysisHelper speakerFeatureLearner = new SpeakerAnalysisHelper();
List<string> ps = new List<string>();
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<NamedEntity> ners = hanlp.GetHanlpNer(text);
List<List<MeaningRepresentationParsingEntity>> srls = hanlp.GetHanlpSrl(text);
List<HanlpConstituencyNode> cons = hanlp.GetHanlpCon(text);
SpeakerAnalysisHelper speakerFeatureLearner = new SpeakerAnalysisHelper();
List<string> ps = new List<string>();
string arg1 = string.Empty;
return GetArg0(hanlp, depRoot, ners, srls, cons, ref arg1);
}
public string GetArg0(Hanlp2 hanlp, string depRoot, List<NamedEntity> ners, List<List<MeaningRepresentationParsingEntity>> srls, List<HanlpConstituencyNode> 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<AiSentenceEntity> 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<string> CommonSurnames { get; set; }
}
}
/*
“文本解析→人物提取→上下文匹配→结果输出”
graph TD
A[对话行] --> B{规则1文本特征匹配};
B -->|匹配成功| C[直接赋值说话者];
B -->|匹配失败| D{规则2紧邻旁白匹配};
D -->|匹配成功| C;
D -->|匹配失败| E{规则3段落内最近人物};
E -->|匹配成功| C;
E -->|匹配失败| F[赋值默认值+标记异常];
*/