dictaite/vttcli
0 forks
forever-concept
Sometimes I'm bored and finish some forever concepts, this is a playground for VTT in a CLI. Currently only works in Linux, but will fix it for windows too, in a far, far future.
id: 26
508 Lines
  • CSharp 75%
  • Markdown 25%
README.md

vttcli

A terminal-based voice-to-text transcriber for Linux. Records your microphone and transcribes speech to text in real time using a local Whisper model — no API keys, no cloud, no internet required after setup.

Requirements

LocalDocker
.NET10.0 SDKIncluded in image
Audioarecord (ALSA) + microphone--device /dev/snd
Modelggml-tiny.bin (~75 MB)Downloaded during build
OSLinuxLinux

Quick start

Local

wget https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-tiny.bin
dotnet run --project src/VttCli ./ggml-tiny.bin

Docker

docker build -t vttcli .
docker run --device /dev/snd -it vttcli

The --device /dev/snd flag passes your host's sound card into the container. Without it, arecord has nothing to read from.

Controls

KeyAction
SpaceToggle recording on/off
SSave transcript to transcript_<timestamp>.txt
QQuit

Architecture

┌──────────┐   raw PCM    ┌──────────────┐   float[]    ┌──────────────┐
│ arecord  │──────────────▶│ AudioRecorder │─────────────▶│ Transcriber  │
│ (ALSA)   │  16kHz/16/1  │  (pipe→buf)   │  32-bit PCM  │ (whisper.net)│
└──────────┘              └──────────────┘              └──────┬───────┘
                                                               │ segments
                                                               ▼
                                                        ┌──────────────┐
                                                        │  Terminal.Gui │
                                                        │     TUI      │
                                                        └──────────────┘

Real-time streaming

Transcription uses a sliding window approach:

  1. arecord streams raw PCM to stdout, which .NET reads in 4 KB chunks
  2. Chunks accumulate in a thread-safe buffer
  3. Every 3 seconds, the last 5 seconds of audio (overlapping window) is sent to Whisper
  4. Recognized segments are appended to the TUI transcript view

This gives ~3 seconds of perceived latency. The overlapping window prevents cutting words at chunk boundaries.

Audio format

All audio is captured as 16 kHz, 16-bit signed little-endian, mono — the native format Whisper expects. Raw bytes are converted to float[] normalized to [-1.0, 1.0] before being passed to the processor.

Model selection

The default model is ggml-tiny.bin (~75 MB). You can swap it for a larger model for better accuracy:

ModelSizeRelative speedNotes
ggml-tiny.bin75 MBFastestDefault. Good for clear speech
ggml-base.bin142 MBFastBetter accuracy
ggml-small.bin466 MBMediumGood balance
ggml-medium.bin1.5 GBSlowHigh accuracy
ggml-large.bin2.9 GBSlowestBest accuracy

Download from HuggingFace and pass the path as the first argument:

dotnet run --project src/VttCli ./ggml-base.bin

Language

The transcriber defaults to English. To change the language, edit the language parameter in Transcriber.cs:

public Transcriber(string modelPath, string language = "en")

Use ISO 639-1 codes (e.g. "de", "fr", "es", "ja").

Tech stack

  • .NET 10 — runtime
  • Terminal.Gui v2.4 — TUI framework
  • Whisper.net v1.9 — C# bindings for whisper.cpp
  • ALSA arecord — audio capture (shells out, no native .NET audio dependencies)

Project structure

vttcli/
├── vttcli.slnx
├── Dockerfile
├── .dockerignore
└── src/VttCli/
    ├── VttCli.csproj       # NuGet refs: Terminal.Gui, Whisper.net, Whisper.net.Runtime
    ├── Program.cs           # Entry point, TUI layout, key bindings, orchestration
    ├── AudioRecorder.cs     # Spawns arecord, reads PCM from stdout into a buffer
    └── Transcriber.cs       # Loads Whisper model, processes audio, fires segment events

Troubleshooting

"arecord: command not found" — Install ALSA utils: sudo apt install alsa-utils

No audio / silent recording — Check your default input device with arecord -l. In Docker, ensure --device /dev/snd is set and the host audio system isn't PulseAudio-exclusive (ALSA needs direct device access).

Model not found — The first CLI argument is the model path. Default is ggml-tiny.bin in the current directory.

Slow transcription — The tiny model runs on CPU. If it can't keep up with real-time, try a smaller audio window or switch to a machine with AVX2 support (the native libs auto-detect this).

Please wait...
Connection lost or session expired, reload to recover
Page is in error, reload to recover