Public Access
Add inactivity safeguard and speaker diagnostics
PR and Push Build/Test / build-and-test (push) Failing after 8m31s
PR and Push Build/Test / build-and-test (push) Failing after 8m31s
This commit is contained in:
@@ -30,14 +30,29 @@ public sealed class PyannoteSpeakerIdentityMatchValidator : ISpeakerIdentityMatc
|
||||
}
|
||||
|
||||
var segments = await DiarizeBytesAsync(wavBytes, cancellationToken);
|
||||
var result = HasSingleSpeaker(segments);
|
||||
if (!result)
|
||||
var result = AnalyzeSingleSpeakerCoverage(segments);
|
||||
if (!result.Accepted)
|
||||
{
|
||||
logger.LogInformation(
|
||||
"pyannote rejected speaker identity sample because it did not contain one dominant speaker");
|
||||
"pyannote rejected speaker identity sample because it did not contain one dominant speaker: segments {SegmentCount}, speakers {SpeakerCount}, dominant speaker {DominantSpeaker}, coverage {Coverage:P2}, required coverage {RequiredCoverage:P2}, duration {Duration}",
|
||||
segments.Count,
|
||||
result.SpeakerCount,
|
||||
result.DominantSpeaker,
|
||||
result.Coverage,
|
||||
Math.Clamp(options.MinimumSingleSpeakerCoverage, 0, 1),
|
||||
result.TotalDuration);
|
||||
return false;
|
||||
}
|
||||
|
||||
return result;
|
||||
logger.LogInformation(
|
||||
"pyannote accepted speaker identity sample: segments {SegmentCount}, speakers {SpeakerCount}, dominant speaker {DominantSpeaker}, coverage {Coverage:P2}, duration {Duration}, sample bytes {SampleBytes}",
|
||||
segments.Count,
|
||||
result.SpeakerCount,
|
||||
result.DominantSpeaker,
|
||||
result.Coverage,
|
||||
result.TotalDuration,
|
||||
wavBytes.Length);
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<bool> ValidateMatchAsync(
|
||||
@@ -63,10 +78,20 @@ public sealed class PyannoteSpeakerIdentityMatchValidator : ISpeakerIdentityMatc
|
||||
TimeSpan.FromSeconds(1));
|
||||
if (layout.UnknownSegment is null || layout.KnownSegments.Count == 0)
|
||||
{
|
||||
logger.LogInformation(
|
||||
"pyannote rejected speaker identity match for {DiarizedSpeaker} and identity {IdentityId} because no readable composite snippets were available",
|
||||
request.DiarizedSpeaker,
|
||||
request.IdentityId);
|
||||
return false;
|
||||
}
|
||||
|
||||
var segments = await DiarizePathAsync(tempPath, cancellationToken);
|
||||
logger.LogInformation(
|
||||
"pyannote diarized speaker identity match for {DiarizedSpeaker} and identity {IdentityId}: {SegmentCount} segment(s), {KnownSnippetCount} known snippet(s)",
|
||||
request.DiarizedSpeaker,
|
||||
request.IdentityId,
|
||||
segments.Count,
|
||||
layout.KnownSegments.Count);
|
||||
var unknownSpeaker = FindBestSpeaker(layout.UnknownSegment.Value, segments);
|
||||
if (string.IsNullOrWhiteSpace(unknownSpeaker))
|
||||
{
|
||||
@@ -98,11 +123,22 @@ public sealed class PyannoteSpeakerIdentityMatchValidator : ISpeakerIdentityMatc
|
||||
if (!accepted)
|
||||
{
|
||||
logger.LogInformation(
|
||||
"pyannote rejected speaker identity match for {DiarizedSpeaker} and identity {IdentityId}: {Matching}/{Compared} known sample(s) matched",
|
||||
"pyannote rejected speaker identity match for {DiarizedSpeaker} and identity {IdentityId}: {Matching}/{Compared} known sample(s) matched, required ratio {RequiredRatio:P2}",
|
||||
request.DiarizedSpeaker,
|
||||
request.IdentityId,
|
||||
matching,
|
||||
compared);
|
||||
compared,
|
||||
minimumRatio);
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.LogInformation(
|
||||
"pyannote accepted speaker identity match for {DiarizedSpeaker} and identity {IdentityId}: {Matching}/{Compared} known sample(s) matched, required ratio {RequiredRatio:P2}",
|
||||
request.DiarizedSpeaker,
|
||||
request.IdentityId,
|
||||
matching,
|
||||
compared,
|
||||
minimumRatio);
|
||||
}
|
||||
|
||||
return accepted;
|
||||
@@ -162,7 +198,7 @@ public sealed class PyannoteSpeakerIdentityMatchValidator : ISpeakerIdentityMatc
|
||||
cancellationToken);
|
||||
}
|
||||
|
||||
private bool HasSingleSpeaker(IReadOnlyList<TranscriptionSegment> segments)
|
||||
private SingleSpeakerCoverage AnalyzeSingleSpeakerCoverage(IReadOnlyList<TranscriptionSegment> segments)
|
||||
{
|
||||
var durationsBySpeaker = segments
|
||||
.Where(segment => !string.IsNullOrWhiteSpace(segment.Speaker) && segment.End > segment.Start)
|
||||
@@ -176,17 +212,29 @@ public sealed class PyannoteSpeakerIdentityMatchValidator : ISpeakerIdentityMatc
|
||||
.ToList();
|
||||
if (durationsBySpeaker.Count == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (durationsBySpeaker.Count == 1)
|
||||
{
|
||||
return true;
|
||||
return new SingleSpeakerCoverage(false, 0, null, 0, TimeSpan.Zero);
|
||||
}
|
||||
|
||||
var total = durationsBySpeaker.Sum(group => group.Duration);
|
||||
var dominant = durationsBySpeaker.Max(group => group.Duration);
|
||||
return total > 0 && dominant / total >= Math.Clamp(options.MinimumSingleSpeakerCoverage, 0, 1);
|
||||
var dominant = durationsBySpeaker.OrderByDescending(group => group.Duration).First();
|
||||
var coverage = total > 0 ? dominant.Duration / total : 0;
|
||||
if (durationsBySpeaker.Count == 1)
|
||||
{
|
||||
return new SingleSpeakerCoverage(
|
||||
true,
|
||||
durationsBySpeaker.Count,
|
||||
dominant.Speaker,
|
||||
coverage,
|
||||
TimeSpan.FromSeconds(total));
|
||||
}
|
||||
|
||||
var accepted = total > 0 && coverage >= Math.Clamp(options.MinimumSingleSpeakerCoverage, 0, 1);
|
||||
return new SingleSpeakerCoverage(
|
||||
accepted,
|
||||
durationsBySpeaker.Count,
|
||||
dominant.Speaker,
|
||||
coverage,
|
||||
TimeSpan.FromSeconds(total));
|
||||
}
|
||||
|
||||
private CompositeLayout WriteCompositeWav(
|
||||
@@ -234,4 +282,11 @@ public sealed class PyannoteSpeakerIdentityMatchValidator : ISpeakerIdentityMatc
|
||||
}
|
||||
|
||||
private sealed record CompositeLayout(TimeSegment? UnknownSegment, IReadOnlyList<TimeSegment> KnownSegments);
|
||||
|
||||
private sealed record SingleSpeakerCoverage(
|
||||
bool Accepted,
|
||||
int SpeakerCount,
|
||||
string? DominantSpeaker,
|
||||
double Coverage,
|
||||
TimeSpan TotalDuration);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user