ElBruno.Whisper 0.9.0

dotnet add package ElBruno.Whisper --version 0.9.0
                    
NuGet\Install-Package ElBruno.Whisper -Version 0.9.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="ElBruno.Whisper" Version="0.9.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="ElBruno.Whisper" Version="0.9.0" />
                    
Directory.Packages.props
<PackageReference Include="ElBruno.Whisper" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add ElBruno.Whisper --version 0.9.0
                    
#r "nuget: ElBruno.Whisper, 0.9.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package ElBruno.Whisper@0.9.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=ElBruno.Whisper&version=0.9.0
                    
Install as a Cake Addin
#tool nuget:?package=ElBruno.Whisper&version=0.9.0
                    
Install as a Cake Tool

ElBruno.Whisper

NuGet NuGet Downloads Build Status License: MIT HuggingFace .NET GitHub stars Twitter Follow

Run local Whisper speech-to-text in .NET 🎀

Transcribe audio to text in .NET using OpenAI's Whisper model. Powered by ONNX Runtime with automatic model download from HuggingFace.

Packages

Package NuGet Downloads Description
ElBruno.Whisper NuGet NuGet Downloads Core speech-to-text library with Whisper ONNX models
ElBruno.Whisper.BlazorComponents NuGet NuGet Downloads Reusable Blazor components for transcription workflows

Features

  • πŸ“¦ Automatic model download β€” models are fetched from HuggingFace on first use
  • πŸ”Š Multiple model sizes β€” tiny β†’ base β†’ small β†’ medium β†’ large (pick your speed/accuracy tradeoff)
  • πŸš€ Zero friction β€” works out of the box with sensible defaults (tiny.en)
  • 🌍 Multilingual support β€” transcribe 99+ languages with multilingual models
  • πŸ’‰ DI-friendly β€” register with AddWhisper() in ASP.NET Core
  • 🧩 Microsoft.Extensions.AI ready β€” resolve ISpeechToTextClient for standard speech-to-text integration
  • 🧡 Concurrent transcription β€” share one client with configurable queueing and pooled inference sessions
  • πŸ”„ Incremental updates β€” get rolling provisional and committed text through GetStreamingTextAsync()
  • πŸ“Ό Raw audio inputs β€” transcribe WAV streams, PCM16 byte streams, and Float32 memory without temporary files
  • πŸ“Š Progress reporting β€” track model downloads with real-time callbacks
  • 🎯 English-optimized models β€” dedicated .en variants for best accuracy on English audio
  • ⏱️ Timestamp-aware results β€” opt into segment and word timings for subtitle and caption workflows

Installation

dotnet add package ElBruno.Whisper

Quick Start

using ElBruno.Whisper;

// Create client (downloads tiny.en model on first run)
using var client = await WhisperClient.CreateAsync();

var result = await client.TranscribeAsync("audio.wav");
Console.WriteLine(result.Text);

First Run

The first time you create a WhisperClient, the model is downloaded from HuggingFace to your local cache directory (~75 MB - 3 GB depending on model size). This typically takes 10-60 seconds depending on your internet connection and chosen model.

Track download progress:

using var client = await WhisperClient.CreateAsync(
    progress: new Progress<ElBruno.HuggingFace.DownloadProgress>(p =>
    {
        if (p.Stage == ElBruno.HuggingFace.DownloadStage.Downloading)
            Console.WriteLine($"{p.CurrentFile}: {p.PercentComplete:F0}%");
        else
            Console.WriteLine($"{p.Stage}: {p.Message}");
    })
);

Subsequent runs load instantly from cache (%LOCALAPPDATA%/ElBruno/Whisper/models).

Model Selection

Whisper offers various model sizes. English-optimized models (.en suffix) are smaller and faster for English audio:

using var client = await WhisperClient.CreateAsync(new WhisperOptions
{
    Model = KnownWhisperModels.WhisperSmallEn
});

var result = await client.TranscribeAsync("english-audio.wav");
Console.WriteLine(result.Text);

Available Models

Size English Multilingual Parameters Approx Size Speed
tiny tiny.en tiny 39M 75 MB ⚑⚑⚑⚑⚑
base base.en base 74M 140 MB ⚑⚑⚑⚑
small small.en small 244M 460 MB ⚑⚑⚑
medium medium.en medium 769M 1.5 GB ⚑⚑
large β€” large 1550M 3.0 GB ⚑

Use English-optimized (.en) models for:

  • English audio only (slightly smaller, faster, better accuracy on English)

Use Multilingual models for:

  • Non-English audio
  • Mixed-language content
  • Language auto-detection

Progress Tracking

Monitor both file downloads and transcription progress:

var downloadProgress = new Progress<ElBruno.HuggingFace.DownloadProgress>(p =>
{
    if (p.Stage == ElBruno.HuggingFace.DownloadStage.Downloading)
        Console.Write($"\r⬇️ {p.PercentComplete:F0}%");
    else
        Console.WriteLine($"\nβœ“ {p.Message}");
});

using var client = await WhisperClient.CreateAsync(progress: downloadProgress);

var result = await client.TranscribeAsync("audio.wav");
Console.WriteLine($"βœ“ Transcribed: {result.Text}");

Dependency Injection

Register Whisper options in ASP.NET Core or other DI-enabled applications:

builder.Services.AddWhisper(options =>
{
    options.Model = KnownWhisperModels.WhisperBaseEn;
    options.Concurrency.MaximumConcurrentRequests = 2;
});

AddWhisper() registers WhisperOptions, WhisperSpeechToTextClient, and ISpeechToTextClient. Create and share a WhisperClient yourself when you want direct control over startup and disposal.

Microsoft.Extensions.AI

Use the adapter when you want the standard ISpeechToTextClient contract:

using ElBruno.Whisper;
using Microsoft.Extensions.AI;

builder.Services.AddWhisper(options =>
{
    options.Model = KnownWhisperModels.WhisperTinyEn;
    options.Language = "en";
});

var speechToText = builder.Services
    .BuildServiceProvider()
    .GetRequiredService<ISpeechToTextClient>();

await using var audioStream = File.OpenRead("audio.wav");
var response = await speechToText.GetTextAsync(
    audioStream,
    new SpeechToTextOptions
    {
        SpeechLanguage = "en",
        AdditionalProperties = new()
        {
            ["elbruno.whisper.enable_timestamps"] = true
        }
    });

Console.WriteLine(response.Text);
Console.WriteLine(response.AdditionalProperties?["elbruno.whisper.detected_language"]);

The adapter keeps caller-owned streams open, supports cancellation, exposes SpeechToTextClientMetadata, and returns these response metadata keys through AdditionalProperties:

  • elbruno.whisper.detected_language
  • elbruno.whisper.audio_duration_ms
  • elbruno.whisper.segments
  • elbruno.whisper.words
  • elbruno.whisper.model_id
  • elbruno.whisper.execution_provider

Thread Safety and Concurrency

WhisperClient can now be shared across concurrent callers. By default it still processes one transcription at a time. Raise the concurrency limit to allow parallel work and reuse pooled ONNX sessions:

using var client = await WhisperClient.CreateAsync(new WhisperOptions
{
    Model = KnownWhisperModels.WhisperTinyEn,
    Concurrency = new WhisperConcurrencyOptions
    {
        MaximumConcurrentRequests = 2,
        QueueTimeout = TimeSpan.FromSeconds(15),
        EnableSessionPooling = true
    }
});

var results = await Task.WhenAll(
    client.TranscribeAsync("audio-1.wav"),
    client.TranscribeAsync("audio-2.wav"));

If all inference slots are busy longer than QueueTimeout, TranscribeAsync throws TimeoutException. Cancelling the request also aborts queue waiting and the next safe decode checkpoint.

Incremental Transcription

WhisperClient.GetStreamingTextAsync() runs Whisper over rolling windows and emits ordered updates with both stable and provisional text:

using var client = await WhisperClient.CreateAsync();

await foreach (var update in client.GetStreamingTextAsync(
    "audio.wav",
    new WhisperStreamingOptions
    {
        WindowSize = TimeSpan.FromSeconds(8),
        StepSize = TimeSpan.FromSeconds(1),
        ContextOverlap = TimeSpan.FromSeconds(2),
        UseLocalAgreement = true,
        AgreementIterations = 2
    }))
{
    Console.WriteLine($"Committed:   {update.CommittedText}");
    Console.WriteLine($"Provisional: {update.ProvisionalText}");

    if (update.IsFinal)
        Console.WriteLine("Final update received.");
}

Each update exposes:

  • CommittedText β€” text that has stabilized across rolling windows
  • ProvisionalText β€” the newest hypothesis that may still change
  • Text β€” the combined transcript for the current update
  • IsFinal β€” true exactly once, after the final flush

Limitations: Whisper is not a native streaming model. This API reads completed file or stream content, re-runs inference over overlapping windows, and uses local agreement to reduce duplicate committed text. Provisional text can still change between updates.

Realtime PCM and Memory Inputs

Use the explicit-audio overloads when your pipeline already has PCM in memory and you want to avoid temporary WAV files:

using var client = await WhisperClient.CreateAsync();

var pcm16Format = new WhisperAudioFormat(
    sampleRate: 48000,
    channels: 2,
    sampleFormat: WhisperAudioSampleFormat.Pcm16);

await using var rawAudioStream = File.OpenRead("call.raw");
var streamResult = await client.TranscribeAsync(rawAudioStream, pcm16Format);

ReadOnlyMemory<byte> pcmBytes = await File.ReadAllBytesAsync("call.raw");
var byteResult = await client.TranscribeAsync(pcmBytes, pcm16Format);

ReadOnlyMemory<float> monoFloatSamples = GetNormalizedSamples();
var floatResult = await client.TranscribeAsync(monoFloatSamples, sampleRate: 16000);

Notes:

  • TranscribeAsync(Stream) auto-detects WAV headers and keeps the caller-owned stream open.
  • Raw PCM byte streams require WhisperAudioFormat so the client can downmix and resample to Whisper's 16 kHz mono input.
  • ReadOnlyMemory<float> overloads expect normalized PCM samples in the [-1, 1] range.

Blazor Components Package

Build speech-to-text interfaces quickly with ElBruno.Whisper.BlazorComponents:

dotnet add package ElBruno.Whisper.BlazorComponents
builder.Services.AddWhisper(options =>
{
    options.Model = KnownWhisperModels.WhisperBaseEn;
});
builder.Services.AddWhisperBlazorComponents();

Available components:

  • WhisperModelSelector
  • FileTranscriptionPanel
  • LiveTranscriptViewer
  • TranscriptionHistoryList

Transcription Result

The TranscriptionResult includes:

var result = await client.TranscribeAsync("audio.wav");

Console.WriteLine(result.Text);                    // Transcribed text
Console.WriteLine(result.DetectedLanguage);       // Detected language (for multilingual models)
Console.WriteLine(result.Duration);               // Audio duration

Enable timestamps to access both segment-level and word-level timing metadata:

using var client = await WhisperClient.CreateAsync(new WhisperOptions
{
    EnableTimestamps = true
});

var result = await client.TranscribeAsync("audio.wav");

foreach (var segment in result.Segments ?? [])
{
    Console.WriteLine($"[{segment.Start:mm\\:ss\\.ff} - {segment.End:mm\\:ss\\.ff}] {segment.Text}");

    foreach (var word in segment.Words)
    {
        Console.WriteLine($"  {word.Start:mm\\:ss\\.ff} - {word.End:mm\\:ss\\.ff}: {word.Text}");
    }
}

Word timings are derived from the timestamped transcript spans produced by Whisper. If a model returns text without explicit spans, the library falls back to a single full-duration segment and derives word timings within that range.

Troubleshooting

Model download fails?

  • Check your internet connection
  • For private HuggingFace models, set the HF_TOKEN environment variable

Out of memory?

  • Use a smaller model (tiny or base instead of medium/large)
  • Transcribe shorter audio files in chunks

For detailed troubleshooting, see docs.

Samples

Sample Description
HelloWhisper Minimal console transcription
BlazorWhisper Blazor app with audio recording and real-time transcription
Blazor Components demo page Demonstrates the reusable Blazor components package

What's New

  1. Blazor Components package β€” Added ElBruno.Whisper.BlazorComponents with model selector, file transcription panel, live transcript viewer, and transcription history list.
  2. Real-time Blazor transcription sample β€” Added chunk-based live transcription UX in the BlazorWhisper sample.
  3. Timestamp-aware transcription results β€” Added optional segment timestamps in TranscriptionResult.
  4. Model download progress reporting β€” Added structured progress callbacks during model download.
  5. OIDC trusted publishing β€” Added secure NuGet publishing with GitHub Actions and NuGet/login.

Repository rule: keep last 5 important features

  • The What’s New section must always list the latest 5 important repository/library features.
  • Before every NuGet release, validate whether the release introduces a notable change and add/update an entry when needed.

Documentation

Building from Source

git clone https://github.com/elbruno/ElBruno.Whisper
cd ElBruno.Whisper
dotnet build ElBruno.Whisper.slnx
dotnet test ElBruno.Whisper.slnx --filter "Category!=Integration"

Testing

The repository includes comprehensive unit and integration tests:

Quick test run (unit tests, no model download):

dotnet test ElBruno.Whisper.slnx --filter "Category!=Integration"

Full test run (includes integration with real models):

dotnet test ElBruno.Whisper.slnx

Test audio files are provided in testdata/audio/ for validation and transcription testing. For details, see the Testing Guide.

🀝 Contributing

Contributions are welcome! Please:

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

πŸ“„ License

This project is licensed under the MIT License β€” see the LICENSE file for details.

πŸ™ Acknowledgments

πŸ‘‹ About the Author

Hi! I'm ElBruno 🧑, a passionate developer and content creator exploring AI, .NET, and modern development practices.

Made with ❀️ by ElBruno

If you like this project, consider following my work across platforms:

  • πŸ“» Podcast: No Tienen Nombre β€” Spanish-language episodes on AI, development, and tech culture
  • πŸ’» Blog: ElBruno.com β€” Deep dives on embeddings, RAG, .NET, and local AI
  • πŸ“Ί YouTube: youtube.com/elbruno β€” Demos, tutorials, and live coding
  • πŸ”— LinkedIn: @elbruno β€” Professional updates and insights
  • 𝕏 Twitter: @elbruno β€” Quick tips, releases, and tech news
Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (2)

Showing the top 2 NuGet packages that depend on ElBruno.Whisper:

Package Downloads
ElBruno.MarkItDotNet.Whisper

Local audio transcription for ElBruno.MarkItDotNet using OpenAI Whisper via ONNX Runtime. Converts audio files to Markdown transcripts offline.

ElBruno.Whisper.BlazorComponents

Reusable Blazor components for ElBruno.Whisper speech-to-text experiences, including model selection, live transcript viewing, and file transcription workflows.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.9.0 129 8/3/2026
0.8.0 141 7/4/2026
0.7.0 104 7/4/2026
0.6.0 119 7/3/2026
0.5.0 119 7/3/2026
0.4.0 112 7/3/2026
0.2.0 400 4/11/2026
0.1.6 129 4/10/2026
0.1.5 346 4/2/2026
0.1.2 121 3/30/2026
0.1.1 107 3/30/2026
0.1.0 120 3/30/2026