Public Access
This commit is contained in:
@@ -1334,7 +1334,11 @@ public sealed class MeetingRecordingCoordinator
|
||||
Options = options;
|
||||
LaunchProfileName = launchProfileName;
|
||||
pipelines.Add(pipeline);
|
||||
speakerSampleCollector = new SpeakerAudioSampleCollector(liveSampleBufferDuration, maxSpeakerSamples);
|
||||
speakerSampleCollector = new SpeakerAudioSampleCollector(
|
||||
liveSampleBufferDuration,
|
||||
maxSpeakerSamples,
|
||||
options.SpeakerIdentification.MinimumSampleSpeechDuration,
|
||||
options.SpeakerIdentification.MaximumSampleSegmentGap);
|
||||
}
|
||||
|
||||
public CancellationTokenSource CaptureCancellationSource { get; }
|
||||
|
||||
@@ -9,11 +9,33 @@ internal sealed class SpeakerAudioSampleCollector
|
||||
private readonly RollingAudioBuffer audioBuffer;
|
||||
private readonly Dictionary<string, List<SpeakerAudioSample>> samplesBySpeaker = new(StringComparer.OrdinalIgnoreCase);
|
||||
private readonly int maxSamplesPerSpeaker;
|
||||
private readonly TimeSpan minimumUninterruptedSpeechDuration;
|
||||
private readonly TimeSpan maximumSegmentGap;
|
||||
private PendingSpeakerSpan? pendingSpan;
|
||||
|
||||
public SpeakerAudioSampleCollector(TimeSpan bufferDuration, int maxSamplesPerSpeaker)
|
||||
: this(
|
||||
bufferDuration,
|
||||
maxSamplesPerSpeaker,
|
||||
TimeSpan.FromSeconds(30),
|
||||
TimeSpan.FromSeconds(1))
|
||||
{
|
||||
}
|
||||
|
||||
public SpeakerAudioSampleCollector(
|
||||
TimeSpan bufferDuration,
|
||||
int maxSamplesPerSpeaker,
|
||||
TimeSpan minimumUninterruptedSpeechDuration,
|
||||
TimeSpan maximumSegmentGap)
|
||||
{
|
||||
audioBuffer = new RollingAudioBuffer(bufferDuration);
|
||||
this.maxSamplesPerSpeaker = Math.Max(1, maxSamplesPerSpeaker);
|
||||
this.minimumUninterruptedSpeechDuration = minimumUninterruptedSpeechDuration > TimeSpan.Zero
|
||||
? minimumUninterruptedSpeechDuration
|
||||
: TimeSpan.Zero;
|
||||
this.maximumSegmentGap = maximumSegmentGap >= TimeSpan.Zero
|
||||
? maximumSegmentGap
|
||||
: TimeSpan.Zero;
|
||||
}
|
||||
|
||||
public void AppendAudio(AudioChunk chunk)
|
||||
@@ -26,6 +48,7 @@ internal sealed class SpeakerAudioSampleCollector
|
||||
lock (gate)
|
||||
{
|
||||
samplesBySpeaker.Clear();
|
||||
pendingSpan = null;
|
||||
audioBuffer.Reset();
|
||||
}
|
||||
}
|
||||
@@ -37,25 +60,31 @@ internal sealed class SpeakerAudioSampleCollector
|
||||
return;
|
||||
}
|
||||
|
||||
var score = Score(segment);
|
||||
TranscriptionSegment sampleSegment;
|
||||
lock (gate)
|
||||
{
|
||||
sampleSegment = ExtendPendingSpan(segment);
|
||||
}
|
||||
|
||||
var score = Score(sampleSegment, minimumUninterruptedSpeechDuration);
|
||||
if (score <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var wavBytes = audioBuffer.TryExtractWav(segment.Start, segment.End);
|
||||
var wavBytes = audioBuffer.TryExtractWav(sampleSegment.Start, sampleSegment.End);
|
||||
if (wavBytes.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var sample = new SpeakerAudioSample(segment.Speaker, segment, wavBytes, score);
|
||||
var sample = new SpeakerAudioSample(sampleSegment.Speaker, sampleSegment, wavBytes, score);
|
||||
lock (gate)
|
||||
{
|
||||
if (!samplesBySpeaker.TryGetValue(segment.Speaker, out var samples))
|
||||
if (!samplesBySpeaker.TryGetValue(sampleSegment.Speaker, out var samples))
|
||||
{
|
||||
samples = [];
|
||||
samplesBySpeaker[segment.Speaker] = samples;
|
||||
samplesBySpeaker[sampleSegment.Speaker] = samples;
|
||||
}
|
||||
|
||||
samples.Add(sample);
|
||||
@@ -86,10 +115,29 @@ internal sealed class SpeakerAudioSampleCollector
|
||||
!string.Equals(speaker, "Unknown", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
private static double Score(TranscriptionSegment segment)
|
||||
private TranscriptionSegment ExtendPendingSpan(TranscriptionSegment segment)
|
||||
{
|
||||
if (pendingSpan is null ||
|
||||
!SpeakerSampleSpanSelector.CanExtend(pendingSpan.Speaker, pendingSpan.End, segment, maximumSegmentGap))
|
||||
{
|
||||
pendingSpan = new PendingSpeakerSpan(
|
||||
segment.Speaker,
|
||||
segment.Start,
|
||||
segment.End,
|
||||
[segment.Text]);
|
||||
return pendingSpan.ToSegment();
|
||||
}
|
||||
|
||||
pendingSpan = pendingSpan.Extend(segment);
|
||||
return pendingSpan.ToSegment();
|
||||
}
|
||||
|
||||
private static double Score(
|
||||
TranscriptionSegment segment,
|
||||
TimeSpan minimumUninterruptedSpeechDuration)
|
||||
{
|
||||
var durationSeconds = (segment.End - segment.Start).TotalSeconds;
|
||||
if (durationSeconds < 2 || durationSeconds > 30)
|
||||
if (durationSeconds < minimumUninterruptedSpeechDuration.TotalSeconds)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
@@ -97,13 +145,13 @@ internal sealed class SpeakerAudioSampleCollector
|
||||
var words = segment.Text
|
||||
.Split(' ', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
|
||||
.Length;
|
||||
if (words < 3 || words > 80)
|
||||
if (words < 3)
|
||||
{
|
||||
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 durationScore = Math.Min(durationSeconds / Math.Max(1, minimumUninterruptedSpeechDuration.TotalSeconds), 2);
|
||||
var wordScore = Math.Min(words / 60.0, 1);
|
||||
var sentenceBonus = segment.Text.TrimEnd().EndsWith('.') ||
|
||||
segment.Text.TrimEnd().EndsWith('?') ||
|
||||
segment.Text.TrimEnd().EndsWith('!')
|
||||
@@ -111,4 +159,29 @@ internal sealed class SpeakerAudioSampleCollector
|
||||
: 0;
|
||||
return durationScore * 70 + wordScore * 30 + sentenceBonus;
|
||||
}
|
||||
|
||||
private sealed record PendingSpeakerSpan(
|
||||
string Speaker,
|
||||
TimeSpan Start,
|
||||
TimeSpan End,
|
||||
IReadOnlyList<string> TextParts)
|
||||
{
|
||||
public PendingSpeakerSpan Extend(TranscriptionSegment segment)
|
||||
{
|
||||
return this with
|
||||
{
|
||||
End = segment.End > End ? segment.End : End,
|
||||
TextParts = TextParts.Append(segment.Text).ToList()
|
||||
};
|
||||
}
|
||||
|
||||
public TranscriptionSegment ToSegment()
|
||||
{
|
||||
return new TranscriptionSegment(
|
||||
Start,
|
||||
End,
|
||||
Speaker,
|
||||
string.Join(' ', TextParts.Where(part => !string.IsNullOrWhiteSpace(part))));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user