Skip to content

Repository files navigation

English · 中文

scribe

Audio in, one line of text out.

scribe < take.wav
pw-record --rate 16000 --channels 1 --format s16 - | scribe --pcm
scribe serve
scribe fetch-model

stdin 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.

Install

Linux, x86_64 or aarch64.

Binary

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 there

On 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/.

From source

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/scribe

fetch-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

nix run github:maplevoid/scribe -- < take.wav

Home 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.

systemd (without Nix)

# ~/.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=simple
systemctl --user daemon-reload
systemctl --user enable --now scribe.socket

Models

Local SenseVoice needs model.int8.onnx and tokens.txt in $XDG_DATA_HOME/scribe/models (usually ~/.local/share/scribe/models):

scribe fetch-model

Already-present files are left alone. Whisper encoder/decoder files are not fetched. Groq / OpenAI need an API key, not a local model.

Usage

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.

Config

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 path
sample_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 up

API keys from the environment: SCRIBE_API_KEY, GROQ_API_KEY, or OPENAI_API_KEY. Do not put them in the toml.

Protocol (serve)

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.

Develop

go test ./...
go build -o scribe ./cmd/scribe

Or nix develop.

About

WAV/PCM on stdin, one UTF-8 line on stdout

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages