Transcribe sample.flac (in the zip). First use downloads several GB of models.
dotnet add package RecursiveAI.OneSpeechRequires .NET 10 (net10.0); .NET 9 and earlier aren’t supported.
using OneSpeech; var words = Speech.Transcribe( "sample.flac", language: "en").Words; foreach (var word in words.Take(3)) Console.WriteLine( $"{word.Text}: {word.Start}");
Unzip the example, then dotnet run.
Prints:
stuffered: 00:00:00.5460000 into: 00:00:00.8660000 you,: 00:00:01.1860000
This is the model’s real output on a real recording, mistake included; word.Text.Trim() removes a recognized word’s leading space.
The default recognizer is Whisper large-v3-turbo: its publisher weights are 1,617.8 MB (about 1.62 GB). This English run also loads Qwen3-ForcedAligner (1,835.5 MB of publisher weights) to time words and CAM++ English (29.4 MB) to group speakers; the built-in Silero detector finds speech. Converted runtime files take additional disk space. By default, models are cached in %LOCALAPPDATA%/OneSpeech/models on Windows, ~/Library/Caches/OneSpeech/models on macOS, or $XDG_CACHE_HOME/onespeech/models on Linux (falling back to ~/.cache/onespeech/models). ONESPEECH_MODEL_CACHE overrides that location. See Model files and pre-fetch and the download sizes.
The full program
The snippet at the top prints the first three word starts; Start can be null, in which case string interpolation prints a blank. The full program checks both boundaries, prints every timed word and saves words.json. TimingMethod tells you how each word received its times; here ForcedAligner means a separate model placed the recognized words on the audio clock.
The input sample.flac is the unmodified LibriSpeech test-clean recording 1089-134686-0001: “stuff it into you his belly counselled him”. It ships in the zip under CC BY 4.0, credited to Vassil Panayotov, Guoguo Chen, Daniel Povey and Sanjeev Khudanpur (LibriSpeech).
using OneSpeech; Transcript transcript = Speech.Transcribe("sample.flac", language: "en"); foreach (Word word in transcript.Words) { if (word.Start is TimeSpan start && word.End is TimeSpan end) Console.WriteLine($"{start.TotalSeconds:0.00}–{end.TotalSeconds:0.00} {word.Text} ({word.TimingMethod})"); else Console.WriteLine($"untimed: {word.Text}"); } File.WriteAllText("words.json", transcript.ToJson());
dotnet run prints:
0.55–0.87 stuffered (ForcedAligner) 0.87–1.19 into (ForcedAligner) 1.19–1.43 you, (ForcedAligner) 1.59–1.75 his (ForcedAligner) 1.75–2.15 belly (ForcedAligner) 2.15–2.63 counseled (ForcedAligner) 2.63–2.79 him. (ForcedAligner)
This is the recognizer’s actual output, including its “stuffered” mistake and leading spaces. Missing models download on first use; subsequent runs reuse verified cache files.
Invalid encoded audio throws UnsupportedAudioException; model download or integrity failures are reported as typed model errors.
Variations
Measure only words with both endpoints. Missing timestamps are skipped, not replaced with zero. Use the same sample.flac from the zip.
using OneSpeech; Transcript transcript = Speech.Transcribe("sample.flac", language: "en"); foreach (Word word in transcript.Words) { if (word.Start is TimeSpan start && word.End is TimeSpan end) Console.WriteLine($"{word.Text.Trim()}: {(end - start).TotalSeconds:0.00} s"); }
stuffered: 0.32 s into: 0.32 s you,: 0.24 s his: 0.16 s belly: 0.40 s counseled: 0.48 s him.: 0.16 s
Related recipes
- Speech to text in C#
- Transcribe an audio file in C#
- Run Whisper in C# without native libraries
- Generate SRT subtitles from audio in C#
The short answer
For speech-to-text word timestamps in C#, call Speech.Transcribe and enumerate Transcript.Words. Each word has nullable Start and End values, expressed as TimeSpan offsets on the original audio clock. Check both for null before seeking, measuring a word’s duration or building a timed highlight. TimingMethod records where the timing came from.
The full program above prints the word, start, end and timing method, and saves JSON with the full result. This run used forced alignment for the boundaries. It still contains a recognition error: a timestamp beside “stuffered” does not make that spelling correct.
What are timestamps in transcription?
They are positions in the recording, not the wall-clock time at which your application processed a word. In this run the first recognized word starts at about 0.55 seconds and ends at about 0.87 seconds. A player can seek to that interval even when recognition took much longer than the audio itself.
Word times and segment times serve different purposes. A segment locates a larger stretch of speech; a word interval can support individual highlighting or search results that jump to a particular term. OneSpeech represents missing boundaries as null rather than pretending every word occupies an equal share of the segment. Nullable confidence is separate too: having a boundary does not imply that a confidence value exists.
The word-timing documentation explicitly says that no method is sample-accurate ground truth. Keep that distinction when displaying rounded seconds. Printing two decimal places makes the console readable; it is not a promise of hundredth-of-a-second accuracy.
Is there a tool that can transcribe speech to text with timestamps?
Yes. OneSpeech’s whole-file helper returns recognized text with word timing and anonymous speaker turns. Native recognizer timing takes priority. Otherwise the pipeline can use Qwen3-ForcedAligner, then omniASR-CTC, with DTW as the final fallback, subject to model and language coverage. Missing boundaries remain null. Alignment models are acquired only when needed by a chunk.
Look at TimingMethod rather than assuming all word boundaries are native Whisper timestamps. The public values distinguish Native, ForcedAligner, CtcAligner, Dtw and None. This sample prints ForcedAligner. Timing provenance lets a downstream application keep estimated boundaries distinct from a missing interval instead of flattening both into a numeric zero.
The pipeline may need several GB of models on first use, including extra models beyond the recognizer. Prepare those files before using it on a disconnected machine. The model-download table lists the extra models an operation may need.
Do you need timestamps in a transcript?
Not for every task. If you only want readable text or a search index with no audio links, Transcript.Text may be enough. Keep word times when your application needs playback navigation, highlighting, duration measurements or later caption export. The variation above prints durations only for words with both measured endpoints.
Save ToJson() when timing, confidence, speaker labels and model provenance need to survive storage. Plain text discards that structure. SRT stores cue-level times but not word times; caption export is therefore not a replacement for the transcript document. Speaker assignment is also independent of missing word timing and cannot fill it in.
When a boundary is null, omit that individual highlight or surface an untimed word explicitly. Don’t substitute zero or divide the enclosing segment by the number of words. That would create times the transcription result did not provide. For corrected written text that you want to align to an existing recording, use the separate alignment operation rather than treating recognition output as a verified transcript.