Recursive AI

Report speech model download progress in C#

OneSpeech is a free, open-source speech recognition library for .NET from Recursive AI. MIT or Apache-2.0, no native dependencies.

Show bytes received for each model file. Cache verification does not emit download progress.

Terminal
dotnet add package RecursiveAI.OneSpeech

Requires .NET 10 (net10.0); .NET 9 and earlier aren’t supported.

C#
using OneSpeech.Catalog;
var progress =
    new Progress<ModelDownloadProgress>(
    p => Console.WriteLine(
        $"{p.BytesReceived}/{p.TotalBytes}"));
await new ModelStore().PullAsync(
    "iic/speech_campplus_sv_en_voxceleb_16k",
    progress: progress);

Unzip the example, then dotnet run.

This is a small English speaker model that needs no account or key; you can use any model ID in the OneSpeech catalog, subject to that model’s publisher access requirements.

Progress<T> posts callbacks asynchronously, so a console app can receive the final callback after PullAsync completes. The full program uses a synchronous IProgress<T> implementation so its messages stay in order.

On an empty cache, prints intermediate byte counts. The final report in this run was:

Text
29357703/29357703

The full program

C#
using OneSpeech.Catalog;
 
using var cancellation = new CancellationTokenSource();
Console.CancelKeyPress += (_, e) =>
{
    e.Cancel = true;
    cancellation.Cancel();
};
try
{
    await new ModelStore().PullAsync(
        "iic/speech_campplus_sv_en_voxceleb_16k",
        progress: new DownloadProgress(),
        cancellationToken: cancellation.Token);
    Console.WriteLine("Model verified and ready.");
}
catch (OperationCanceledException) when (cancellation.IsCancellationRequested)
{
    Console.WriteLine("Cancelled; incomplete download removed.");
}
 
sealed class DownloadProgress : IProgress<ModelDownloadProgress>
{
    public void Report(ModelDownloadProgress progress)
    {
        if (progress.BytesReceived == progress.TotalBytes)
            Console.WriteLine($"{progress.FileName}: {progress.BytesReceived}/{progress.TotalBytes} bytes");
    }
}

This downloads an English speaker model that needs no account or key, not a recognizer. The full program prints only each file’s final byte count and supports Ctrl+C. Cancellation throws OperationCanceledException and removes an incomplete transfer; completed files remain.

Text
campplus_voxceleb.bin: 29357703/29357703 bytes
Model verified and ready.

With the same complete cache, only Model verified and ready. prints. Receiving all bytes is not the same as passing integrity checks; await the pull before using the model.

Variations

Cancel before starting a pull:

C#
using OneSpeech.Catalog;
using var cancellation = new CancellationTokenSource();
cancellation.Cancel();
try
{
    await new ModelStore().PullAsync(
        "iic/speech_campplus_sv_en_voxceleb_16k",
        cancellationToken: cancellation.Token);
}
catch (OperationCanceledException)
{
    Console.WriteLine("Cancelled before download.");
}
Text
Cancelled before download.

The short answer

Pass an IProgress<ModelDownloadProgress> to ModelStore.PullAsync. Each report contains the model ID, filename, received bytes and expected bytes for that file. Pass a cancellation token to the same call if the user can stop it.

The example uses the English speaker model that needs no account or key so it can demonstrate an actual download without a service key or audio recording. Its reporter prints only each file’s final byte count. The underlying API supplies intermediate reports too; a UI can use those to update a per-file progress bar.

How do I show model download progress in C#?

Use the filename as part of the progress display. BytesReceived and TotalBytes describe one file, not all files in the model and not all companion models needed by transcription. When another file starts, its received count starts again. A percentage calculated from these fields is therefore a file percentage, not an overall installation percentage.

The snippet at the top uses Progress<T>, the standard .NET callback adapter. The downloadable console program implements IProgress<T> directly so its final-file messages are written synchronously before its success message. In a UI, construct Progress<T> on the UI context so its callback can update the display; Microsoft explains that synchronization behavior in its progress and cancellation guidance.

A received-byte count is not a ready-to-run signal. OneSpeech still checks file integrity and prepares registered runtime conversions. Treat successful completion of the awaited pull as readiness. A transfer can reach its expected byte count and still fail verification.

Can I cancel a download in progress?

Yes. Create a CancellationTokenSource, pass its token to PullAsync, and cancel the source from your cancel control. In the console program, Ctrl+C requests cancellation and prevents the default immediate console exit so the library can unwind. The catch handles OperationCanceledException only when that source was cancelled.

A cancelled or failed transfer removes its temporary file. Already-complete files survive interruption. If the whole process is interrupted instead of cooperatively cancelled, a temporary .download file can remain; the next pull restarts it rather than trusting it. There is no byte-range resume. Cancellation also interrupts a wait for another process’s cache lock.

The variation demonstrates an already-cancelled token. It is useful for checking the cancellation path without waiting for a transfer, but it is not a demonstration of partial-file cleanup during an active download.

Why is there no download progress for a cached model?

Reports are emitted only while downloading. Rehashing cached bytes is not download progress, and local conversion is not represented by the byte counter either. On a repeat run with the same complete cache, the full example prints only “Model verified and ready.”

Do not wait for a final progress callback as your completion condition: a cache hit may produce none. Await the pull itself. Keep permission and storage errors separate from cancellation, and show integrity errors rather than relabeling them as a successful cached download. The model-store documentation describes these error categories and the cache lifecycle.

All onespeech recipes