Public Access
107 lines
3.9 KiB
C#
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);
|