Hold CapsLock, speak, release — your words are typed. A fully offline voice input tool that can also run as a shared speech-to-text service on your NAS.
English · 繁體中文
Latest published: fork-v2.1.0 (2026-10-31). fork-v2.2.0 is unreleased
preparation; upstream stays v2.6 / 84912d5. See the
candidate notes and draft checklist.
Diagrams and screenshots use the Traditional Chinese UI; every label is explained in the text.
- What it does
- Two roles: Server and Client
- Which setup should I use?
- Quick start A: Windows desktop dictation
- Quick start B: Linux Docker server
- Quick start C: Web, CLI, TUI and SDK clients
- Everyday use
- Choosing a model
- Documentation map
- FAQ
- Versions, upstream and license
| Feature | Details | |
|---|---|---|
| 🎙️ | Dictation | Hold CapsLock (or a mouse side button) in any text field, speak, release — the text is typed. A short press is still a normal CapsLock. |
| 🔒 | Fully offline | ASR, punctuation and number formatting run locally; audio never leaves your machine. LLM polishing is optional and can use local Ollama / LM Studio. |
| 📁 | Files to subtitles | Drop audio/video on the client to get .srt, .txt, .json; HTTP clients also get .vtt. |
| 🔥 | Hotwords and rules | hot.txt fixes names and jargon by phoneme similarity; hot-rule.txt applies regex replacements. |
| 🤖 | LLM roles | Start a sentence with a trigger word such as "translate" or "assistant" to translate, polish or answer. |
| 🐳 | Docker server | One command on Linux, models download automatically; CPU works, NVIDIA / Intel / AMD GPUs are optional. |
| 🔌 | OpenAI-compatible API | POST /v1/audio/transcriptions (whisper-1) — point an existing OpenAI SDK at a new base URL. |
| 🖥️ | Many clients | Windows / Linux X11 desktop, browser Web Console, no-GUI CLI, Textual TUI. |
Real Windows session (from upstream): server on the left, client on the right; short-phrase latency is about 0.1–0.2 s.
CapsWriter always consists of two cooperating programs:
| Component | Owns | Does not own |
|---|---|---|
| Server | Loads ASR models, decodes audio, applies server hotwords, schedules inference, produces transcripts/subtitles, reports health | No browser/terminal UI; never touches the user's clipboard or global shortcuts |
| Client | Records or picks files, sends audio, shows/saves results; the desktop client also owns tray, hotkeys and typing | Does not load models or run ASR inference |
The server exposes two interfaces for different clients:
| Interface | Default | Used by |
|---|---|---|
WebSocket ws://127.0.0.1:6016 |
On | Windows / Linux X11 desktop client |
OpenAI-compatible HTTP http://127.0.0.1:6017 |
Off; explicit opt-in | Web Console, CLI, TUI, OpenAI SDK, curl |
Web Console http://127.0.0.1:8080 |
Optional | UI only; inference still runs on the server behind :6017 |
Tip
Web, CLI and TUI are not separate recognition engines — they all need a CapsWriter server with the HTTP API enabled. The desktop client talks WebSocket and normally needs no HTTP API. See Server and client roles.
| I want to… | Server runs on | Client | Start here |
|---|---|---|---|
| Dictate on my own Windows PC | The same PC (start_server.exe) |
Desktop client (start_client.exe) |
Quick start A |
| Share one Linux box / NAS with several computers | Linux Docker | Desktop clients, Web, CLI | Quick start B → C |
| Record or upload in a browser | Any server with HTTP enabled | Web Console | Quick start C |
| Batch-transcribe from scripts / SSH / CI | Any server with HTTP enabled | CLI | CLI guide |
| Move existing OpenAI Whisper code on-prem | Any server with HTTP enabled | OpenAI SDK / curl | API guide |
| Dictate on a Linux desktop | The same Linux machine (source) | Linux X11 desktop client | Getting started: Linux X11 |
Download CapsWriter-Offline-windows-x86_64.zip from
GitHub Releases and
extract the whole folder to a normal path such as D:\CapsWriter-Offline. Verify
it with the attached SHA256SUMS if you like.
It contains two programs with different jobs — never move one EXE out on its own:
CapsWriter-Offline/
├─ start_server.exe ← Server: loads the model, recognizes speech
├─ start_client.exe ← Client: tray, hotkeys, recording, typing
├─ config_server.py ← advanced server settings (model, ports…)
├─ config_client.py ← advanced client settings (hotkeys, hotwords, LLM…)
├─ hot.txt / hot-rule.txt / hot-server.txt
├─ LLM/ ← LLM roles
└─ models/ ← put models here (empty in the ZIP)
To keep the download small, the ZIP does not include models, llama.cpp DLLs or FFmpeg. The default Qwen3-ASR model needs two SHA-256-pinned downloads:
| Download | Extract to |
|---|---|
Qwen3-ASR-1.7B-q5_k.zip (~1.8 GB) |
models/Qwen3-ASR/, giving models/Qwen3-ASR/Qwen3-ASR-1.7B/ |
llama-b7798-bin-win-vulkan-x64.zip |
copy its *.dll files into core/server/engines/llama/bin/ |
The desktop portability guide
has a PowerShell block that downloads, verifies and places both for you. For file
transcription, add a trusted ffmpeg.exe (and ffprobe.exe) to PATH or the
package root; microphone dictation does not need it.
- Double-click
start_server.exeand wait until the model has loaded ("开始服务"). - Double-click
start_client.exe; the CapsWriter icon appears in the tray.
On first use, or whenever you want to change something, run
start_client.exe --settings or choose 設定 (Settings) from the tray menu:
Real rendering of the same Tk settings window on Linux (Xvfb); Windows uses its native theme and fonts.
Save and restart the client. Settings live in
%LOCALAPPDATA%\CapsWriter\client-settings.json and only store the fields you changed;
everything else comes from config_client.py. See daily settings.
Note
The release ZIP is built by the GitHub Actions windows-package job: a hash-locked
PyInstaller build that is moved out of the checkout, zipped and re-extracted with
reparse points rejected, then both EXEs pass --artifact-self-check. Please still
confirm microphone, tray, hotkeys, model and GPU on your own PC. To build it
yourself, see BUILD_GUIDE.
Requirements: linux/amd64, Docker Engine with the Compose plugin, and a few GB for models. GPUs are optional.
git clone https://github.com/DF-wu/CapsWriter-Offline-Container.git
cd CapsWriter-Offline-Container
cp .env.example .env
cp hot-server.example.txt hot-server.txt
docker compose up -d capswriter-server
docker compose logs -f capswriter-server # the first start downloads the modelWebSocket :6016 is now ready for desktop clients (enter this host's IP in their settings).
All deployment options live in one docker-compose.yml.
Uncomment its NVIDIA or DRI hardware block when needed; model selection, storage
and settings use .env. Web is optional via docker compose --profile web up -d.
See the single-file guide and migration notes.
Tesla P4 users can opt into the SM61 CUDA runtime with
CAPSWRITER_TESLA_P4_OPTIMIZATION=true; the default is false, retaining
the original CPU/Vulkan selection. See the P4 guide.
The optional NVIDIA pre-boost uses the same file's commented NVIDIA_BOOST permissions block. It remains off, with non-root appuser, unless explicitly enabled. P4 inference does not need that privilege block.
Set in .env:
CAPSWRITER_HTTP_API_ENABLE=true
CAPSWRITER_HTTP_API_KEY=replace-with-a-long-random-token
CAPSWRITER_HTTP_API_PUBLISH_HOST=127.0.0.1
CAPSWRITER_HTTP_API_PORT=6017
CAPSWRITER_HTTP_API_CORS_ORIGINS=http://127.0.0.1:8080,http://localhost:8080,http://127.0.0.1:5173,http://localhost:5173The HTTP mapping under ports: is already present and defaults to host loopback; the API
listener remains disabled until the flag is enabled. Recreate and check. /health only means the process is alive; /ready means the model can accept audio.
docker compose up -d --force-recreate capswriter-server
curl http://127.0.0.1:6017/health
curl http://127.0.0.1:6017/readyWarning
When exposing HTTP beyond loopback, keep the API key and put the server behind a TLS reverse proxy or a private overlay network (e.g. Tailscale). See support and security.
These examples assume the HTTP API is at http://127.0.0.1:6017 with the key
replace-with-a-long-random-token.
CAPSWRITER_WEB_API_BASE=http://127.0.0.1:6017 \
docker compose --profile web up -d capswriter-webOpen http://127.0.0.1:8080, confirm the API root http://127.0.0.1:6017, paste the
key into the masked field, then record or drop a file and download text/json/srt/vtt.
Real capture against a local Qwen3-ASR 1.7B (CPU) server transcribing a 5.6-second Chinese test clip.
Development mode for frontend work (Node.js 24):
cd client/web
npm ci --no-audit --no-fund
npm run dev # open http://127.0.0.1:5173Needs only the Python 3.10+ standard library:
export CAPSWRITER_API_BASE=http://127.0.0.1:6017
export CAPSWRITER_HTTP_API_KEY=replace-with-a-long-random-token
python client/cli/capswriter_cli.py ready
python client/cli/capswriter_cli.py transcribe meeting.wav --format text
python client/cli/capswriter_cli.py transcribe audio/*.mp3 --format srt --output-dir subs/python3.12 -m venv .venv-tui
.venv-tui/bin/python -m pip install \
--require-hashes --only-binary=:all: \
--requirement requirements/tui.lock
.venv-tui/bin/python -m client.tui --base-url http://127.0.0.1:6017Paste the key into the memory-only API key field, press F5 to check the server, enter a file path, Ctrl+T to transcribe and Ctrl+S to save.
from openai import OpenAI
client = OpenAI(base_url="http://127.0.0.1:6017/v1", api_key="replace-with-a-long-random-token")
with open("meeting.wav", "rb") as f:
print(client.audio.transcriptions.create(model="whisper-1", file=f).text)Formats: text, json, verbose_json, srt, vtt. Translation, streaming and
diarization are not supported. See the OpenAI-compatible API.
Full illustrated walkthrough: Usage guide. Highlights:
| I want to… | Do this |
|---|---|
| Dictate | Hold CapsLock or mouse X2, speak, release; a short press keeps its normal function |
| Turn a video into subtitles | Drop the file on start_client.exe; .srt / .txt / .json appear next to it |
| Fix subtitle typos | Edit the .txt and drop it back; a new .srt is aligned to the original timing |
| Get names right | Add one term per line to hot.txt; it reloads within ~3 s |
| Always replace something | Add pattern = replacement (regex) to hot-rule.txt |
| Translate by voice | Start with the role's trigger word, e.g. "翻译…" (configure LLM/*.py first) |
| Output Traditional Chinese | Settings → Output → convert to Traditional (zh-tw / zh-hk available) |
| Review what I said today | Tray → 日记 (Diary): daily Markdown with recordings |
| Stop LLM output | Press Esc |
Tray menu: Settings, copy result, diary, context, hotwords, clear memory, restart audio, restart, exit.
Model (model_type) |
Size | Notes | Docker auto-download |
|---|---|---|---|
Qwen3-ASR 1.7B (qwen_asr, default) |
1.3–1.8 GB | Most accurate, good with mixed Chinese/English; Vulkan/CUDA acceleration | ✅ |
Fun-ASR-Nano (fun_asr_nano) |
~0.8 GB | Low latency, server hotwords, smooth on CPU | ✅ |
SenseVoice (sensevoice) |
~0.4 GB | Light, multilingual (zh/en/ja/ko/yue) | manual |
Paraformer (paraformer) |
~0.5 GB incl. punctuation | Light, Chinese | manual |
Docker selects with CAPSWRITER_MODEL_TYPE; Windows uses model_type in
config_server.py. Models come from the
upstream model release.
| Reader | Documents |
|---|---|
| New users | Server and client roles → Getting started → Usage guide |
| Windows users | Desktop portability · Daily settings |
| Server operators | Deployment · Support and security · Troubleshooting |
| Clients | Web Console · CLI · TUI · OpenAI-compatible API |
| Releases | Release notes · v1/v2 policy · Verification |
| Developers | Development · Architecture · Upstream sync · Docs home |
Upstream's own guides for hotwords, LLM roles and file transcription are in
Simplified Chinese under docs/.
Nothing happens when I hold CapsLock
Check that the server window shows the model loaded and the client window says it
connected ("已连接服务端"). To type into programs running as administrator (Task Manager,
some games) the client must also run as administrator. Logs: logs/client_latest.log
and logs/server_latest.log.
Docker stays in "starting"
The first start downloads 1–2 GB of models; the healthcheck allows 20 minutes. Follow
docker compose logs -f capswriter-server; with HTTP enabled, /ready lists what is
not ready yet. See troubleshooting.
The Web Console reports CORS or 401
CORS: add the Web origin (e.g. http://127.0.0.1:8080) to
CAPSWRITER_HTTP_API_CORS_ORIGINS and recreate the server. 401: the key entered in the
Web UI must match the server's CAPSWRITER_HTTP_API_KEY.
Do hotkeys work on Linux?
On X11 only; Wayland and headless sessions have no reliable global hotkeys. X11 cannot suppress a single key, so CapsLock will still toggle — F12 or a mouse side button works better.
Do I need a GPU?
No. CPU works. Response time depends on your model, audio and hardware; compare CPU/GPU or Fun-ASR-Nano with a known clip on your own system.
- fork v2 (
master) is the actively developed line, taggedfork-v2.x.y; fork v1 is a separate security/compatibility maintenance line. See the versioning policy. - Server and Web images are published to GHCR with immutable
sha-<commit>tags plus SBOM/provenance;latestonly follows amastercommit that passed the gates. - Source/image workflows share canonical
DF-wu/CapsWriter-Offline-Container(the old repository name redirects here). Publishingmastertriggers image publication automatically. The separatemyServicesrecipe must be pushed to Portainer's tracked branch; its local model-volume fix is not yet deployed. - Candidate runtime/package metadata is
2.2.0;/healthand/readyaddfork_version: "2.2.0"while retaining upstreamversion: "2.6". This does not establish a published release. See the release checklist. - This fork is based on HaujetZhao/CapsWriter-Offline. Models, inference algorithms and the desktop experience come from upstream; the fork adds cross-platform deployment, the HTTP API, extra clients, safety limits and the release pipeline. If you like it, please support the upstream author too.
License: MIT.


