Files
meeting-assistant/MeetingAssistant/Transcription/AsrDiagnosticService.cs
T
codex aecef30627
PR and Push Build/Test / build-and-test (push) Successful in 9m19s
Archive completed meeting assistant changes
2026-06-26 13:15:47 +02:00

107 lines
3.9 KiB
C#

using MeetingAssistant.Recording;
namespace MeetingAssistant.Transcription;
public sealed class AsrDiagnosticService
{
private readonly ISpeechRecognitionPipelineFactory pipelineFactory;
public AsrDiagnosticService(ISpeechRecognitionPipelineFactory pipelineFactory)
{
this.pipelineFactory = pipelineFactory;
}
public Task<AsrDiagnosticResult> TranscribeWavAsync(string path, CancellationToken cancellationToken)
{
return TranscribeWavAsync(path, launchProfileName: null, cancellationToken);
}
public async Task<AsrDiagnosticResult> TranscribeWavAsync(
string path,
string? launchProfileName,
CancellationToken cancellationToken)
{
var fullPath = ResolveExistingWavPath(path);
await using var pipeline = pipelineFactory.Create(launchProfileName);
await pipeline.InitializeAsync(cancellationToken);
var liveTranscriptTask = CollectLiveTranscriptAsync(pipeline, cancellationToken);
await WriteWavToPipelineAsync(fullPath, pipeline, paceAudio: true, cancellationToken);
await pipeline.CompleteAsync(cancellationToken);
return new AsrDiagnosticResult(fullPath, await liveTranscriptTask);
}
public async Task<AsrDiagnosticResult> DiarizeWavAsync(
string path,
SpeechRecognitionPipelineOptions options,
CancellationToken cancellationToken)
{
var fullPath = ResolveExistingWavPath(path);
return await DiarizeWavAsync(fullPath, options, launchProfileName: null, cancellationToken);
}
public async Task<AsrDiagnosticResult> DiarizeWavAsync(
string path,
SpeechRecognitionPipelineOptions options,
string? launchProfileName,
CancellationToken cancellationToken)
{
var fullPath = ResolveExistingWavPath(path);
await using var pipeline = pipelineFactory.Create(launchProfileName);
await pipeline.InitializeAsync(cancellationToken);
var liveTranscriptTask = CollectLiveTranscriptAsync(pipeline, cancellationToken);
await WriteWavToPipelineAsync(fullPath, pipeline, paceAudio: true, cancellationToken);
await pipeline.CompleteAsync(cancellationToken);
await liveTranscriptTask;
var finishedSegments = await pipeline.ReadFinishedTranscriptAsync(fullPath, options, cancellationToken);
return new AsrDiagnosticResult(fullPath, finishedSegments);
}
private static async Task<IReadOnlyList<TranscriptionSegment>> CollectLiveTranscriptAsync(
ISpeechRecognitionPipeline pipeline,
CancellationToken cancellationToken)
{
var segments = new List<TranscriptionSegment>();
await foreach (var segment in pipeline.ReadLiveTranscriptAsync(cancellationToken))
{
segments.Add(segment);
}
return segments;
}
private static async Task WriteWavToPipelineAsync(
string fullPath,
ISpeechRecognitionPipeline pipeline,
bool paceAudio,
CancellationToken cancellationToken)
{
await foreach (var chunk in PcmWavAudioChunkReader.ReadChunksAsync(fullPath, cancellationToken: cancellationToken))
{
await pipeline.WriteAsync(chunk, cancellationToken);
if (paceAudio && chunk.Duration > TimeSpan.Zero)
{
await Task.Delay(chunk.Duration, cancellationToken);
}
}
}
private static string ResolveExistingWavPath(string path)
{
if (string.IsNullOrWhiteSpace(path))
{
throw new ArgumentException("A WAV file path is required.", nameof(path));
}
var fullPath = Path.GetFullPath(Environment.ExpandEnvironmentVariables(path));
if (!File.Exists(fullPath))
{
throw new FileNotFoundException($"WAV file was not found at '{fullPath}'.", fullPath);
}
return fullPath;
}
}
public sealed record AsrDiagnosticResult(string Path, IReadOnlyList<TranscriptionSegment> Segments);