Public Access
feat: add local Resemblyzer speaker recognition
PR and Push Build/Test / build-and-test (push) Successful in 12m37s
PR and Push Build/Test / build-and-test (push) Successful in 12m37s
This commit is contained in:
@@ -0,0 +1,106 @@
|
||||
using MeetingAssistant.Speakers;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace MeetingAssistant.Tests;
|
||||
|
||||
public sealed class SpeakerVoiceVectorPersistenceTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task IdentityStoresVersionedVoiceVectorAndDeletesItWithIdentity()
|
||||
{
|
||||
var databasePath = Path.Combine(
|
||||
Path.GetTempPath(),
|
||||
"meeting-assistant-tests",
|
||||
Guid.NewGuid().ToString("N"),
|
||||
"speaker-identities.db");
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(databasePath)!);
|
||||
var options = new DbContextOptionsBuilder<SpeakerIdentityDbContext>()
|
||||
.UseSqlite($"Data Source={databasePath}")
|
||||
.Options;
|
||||
|
||||
await using (var context = new SpeakerIdentityDbContext(options))
|
||||
{
|
||||
await SpeakerIdentitySchema.EnsureCreatedOrUpdatedAsync(context, CancellationToken.None);
|
||||
context.SpeakerIdentities.Add(new SpeakerIdentity
|
||||
{
|
||||
CanonicalName = "Chris",
|
||||
CreatedAt = DateTimeOffset.UtcNow,
|
||||
UpdatedAt = DateTimeOffset.UtcNow,
|
||||
VoiceVectors =
|
||||
[
|
||||
new SpeakerVoiceVector
|
||||
{
|
||||
ModelId = "resemblyzer-0.1.4-pretrained",
|
||||
Dimensions = 256,
|
||||
VectorBytes = new byte[256 * sizeof(float)],
|
||||
Fingerprint = "vector-a",
|
||||
CreatedAt = DateTimeOffset.UtcNow
|
||||
}
|
||||
]
|
||||
});
|
||||
await context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
await using (var context = new SpeakerIdentityDbContext(options))
|
||||
{
|
||||
var identity = await context.SpeakerIdentities
|
||||
.Include(candidate => candidate.VoiceVectors)
|
||||
.SingleAsync();
|
||||
var vector = Assert.Single(identity.VoiceVectors);
|
||||
Assert.Equal("resemblyzer-0.1.4-pretrained", vector.ModelId);
|
||||
Assert.Equal(256, vector.Dimensions);
|
||||
Assert.Equal(256 * sizeof(float), vector.VectorBytes.Length);
|
||||
|
||||
context.SpeakerIdentities.Remove(identity);
|
||||
await context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
await using (var context = new SpeakerIdentityDbContext(options))
|
||||
{
|
||||
Assert.Empty(await context.SpeakerVoiceVectors.ToListAsync());
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task IdentityRejectsDuplicateVoiceVectorFingerprint()
|
||||
{
|
||||
var databasePath = Path.Combine(
|
||||
Path.GetTempPath(),
|
||||
"meeting-assistant-tests",
|
||||
Guid.NewGuid().ToString("N"),
|
||||
"speaker-identities.db");
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(databasePath)!);
|
||||
var options = new DbContextOptionsBuilder<SpeakerIdentityDbContext>()
|
||||
.UseSqlite($"Data Source={databasePath}")
|
||||
.Options;
|
||||
await using var context = new SpeakerIdentityDbContext(options);
|
||||
await SpeakerIdentitySchema.EnsureCreatedOrUpdatedAsync(context, CancellationToken.None);
|
||||
var identity = new SpeakerIdentity
|
||||
{
|
||||
CanonicalName = "Chris",
|
||||
CreatedAt = DateTimeOffset.UtcNow,
|
||||
UpdatedAt = DateTimeOffset.UtcNow
|
||||
};
|
||||
context.SpeakerIdentities.Add(identity);
|
||||
await context.SaveChangesAsync();
|
||||
|
||||
context.SpeakerVoiceVectors.AddRange(
|
||||
CreateVector(identity.Id, "same-vector"),
|
||||
CreateVector(identity.Id, "same-vector"));
|
||||
|
||||
await Assert.ThrowsAsync<DbUpdateException>(() => context.SaveChangesAsync());
|
||||
}
|
||||
|
||||
private static SpeakerVoiceVector CreateVector(int identityId, string fingerprint)
|
||||
{
|
||||
return new SpeakerVoiceVector
|
||||
{
|
||||
SpeakerIdentityId = identityId,
|
||||
ModelId = "resemblyzer-0.1.4-pretrained",
|
||||
Dimensions = 256,
|
||||
VectorBytes = new byte[256 * sizeof(float)],
|
||||
Fingerprint = fingerprint,
|
||||
CreatedAt = DateTimeOffset.UtcNow
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user