adeldaoud/wiska

Wiska — local-first voice dictation for Windows; speech recognition runs on your machine.

0

stars

3

commits

Python

primary language

Aug 22, 2026

updated

dictation
local-first
privacy
speech-recognition
voice-to-text
windows
Browse cluster: Local-first AI and privacy-preserving automation

README

Wiska

Wiska

Local-first voice dictation for Windows, from the Swedish viska, to whisper. Hold CapsLock, speak, release — the text appears in any app. Speech recognition runs entirely on your machine. Optional text cleanup can run locally or through a provider you choose; it is off by default.

Built on NVIDIA Parakeet TDT for transcription (~36x realtime on CPU) and Gemma 4 for smart editing. Fork of savbell/whisper-writer.

Features

  • Hold-to-record — CapsLock as a dictation key (fully suppressed, no caps toggle)
  • Parakeet TDT 0.6B v3 — 10x faster than Whisper with equal or better accuracy
  • Always-on microphone — 0.5s pre-roll buffer captures speech from before you press the key
  • Mode system — dictation, email, notes, code — switchable via system tray
  • AI post-processing — optional cleanup via local Gemma, tool-disabled Claude CLI, Ollama, Anthropic, or an OpenAI-compatible endpoint
  • Name-safety harness — SENSE → ALIGN → CLEAN → VERIFY prevents a cleanup model from silently rewriting names
  • Custom dictionary — a user-owned JSON file for exact spellings and explicit aliases
  • Recipient-aware corrections — verified Outlook To: names can be offered as tightly bounded spelling evidence
  • File transcription — drag-and-drop audio files for Markdown transcripts with timestamps
  • Multilingual — English, Swedish, Arabic (voice commands + filler removal for all three)
  • System-wide — works in any app (Outlook, Word, PowerShell, browsers, IDEs)
  • Visual feedback — floating pill overlay with pulsing glow during recording

Requirements

  • Windows 10/11 (64-bit)
  • Python 3.12 (download)
  • Git (download)
  • ~2 GB RAM for the dictation models (Parakeet ~600 MB; Whisper ~500 MB for file transcription); the optional local cleanup model (Gemma E4B Q4, ~5 GB download) wants 8 GB+ RAM
  • ~2 GB disk for the core environment and speech model; optional local cleanup adds ~5 GB
  • A microphone

Installation

1. Clone the repository

git clone https://github.com/adeldaoud/wiska.git
cd wiska

2. Create a virtual environment

Important: use Python 3.12, NOT 3.14 (several dependencies lack 3.14 wheels).

python -m venv C:\Users\YOUR_USERNAME\venvs\wiska

Put the venv outside the repo folder — a venv inside Dropbox/OneDrive thrashes sync with tens of thousands of small files.

3. Install dependencies

C:\Users\YOUR_USERNAME\venvs\wiska\Scripts\pip install -U pip setuptools wheel
C:\Users\YOUR_USERNAME\venvs\wiska\Scripts\pip install .

Optional local cleanup is off by default. Install it only if you plan to enable it:

C:\Users\YOUR_USERNAME\venvs\wiska\Scripts\pip install ".[local-cleanup]"

File transcription and the Explorer context menu also require an FFmpeg executable on PATH; use a Windows build linked from ffmpeg.org.

4. Configure

Copy the example config or let the app create one on first launch:

# Option A: start the app — Settings window opens automatically
C:\Users\YOUR_USERNAME\venvs\wiska\Scripts\python run.py

# Option B: copy an existing config from another machine
copy path\to\old\src\config.yaml src\config.yaml

5. Create a launcher

Create a file called Wiska.bat on your Desktop:

@echo off
cd /d "C:\path\to\Wiska"
start "" "C:\Users\YOUR_USERNAME\venvs\wiska\Scripts\pythonw.exe" run.py

Double-click to launch. The app runs in the system tray.

6. (Optional) Autostart on boot

Press Win+R → type shell:startup → drop a copy of Wiska.bat into the folder.

Usage

Dictation (hold-to-record)

  1. Hold CapsLock — red pill overlay appears, recording starts
  2. Speak — say whatever you want
  3. Release CapsLock — text is transcribed and pasted into the focused window

CapsLock is fully suppressed while the app is running — it will never toggle caps lock.

Smart Edit (Shift+CapsLock)

Smart Edit is available when Smart Cleanup is enabled with the local provider. If the provider is unavailable or times out, the selected text is left unchanged.

Select text in any app, then hold Shift+CapsLock and speak an editing instruction:

  • "make this more concise"
  • "translate to Swedish"
  • "make this more formal"
  • "turn this into bullet points"
  • "fix the grammar"
  • "rewrite as an email"

Release the key — the selected text is replaced with the provider's edited version.

Any natural language instruction works.

File transcription

Right-click the tray icon → Transcribe File... → pick an audio file (.mp3, .wav, .m4a, etc.). A Markdown transcript with timestamps is saved next to the source file.

Switching modes

Right-click the tray icon → Mode → pick from:

  • dictation — general-purpose
  • email — professional tone (when AI processing is enabled)
  • notes — bullet-point formatting
  • code — preserves technical terms

Settings

Right-click the tray icon → Settings, or edit src/config.yaml directly and restart.

Key settings:

SettingDefaultDescription
recording_options.activation_keycapslockDictation hotkey
recording_options.recording_modehold_to_recordHold key = record, release = transcribe
recording_options.dictation_backendparakeetParakeet TDT for live dictation
model_options.local.modelsmallWhisper model for file transcription
model_options.common.vocabulary""Comma-separated custom terms
ai_processing.enabledfalseEnable Smart Cleanup post-processing
ai_processing.providerlocallocal, claude_cli, ollama, anthropic, openai_compatible
ai_processing.always_cleantrueRun cleanup on every dictation
ai_processing.claude_cli_modelhaikuClaude model: haiku (fast) or sonnet (smarter)
language_options.primaryenPrimary dictation language

Architecture

Full dictation pipeline (one CapsLock press → release)

YOU PRESS CAPSLOCK
│
├─ 1. CONTEXT AGENT (instant, ~5ms, no model)
│     Reads: foreground app (Win32), Outlook email body (COM),
│            or any app's text (UI Automation)
│     Decides:
│       mode     = email / code / notes / dictation  (from APP_RULES)
│       language = en / sv                            (langdetect on email body)
│       style    = formal / casual / neutral          (heuristics on email tone)
│       vocab    = "Nils, Grenfell, Sundsgatan"       (regex from context text)
│
├─ 2. AUDIO CAPTURE (always-on mic, 0.5s pre-roll ring buffer)
│     CapsLock press marks "start saving from here" (+ 0.5s pre-roll)
│
YOU SPEAK
│
YOU RELEASE CAPSLOCK
│
├─ 3. PARAKEET TDT 0.6B v3  ← speech-to-text model
│     Input:  raw audio + language hint from step 1
│     Output: raw transcription
│     Speed:  ~0.14s for 5s of audio (36x realtime on CPU)
│
├─ 4. SENSE + ALIGN (no model)
│     Collects bounded name evidence from the current window, custom dictionary,
│     and a press-time-verified Outlook recipient list. Deterministic fixes are
│     occurrence-scoped; ordinary EN/SV words are protected by a 17,977-word lexicon.
│
├─ 5. CLEAN (optional Smart Cleanup provider)
│     Removes fillers, fixes grammar, applies detected style (formal/casual)
│
├─ 6. VERIFY (no model)
│     Rejects language/content drift and reverts unlicensed name changes or reuse
│
├─ 7. FORMATTING (no model)
│     Voice commands ("new line" → \n), auto-capitalization
│
├─ 8. PASTE into focused app (Ctrl+V)
│
└─ 9. CORPUS LOGGING (background)
      Audio WAV + metadata sidecar → local corpus (opt-in, off by default)
      If Outlook send: outcome classification + learned vocabulary events

Smart Edit (Shift+CapsLock)

  Shift+CapsLock press → Copies selected text (Ctrl+C)
                       → Starts recording spoken instruction
  Shift+CapsLock release → Parakeet transcribes instruction
                         → Gemma E4B Q4 processes (selected text + instruction)
                         → Pastes edited result, replacing selection

Where each model sits

ModelPipeline stepWhat it doesAlways on?
Parakeet TDT 0.6BStep 3Sound → textYes — this IS the transcription
Gemma E4B Q4Step 5Text → cleaner textNo — tray toggle "Smart Cleanup"
Whisper smallFile transcription onlyLonger audio files → timestamped transcriptLazy-loaded on first use, not used for dictation

Key modules

ModulePurpose
src/main.pyPyQt5 app, tray icon, Win32 hotkey, mode manager
src/audio_capture.pyAlways-on mic with 0.5s ring buffer pre-roll
src/result_thread.pyRecording → transcription → paste pipeline
src/transcription/parakeet_backend.pyNVIDIA Parakeet TDT via onnx-asr
src/transcription/whisper_backend.pyfaster-whisper for file transcription + Arabic
src/postprocessing/ai_processor.pyGemma 4 / Claude / OpenAI-compatible LLM
src/name_harness.pyName-evidence alignment and post-model verification
src/custom_dictionary.pyRead-only user dictionary and explicit aliases
src/modes.pyMode system (dictation, email, notes, code)
src/formatting/processor.pyVoice commands, filler removal (EN/SV/AR)
src/input_simulation.pyClipboard paste into focused window
src/ui/status_window.pyFloating pill overlay with animations

Models

ModelSizeUse caseSpeed (CPU)
Parakeet TDT 0.6B v3~600 MBLive dictation~36x realtime
Whisper small~500 MBFile transcription, Arabic~2.4x realtime
Whisper base~140 MBFallback dictation~7x realtime
Gemma E4B Q4~5 GBSmart edits, context reasoning (optional)~30-50 tok/s

All models auto-download from HuggingFace on first use. Cache location: ~/.cache/huggingface/hub/.

Portability

To install on a new machine, you need:

  1. This repo (git clone)
  2. Python 3.12
  3. pip install .
  4. Your src/config.yaml (copy from old machine, or configure fresh)

Models re-download automatically. The venv must be recreated per machine.

Privacy & Power

Wiska's mic is always-on by design, and the tray icon tells you exactly what the mic is doing at any moment. This section explains what that means in practice.

What the always-on mic actually captures. The microphone feeds a 0.5-second ring buffer in RAM that's continuously overwritten. At any moment, the buffer holds only the last half-second of audio — everything older is discarded the instant a new frame arrives. This exists so the first word of your dictation isn't lost: when you press CapsLock, the ring buffer's last 0.5s is prepended to the recording, so speech that started slightly before the key press still lands in the transcript. Without this design, every dictation would eat its first ~200ms.

What gets persisted. By default: no audio and no transcript content. Audio is processed in memory and discarded. A rotating diagnostic log (logs/wiska.log) records events, timings, and character counts; transcript text and name details appear only if you explicitly raise the log level to DEBUG. Two debug/measurement opt-ins are off by default: logging_options.save_audio (keeps recordings locally for troubleshooting) and the corpus feature described below. If you enable the optional corpus feature (corpus_options.enabled, default false), audio captured between press and release is written as WAV files with metadata sidecars (transcript, timestamps, quality metrics) to a local folder you configure (default ~/wiska-corpus). A second opt-in (capture_sent_emails, default false) stores the text and addressing metadata of emails you send from Outlook to compare transcripts with what you actually sent. If you put the corpus in Dropbox, OneDrive, or another synchronized folder, that provider receives those files. API or Hugging Face tokens entered in Settings are stored locally in gitignored plaintext configuration files; prefer environment variables on shared computers. There is no telemetry. Model files download on first use; after that, speech recognition runs locally, and Wiska never uploads audio. If you deliberately enable a nonlocal cleanup provider, it receives the transcript plus sanitized proposed name substitutions derived from the active-window or Outlook context. Smart Edit is available only with the local cleanup provider, so selected text and spoken edit instructions are not sent to a nonlocal provider. Wiska never sends audio to cleanup providers. The Claude CLI cleanup subprocess is launched without tools, custom settings, hooks, plugins, skills, MCP servers, browser access, or project-directory access.

The three visible mic states. The tray icon shows a colored dot indicating live capture state:

DotStateWhat's happening
🟢 GreenPre-roll onlyMic is hot, ring buffer is cycling. Nothing is being saved.
🔴 RedCapturingYou're holding CapsLock (or Shift+CapsLock). Audio is being recorded to RAM for transcription.
⚫ GrayPausedMic stream released. Windows mic-in-use indicator turns off. No audio is reaching Wiska at all.

Hover the tray icon for a tooltip confirming the current state.

Pausing the mic. Right-click the tray → Pause microphone. The audio stream is released, which means:

  • Windows 11's mic-in-use privacy indicator turns off (visible confirmation that no app is reading the mic via Wiska)
  • The ring buffer is cleared
  • Dictation hotkeys are blocked with a tray balloon until you resume
  • Other apps (Zoom, Teams) can take exclusive mic access if they want

Uncheck "Pause microphone" to resume. Takes ~200ms to re-open the stream.

Coexistence with Zoom, Teams, Meet. Windows shared-mode audio allows multiple apps to read the mic simultaneously. Wiska and Zoom can both be active at once with no conflict — each app gets its own copy of the audio stream. If a pro-audio app requests WASAPI exclusive mode (rare), Wiska silently loses the stream; pausing Wiska releases it cleanly.

Power impact. The ring buffer is ~16 KB of RAM rewritten 33 times per second. Parakeet stays loaded but idle between dictations; optional Gemma loads only after local cleanup is enabled and then stays idle between uses. Actual battery impact depends on the microphone driver, hardware, and dictation frequency. Pause the mic when you want the privacy indicator off or want to minimize background power use.

Testing & CI

tests

Unit tests (fast, no audio, no Outlook needed). The suite covers metrics, corpus logging, name alignment and verification, recipient-tier safety, custom dictionaries, context capture, cleanup providers, audio integrity, version provenance, privacy defaults, and shell-command safety. The exact script list is the one in .github/workflows/tests.yml.

GitHub Actions CI. Every push to main or master and every pull request triggers .github/workflows/tests.yml, which runs the same unit scripts on a clean windows-latest VM with Python 3.12. Results appear at github.com/adeldaoud/wiska/actions.

CI does not run model benchmarks — those require separately obtained audio fixtures and large model downloads.

Single-instance guard. Wiska binds 127.0.0.1:17459 at startup. If the port is taken, a tray balloon appears and the second instance exits. This prevents hotkey conflicts and duplicate Outlook capture when run.py is launched more than once.

Version provenance

Wiska derives the displayed version from git describe --tags --always --dirty. The tray, log, and optional corpus records therefore carry the exact release tag and commit instead of a manually maintained label. A packaged copy without .git falls back to the release version declared in src/version_info.py.

Compatibility after the rename

Wiska was previously named LocalWisper. The application, commands, package metadata, icons, log filename, and context-menu entry now use Wiska. Two old identifiers remain deliberately for non-destructive upgrades: existing optional corpus records may contain the localwisper_version field, and the installer removes the old TranscribeWithLocalWisper registry entry. New records also write wiska_version. An old codex_spark cleanup selection is migrated to disabled local cleanup because the tool-capable Codex provider is not part of the public release. The old version field is compatibility data, not the current product name.

Known issues

See GitHub Issues for the full tracker.

  • import torch must come before import PyQt5 on Windows — Qt DLLs shadow torch's c10.dll ([WinError 1114]). Already handled in main.py.
  • CapsLock LED may stay in its previous state after the app exits. Toggle CapsLock once manually to reset.

Roadmap

Development happens separately; this public mirror receives reviewed release snapshots. Current priorities, in rough order:

  • Put Smart Cleanup and Smart Edit behind a hard latency budget so paste never waits indefinitely on a language model
  • Streaming transcription: recognize speech during the recording, so releasing the key feels instant even for long dictations
  • Smart snippets, a custom-dictionary UI, and a visual overhaul

Credits

Wiska exists because of the projects it stands on:

The direct library-by-library inventory with licenses is in THIRD_PARTY_NOTICES.md.

License

This project is licensed under the GNU General Public License v3.0 only. See the LICENSE file for details.

Contributors

adeldaoud

3 commits

adeldaoud/wiska

Wiska — local-first voice dictation for Windows; speech recognition runs on your machine.

0

stars

3

commits

Python

primary language

Aug 22, 2026

updated

dictation
local-first
privacy
speech-recognition
voice-to-text
windows
Browse cluster: Local-first AI and privacy-preserving automation

README

Wiska

Wiska

Local-first voice dictation for Windows, from the Swedish viska, to whisper. Hold CapsLock, speak, release — the text appears in any app. Speech recognition runs entirely on your machine. Optional text cleanup can run locally or through a provider you choose; it is off by default.

Built on NVIDIA Parakeet TDT for transcription (~36x realtime on CPU) and Gemma 4 for smart editing. Fork of savbell/whisper-writer.

Features

  • Hold-to-record — CapsLock as a dictation key (fully suppressed, no caps toggle)
  • Parakeet TDT 0.6B v3 — 10x faster than Whisper with equal or better accuracy
  • Always-on microphone — 0.5s pre-roll buffer captures speech from before you press the key
  • Mode system — dictation, email, notes, code — switchable via system tray
  • AI post-processing — optional cleanup via local Gemma, tool-disabled Claude CLI, Ollama, Anthropic, or an OpenAI-compatible endpoint
  • Name-safety harness — SENSE → ALIGN → CLEAN → VERIFY prevents a cleanup model from silently rewriting names
  • Custom dictionary — a user-owned JSON file for exact spellings and explicit aliases
  • Recipient-aware corrections — verified Outlook To: names can be offered as tightly bounded spelling evidence
  • File transcription — drag-and-drop audio files for Markdown transcripts with timestamps
  • Multilingual — English, Swedish, Arabic (voice commands + filler removal for all three)
  • System-wide — works in any app (Outlook, Word, PowerShell, browsers, IDEs)
  • Visual feedback — floating pill overlay with pulsing glow during recording

Requirements

  • Windows 10/11 (64-bit)
  • Python 3.12 (download)
  • Git (download)
  • ~2 GB RAM for the dictation models (Parakeet ~600 MB; Whisper ~500 MB for file transcription); the optional local cleanup model (Gemma E4B Q4, ~5 GB download) wants 8 GB+ RAM
  • ~2 GB disk for the core environment and speech model; optional local cleanup adds ~5 GB
  • A microphone

Installation

1. Clone the repository

git clone https://github.com/adeldaoud/wiska.git
cd wiska

2. Create a virtual environment

Important: use Python 3.12, NOT 3.14 (several dependencies lack 3.14 wheels).

python -m venv C:\Users\YOUR_USERNAME\venvs\wiska

Put the venv outside the repo folder — a venv inside Dropbox/OneDrive thrashes sync with tens of thousands of small files.

3. Install dependencies

C:\Users\YOUR_USERNAME\venvs\wiska\Scripts\pip install -U pip setuptools wheel
C:\Users\YOUR_USERNAME\venvs\wiska\Scripts\pip install .

Optional local cleanup is off by default. Install it only if you plan to enable it:

C:\Users\YOUR_USERNAME\venvs\wiska\Scripts\pip install ".[local-cleanup]"

File transcription and the Explorer context menu also require an FFmpeg executable on PATH; use a Windows build linked from ffmpeg.org.

4. Configure

Copy the example config or let the app create one on first launch:

# Option A: start the app — Settings window opens automatically
C:\Users\YOUR_USERNAME\venvs\wiska\Scripts\python run.py

# Option B: copy an existing config from another machine
copy path\to\old\src\config.yaml src\config.yaml

5. Create a launcher

Create a file called Wiska.bat on your Desktop:

@echo off
cd /d "C:\path\to\Wiska"
start "" "C:\Users\YOUR_USERNAME\venvs\wiska\Scripts\pythonw.exe" run.py

Double-click to launch. The app runs in the system tray.

6. (Optional) Autostart on boot

Press Win+R → type shell:startup → drop a copy of Wiska.bat into the folder.

Usage

Dictation (hold-to-record)

  1. Hold CapsLock — red pill overlay appears, recording starts
  2. Speak — say whatever you want
  3. Release CapsLock — text is transcribed and pasted into the focused window

CapsLock is fully suppressed while the app is running — it will never toggle caps lock.

Smart Edit (Shift+CapsLock)

Smart Edit is available when Smart Cleanup is enabled with the local provider. If the provider is unavailable or times out, the selected text is left unchanged.

Select text in any app, then hold Shift+CapsLock and speak an editing instruction:

  • "make this more concise"
  • "translate to Swedish"
  • "make this more formal"
  • "turn this into bullet points"
  • "fix the grammar"
  • "rewrite as an email"

Release the key — the selected text is replaced with the provider's edited version.

Any natural language instruction works.

File transcription

Right-click the tray icon → Transcribe File... → pick an audio file (.mp3, .wav, .m4a, etc.). A Markdown transcript with timestamps is saved next to the source file.

Switching modes

Right-click the tray icon → Mode → pick from:

  • dictation — general-purpose
  • email — professional tone (when AI processing is enabled)
  • notes — bullet-point formatting
  • code — preserves technical terms

Settings

Right-click the tray icon → Settings, or edit src/config.yaml directly and restart.

Key settings:

SettingDefaultDescription
recording_options.activation_keycapslockDictation hotkey
recording_options.recording_modehold_to_recordHold key = record, release = transcribe
recording_options.dictation_backendparakeetParakeet TDT for live dictation
model_options.local.modelsmallWhisper model for file transcription
model_options.common.vocabulary""Comma-separated custom terms
ai_processing.enabledfalseEnable Smart Cleanup post-processing
ai_processing.providerlocallocal, claude_cli, ollama, anthropic, openai_compatible
ai_processing.always_cleantrueRun cleanup on every dictation
ai_processing.claude_cli_modelhaikuClaude model: haiku (fast) or sonnet (smarter)
language_options.primaryenPrimary dictation language

Architecture

Full dictation pipeline (one CapsLock press → release)

YOU PRESS CAPSLOCK
│
├─ 1. CONTEXT AGENT (instant, ~5ms, no model)
│     Reads: foreground app (Win32), Outlook email body (COM),
│            or any app's text (UI Automation)
│     Decides:
│       mode     = email / code / notes / dictation  (from APP_RULES)
│       language = en / sv                            (langdetect on email body)
│       style    = formal / casual / neutral          (heuristics on email tone)
│       vocab    = "Nils, Grenfell, Sundsgatan"       (regex from context text)
│
├─ 2. AUDIO CAPTURE (always-on mic, 0.5s pre-roll ring buffer)
│     CapsLock press marks "start saving from here" (+ 0.5s pre-roll)
│
YOU SPEAK
│
YOU RELEASE CAPSLOCK
│
├─ 3. PARAKEET TDT 0.6B v3  ← speech-to-text model
│     Input:  raw audio + language hint from step 1
│     Output: raw transcription
│     Speed:  ~0.14s for 5s of audio (36x realtime on CPU)
│
├─ 4. SENSE + ALIGN (no model)
│     Collects bounded name evidence from the current window, custom dictionary,
│     and a press-time-verified Outlook recipient list. Deterministic fixes are
│     occurrence-scoped; ordinary EN/SV words are protected by a 17,977-word lexicon.
│
├─ 5. CLEAN (optional Smart Cleanup provider)
│     Removes fillers, fixes grammar, applies detected style (formal/casual)
│
├─ 6. VERIFY (no model)
│     Rejects language/content drift and reverts unlicensed name changes or reuse
│
├─ 7. FORMATTING (no model)
│     Voice commands ("new line" → \n), auto-capitalization
│
├─ 8. PASTE into focused app (Ctrl+V)
│
└─ 9. CORPUS LOGGING (background)
      Audio WAV + metadata sidecar → local corpus (opt-in, off by default)
      If Outlook send: outcome classification + learned vocabulary events

Smart Edit (Shift+CapsLock)

  Shift+CapsLock press → Copies selected text (Ctrl+C)
                       → Starts recording spoken instruction
  Shift+CapsLock release → Parakeet transcribes instruction
                         → Gemma E4B Q4 processes (selected text + instruction)
                         → Pastes edited result, replacing selection

Where each model sits

ModelPipeline stepWhat it doesAlways on?
Parakeet TDT 0.6BStep 3Sound → textYes — this IS the transcription
Gemma E4B Q4Step 5Text → cleaner textNo — tray toggle "Smart Cleanup"
Whisper smallFile transcription onlyLonger audio files → timestamped transcriptLazy-loaded on first use, not used for dictation

Key modules

ModulePurpose
src/main.pyPyQt5 app, tray icon, Win32 hotkey, mode manager
src/audio_capture.pyAlways-on mic with 0.5s ring buffer pre-roll
src/result_thread.pyRecording → transcription → paste pipeline
src/transcription/parakeet_backend.pyNVIDIA Parakeet TDT via onnx-asr
src/transcription/whisper_backend.pyfaster-whisper for file transcription + Arabic
src/postprocessing/ai_processor.pyGemma 4 / Claude / OpenAI-compatible LLM
src/name_harness.pyName-evidence alignment and post-model verification
src/custom_dictionary.pyRead-only user dictionary and explicit aliases
src/modes.pyMode system (dictation, email, notes, code)
src/formatting/processor.pyVoice commands, filler removal (EN/SV/AR)
src/input_simulation.pyClipboard paste into focused window
src/ui/status_window.pyFloating pill overlay with animations

Models

ModelSizeUse caseSpeed (CPU)
Parakeet TDT 0.6B v3~600 MBLive dictation~36x realtime
Whisper small~500 MBFile transcription, Arabic~2.4x realtime
Whisper base~140 MBFallback dictation~7x realtime
Gemma E4B Q4~5 GBSmart edits, context reasoning (optional)~30-50 tok/s

All models auto-download from HuggingFace on first use. Cache location: ~/.cache/huggingface/hub/.

Portability

To install on a new machine, you need:

  1. This repo (git clone)
  2. Python 3.12
  3. pip install .
  4. Your src/config.yaml (copy from old machine, or configure fresh)

Models re-download automatically. The venv must be recreated per machine.

Privacy & Power

Wiska's mic is always-on by design, and the tray icon tells you exactly what the mic is doing at any moment. This section explains what that means in practice.

What the always-on mic actually captures. The microphone feeds a 0.5-second ring buffer in RAM that's continuously overwritten. At any moment, the buffer holds only the last half-second of audio — everything older is discarded the instant a new frame arrives. This exists so the first word of your dictation isn't lost: when you press CapsLock, the ring buffer's last 0.5s is prepended to the recording, so speech that started slightly before the key press still lands in the transcript. Without this design, every dictation would eat its first ~200ms.

What gets persisted. By default: no audio and no transcript content. Audio is processed in memory and discarded. A rotating diagnostic log (logs/wiska.log) records events, timings, and character counts; transcript text and name details appear only if you explicitly raise the log level to DEBUG. Two debug/measurement opt-ins are off by default: logging_options.save_audio (keeps recordings locally for troubleshooting) and the corpus feature described below. If you enable the optional corpus feature (corpus_options.enabled, default false), audio captured between press and release is written as WAV files with metadata sidecars (transcript, timestamps, quality metrics) to a local folder you configure (default ~/wiska-corpus). A second opt-in (capture_sent_emails, default false) stores the text and addressing metadata of emails you send from Outlook to compare transcripts with what you actually sent. If you put the corpus in Dropbox, OneDrive, or another synchronized folder, that provider receives those files. API or Hugging Face tokens entered in Settings are stored locally in gitignored plaintext configuration files; prefer environment variables on shared computers. There is no telemetry. Model files download on first use; after that, speech recognition runs locally, and Wiska never uploads audio. If you deliberately enable a nonlocal cleanup provider, it receives the transcript plus sanitized proposed name substitutions derived from the active-window or Outlook context. Smart Edit is available only with the local cleanup provider, so selected text and spoken edit instructions are not sent to a nonlocal provider. Wiska never sends audio to cleanup providers. The Claude CLI cleanup subprocess is launched without tools, custom settings, hooks, plugins, skills, MCP servers, browser access, or project-directory access.

The three visible mic states. The tray icon shows a colored dot indicating live capture state:

DotStateWhat's happening
🟢 GreenPre-roll onlyMic is hot, ring buffer is cycling. Nothing is being saved.
🔴 RedCapturingYou're holding CapsLock (or Shift+CapsLock). Audio is being recorded to RAM for transcription.
⚫ GrayPausedMic stream released. Windows mic-in-use indicator turns off. No audio is reaching Wiska at all.

Hover the tray icon for a tooltip confirming the current state.

Pausing the mic. Right-click the tray → Pause microphone. The audio stream is released, which means:

  • Windows 11's mic-in-use privacy indicator turns off (visible confirmation that no app is reading the mic via Wiska)
  • The ring buffer is cleared
  • Dictation hotkeys are blocked with a tray balloon until you resume
  • Other apps (Zoom, Teams) can take exclusive mic access if they want

Uncheck "Pause microphone" to resume. Takes ~200ms to re-open the stream.

Coexistence with Zoom, Teams, Meet. Windows shared-mode audio allows multiple apps to read the mic simultaneously. Wiska and Zoom can both be active at once with no conflict — each app gets its own copy of the audio stream. If a pro-audio app requests WASAPI exclusive mode (rare), Wiska silently loses the stream; pausing Wiska releases it cleanly.

Power impact. The ring buffer is ~16 KB of RAM rewritten 33 times per second. Parakeet stays loaded but idle between dictations; optional Gemma loads only after local cleanup is enabled and then stays idle between uses. Actual battery impact depends on the microphone driver, hardware, and dictation frequency. Pause the mic when you want the privacy indicator off or want to minimize background power use.

Testing & CI

tests

Unit tests (fast, no audio, no Outlook needed). The suite covers metrics, corpus logging, name alignment and verification, recipient-tier safety, custom dictionaries, context capture, cleanup providers, audio integrity, version provenance, privacy defaults, and shell-command safety. The exact script list is the one in .github/workflows/tests.yml.

GitHub Actions CI. Every push to main or master and every pull request triggers .github/workflows/tests.yml, which runs the same unit scripts on a clean windows-latest VM with Python 3.12. Results appear at github.com/adeldaoud/wiska/actions.

CI does not run model benchmarks — those require separately obtained audio fixtures and large model downloads.

Single-instance guard. Wiska binds 127.0.0.1:17459 at startup. If the port is taken, a tray balloon appears and the second instance exits. This prevents hotkey conflicts and duplicate Outlook capture when run.py is launched more than once.

Version provenance

Wiska derives the displayed version from git describe --tags --always --dirty. The tray, log, and optional corpus records therefore carry the exact release tag and commit instead of a manually maintained label. A packaged copy without .git falls back to the release version declared in src/version_info.py.

Compatibility after the rename

Wiska was previously named LocalWisper. The application, commands, package metadata, icons, log filename, and context-menu entry now use Wiska. Two old identifiers remain deliberately for non-destructive upgrades: existing optional corpus records may contain the localwisper_version field, and the installer removes the old TranscribeWithLocalWisper registry entry. New records also write wiska_version. An old codex_spark cleanup selection is migrated to disabled local cleanup because the tool-capable Codex provider is not part of the public release. The old version field is compatibility data, not the current product name.

Known issues

See GitHub Issues for the full tracker.

  • import torch must come before import PyQt5 on Windows — Qt DLLs shadow torch's c10.dll ([WinError 1114]). Already handled in main.py.
  • CapsLock LED may stay in its previous state after the app exits. Toggle CapsLock once manually to reset.

Roadmap

Development happens separately; this public mirror receives reviewed release snapshots. Current priorities, in rough order:

  • Put Smart Cleanup and Smart Edit behind a hard latency budget so paste never waits indefinitely on a language model
  • Streaming transcription: recognize speech during the recording, so releasing the key feels instant even for long dictations
  • Smart snippets, a custom-dictionary UI, and a visual overhaul

Credits

Wiska exists because of the projects it stands on:

The direct library-by-library inventory with licenses is in THIRD_PARTY_NOTICES.md.

License

This project is licensed under the GNU General Public License v3.0 only. See the LICENSE file for details.

Contributors

adeldaoud

3 commits

Languages

Python

99.9%