Audio in, one line of text out.
scribe < take.wav
pw-record --rate 16000 --channels 1 --format s16 - | scribe --pcm
scribe serve
scribe fetch-modelstdin is WAV or s16le mono PCM. stdout is one UTF-8 line. Errors and
serve logs go to stderr.
serve listens on $XDG_RUNTIME_DIR/scribe.sock. Local engines
(SenseVoice, Whisper) load on the first request — including an empty
PCM warmup — and exit after [scribe].idle (default 10m). Groq and
OpenAI have nothing to unload.
voicein is one client. This binary only transcribes.
Linux, x86_64 or aarch64.
Tag a v* release and GitHub Actions uploads:
| Arch | Archive |
|---|---|
x86_64 |
scribe-linux-amd64.tar.gz |
aarch64 |
scribe-linux-arm64.tar.gz |
Each archive is the binary plus lib/*.so (sherpa-onnx + onnxruntime).
The binary's rpath is $ORIGIN:$ORIGIN/lib, so keep that layout.
curl -fsSL -o scribe.tar.gz \
https://github.com/maplevoid/scribe/releases/latest/download/scribe-linux-amd64.tar.gz
tar -xzf scribe.tar.gz
sudo install -m 755 scribe-linux-amd64/scribe /usr/local/bin/scribe
sudo mkdir -p /usr/local/lib/scribe
sudo cp -a scribe-linux-amd64/lib/*.so /usr/local/lib/scribe/
sudo patchelf --set-rpath '/usr/local/lib/scribe' /usr/local/bin/scribe
# or leave the unpacked directory together and run ./scribe from thereOn Debian/Ubuntu, patchelf is in the patchelf package. If you run
from the unpacked directory, you do not need it.
Remote engines (groq, openai) still work if you only copy the
binary and skip lib/.
A C compiler is required for local engines.
git clone https://github.com/maplevoid/scribe.git
cd scribe
scripts/fetch-sherpa.sh x86_64-unknown-linux-gnu # or aarch64-unknown-linux-gnu
go build -o scribe ./cmd/scribe
sudo install -m 755 scribe /usr/local/bin/scribefetch-sherpa.sh puts the .so files under
vendor/github.com/k2-fsa/sherpa-onnx-go-linux/lib/<triple>/. The
binary's build-time rpath points there. To install elsewhere, copy
those .so files next to the binary (or onto LD_LIBRARY_PATH).
Remote engines do not need them.
nix run github:maplevoid/scribe -- < take.wavHome Manager:
{
inputs.scribe.url = "github:maplevoid/scribe";
inputs.scribe.inputs.nixpkgs.follows = "nixpkgs";
}{
imports = [ inputs.scribe.homeManagerModules.default ];
services.scribe.enable = true;
}That puts scribe on PATH and installs scribe.socket →
$XDG_RUNTIME_DIR/scribe.sock. The first connection starts the
service. Idle-exit is the process's own timer. Models are not
downloaded.
# ~/.config/systemd/user/scribe.socket
[Socket]
ListenStream=%t/scribe.sock
SocketMode=0600
[Install]
WantedBy=sockets.target# ~/.config/systemd/user/scribe.service
[Service]
ExecStart=/usr/local/bin/scribe serve
Type=simplesystemctl --user daemon-reload
systemctl --user enable --now scribe.socketLocal SenseVoice needs model.int8.onnx and tokens.txt in
$XDG_DATA_HOME/scribe/models (usually ~/.local/share/scribe/models):
scribe fetch-modelAlready-present files are left alone. Whisper encoder/decoder files are not fetched. Groq / OpenAI need an API key, not a local model.
scribe < take.wav
scribe --pcm < take.pcm
scribe --engine groq --language zh --pcm < take.pcm
scribe serve
scribe --engine whisper serve--engine and --language override the toml for this process only.
They apply to the filter and to serve. They do not write the config
file. fetch-model ignores them.
Empty PCM (n_bytes = 0 on the socket, or a zero-length --pcm
stdin) is a warmup: load the engine, print nothing, do not decode.
Default path is ~/.config/scribe/config.toml. SCRIBE_CONFIG
overrides it. A missing file uses built-in defaults.
scribe config # print an example
scribe config get
scribe config set engine whisper
scribe config pathsample_rate = 16000
language = "auto" # auto | zh | en | yue | ja | ko
itn = true
threads = 4
[model]
engine = "sensevoice" # sensevoice | whisper | groq | openai
onnx = "model.int8.onnx"
tokens = "tokens.txt"
[scribe]
socket = "" # default: $XDG_RUNTIME_DIR/scribe.sock
idle = "10m" # 0 = stay upAPI keys from the environment: SCRIBE_API_KEY, GROQ_API_KEY, or
OPENAI_API_KEY. Do not put them in the toml.
One request per connection. Little-endian.
u32 magic = 0x53435231 # "SCR1"
u32 sample_rate # 16000
u32 n_bytes # PCM s16le mono
[n_bytes]
Reply:
u32 status # 0 ok
u32 n_bytes
[utf-8]
status != 0 means the payload is an error string. Do not half-close
the client before reading the reply; the first SenseVoice load can
take tens of seconds.
Empty PCM is a warmup: status = 0 and an empty string, no decode.
Non-empty PCM that decodes to whitespace is the same reply — clients
must not treat that as a socket error.
Silence is a valid request. SenseVoice may still emit a short hallucination. That is the model, not a protocol bug.
go test ./...
go build -o scribe ./cmd/scribeOr nix develop.