Skip to content

Developer Guide

rcspam edited this page Jul 7, 2026 · 6 revisions

🌐 Language: English | Français

Developer Guide

Everything you need to build, test, and contribute to dictee. The project combines Rust (ASR engine via ONNX Runtime), Python / PyQt6 (setup wizard + tray), QML (KDE plasmoid), and Bash (main orchestration script).

Build artifacts are packaged as .deb, .rpm, Arch PKGBUILD, and a portable tarball. This guide covers repo layout, Cargo features, build scripts, testing harnesses, i18n, and the contribution workflow.

Table of Contents


Repo layout

dictee/                         # git repo root
β”œβ”€β”€ Cargo.toml                  # Rust workspace
β”œβ”€β”€ src/                        # Rust β€” moteur ASR (bibliothΓ¨que + binaires)
β”‚   β”œβ”€β”€ lib.rs                  # Public API
β”‚   β”œβ”€β”€ audio.rs                # WAV, STFT, mel-spectrograms
β”‚   β”œβ”€β”€ parakeet_tdt.rs         # TDT model (25 languages)
β”‚   β”œβ”€β”€ canary.rs               # Canary AED model
β”‚   β”œβ”€β”€ nemotron.rs             # Nemotron streaming (EN only)
β”‚   β”œβ”€β”€ sortformer.rs           # Diarization, streaming (4 speakers)
β”‚   β”œβ”€β”€ diar/                   # Multi-speaker diarization engine (no cap, v1.4)
β”‚   β”œβ”€β”€ model*.rs, decoder*.rs  # ONNX wrappers
β”‚   β”œβ”€β”€ timestamps.rs           # Tokenβ†’wordβ†’sentence alignment
β”‚   └── bin/                    # Executables
β”‚       β”œβ”€β”€ transcribe.rs               # CLI batch
β”‚       β”œβ”€β”€ transcribe_daemon.rs        # Unix socket server
β”‚       β”œβ”€β”€ transcribe_client.rs        # Daemon client + mic recording
β”‚       β”œβ”€β”€ transcribe_diarize.rs       # TDT + Sortformer
β”‚       β”œβ”€β”€ transcribe_stream_diarize.rs  # Nemotron + Sortformer
β”‚       └── transcribe_diarize_batch.rs # Chunked pipeline (v1.3 final)
β”œβ”€β”€ dictee                      # Main shell script (source of truth)
β”œβ”€β”€ dictee-setup.py             # PyQt6 setup wizard
β”œβ”€β”€ dictee-tray.py              # PyQt6 tray icon
β”œβ”€β”€ plasmoid/                   # KDE Plasma 6 widget
β”‚   └── package/
β”‚       β”œβ”€β”€ metadata.json
β”‚       └── contents/
β”‚           β”œβ”€β”€ ui/             # QML (main.qml, CompactRepresentation, etc.)
β”‚           β”œβ”€β”€ assets/         # SVG animations (bars, wave, pulse, dots, waveform)
β”‚           └── locale/{fr,de,es,it,uk,pt}/
β”œβ”€β”€ po/                         # gettext translations (setup + tray)
β”‚   β”œβ”€β”€ dictee.pot              # Template
β”‚   └── {fr,de,es,it,uk,pt}.po
β”œβ”€β”€ pkg/                        # dpkg-deb tree (artifact, NOT source of truth)
β”œβ”€β”€ tests/                      # Test suites
β”‚   β”œβ”€β”€ test-postprocess.py     # 682 unittest cases
β”‚   β”œβ”€β”€ test-pipeline.py        # 148 pipeline cases
β”‚   └── test-wer.py             # WER benchmark harness
β”œβ”€β”€ build-deb.sh                # .deb build (CPU + CUDA)
β”œβ”€β”€ build-rpm.sh                # .rpm build (CPU + CUDA)
β”œβ”€β”€ PKGBUILD                    # Arch package
└── install.sh / uninstall.sh

Source of truth

Files at the repo root (dictee, dictee-setup.py, dictee-tray.py) are the source of truth.

build-deb.sh copies them into pkg/ at build time. Never edit files in pkg/ directly β€” they will be overwritten on the next build.


Building from source

Rust ASR engine

git clone https://github.com/rcspam/dictee.git
cd dictee

# CPU-only (default)
cargo build --release

# CUDA + Sortformer diarization
cargo build --release --features "cuda,sortformer"

Output binaries in target/release/:

  • libparakeet_rs.so (library, for future bindings)
  • transcribe, transcribe-daemon, transcribe-client
  • transcribe-diarize, transcribe-stream-diarize, transcribe-diarize-batch

Cargo features

Declared in Cargo.toml:

Feature Default Purpose
cpu βœ… CPU execution provider
ort-defaults βœ… ONNX Runtime sensible defaults
cuda ❌ CUDA 12 execution provider
tensorrt ❌ TensorRT optimization (experimental)
coreml ❌ Apple CoreML (macOS-only, unused in dictee)
directml ❌ DirectX 12 (Windows-only, unused)
openvino ❌ Intel OpenVINO
webgpu ❌ WebGPU (browser use)
nnapi ❌ Android NNAPI (unused)
sortformer ❌ Speaker diarization via Sortformer

Typical combos:

  • Dev machine: --features cuda,sortformer
  • CI CPU build: (default, no features)
  • CI CUDA build: --features cuda,sortformer

Python UI

No build step β€” files at dictee-setup.py and dictee-tray.py are interpreted at runtime.

Dependencies (install for development):

pip install PyQt6 PyQt6-WebEngine text2num faster-whisper vosk

For PyQt6 on Ubuntu 22.04 (which ships 5.15):

pip install --user 'PyQt6>=6.5' 'PyQt6-Qt6>=6.5'

Plasma plasmoid

Dev install (doesn't require packaging):

kpackagetool6 -t Plasma/Applet -i plasmoid/package

Reinstall after edits:

kpackagetool6 -t Plasma/Applet -u plasmoid/package
# Restart plasmashell if the widget is already on a panel:
killall -15 plasmashell && kstart plasmashell &

Package builds

.deb build

./build-deb.sh

Produces:

  • dictee-cpu_1.3.1_amd64.deb
  • dictee-cuda_1.3.1_amd64.deb
  • dictee-plasmoid_1.3.1_all.deb
  • dictee-1.3.1_amd64.tar.gz (portable tarball)

Key steps:

  1. cargo build --release (for CPU variant) + cargo build --release --features cuda,sortformer (for CUDA)
  2. Copy Rust binaries to pkg/dictee/usr/bin/
  3. Copy shell scripts (dictee, dictee-postprocess, etc.) to pkg/dictee/usr/bin/
  4. Copy Python scripts (dictee-setup.py β†’ /usr/bin/dictee-setup) β€” note: renamed without .py
  5. Copy plasmoid to pkg/dictee-plasmoid/usr/share/dictee/dictee.plasmoid
  6. Build CUDA variant: bundle CUDA libs via pip venv (since v1.3)
  7. dpkg-deb --build pkg/dictee-cpu etc.

.rpm build

./build-rpm.sh

Same outputs but .rpm. Uses rpmbuild with a spec file.

Arch

makepkg -si

Uses PKGBUILD in the repo root. Builds from source, no prebuilt binaries.

Common gotchas

See the memory note feedback-cuda-build-flags: certain cargo flags combinations have been problematic 5+ times. Always double-check:

  • LIBTORCH not set globally
  • ORT_STRATEGY=download for CI
  • ORT_DYLIB_PATH set at runtime in the systemd unit

Audio pipeline internals

Microphone (PipeWire)
   ↓ parecord --format=s16le --rate=48000 --channels=1
   ↓
/dev/shm/.dictee_rec.wav  (raw capture)
   ↓ sox resample to 16 kHz mono
   ↓
/dev/shm/.dictee_rec_16k.wav
   ↓ transcribe-client sends via Unix socket
   ↓
Unix socket: $XDG_RUNTIME_DIR/transcribe.sock
   ↓
transcribe-daemon (Rust)
   ↓
src/audio.rs: preemphasis(0.97) β†’ STFT(n_fft=512, hop=160, win=400, Hann)
   ↓
Mel filterbank (128 bins, Slaney, log guard 5.96e-8)
   ↓
ONNX tensor (1 Γ— 128 Γ— T)
   ↓
Encoder (FastConformer) β†’ Decoder (TDT)
   ↓
Text (with native punctuation)
   ↓
Unix socket response
   ↓
dictee shell script β†’ dictee-postprocess β†’ dotool type

Full source: src/audio.rs (Rust side), pkg/dictee/usr/bin/dictee-transcribe (Python wrapper).


Runtime state & IPC

State files

See CLI-Reference#state-files for the full list. Multi-user safe via $UID suffix.

Socket protocol

Daemon listens on $XDG_RUNTIME_DIR/transcribe.sock. Protocol is line-based:

Request:

TRANSCRIBE /path/to/audio.wav [lang=fr] [translate_to=en]

Response:

OK <json-payload>

Payload includes: transcription text, word-level timestamps, speaker labels (if diarization), language detected, confidence.

Implementation in src/bin/transcribe_daemon.rs.


Testing

Unit tests (Rust)

cargo test
cargo test --features sortformer

Post-processing tests (Python)

682 tests in tests/test-postprocess.py β€” 12 pipeline steps Γ— 7 languages Γ— edge cases:

cd tests
python -m unittest test-postprocess.py -v

Pipeline tests (Python)

148 tests in tests/test-pipeline.py β€” full transcription pipeline with mocked ASR:

python -m unittest test-pipeline.py -v

WER benchmark

python tests/test-wer.py --backend parakeet --lang fr --corpus commonvoice

Manual UI tests

CI

GitHub Actions runs all automated tests on every push:

  • Post-processing (isolated XDG_CONFIG_HOME)
  • Pipeline
  • Rust unit tests
  • msgfmt --check on all .po files

Internationalization

gettext (setup + tray)

dictee-setup.py and dictee-tray.py use gettext with domain dictee and catalogs in po/:

  • po/dictee.pot β€” template (regenerated from source strings via xgettext)
  • po/{fr,de,es,it,uk,pt}.po β€” translations
  • po/{lang}/LC_MESSAGES/dictee.mo β€” compiled (generated by msgfmt)

Workflow:

# Extract new strings into the template
xgettext -L Python -o po/dictee.pot dictee-setup.py dictee-tray.py

# Update existing translations
msgmerge --update po/fr.po po/dictee.pot

# Compile to .mo
msgfmt po/fr.po -o po/fr/LC_MESSAGES/dictee.mo

Plasmoid translations

Separate domain plasma_applet_com.github.rcspam.dictee:

plasmoid/package/contents/locale/{fr,de,es,it,uk,pt}/LC_MESSAGES/plasma_applet_com.github.rcspam.dictee.mo

Built by the plasmoid packager.

.desktop translations

Inline, e.g.:

Name=DictΓ©e
Name[fr]=DictΓ©e
Name[de]=Diktat
GenericName=Voice dictation
GenericName[fr]=DictΓ©e vocale

Contributing

Workflow

  1. Fork the repo at github.com/rcspam/dictee
  2. Create a feature branch: git checkout -b feat/my-feature
  3. Commit with conventional commits: feat(scope): description / fix(scope): description
  4. Push and open a PR against master
  5. CI must be green
  6. Code review by maintainer

Commit style

Examples from git log:

  • feat(plasmoid): combo langue compact + popup wide + close on deactivate
  • fix(setup): wizard trans sync + langues cibles complΓ¨tes + largeur combos
  • docs(wiki): scaffold 20 pages (stubs)
  • chore(version): bump 1.3.0~beta5 β†’ 1.3.0~rc1

Adding an ASR backend

  1. Create src/<backend>.rs with the ONNX model wrapper
  2. Add a binary in src/bin/ or extend transcribe_daemon to support it
  3. Create a systemd unit at pkg/dictee/usr/lib/systemd/user/dictee-<backend>.service
  4. Update dictee-switch-backend to recognize the new backend name
  5. Add UI support in dictee-setup.py (ASR backend combo + model download logic)
  6. Document in ASR-Backends and CLI-Reference

Adding a new language

  1. Add regex rules in pkg/dictee/usr/share/dictee/rules.conf.default with [xx] prefix
  2. Add continuation trigger word in continuation.conf.default
  3. Add .po translation for setup + tray: po/xx.po
  4. Add plasmoid translation: plasmoid/package/contents/locale/xx/
  5. Test manually against test-protocol-vocal.md

Next steps

πŸ“– dictee Wiki

πŸ‡¬πŸ‡§ Home Β· πŸ‡«πŸ‡· Accueil


Getting started / Premiers pas

Speech recognition / ASR

Translation / Traduction

Post-processing / Post-traitement

CLI

Reference / RΓ©fΓ©rence


🏠 Repo Β· πŸ“¦ Releases Β· πŸ› Issues

Clone this wiki locally