Public Access
128 lines
4.6 KiB
C#
128 lines
4.6 KiB
C#
using System.Threading.Channels;
|
|
using MeetingAssistant.Transcription;
|
|
using Microsoft.CognitiveServices.Speech;
|
|
using Microsoft.CognitiveServices.Speech.Audio;
|
|
using Microsoft.CognitiveServices.Speech.Transcription;
|
|
using Microsoft.Extensions.Options;
|
|
|
|
namespace MeetingAssistant.Speakers;
|
|
|
|
public sealed class AzureSpeechSpeakerIdentityDiarizationClient : ISpeakerIdentityDiarizationClient
|
|
{
|
|
private readonly AzureSpeechOptions options;
|
|
private readonly ILogger<AzureSpeechSpeakerIdentityDiarizationClient> logger;
|
|
|
|
public AzureSpeechSpeakerIdentityDiarizationClient(
|
|
IOptions<MeetingAssistantOptions> options,
|
|
ILogger<AzureSpeechSpeakerIdentityDiarizationClient> logger)
|
|
{
|
|
this.options = options.Value.SpeakerIdentification.AzureSpeech;
|
|
this.logger = logger;
|
|
}
|
|
|
|
public async Task<IReadOnlyList<TranscriptionSegment>> DiarizeAsync(
|
|
string wavPath,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
var key = AzureSpeechStreamingTranscriptionProvider.ResolveKey(options);
|
|
if (string.IsNullOrWhiteSpace(key))
|
|
{
|
|
logger.LogWarning(
|
|
"Speaker identity Azure Speech matching is enabled but no key is configured directly or via {KeyEnv}",
|
|
options.KeyEnv);
|
|
return [];
|
|
}
|
|
|
|
var speechConfig = string.IsNullOrWhiteSpace(options.Region)
|
|
? SpeechConfig.FromEndpoint(new Uri(options.Endpoint), key)
|
|
: SpeechConfig.FromSubscription(key, options.Region);
|
|
speechConfig.SpeechRecognitionLanguage = ResolveRecognitionLanguage(options);
|
|
speechConfig.OutputFormat = OutputFormat.Detailed;
|
|
speechConfig.SetProperty(
|
|
PropertyId.SpeechServiceResponse_DiarizeIntermediateResults,
|
|
options.DiarizeIntermediateResults ? "true" : "false");
|
|
|
|
using var audioConfig = AudioConfig.FromWavFileInput(wavPath);
|
|
using var recognizer = new ConversationTranscriber(speechConfig, audioConfig);
|
|
var channel = Channel.CreateUnbounded<TranscriptionSegment>();
|
|
|
|
recognizer.Transcribed += (_, args) =>
|
|
{
|
|
if (args.Result.Reason != ResultReason.RecognizedSpeech)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var start = TimeSpan.FromTicks(args.Result.OffsetInTicks);
|
|
channel.Writer.TryWrite(new TranscriptionSegment(
|
|
start,
|
|
start + args.Result.Duration,
|
|
NormalizeSpeakerId(args.Result.SpeakerId),
|
|
args.Result.Text.Trim()));
|
|
};
|
|
recognizer.Canceled += (_, args) =>
|
|
{
|
|
if (args.Reason == CancellationReason.Error)
|
|
{
|
|
channel.Writer.TryComplete(new InvalidOperationException(
|
|
$"Azure Speech identity matching canceled: {args.ErrorCode} {args.ErrorDetails}"));
|
|
return;
|
|
}
|
|
|
|
channel.Writer.TryComplete();
|
|
};
|
|
recognizer.SessionStopped += (_, _) => channel.Writer.TryComplete();
|
|
|
|
await recognizer.StartTranscribingAsync();
|
|
var segments = new List<TranscriptionSegment>();
|
|
try
|
|
{
|
|
await foreach (var segment in channel.Reader.ReadAllAsync(cancellationToken))
|
|
{
|
|
segments.Add(segment);
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
try
|
|
{
|
|
var stopTask = recognizer.StopTranscribingAsync();
|
|
if (options.RecognitionStopTimeout > TimeSpan.Zero)
|
|
{
|
|
await stopTask.WaitAsync(options.RecognitionStopTimeout, CancellationToken.None);
|
|
}
|
|
else
|
|
{
|
|
await stopTask.WaitAsync(CancellationToken.None);
|
|
}
|
|
}
|
|
catch (TimeoutException)
|
|
{
|
|
logger.LogWarning("Timed out while stopping Azure Speech identity diarization recognizer");
|
|
}
|
|
}
|
|
|
|
return segments;
|
|
}
|
|
|
|
internal static string ResolveRecognitionLanguage(AzureSpeechOptions options)
|
|
{
|
|
if (!options.Language.Equals("auto", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
return options.Language;
|
|
}
|
|
|
|
return options.AutoDetectLanguages
|
|
.Select(language => language.Trim())
|
|
.FirstOrDefault(language => !string.IsNullOrWhiteSpace(language))
|
|
?? "de-DE";
|
|
}
|
|
|
|
private static string NormalizeSpeakerId(string? speakerId)
|
|
{
|
|
return string.IsNullOrWhiteSpace(speakerId) || speakerId.Equals("Unknown", StringComparison.OrdinalIgnoreCase)
|
|
? "Unknown"
|
|
: speakerId;
|
|
}
|
|
}
|