feat: add local Resemblyzer speaker recognition
PR and Push Build/Test / build-and-test (push) Successful in 12m37s

This commit is contained in:
2026-09-11 13:49:27 +02:00
parent 43fc8aaec0
commit f86af983e8
48 changed files with 5250 additions and 273 deletions
@@ -2,10 +2,24 @@ namespace MeetingAssistant.Speakers;
internal static class SpeakerIdentityMerger
{
public static void MergeIntoAndPrune(
SpeakerIdentity target,
SpeakerIdentity source,
int maxSnippets,
int maxVoiceVectors,
ResemblyzerVoiceVectorOutlierPruner outlierPruner)
{
outlierPruner.Prune(target);
outlierPruner.Prune(source);
MergeInto(target, source, maxSnippets, maxVoiceVectors);
outlierPruner.Prune(target);
}
public static void MergeInto(
SpeakerIdentity target,
SpeakerIdentity source,
int maxSnippets)
int maxSnippets,
int maxVoiceVectors = int.MaxValue)
{
AddAlias(target, source.CanonicalName);
foreach (var alias in source.Aliases)
@@ -35,6 +49,26 @@ internal static class SpeakerIdentityMerger
.ToList();
target.Snippets.Clear();
target.Snippets.AddRange(retainedSnippets);
var retainedVectors = target.VoiceVectors
.Concat(source.VoiceVectors)
.GroupBy(vector => vector.Fingerprint, StringComparer.Ordinal)
.Select(group => group.OrderByDescending(vector => vector.CreatedAt).First())
.OrderByDescending(vector => vector.CreatedAt)
.ThenBy(vector => vector.Fingerprint, StringComparer.Ordinal)
.Take(Math.Max(1, maxVoiceVectors))
.Select(vector => new SpeakerVoiceVector
{
SpeakerIdentity = target,
ModelId = vector.ModelId,
Dimensions = vector.Dimensions,
VectorBytes = vector.VectorBytes.ToArray(),
Fingerprint = vector.Fingerprint,
CreatedAt = vector.CreatedAt
})
.ToList();
target.VoiceVectors.Clear();
target.VoiceVectors.AddRange(retainedVectors);
target.UpdatedAt = DateTimeOffset.UtcNow;
}