Public Access
138 lines
5.1 KiB
C#
138 lines
5.1 KiB
C#
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);
|
|
}
|