Functions/GenXdev.AI/Get-SpeechToText.cs
|
// ################################################################################
// Part of PowerShell module : GenXdev.AI // Original cmdlet filename : Get-SpeechToText.cs // Original author : René Vaessen / GenXdev // Version : 3.28.2026 // ################################################################################ // Copyright (c) 2026 René Vaessen / GenXdev // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // ################################################################################ using System.Collections.Concurrent; using System.Management; using System.Management.Automation; using System.Runtime.Intrinsics.X86; using Whisper.net; using Whisper.net.Ggml; using Whisper.net.LibraryLoader; namespace GenXdev.AI { [System.ComponentModel.Description(@" .SYNOPSIS Converts audio files to text using OpenAI's Whisper speech recognition model. .DESCRIPTION Processes audio files and converts speech to text using the Whisper.NET library, which implements OpenAI's Whisper automatic speech recognition (ASR) system. It supports multiple languages, translation capabilities, and various transcription quality settings. .EXAMPLE ```powershell Get-SpeechToText -Input ""C:\audio\recording.wav"" ``` Transcribes an audio file to text using default settings. .EXAMPLE ```powershell Get-ChildItem ""C:\audio\*.wav"" | Get-SpeechToText ``` Transcribes all WAV files in a directory. .EXAMPLE ```powershell Get-SpeechToText -Input ""audio.mp3"" -LanguageIn ""es"" -WithTranslate ``` Transcribes Spanish audio and translates it to English. .EXAMPLE ```powershell Get-SpeechToText -Input ""recording.wav"" -Passthru -WithTokenTimestamps ``` Returns SegmentData objects with precise timing information. ")] [Cmdlet(VerbsCommon.Get, "SpeechToText")] [OutputType(typeof(string), typeof(SegmentData))] public class GetSpeechToText : PSGenXdevCmdlet { #region Cmdlet Parameters /// <summary> /// Directory path where Whisper model files are stored or downloaded /// </summary> [Parameter( Mandatory = false, HelpMessage = "Path to the model file directory")] public string ModelFileDirectoryPath { get; set; } /// <summary> /// Audio file path, FileInfo object, or any audio format supported by Whisper /// </summary> [Alias("WaveFile")] [Parameter( Mandatory = true, Position = 0, ValueFromPipeline = true, HelpMessage = "Audio file path, FileInfo object, or any audio " + "format supported by Whisper.")] public object Input { get; set; } /// <summary> /// Input language code for speech recognition /// </summary> [Parameter( Mandatory = false, HelpMessage = "Sets the input language to detect, defaults to 'en'")] public string LanguageIn { get; set; } = "en"; /// <summary> /// Number of CPU threads to use for processing /// </summary> [Parameter( Mandatory = false, HelpMessage = "Sets the output language")] public int CpuThreads { get; set; } = 0; /// <summary> /// Temperature for speech detection (0.0-1.0) /// </summary> [Parameter( Mandatory = false, HelpMessage = "Temperature for speech detection")] [ValidateRange(0, 1)] public float? Temperature { get; set; } = 0.7f; /// <summary> /// Temperature increment for fallback attempts /// </summary> [Parameter( Mandatory = false, HelpMessage = "Temperature increment")] [ValidateRange(0, 1)] public float? TemperatureInc { get; set; } /// <summary> /// Threshold for detecting silence vs speech (0.0-1.0) /// </summary> [Parameter( Mandatory = false, HelpMessage = "No speech threshold")] [ValidateRange(0, 1)] public float? NoSpeechThreshold { get; set; } = 0.1f; /// <summary> /// Optional text prompt to guide the model's transcription style /// </summary> [Parameter( Mandatory = false, HelpMessage = "Prompt to use for the model")] public string Prompt { get; set; } /// <summary> /// Regular expression pattern to suppress specific tokens from output /// </summary> [Parameter( Mandatory = false, HelpMessage = "Regex to suppress tokens from the output")] public string SuppressRegex { get; set; } = null; /// <summary> /// Sum threshold for token timestamps when WithTokenTimestamps is enabled /// </summary> [Parameter( Mandatory = false, HelpMessage = "Sum threshold for token timestamps, defaults to 0.5")] public float TokenTimestampsSumThreshold { get; set; } = 0.5f; /// <summary> /// Maximum number of tokens per transcription segment /// </summary> [Parameter( Mandatory = false, HelpMessage = "Maximum number of tokens per segment")] public int? MaxTokensPerSegment { get; set; } /// <summary> /// Size of the audio context window for processing /// </summary> [Parameter( Mandatory = false, HelpMessage = "Size of the audio context")] public int? AudioContextSize { get; set; } /// <summary> /// Maximum duration of audio to process /// </summary> [Parameter( Mandatory = false, HelpMessage = "Maximum duration of the audio")] public TimeSpan? MaxDuration { get; set; } /// <summary> /// Time offset to start processing audio from /// </summary> [Parameter( Mandatory = false, HelpMessage = "Offset for the audio")] public TimeSpan? Offset { get; set; } /// <summary> /// Maximum number of last text tokens to consider for context /// </summary> [Parameter( Mandatory = false, HelpMessage = "Maximum number of last text tokens")] public int? MaxLastTextTokens { get; set; } /// <summary> /// Maximum length of each transcription segment /// </summary> [Parameter( Mandatory = false, HelpMessage = "Maximum segment length")] public int? MaxSegmentLength { get; set; } /// <summary> /// Start timestamps at this moment in the audio /// </summary> [Parameter( Mandatory = false, HelpMessage = "Start timestamps at this moment")] public TimeSpan? MaxInitialTimestamp { get; set; } /// <summary> /// Penalty applied to longer segments (0.0-1.0) /// </summary> [Parameter( Mandatory = false, HelpMessage = "Length penalty")] [ValidateRange(0, 1)] public float? LengthPenalty { get; set; } /// <summary> /// Entropy threshold for segment quality assessment (0.0-1.0) /// </summary> [Parameter( Mandatory = false, HelpMessage = "Entropy threshold")] [ValidateRange(0, 1)] public float? EntropyThreshold { get; set; } /// <summary> /// Log probability threshold for filtering low-confidence segments (0.0-1.0) /// </summary> [Parameter( Mandatory = false, HelpMessage = "Log probability threshold")] [ValidateRange(0, 1)] public float? LogProbThreshold { get; set; } /// <summary> /// Whisper model type to use for transcription /// </summary> [Parameter( Mandatory = false, HelpMessage = "Whisper model type to use, defaults to LargeV3Turbo")] public GgmlType ModelType { get; set; } = GgmlType.LargeV3Turbo; /// <summary> /// Returns SegmentData objects instead of plain text strings /// </summary> [Parameter( Mandatory = false, HelpMessage = "Returns objects instead of strings")] public SwitchParameter Passthru { get; set; } /// <summary> /// Includes precise timestamps for each token in the output /// </summary> [Parameter( Mandatory = false, HelpMessage = "Whether to include token timestamps")] public SwitchParameter WithTokenTimestamps { get; set; } /// <summary> /// Splits transcription segments on word boundaries /// </summary> [Parameter( Mandatory = false, HelpMessage = "Whether to split on word boundaries")] public SwitchParameter SplitOnWord { get; set; } /// <summary> /// Translates non-English audio to English /// </summary> [Parameter( Mandatory = false, HelpMessage = "Whether to translate the output")] public SwitchParameter WithTranslate { get; set; } /// <summary> /// Shows progress indicator during processing /// </summary> [Parameter( Mandatory = false, HelpMessage = "Whether to show progress")] public SwitchParameter WithProgress { get; set; } /// <summary> /// Includes blank/silent segments in output /// </summary> [Parameter( Mandatory = false, HelpMessage = "Whether to NOT suppress blank lines")] public SwitchParameter DontSuppressBlank { get; set; } /// <summary> /// Forces output as a single segment instead of multiple segments /// </summary> [Parameter( Mandatory = false, HelpMessage = "Whether to use single segment only")] public SwitchParameter SingleSegmentOnly { get; set; } /// <summary> /// Includes special tokens (like timestamps markers) in output /// </summary> [Parameter( Mandatory = false, HelpMessage = "Whether to print special tokens")] public SwitchParameter PrintSpecialTokens { get; set; } /// <summary> /// Disables context from previous segments /// </summary> [Parameter( Mandatory = false, HelpMessage = "Don't use context")] public SwitchParameter NoContext { get; set; } /// <summary> /// Uses beam search sampling strategy for improved accuracy /// </summary> [Parameter( Mandatory = false, HelpMessage = "Use beam search sampling strategy")] public SwitchParameter WithBeamSearchSamplingStrategy { get; set; } #endregion #region Private Fields // Thread-safe queue for storing transcription results private readonly ConcurrentQueue<SegmentData> _results = new(); // Thread-safe queue for storing error records from background processing private readonly ConcurrentQueue<ErrorRecord> _errorQueue = new(); // Thread-safe queue for storing verbose messages from background processing private readonly ConcurrentQueue<string> _verboseQueue = new(); // Cancellation token source for aborting long-running operations private CancellationTokenSource _cts; // Whisper processor instance for speech-to-text conversion private WhisperProcessor _processor; // Whisper factory instance (kept for proper disposal order) private WhisperFactory _whisperFactory; // Flag to track if resources have been disposed private bool _isDisposed = false; // Lock object for thread-safe disposal private readonly object _disposeLock = new object(); #endregion #region Lifecycle Methods /// <summary> /// Begin processing - initializes Whisper model and processor /// </summary> protected override void BeginProcessing() { base.BeginProcessing(); //if (!Avx.IsSupported || Environment.OSVersion.Version.Major == 10) //{ // RuntimeOptions.LoadedLibrary = null; // Environment.SetEnvironmentVariable("DOTNET_SYSTEM_GLOBALIZATION_INVARIANT", "false"); // Environment.SetEnvironmentVariable("DOTNET_EnableWriteXorExecute", "1"); // RuntimeOptions.LoadedLibrary = null; // critical // RuntimeOptions.RuntimeLibraryOrder = new List<RuntimeLibrary> // { // RuntimeLibrary.CpuNoAvx // }; // System.Console.WriteLine("Using no-avx -> " + Whisper.net.LibraryLoader.RuntimeOptions.LibraryPath); //} //Whisper.net.LibraryLoader.RuntimeOptions.LibraryPath = // Path.Combine(this.GetGenXdevModuleBase("GenXdev")); // # cuda12, openvino, vulkan, noavx, cpu // WhisperRuntimeSelector.Initialize(); // Set default model directory if not specified or invalid if (string.IsNullOrEmpty(ModelFileDirectoryPath) || !Directory.Exists(ModelFileDirectoryPath)) { var localAppData = System.Environment.GetEnvironmentVariable("LOCALAPPDATA"); if (!string.IsNullOrEmpty(localAppData)) { ModelFileDirectoryPath = Path.Combine(localAppData, "GenXdev.PowerShell"); } // Create directory if it doesn't exist if (!Directory.Exists(ModelFileDirectoryPath)) { try { Directory.CreateDirectory(ModelFileDirectoryPath); } catch (Exception ex) { ThrowTerminatingError(new ErrorRecord( ex, "ModelPathCreationFailed", ErrorCategory.ResourceUnavailable, ModelFileDirectoryPath)); } } } // Set language to auto-detect if not explicitly specified if (!MyInvocation.BoundParameters.ContainsKey("LanguageIn")) { LanguageIn = "auto"; } // Log all user-specified parameters WriteVerbose($"ModelFileDirectoryPath: {ModelFileDirectoryPath}"); if (MyInvocation.BoundParameters.ContainsKey("Input")) WriteVerbose($"Input: {Input}"); if (MyInvocation.BoundParameters.ContainsKey("Passthru")) WriteVerbose($"Passthru: {Passthru}"); if (MyInvocation.BoundParameters.ContainsKey("WithTokenTimestamps")) WriteVerbose($"WithTokenTimestamps: {WithTokenTimestamps}"); if (MyInvocation.BoundParameters.ContainsKey( "TokenTimestampsSumThreshold")) WriteVerbose( $"TokenTimestampsSumThreshold: {TokenTimestampsSumThreshold}"); if (MyInvocation.BoundParameters.ContainsKey("SplitOnWord")) WriteVerbose($"SplitOnWord: {SplitOnWord}"); if (MyInvocation.BoundParameters.ContainsKey("MaxTokensPerSegment")) WriteVerbose($"MaxTokensPerSegment: {MaxTokensPerSegment}"); if (MyInvocation.BoundParameters.ContainsKey("LanguageIn")) WriteVerbose($"LanguageIn: {LanguageIn}"); if (MyInvocation.BoundParameters.ContainsKey("CpuThreads")) WriteVerbose($"CpuThreads: {CpuThreads}"); if (MyInvocation.BoundParameters.ContainsKey("Temperature")) WriteVerbose($"Temperature: {Temperature}"); if (MyInvocation.BoundParameters.ContainsKey("TemperatureInc")) WriteVerbose($"TemperatureInc: {TemperatureInc}"); if (MyInvocation.BoundParameters.ContainsKey("WithTranslate")) WriteVerbose($"WithTranslate: {WithTranslate}"); if (MyInvocation.BoundParameters.ContainsKey("Prompt")) WriteVerbose($"Prompt: {Prompt}"); if (MyInvocation.BoundParameters.ContainsKey("SuppressRegex")) WriteVerbose($"SuppressRegex: {SuppressRegex}"); if (MyInvocation.BoundParameters.ContainsKey("WithProgress")) WriteVerbose($"WithProgress: {WithProgress}"); if (MyInvocation.BoundParameters.ContainsKey("AudioContextSize")) WriteVerbose($"AudioContextSize: {AudioContextSize}"); if (MyInvocation.BoundParameters.ContainsKey("DontSuppressBlank")) WriteVerbose($"DontSuppressBlank: {DontSuppressBlank}"); if (MyInvocation.BoundParameters.ContainsKey("MaxDuration")) WriteVerbose($"MaxDuration: {MaxDuration}"); if (MyInvocation.BoundParameters.ContainsKey("Offset")) WriteVerbose($"Offset: {Offset}"); if (MyInvocation.BoundParameters.ContainsKey("MaxLastTextTokens")) WriteVerbose($"MaxLastTextTokens: {MaxLastTextTokens}"); if (MyInvocation.BoundParameters.ContainsKey("SingleSegmentOnly")) WriteVerbose($"SingleSegmentOnly: {SingleSegmentOnly}"); if (MyInvocation.BoundParameters.ContainsKey("PrintSpecialTokens")) WriteVerbose($"PrintSpecialTokens: {PrintSpecialTokens}"); if (MyInvocation.BoundParameters.ContainsKey("MaxSegmentLength")) WriteVerbose($"MaxSegmentLength: {MaxSegmentLength}"); if (MyInvocation.BoundParameters.ContainsKey("MaxInitialTimestamp")) WriteVerbose($"MaxInitialTimestamp: {MaxInitialTimestamp}"); if (MyInvocation.BoundParameters.ContainsKey("LengthPenalty")) WriteVerbose($"LengthPenalty: {LengthPenalty}"); if (MyInvocation.BoundParameters.ContainsKey("EntropyThreshold")) WriteVerbose($"EntropyThreshold: {EntropyThreshold}"); if (MyInvocation.BoundParameters.ContainsKey("LogProbThreshold")) WriteVerbose($"LogProbThreshold: {LogProbThreshold}"); if (MyInvocation.BoundParameters.ContainsKey("NoSpeechThreshold")) WriteVerbose($"NoSpeechThreshold: {NoSpeechThreshold}"); if (MyInvocation.BoundParameters.ContainsKey("NoContext")) WriteVerbose($"NoContext: {NoContext}"); if (MyInvocation.BoundParameters.ContainsKey( "WithBeamSearchSamplingStrategy")) WriteVerbose( $"WithBeamSearchSamplingStrategy: " + $"{WithBeamSearchSamplingStrategy}"); if (MyInvocation.BoundParameters.ContainsKey("ModelType")) WriteVerbose($"ModelType: {ModelType}"); // Initialize cancellation token for aborting operations _cts = new CancellationTokenSource(); // Construct full model file path var ggmlType = ModelType; var modelFileName = Path.GetFullPath( Path.Combine(ModelFileDirectoryPath, GetModelFileName(ModelType))); // Download model if not already present if (!File.Exists(modelFileName)) { DownloadModel(modelFileName, ggmlType).GetAwaiter().GetResult(); } // Initialize Whisper factory and processor _whisperFactory = WhisperFactory.FromPath(modelFileName); var builder = ConfigureWhisperBuilder(_whisperFactory.CreateBuilder()); _processor = builder.Build(); } /// <summary> /// Process record - transcribes each input audio file /// </summary> protected override void ProcessRecord() { base.ProcessRecord(); // Extract file path from various input types string filePath = GetFilePathFromInput(Input); if (string.IsNullOrEmpty(filePath)) { WriteError(new ErrorRecord( new ArgumentException( "Input parameter is required and must be a valid " + "file path or FileInfo object."), "MissingInput", ErrorCategory.InvalidArgument, Input)); return; } // Validate that the audio file exists if (!File.Exists(filePath)) { WriteError(new ErrorRecord( new FileNotFoundException( $"Audio file not found: {filePath}"), "FileNotFound", ErrorCategory.ObjectNotFound, filePath)); return; } WriteVerbose($"Processing audio file: {filePath}"); // Process the audio file using the initialized processor ProcessAudioFile(filePath); } /// <summary> /// End processing - cleanup and dispose resources /// </summary> protected override void EndProcessing() { lock (_disposeLock) { if (_isDisposed) return; _isDisposed = true; } try { // Cancel any ongoing operations if (_cts != null && !_cts.IsCancellationRequested) { _cts.Cancel(); } // Dispose Whisper processor (must be disposed before factory) if (_processor != null) { try { // Handle async disposable resources if (_processor is IAsyncDisposable asyncDisposable) { asyncDisposable.DisposeAsync() .AsTask() .Wait(TimeSpan.FromSeconds(50)); } else if (_processor is IDisposable disposable) { disposable.Dispose(); } } catch (Exception ex) { WriteVerbose( $"Error disposing Whisper processor: {ex.Message}"); } finally { _processor = null; } } // Dispose Whisper factory if (_whisperFactory != null) { try { _whisperFactory.Dispose(); } catch (Exception ex) { WriteVerbose( $"Error disposing Whisper factory: {ex.Message}"); } finally { _whisperFactory = null; } } // Dispose cancellation token source if (_cts != null) { try { _cts.Dispose(); } catch (Exception ex) { WriteVerbose( $"Error disposing cancellation token source: " + $"{ex.Message}"); } finally { _cts = null; } } } catch (Exception ex) { WriteVerbose($"Error in EndProcessing: {ex.Message}"); } base.EndProcessing(); } #endregion #region Private Helper Methods /// <summary> /// Extracts file path from various input object types /// </summary> /// <param name="input">Input object (string, FileInfo, or PSObject)</param> /// <returns>File path string or null if extraction fails</returns> private string GetFilePathFromInput(object input) { if (input == null) return null; // Handle FileInfo objects directly if (input is FileInfo fileInfo) { return fileInfo.FullName; } // Handle string paths if (input is string stringPath) { return stringPath; } // Handle PSObject wrappers around FileInfo or strings if (input is PSObject psObject) { var baseObject = psObject.BaseObject; if (baseObject is FileInfo fi) { return fi.FullName; } if (baseObject is string str) { return str; } } // Fallback: attempt to convert to string try { return input.ToString(); } catch { return null; } } /// <summary> /// Processes an audio file and transcribes it to text segments /// </summary> /// <param name="filePath">Path to the audio file</param> private void ProcessAudioFile(string filePath) { using var audioStream = File.OpenRead(filePath); // Start async processing task in background var processingTask = Task.Run(async () => { try { // Process audio stream and collect segments await foreach (var segment in _processor.ProcessAsync(audioStream, _cts.Token)) { // Check for cancellation or disposal if (_cts.IsCancellationRequested || _isDisposed) break; // Filter out empty or blank audio segments if (!string.IsNullOrWhiteSpace(segment.Text)) { if (!(segment.Text.Trim( "\r\n\t ".ToCharArray()) == "[BLANK_AUDIO]")) { _results.Enqueue(segment); } } } } catch (OperationCanceledException) { } catch (Exception ex) { _errorQueue.Enqueue(new ErrorRecord( ex, "ProcessingError", ErrorCategory.OperationStopped, null)); } }); System.Console.WriteLine("Processing audio file. Press Q to abort..."); // Main processing loop with keyboard interrupt support while (!processingTask.IsCompleted) { try { // Check for Q key to abort processing if (System.Console.KeyAvailable && System.Console.ReadKey(true).Key == ConsoleKey.Q) { _cts.Cancel(); _errorQueue.Enqueue(new ErrorRecord( new Exception("Processing aborted"), "ProcessingAborted", ErrorCategory.OperationStopped, null)); break; } Thread.Sleep(100); } catch (Exception ex) { WriteError(new ErrorRecord( ex, "ProcessingLoopError", ErrorCategory.OperationStopped, null)); break; } } // Wait for processing task to complete with timeout bool taskCompleted = false; try { // First attempt: graceful wait taskCompleted = processingTask.Wait(TimeSpan.FromSeconds(10)); if (!taskCompleted) { // Second attempt: cancel and wait again _cts.Cancel(); taskCompleted = processingTask.Wait(TimeSpan.FromSeconds(5)); } } catch (AggregateException ex) { // Handle task exceptions foreach (var innerEx in ex.InnerExceptions) { if (!(innerEx is OperationCanceledException)) { WriteVerbose( $"Processing task error: {innerEx.Message}"); } } } catch (Exception ex) { WriteVerbose( $"Error waiting for processing task: {ex.Message}"); } // Collect all queued results int timeout = 0; List<SegmentData> allSegments = new List<SegmentData>(); while (timeout < 50) { bool hasResults = false; // Process all queued error records while (_errorQueue.TryDequeue(out var errorRecord)) { WriteError(errorRecord); hasResults = true; } // Process all queued verbose messages while (_verboseQueue.TryDequeue(out var verboseMessage)) { WriteVerbose(verboseMessage); hasResults = true; } // Collect all transcription segments while (_results.TryDequeue(out var segment)) { allSegments.Add(segment); hasResults = true; } // Exit if no more results and task completed if (!hasResults && taskCompleted) { break; } Thread.Sleep(100); timeout++; } // Output all collected segments foreach (var segment in allSegments) { WriteObject(Passthru ? segment : segment.Text.Trim()); } } /// <summary> /// Configures the Whisper processor builder with all specified parameters /// </summary> /// <param name="builder">Whisper processor builder instance</param> /// <returns>Configured builder instance</returns> private WhisperProcessorBuilder ConfigureWhisperBuilder( WhisperProcessorBuilder builder) { // Detect physical CPU core count for optimal threading int physicalCoreCount = 0; var searcher = new ManagementObjectSearcher( "select NumberOfCores from Win32_Processor"); foreach (var item in searcher.Get()) { physicalCoreCount += Convert.ToInt32(item["NumberOfCores"]); } // Set language and thread count builder.WithLanguage(LanguageIn) .WithThreads(CpuThreads > 0 ? CpuThreads : physicalCoreCount); // Enable translation if language detection is active if (MyInvocation.BoundParameters.ContainsKey("LanguageIn")) { builder.WithTranslate(); } // Configure temperature for speech detection consistency if (Temperature.HasValue) { builder.WithTemperature(Temperature.Value); } else { builder.WithTemperature(0.0f); } if (TemperatureInc.HasValue) builder.WithTemperatureInc(TemperatureInc.Value); if (WithTokenTimestamps.IsPresent) builder.WithTokenTimestamps() .WithTokenTimestampsSumThreshold(TokenTimestampsSumThreshold); if (WithTranslate.IsPresent) builder.WithTranslate(); if (!string.IsNullOrWhiteSpace(Prompt)) builder.WithPrompt(Prompt); if (!string.IsNullOrWhiteSpace(SuppressRegex)) builder.WithSuppressRegex(SuppressRegex); if (WithProgress.IsPresent) { builder.WithProgressHandler(progress => WriteProgress(new ProgressRecord( 1, "Processing", $"Progress: {progress}%") { PercentComplete = progress })); } if (SplitOnWord.IsPresent) builder.SplitOnWord(); if (MaxTokensPerSegment.HasValue) builder.WithMaxTokensPerSegment(MaxTokensPerSegment.Value); // Configure silence detection threshold if (NoSpeechThreshold.HasValue) { builder.WithNoSpeechThreshold(NoSpeechThreshold.Value); } else { builder.WithNoSpeechThreshold(0.6f); } if (AudioContextSize.HasValue) builder.WithAudioContextSize(AudioContextSize.Value); if (DontSuppressBlank.IsPresent) builder.WithoutSuppressBlank(); if (MaxDuration.HasValue) builder.WithDuration(MaxDuration.Value); if (Offset.HasValue) builder.WithOffset(Offset.Value); if (MaxLastTextTokens.HasValue) builder.WithMaxLastTextTokens(MaxLastTextTokens.Value); if (SingleSegmentOnly.IsPresent) builder.WithSingleSegment(); if (PrintSpecialTokens.IsPresent) builder.WithPrintSpecialTokens(); if (MaxSegmentLength.HasValue) builder.WithMaxSegmentLength(MaxSegmentLength.Value); if (MaxInitialTimestamp.HasValue) builder.WithMaxInitialTs( (int)MaxInitialTimestamp.Value.TotalSeconds); if (LengthPenalty.HasValue) builder.WithLengthPenalty(LengthPenalty.Value); if (EntropyThreshold.HasValue) builder.WithEntropyThreshold(EntropyThreshold.Value); if (LogProbThreshold.HasValue) builder.WithLogProbThreshold(LogProbThreshold.Value); if (NoContext.IsPresent) builder.WithNoContext(); if (WithBeamSearchSamplingStrategy.IsPresent) builder.WithBeamSearchSamplingStrategy(); return builder; } /// <summary> /// Downloads a Whisper model file if not already present /// </summary> /// <param name="fileName">Target file path for the model</param> /// <param name="ggmlType">Model type to download</param> private static async Task DownloadModel(string fileName, GgmlType ggmlType) { System.Console.WriteLine($"Downloading Model {fileName}"); using var modelStream = await WhisperGgmlDownloader.Default.GetGgmlModelAsync(ggmlType); using var fileWriter = File.OpenWrite(fileName); await modelStream.CopyToAsync(fileWriter); } /// <summary> /// Generates the standard model filename for a given model type /// </summary> /// <param name="modelType">Whisper model type</param> /// <returns>Model filename string</returns> private static string GetModelFileName(GgmlType modelType) { return $"ggml-{modelType}.bin"; } #endregion } } |