Files
meeting-assistant/MeetingAssistant/Recording/SpeakerAudioSampleCollector.cs
T
codex 7777b349a5
PR and Push Build/Test / build-and-test (push) Successful in 6m37s
Archive summary refinements and profile switching
2026-05-27 23:11:21 +02:00

115 lines
3.3 KiB
C#

using MeetingAssistant.Speakers;
using MeetingAssistant.Transcription;
namespace MeetingAssistant.Recording;
internal sealed class SpeakerAudioSampleCollector
{
private readonly object gate = new();
private readonly RollingAudioBuffer audioBuffer;
private readonly Dictionary<string, List<SpeakerAudioSample>> samplesBySpeaker = new(StringComparer.OrdinalIgnoreCase);
private readonly int maxSamplesPerSpeaker;
public SpeakerAudioSampleCollector(TimeSpan bufferDuration, int maxSamplesPerSpeaker)
{
audioBuffer = new RollingAudioBuffer(bufferDuration);
this.maxSamplesPerSpeaker = Math.Max(1, maxSamplesPerSpeaker);
}
public void AppendAudio(AudioChunk chunk)
{
audioBuffer.Append(chunk);
}
public void Reset()
{
lock (gate)
{
samplesBySpeaker.Clear();
audioBuffer.Reset();
}
}
public void TryAdd(TranscriptionSegment segment)
{
if (!IsDiarizedSpeaker(segment.Speaker))
{
return;
}
var score = Score(segment);
if (score <= 0)
{
return;
}
var wavBytes = audioBuffer.TryExtractWav(segment.Start, segment.End);
if (wavBytes.Length == 0)
{
return;
}
var sample = new SpeakerAudioSample(segment.Speaker, segment, wavBytes, score);
lock (gate)
{
if (!samplesBySpeaker.TryGetValue(segment.Speaker, out var samples))
{
samples = [];
samplesBySpeaker[segment.Speaker] = samples;
}
samples.Add(sample);
var bestSamples = samples
.OrderByDescending(candidate => candidate.Score)
.Take(maxSamplesPerSpeaker)
.ToList();
samples.Clear();
samples.AddRange(bestSamples);
}
}
public IReadOnlyList<SpeakerAudioSample> Snapshot()
{
lock (gate)
{
return samplesBySpeaker.Values
.SelectMany(samples => samples)
.OrderBy(sample => sample.Speaker, StringComparer.OrdinalIgnoreCase)
.ThenByDescending(sample => sample.Score)
.ToList();
}
}
private static bool IsDiarizedSpeaker(string speaker)
{
return !string.IsNullOrWhiteSpace(speaker) &&
!string.Equals(speaker, "Unknown", StringComparison.OrdinalIgnoreCase);
}
private static double Score(TranscriptionSegment segment)
{
var durationSeconds = (segment.End - segment.Start).TotalSeconds;
if (durationSeconds < 2 || durationSeconds > 30)
{
return 0;
}
var words = segment.Text
.Split(' ', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
.Length;
if (words < 3 || words > 80)
{
return 0;
}
var durationScore = 1 - Math.Min(Math.Abs(durationSeconds - 8) / 8, 1);
var wordScore = 1 - Math.Min(Math.Abs(words - 18) / 18.0, 1);
var sentenceBonus = segment.Text.TrimEnd().EndsWith('.') ||
segment.Text.TrimEnd().EndsWith('?') ||
segment.Text.TrimEnd().EndsWith('!')
? 5
: 0;
return durationScore * 70 + wordScore * 30 + sentenceBonus;
}
}