using MeetingAssistant.Recording; namespace MeetingAssistant.Transcription; public sealed class AsrDiagnosticService { private readonly ISpeechRecognitionPipelineFactory pipelineFactory; public AsrDiagnosticService(ISpeechRecognitionPipelineFactory pipelineFactory) { this.pipelineFactory = pipelineFactory; } public Task TranscribeWavAsync(string path, CancellationToken cancellationToken) { return TranscribeWavAsync(path, launchProfileName: null, cancellationToken); } public async Task 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 DiarizeWavAsync( string path, SpeechRecognitionPipelineOptions options, CancellationToken cancellationToken) { var fullPath = ResolveExistingWavPath(path); return await DiarizeWavAsync(fullPath, options, launchProfileName: null, cancellationToken); } public async Task 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> CollectLiveTranscriptAsync( ISpeechRecognitionPipeline pipeline, CancellationToken cancellationToken) { var segments = new List(); 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 Segments);