Skip to main content

Dictum

Dictum is a Python voice-to-text command tool designed for Wayland compositors such as Hyprland and Sway. It is intended to be triggered from a hotkey, record microphone audio, transcribe it with Parakeet TDT v3 0.6B, polish the transcription with a local or cloud LLM, and send the final text to the active window, clipboard, stdout, or a file.

Goals

  • Fast hotkey workflow for dictation and text transformation.
  • Persistent daemon for state, warm models, and lower latency.
  • Companion CLI for key bindings and scripting.
  • Pluggable ASR, LLM, recorder, and output backends.
  • Wayland-first output through wtype, ydotool, wl-copy, or stdout.

Architecture

Hyprland/Sway hotkey
  -> dictum CLI (thin IPC client)
  -> daemon command over local Unix socket
  -> record mic audio (sounddevice/PipeWire)
  -> transcribe with Parakeet (CrispASR HTTP server, port 8081)
  -> polish with LLM (llama.cpp server, port 8080)
  -> paste/copy/print/save result

Moving Parts

Component Technology Purpose
CLI Python + Typer Hotkey-friendly commands (toggle, start, stop, once, status)
Daemon Python + asyncio State machine, IPC server, pipeline orchestration
Recorder sounddevice (PortAudio) PipeWire/PulseAudio capture, silence watchdog
ASR CrispASR (C++/Vulkan) Parakeet TDT v3 0.6B GGUF, HTTP server mode (warm)
LLM llama.cpp (C++/Vulkan) Qwen 3.5-4B GGUF, OpenAI-compatible HTTP server (warm)
Output wtype / ydotool / wl-copy Wayland text insertion
Notify D-Bus (gdbus) Desktop notifications

State Machine

idle -> recording -> transcribing -> polishing -> pasting -> idle
              \-> failed (on error) ----------------------------> idle

polishing is skipped if the profile disables LLM rewriting.

ASR Backends

asr.backend Engine Notes
parakeet CrispASR + Parakeet TDT v3 0.6B (default) local GPU/CPU GGUF, warm server on port 8081
gemini Gemini hosted gemini-3.5-transcribe no local model, no GPU; needs an API key + ffmpeg

Both share the same AsrBackend interface, so profiles choose between them with one config key.


Quick Start

1. Prerequisites

  • GPU: Discrete GPU with Vulkan support (optional but recommended)
  • Vulkan loader: libvulkan1 / vulkan-loader + a compatible ICD
  • Python: 3.11+ with uv (recommended) or pip

2. Install Dictum

# From PyPI, once published
uv tool install dictum-asr

# Or from a local source checkout while developing
git clone https://github.com/hermitm0nk/dictum.git
cd dictum
uv tool install .

The wheel is pure Python and tiny (tens of KB). The native C++ binaries are no longer built from source at install time. Instead, Dictum downloads pinned upstream releases on first use:

  • llama.cpp → llama-server (Vulkan build, b9699)
  • CrispASR → parakeet-main (Vulkan build, v0.7.2)

Pre-fetch them explicitly (recommended before binding a hotkey):

dictum native install            # fetch both, Vulkan variant
dictum native install --lib llama
dictum native install --variant cpu   # if no Vulkan ICD is present
dictum native status             # show what's installed and where

Binaries land under $DICTUM_NATIVE_DIR when set, otherwise under $XDG_DATA_HOME/dictum/native/, or ~/.local/share/dictum/native/. The llama-server binary ships with RUNPATH=$ORIGIN so it finds its sibling .so files automatically; parakeet-main is statically linked. No LD_LIBRARY_PATH is required in normal use.

To override the bundled binaries entirely, set DICTUM_LLM_BIN and/or DICTUM_PARAKEET_BIN to point at your own builds — Dictum will not attempt to download when an override is set.

3. Download Models

# Parakeet ASR (multilingual, 4-bit quant)
huggingface-cli download cstr/parakeet-tdt-0.6b-v3-GGUF \
  parakeet-tdt-0.6b-v3-q4_k.gguf \
  --local-dir ~/.cache/dictum/models/

# Qwen LLM (Q3 quant for polishing)
huggingface-cli download unsloth/Qwen3.5-4B-GGUF \
  Qwen3.5-4B-Q3_K_M.gguf \
  --local-dir ~/.cache/dictum/models/

By default Dictum downloads models on first use into $DICTUM_MODEL_DIR when set, otherwise into $XDG_CACHE_HOME/dictum/models or ~/.cache/dictum/models.

4. Configure (Optional)

mkdir -p ~/.config/dictum
cp examples/config.toml ~/.config/dictum/config.toml
# Edit profiles, ASR/LLM settings, output target

Hosted Transcription (Gemini)

For machines without a GPU — or anyone who would rather not download and run a local ASR model — the gemini ASR backend transcribes through Google's hosted non-live API. Dictum Opus-compresses the recording locally (mono, 32 kbps by default) and sends clips up to 128 KiB inline in one request. Larger clips use the resumable Files API and are deleted after transcription. Inline audio avoids upload setup and cleanup latency at the cost of about 33% more request data; HTTP connections are reused by the daemon.

cp examples/config-gemini.toml ~/.config/dictum/config.toml

1. Install FFmpeg

The encoder must include libopus:

sudo apt install ffmpeg        # Debian/Ubuntu
sudo dnf install ffmpeg        # Fedora
sudo pacman -S ffmpeg          # Arch

ffmpeg -encoders | grep opus   # expect: libopus

No live/streaming API is required — Dictum uploads a finished recording.

2. Store the API key outside the repo

mkdir -p ~/.config/dictum
printf 'GEMINI_API_KEY=AIza...\n' > ~/.config/dictum/.env
chmod 600 ~/.config/dictum/.env

GEMINI_API_KEY is the default variable name; override it with asr.gemini_api_key_env if you prefer another name. The key is sent as an x-goog-api-key header, is never written to logs or error messages, and is never put in config.toml.

3. Give the daemon access to the key

The daemon reads its profile at startup, so a systemd drop-in must both export the key and select the gemini profile — otherwise the bundled unit runs the default profile and spins up the local Parakeet/LLM models. Recommended:

mkdir -p ~/.config/systemd/user/dictum.service.d
cat > ~/.config/systemd/user/dictum.service.d/10-gemini.conf <<'EOF'
[Service]
# An empty ExecStart= clears the bundled ExecStart before the replacement.
ExecStart=
ExecStart=%h/.local/bin/dictum daemon --profile gemini
EnvironmentFile=%h/.config/dictum/.env
EOF
systemctl --user daemon-reload
systemctl --user restart dictum

Or in the foreground / for one-shot runs:

set -a; . ~/.config/dictum/.env; set +a
dictum daemon --profile gemini
dictum once --profile gemini --result stdout --duration 30

4. Useful knobs

[profiles.gemini.asr]
gemini_model = "gemini-3.5-transcribe"  # hosted model
gemini_bitrate_kbps = 32                # mono Opus upload bitrate (24-32 suits speech)
gemini_transcription_mode = "smart"     # "smart" (cleanup) or "verbatim" (raw)
gemini_language_codes = []              # e.g. ["en-US"]; empty = auto-detect
gemini_timeout_seconds = 120.0          # upload budget for slow links

Since the hosted model already returns readable text, the Gemini profile sets [profiles.gemini.llm] backend = "none": no local llama.cpp server, no model download, and no polishing step in the pipeline.

Failures (missing key, missing ffmpeg, auth, quota, upload errors) raise an actionable message and stop the job — Dictum never silently falls back to a local model or retries a billable transcription.


Usage

Start the Daemon

# Foreground (testing)
dictum daemon --profile default

# Or as systemd user service (persistent, see below)

Hotkey Commands

# Toggle recording (press once to start, again to stop & process)
dictum toggle --profile default --result paste

# Explicit start/stop
dictum start --profile default
dictum stop --result paste

# One-shot (no daemon needed)
dictum once --result stdout --duration 30

# Status
dictum status
dictum status --json

Output Targets (--result)

Target Behavior
paste Types into focused window via wtype/ydotool (fallback: clipboard)
clipboard Copies to clipboard via wl-copy
stdout Prints to terminal
file Writes to /tmp/dictum-output.txt
none No output

Systemd User Service (Persistent Daemon)

# Install, enable (starts at login), but do not start now
dictum service install

# Install and start immediately
dictum service install --now

# Check status
dictum service status

# Remove the service
dictum service uninstall

The unit file is bundled with the wheel and copied to ~/.config/systemd/user/dictum.service (or $XDG_CONFIG_HOME/systemd/user/). User units start at graphical/login session, not at boot.

The bundled unit (dictum/data/dictum.service):

[Unit]
Description=Dictum voice dictation daemon
After=graphical-session.target pipewire.service

[Service]
Type=simple
ExecStart=%h/.local/bin/dictum daemon
Restart=on-failure
RestartSec=2

[Install]
WantedBy=default.target

You can still manage the service directly with systemctl --user {start,stop,restart,status} dictum. Edit the installed unit file to customize ExecStart (e.g. add --profile flags) and run systemctl --user daemon-reload.


Hyprland Binding

Add to ~/.config/hypr/hyprland.conf:

# Dictum voice dictation (ScrollLock to toggle)
bind = , Scroll_Lock, exec, dictum toggle --profile default --result paste

Reload config:

hyprctl reload

Metadata

Release files for dictum-asr 0.4.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for dictum-asr 0.4.0
File Size Uploaded
dictum_asr-0.4.0.tar.gz 75.6 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for dictum-asr 0.4.0
File Interpreter ABI Platform
dictum_asr-0.4.0-py3-none-any.whl Python 3 none any Details

Total release size: 128.4 kB

Release files / dictum_asr-0.4.0.tar.gz

Download URL dictum_asr-0.4.0.tar.gz
Size 75.6 kB
Tags Source
SHA-256 checksum
How to use checksums
cada3368c36b51087b19636463b19c9303f4c4a7041c964ef425933703b75ce4
BLAKE2b-256 checksum
How to use checksums
ab236d7a818bc6d36dc6eea1322b9ad9165f39f1a10fbb550664b605dcd96d2c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 12, 2026.

Transparency log

Release files / dictum_asr-0.4.0-py3-none-any.whl

Download URL dictum_asr-0.4.0-py3-none-any.whl
Size 52.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
d5f7d0cfc5ec60bda5a032bdadcb68fba3625701f353fd8bdca65e9ebf851d85
BLAKE2b-256 checksum
How to use checksums
803eefd692b3ecd7324514d1b7a0903e067df81eda4cf5c8610df9391d406821
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 12, 2026.

Transparency log

Release history Release notifications | RSS feed

0.4.1

2 release files

This release

0.4.0 This release

2 release files

0.3.0

2 release files

0.2.0

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page