Public Access
Add meeting assistant speech and summary automation
This commit is contained in:
@@ -0,0 +1,148 @@
|
||||
using MeetingAssistant;
|
||||
using MeetingAssistant.Speakers;
|
||||
using MeetingAssistant.Transcription;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Options;
|
||||
using NAudio.Wave;
|
||||
|
||||
namespace MeetingAssistant.Tests;
|
||||
|
||||
public sealed class AzureSpeechSpeakerIdentityMatcherTests
|
||||
{
|
||||
[Fact]
|
||||
public void IdentityDiarizationLanguageUsesFirstAutoDetectLanguageWhenConfiguredLanguageIsAuto()
|
||||
{
|
||||
Assert.Equal(
|
||||
"de-DE",
|
||||
AzureSpeechSpeakerIdentityDiarizationClient.ResolveRecognitionLanguage(new AzureSpeechOptions
|
||||
{
|
||||
Language = "auto",
|
||||
AutoDetectLanguages = ["de-DE", "en-US"]
|
||||
}));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IdentityDiarizationLanguageUsesConfiguredConcreteLanguage()
|
||||
{
|
||||
Assert.Equal(
|
||||
"en-US",
|
||||
AzureSpeechSpeakerIdentityDiarizationClient.ResolveRecognitionLanguage(new AzureSpeechOptions
|
||||
{
|
||||
Language = "en-US",
|
||||
AutoDetectLanguages = ["de-DE"]
|
||||
}));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task MatcherReturnsIdentityWhenAzureDiarizationAssignsUnknownSnippetToKnownSpeaker()
|
||||
{
|
||||
var diarizationClient = new FakeSpeakerIdentityDiarizationClient(
|
||||
[
|
||||
new TranscriptionSegment(TimeSpan.Zero, TimeSpan.FromSeconds(1), "Guest-1", "known"),
|
||||
new TranscriptionSegment(TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(3), "Guest-1", "unknown")
|
||||
]);
|
||||
var matcher = CreateMatcher(diarizationClient);
|
||||
var snippet = CreateWavSnippet();
|
||||
|
||||
var match = await matcher.MatchAsync(
|
||||
new SpeakerIdentityMatchRequest(
|
||||
"Guest03",
|
||||
snippet,
|
||||
[new SpeakerIdentityMatchCandidate(42, "Chris", 5, [snippet])]),
|
||||
CancellationToken.None);
|
||||
|
||||
Assert.NotNull(match);
|
||||
Assert.Equal(42, match.IdentityId);
|
||||
Assert.NotNull(diarizationClient.WavPath);
|
||||
Assert.False(File.Exists(diarizationClient.WavPath));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task MatcherRequiresTwoMatchingKnownSnippetsWhenIdentityHasMultipleSnippets()
|
||||
{
|
||||
var diarizationClient = new FakeSpeakerIdentityDiarizationClient(
|
||||
[
|
||||
new TranscriptionSegment(TimeSpan.Zero, TimeSpan.FromSeconds(1), "Guest-1", "known one"),
|
||||
new TranscriptionSegment(TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(3), "Guest-2", "known two"),
|
||||
new TranscriptionSegment(TimeSpan.FromSeconds(4), TimeSpan.FromSeconds(5), "Guest-1", "unknown")
|
||||
]);
|
||||
var matcher = CreateMatcher(diarizationClient);
|
||||
var snippet = CreateWavSnippet();
|
||||
|
||||
var match = await matcher.MatchAsync(
|
||||
new SpeakerIdentityMatchRequest(
|
||||
"Guest03",
|
||||
snippet,
|
||||
[new SpeakerIdentityMatchCandidate(42, "Chris", 5, [snippet, snippet])]),
|
||||
CancellationToken.None);
|
||||
|
||||
Assert.Null(match);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task MatcherReturnsNullWhenAzureDiarizationAssignsDifferentSpeakers()
|
||||
{
|
||||
var diarizationClient = new FakeSpeakerIdentityDiarizationClient(
|
||||
[
|
||||
new TranscriptionSegment(TimeSpan.Zero, TimeSpan.FromSeconds(1), "Guest-1", "known"),
|
||||
new TranscriptionSegment(TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(3), "Guest-2", "unknown")
|
||||
]);
|
||||
var matcher = CreateMatcher(diarizationClient);
|
||||
var snippet = CreateWavSnippet();
|
||||
|
||||
var match = await matcher.MatchAsync(
|
||||
new SpeakerIdentityMatchRequest(
|
||||
"Guest03",
|
||||
snippet,
|
||||
[new SpeakerIdentityMatchCandidate(42, "Chris", 5, [snippet])]),
|
||||
CancellationToken.None);
|
||||
|
||||
Assert.Null(match);
|
||||
}
|
||||
|
||||
private static AzureSpeechSpeakerIdentityMatcher CreateMatcher(ISpeakerIdentityDiarizationClient diarizationClient)
|
||||
{
|
||||
return new AzureSpeechSpeakerIdentityMatcher(
|
||||
diarizationClient,
|
||||
Options.Create(new MeetingAssistantOptions
|
||||
{
|
||||
SpeakerIdentification = new SpeakerIdentificationOptions
|
||||
{
|
||||
SilenceBetweenSnippetsSeconds = 1
|
||||
}
|
||||
}),
|
||||
NullLogger<AzureSpeechSpeakerIdentityMatcher>.Instance);
|
||||
}
|
||||
|
||||
private static byte[] CreateWavSnippet()
|
||||
{
|
||||
using var stream = new MemoryStream();
|
||||
using (var writer = new WaveFileWriter(stream, new WaveFormat(16000, 16, 1)))
|
||||
{
|
||||
writer.Write(new byte[16000 * 2], 0, 16000 * 2);
|
||||
}
|
||||
|
||||
return stream.ToArray();
|
||||
}
|
||||
|
||||
private sealed class FakeSpeakerIdentityDiarizationClient : ISpeakerIdentityDiarizationClient
|
||||
{
|
||||
private readonly IReadOnlyList<TranscriptionSegment> segments;
|
||||
|
||||
public FakeSpeakerIdentityDiarizationClient(IReadOnlyList<TranscriptionSegment> segments)
|
||||
{
|
||||
this.segments = segments;
|
||||
}
|
||||
|
||||
public string? WavPath { get; private set; }
|
||||
|
||||
public Task<IReadOnlyList<TranscriptionSegment>> DiarizeAsync(
|
||||
string wavPath,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
WavPath = wavPath;
|
||||
Assert.True(File.Exists(wavPath));
|
||||
return Task.FromResult(segments);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user