Public Access
This commit is contained in:
@@ -0,0 +1,137 @@
|
||||
using MeetingAssistant;
|
||||
using MeetingAssistant.Recording;
|
||||
using MeetingAssistant.Speakers;
|
||||
using MeetingAssistant.Transcription;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Options;
|
||||
using NAudio.Wave;
|
||||
|
||||
namespace MeetingAssistant.Tests;
|
||||
|
||||
public sealed class PyannoteSpeakerIdentityMatchValidatorTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task ValidatorRejectsSampleWhenPyannoteReportsMultipleSpeakers()
|
||||
{
|
||||
var commandRunner = new CapturingCommandRunner(
|
||||
"""
|
||||
__MEETING_ASSISTANT_PYANNOTE_JSON_START__
|
||||
[{"start":0.0,"end":10.0,"speaker":"SPEAKER_00"},{"start":10.0,"end":20.0,"speaker":"SPEAKER_01"}]
|
||||
__MEETING_ASSISTANT_PYANNOTE_JSON_END__
|
||||
""");
|
||||
var validator = CreateValidator(commandRunner);
|
||||
|
||||
var valid = await validator.ValidateSampleAsync(CreateWav(TimeSpan.FromSeconds(20)), CancellationToken.None);
|
||||
|
||||
Assert.False(valid);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ValidatorAcceptsMatchWhenPyannoteAssignsKnownAndUnknownSamplesToSameSpeaker()
|
||||
{
|
||||
var commandRunner = new CapturingCommandRunner(
|
||||
"""
|
||||
__MEETING_ASSISTANT_PYANNOTE_JSON_START__
|
||||
[{"start":0.0,"end":1.0,"speaker":"SPEAKER_00"},{"start":2.0,"end":3.0,"speaker":"SPEAKER_00"}]
|
||||
__MEETING_ASSISTANT_PYANNOTE_JSON_END__
|
||||
""");
|
||||
var validator = CreateValidator(commandRunner);
|
||||
var wav = CreateWav(TimeSpan.FromSeconds(1));
|
||||
|
||||
var valid = await validator.ValidateMatchAsync(
|
||||
new SpeakerIdentityMatchValidationRequest("Guest03", 42, wav, [wav]),
|
||||
CancellationToken.None);
|
||||
|
||||
Assert.True(valid);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DisabledValidatorDoesNotRunPyannote()
|
||||
{
|
||||
var commandRunner = new CapturingCommandRunner("");
|
||||
var validator = CreateValidator(commandRunner, enabled: false);
|
||||
|
||||
var valid = await validator.ValidateSampleAsync(CreateWav(TimeSpan.FromSeconds(1)), CancellationToken.None);
|
||||
|
||||
Assert.True(valid);
|
||||
Assert.Empty(commandRunner.Commands);
|
||||
}
|
||||
|
||||
private static PyannoteSpeakerIdentityMatchValidator CreateValidator(
|
||||
CapturingCommandRunner commandRunner,
|
||||
bool enabled = true)
|
||||
{
|
||||
var finalizer = new PyannoteTranscriptFinalizer(
|
||||
commandRunner,
|
||||
Options.Create(new MeetingAssistantOptions()),
|
||||
NullLogger<PyannoteTranscriptFinalizer>.Instance);
|
||||
return new PyannoteSpeakerIdentityMatchValidator(
|
||||
finalizer,
|
||||
Options.Create(new MeetingAssistantOptions
|
||||
{
|
||||
SpeakerIdentification = new SpeakerIdentificationOptions
|
||||
{
|
||||
PyannoteValidation = new SpeakerIdentityPyannoteValidationOptions
|
||||
{
|
||||
Enabled = enabled,
|
||||
MinimumSingleSpeakerCoverage = 0.90,
|
||||
MinimumMatchingKnownSnippetRatio = 1,
|
||||
Diarization = new PyannoteDiarizationOptions
|
||||
{
|
||||
Enabled = true,
|
||||
BuildImage = false,
|
||||
DockerCommand = "docker",
|
||||
Image = "meeting-assistant-pyannote:local",
|
||||
ModelsFolder = Path.Combine(
|
||||
Path.GetTempPath(),
|
||||
"meeting-assistant-tests",
|
||||
Guid.NewGuid().ToString("N"),
|
||||
"models"),
|
||||
Token = "hf_test",
|
||||
TokenEnv = "",
|
||||
CommandTimeout = TimeSpan.FromMinutes(1),
|
||||
AlignmentMode = PyannoteAlignmentMode.PyannoteTurns
|
||||
}
|
||||
}
|
||||
}
|
||||
}),
|
||||
NullLogger<PyannoteSpeakerIdentityMatchValidator>.Instance);
|
||||
}
|
||||
|
||||
private static byte[] CreateWav(TimeSpan duration)
|
||||
{
|
||||
const int sampleRate = 16000;
|
||||
using var stream = new MemoryStream();
|
||||
using (var writer = new WaveFileWriter(stream, new WaveFormat(sampleRate, 16, 1)))
|
||||
{
|
||||
var bytes = new byte[(int)(duration.TotalSeconds * sampleRate * sizeof(short))];
|
||||
writer.Write(bytes, 0, bytes.Length);
|
||||
}
|
||||
|
||||
return stream.ToArray();
|
||||
}
|
||||
|
||||
private sealed class CapturingCommandRunner : ICommandRunner
|
||||
{
|
||||
private readonly string output;
|
||||
|
||||
public CapturingCommandRunner(string output)
|
||||
{
|
||||
this.output = output;
|
||||
}
|
||||
|
||||
public IReadOnlyList<CapturedCommand> Commands { get; private set; } = [];
|
||||
|
||||
public Task<CommandResult> RunAsync(
|
||||
string fileName,
|
||||
IReadOnlyList<string> arguments,
|
||||
CancellationToken cancellationToken,
|
||||
IReadOnlyDictionary<string, string>? environment = null)
|
||||
{
|
||||
Commands = Commands.Append(new CapturedCommand(fileName, arguments)).ToList();
|
||||
return Task.FromResult(new CommandResult(0, output, ""));
|
||||
}
|
||||
}
|
||||
|
||||
private sealed record CapturedCommand(string FileName, IReadOnlyList<string> Arguments);
|
||||
}
|
||||
Reference in New Issue
Block a user