Skip to main content

stenograf

Accuracy-first, fully local meeting transcription for German and English (one language per meeting), with speaker labels. Audio is processed entirely in memory — nothing lands on disk but the transcript, unless you explicitly opt into keeping a recording.

Built for Apple Silicon (M-series) first; Linux and Windows support is designed in from the start.

Status: early release. On macOS, Linux and Windows the pipeline is complete end to end: live system-audio + microphone capture, live captions, and the high-accuracy speaker-labelled finalize pass. Meeting notes need Ollama on Linux and Windows; on Apple Silicon they run in-process with no setup.

Why another transcription tool?

  • No audio on disk. Live transcription of a meeting has far lighter legal requirements than recording it. stenograf keeps the session's audio in RAM only and writes nothing but text; keeping a WAV is a per-run opt-in (--record-audio).
  • Accuracy first. A two-pass design: fast live captions while the meeting runs, then a high-accuracy re-transcription of the full in-memory buffer the moment it ends. German is a first-class citizen, not an afterthought.
  • Channel-aware speakers. Microphone and system audio are captured as separate streams, so local and remote voices never get confused; optional diarization separates further speakers within a channel (2–8 speakers).
  • Speakers, not headphones. Remote voices leaving your laptop speakers and re-entering the mic are cancelled in the audio domain (WebRTC AEC3, with the system channel as the far-end reference), so they are never transcribed as a local speaker.

Install

One command sets up everything — uv (installed for you if missing), stenograf itself, the permission prompts, the model downloads, and a desktop launcher:

curl -fsSL https://raw.githubusercontent.com/daniel-om-weber/stenograf/main/install.sh | sh

That's the only command you have to type. Afterwards, open Stenograf from Spotlight or the Dock (macOS), the application menu (Linux) or the Start menu (Windows) — every workflow below is reachable there with the mouse. Re-running the command upgrades stenograf.

Works on macOS 14.4+ on Apple Silicon (the wheel ships the signed capture helper — no toolchain needed), Linux with PipeWire or PulseAudio (the wheel ships a capture helper that speaks the PulseAudio protocol — mic plus the default output's monitor), and Windows 10/11 (capture is WASAPI, mic plus loopback of whatever is playing). Off Apple Silicon, ASR runs ONNX on CPU; on Windows any DX12 GPU can be opted into with [asr] provider = "dml".

On Linux the desktop app (Qt) sets the install floor: glibc 2.34 on x86_64, 2.39 on arm64, and no musl (Alpine) builds — Qt ships no wheels for those.

Manual install

With uv already installed:

uv tool install stenograf
steno doctor    # environment checks
steno setup     # one-time: permission prompts, desktop launcher, model downloads

macOS scopes the permission grant to the app the prompt came from, so run steno setup once from each terminal app (or IDE) you'll start meetings from.

Pre-release channel: uv tool install git+https://github.com/daniel-om-weber/stenograf installs the current main branch; building from the repository compiles the capture helper on your machine, which needs the Xcode command-line tools (xcode-select --install).

Windows

Same one command, run from PowerShell — installs uv if missing, then stenograf, then the launcher:

powershell -ExecutionPolicy Bypass -c "irm https://raw.githubusercontent.com/daniel-om-weber/stenograf/main/install.ps1 | iex"

Windows never prompts for the microphone, so capture stays silent until you flip the toggle under Settings > Privacy & security > Microphone — steno setup reads that toggle up front and says so rather than letting you find out from a transcript of silence.

steno setup puts Stenograf in the Start menu and on the Desktop. Meeting notes need Ollama installed and running; everything else works without it (steno doctor reports it as an optional check).

From a checkout

git clone https://github.com/daniel-om-weber/stenograf
cd stenograf
uv sync
sh native/stenocap-macos/build.sh   # builds + ad-hoc signs the capture helper
uv run steno doctor
uv run steno setup

Every command below is then uv run steno … from the repo.

The desktop app

Open Stenograf (Spotlight or the Dock on macOS, the application-menu entry on Linux, the Start-menu entry on Windows) — or run bare steno in a terminal — and a native window (Qt Quick) opens with one screen per workflow:

  • Start meeting — capture this meeting with live captions; the speaker-labelled transcript replaces them the moment you stop.
  • Transcribe a recording — turn an existing audio file into a transcript.
  • Generate notes — summarize a finished meeting's transcript.
  • Settings — show the active configuration.
  • Check setup — verify models, permissions, and audio devices.

Everything the app does is also a plain CLI command — the rest of this document — so terminal users and scripts lose nothing. (Bare steno opens the window only from an interactive terminal with a display; in a pipe, a script, or an SSH session it prints the command overview instead.)

On macOS the app is a real Stenograf.app in ~/Applications — its own icon, Dock tile and Spotlight entry. It asks for microphone access once, under its own name rather than your terminal's, and keeps that permission across every later upgrade.

On Windows steno setup writes a pair of real shortcuts — one in the Start menu, one on the Desktop — with the app's icon and no console window behind them. The Start-menu entry is the one worth pinning: Windows matches a running window to it, so the app gets its own taskbar button and its notifications arrive under the name Stenograf rather than pythonw.exe.

The app also lives in the menu bar (system tray on Windows and Linux), which is where it belongs for most of a meeting: the icon turns red while you are recording, and its menu can start, stop and finish a meeting without a window. Closing the window puts the app there rather than quitting it — a meeting in progress keeps running — and re-opening the app brings the window back. Start that way with:

steno --gui --tray                         # menu bar only, no window

Desktops with no tray host (stock GNOME, unless the AppIndicator extension is installed) simply get a window, as before. On Windows 11 the icon starts in the notification-area overflow — click the ^ next to the clock and drag Stenograf onto the taskbar to keep it in sight; Windows hides every new notification icon that way, not just this one.

It runs the same library the CLI does — same meeting folders, same settings.toml, same transcripts — so you can switch between the two freely, or never use it at all.

Usage

steno start                                 # live captions, everything auto-detected
steno start --lang de --local 3 --remote 2  # hybrid meeting, German
steno transcribe recording.mov              # batch-transcribe an existing file

steno start streams live captions while the meeting runs — committed lines, one per utterance, equally readable on a terminal or piped into a log — and replaces them with the high-accuracy, speaker-labelled transcript the moment you stop (Ctrl-C). The audio stays in RAM throughout; only the transcript is written. (The desktop app's meeting screen shows the same captions with a live interim tail.)

Useful flags:

steno start --no-live               # skip live captions; just finalize on stop
steno start --title "Weekly sync"   # name the meeting (notes + export use it)
steno start --flush-interval 60     # crash-checkpoint the captions every 60s
steno start --no-aec                # disable echo cancellation (headphones)
steno start --diarization           # separate speakers within each channel (off by default)
steno start --record-audio          # opt in to keeping a WAV (off by default)
steno start --max-seconds 3600      # stop capture automatically after an hour
steno start --replay mic.wav        # dev: drive the live pass from a file

Both start and transcribe accept --format md,json,txt,srt,vtt (default md,json,txttxt is the plain prose without speaker labels or timestamps), --lang de|en, --diarization/--no-diarization to run or skip speaker separation for one run (off by default: each channel is one speaker and the diarizer model is never loaded — it costs minutes on some machines; [speakers] diarization = true in the settings makes running it the default), and --print to echo the transcript to stdout.

If you know how many people spoke in a recording, tell steno transcribe with --speakers N — a count above 1 turns diarization on, and a known count is the biggest diarization accuracy lever (omitted, the count is estimated whenever diarization runs).

steno transcribe recognizes 2-channel recordings whose channels are separate voice feeds — a --record-audio tee (mic left, system right) or a dual-channel call recording — and transcribes them per channel through the meeting pipeline (Local-N/Remote-N labels, per-channel diarization with --local/--remote counts) instead of downmixing; ordinary stereo still downmixes to mono. Force either way with --channels split|mix.

Where your files land

Every run writes its own date-named folder into Meetings inside your documents folder — ~/Documents/Meetings, or your desktop's localised name for it on Linux (~/Dokumente/Meetings on a German one):

~/Documents/Meetings/meeting-20260710-091500/
    transcript.md / .json / .txt        # the transcript (--format adds srt/vtt)
    transcript.notes.md                 # if you generated notes
    audio.wav                           # only with --record-audio

That's it — there is no separate library or index to manage. Browse with Finder or ls, read with anything that opens markdown, delete with rm. steno settings show prints the folder it resolved. Change the standing location with [output] dir in settings.toml, or give one run its own folder with --out DIR (files land directly in it; if DIR already holds a transcript, stenograf refuses to replace it unless you add --force). Audio is stored only when you passed --record-audio; without it a meeting folder holds text alone.

Meeting notes (LLM summaries)

Turn any transcript into structured notes — summary, decisions, action items, open questions — with the LLM of your choice:

steno notes --last                    # notes for the newest meeting
steno notes ~/Documents/Meetings/meeting-20260710-091500
steno notes path/to/transcript.json   # …or any transcript file
steno start --notes                   # generate notes right after the meeting

One meeting can use a different notes setup without touching settings.toml: --notes-backend, --notes-model and --instructions FILE on steno start (and steno transcribe) steer that run's notes step only, and steno notes takes the same per-run choices as --backend, --model and --instructions.

Meeting presets

A recurring kind of meeting gets a preset — one named section in settings.toml bundling its title, language, extra vocabulary, notes setup, style instructions and protocol template:

[meetings.controlling]
title    = "Controlling-Runde"
language = "de"
template = "~/steno/controlling.md"     # protocol layout: your markdown headings

[meetings.controlling.notes]            # sparse: unset keys use [notes] above
backend = "command"
command = ["claude", "-p"]

[meetings.controlling.vocab]            # merges with the standing [vocab]
attendees = ["Anja Müller"]
steno start --preset controlling        # transcribe and notes take it too
steno presets                           # list what's defined

Typed flags still beat the preset's values. On a path key, "" switches the standing value off for that preset — [meetings.x.notes.export] dir = "" keeps a confidential meeting out of the vault. A command notes backend runs in the meeting's folder with STENOGRAF_MEETING_DIR/STENOGRAF_OUTPUT_HOME set, so an agentic CLI can fetch whatever context the preset's instructions point it at (an issue board, earlier protocols); steno doctor checks every preset's backend, not just the standing one.

Notes land as a sibling transcript.notes.md file. On Apple Silicon the default backend is mlx — a fully local in-process model, nothing to set up. To use a different backend, configure it once in ~/Library/Application Support/stenograf/settings.toml:

[notes]
backend = "ollama"          # fully local via `ollama serve`
model = "qwen3:8b"

or drive any CLI you already have (prompt on stdin, JSON out):

[notes]
backend = "command"
command = ["claude", "-p"]

[notes.export]
dir = "~/Documents/Obsidian/Meetings"   # optional: also write one combined
                                        # "YYYY-MM-DD – Title.md" note here

With [notes.export] set, every summarized meeting also produces a single self-contained markdown note (frontmatter, summary, action items, collapsible transcript) — drop the dir inside an Obsidian vault and meetings file themselves. Three more levers in [notes]: instructions = "~/style.md" appends your house style to the built-in prompt, thinking = false skips the mlx model's reasoning pass (faster, less careful), and auto = true makes notes the default for every meeting — steno start summarizes without --notes, and the app's "Generate notes after the meeting" switch starts on. Out of the box notes never run unless you ask (--notes, the app's switch, or steno notes afterwards); with auto = true, --no-notes still skips them for one run. A notes failure never touches the transcript.

Naming speakers across meetings

Enroll a voice once and every later meeting relabels that speaker automatically (cross-meeting re-identification):

steno profiles enroll Daniel daniel-sample.wav   # a short clip of one speaker
steno profiles enroll Daniel more.wav --reinforce  # fold in another sample
steno profiles list                              # show enrolled voiceprints
steno profiles rename Daniel "Daniel W."
steno profiles remove Daniel

To name one person from a multi-speaker recording (e.g. a meeting saved with --record-audio), diarize it and pick their cluster:

steno profiles enroll Anna meeting.wav --speakers 4          # lists the clusters
steno profiles enroll Anna meeting.wav --speakers 4 --speaker S2

Matching is on by default in steno start/transcribe and does nothing until you enroll someone; disable it with --no-reid, or adjust the match strictness with --reid-threshold (0–1, default 0.5). Voiceprints live in the platform data dir (not the model cache) and are never uploaded.

Vocabulary

Domain terms and attendee names steer the transcript twice: they boost the decoder toward those spellings while it transcribes, and then correct the near-misses it still got wrong in the finalized text.

steno transcribe rec.mov --attendee "Anja Müller" --glossary Kubernetes,gRPC
steno transcribe rec.mov --glossary-file terms.txt

Write a term the way it appears in a sentence. A capitalized term (Kubernetes, iOS) is imposed verbatim; an all-lowercase one asserts the spelling but leaves the model's capitalization alone. In the correction pass a term and its transcription must share a word count — gRPC can fix G R P C spoken as one word, but not a term split across word boundaries.

An acronym you pronounce as a word needs both spellings. Biasing rewards the exact token path you write, so NIRS only ever rewards N-I-R-S — a spelling the acoustics never take when you say "nirs". List NIRS and Nirs: the decoder can reach the second, and the correction pass snaps it back to the first, since the two normalize to the same word. Only case variants of the same letters work — a respelling (Ekmo for ECMO) is reachable but never snaps back.

Both layers are on whenever there are terms. [asr] boost scales the decoder biasing (default 1.0; 0 turns it off, and much above ~3 it starts rewriting words that were never in your list), and [vocab] glossary_threshold is the similarity a word must reach before the correction pass touches it (default 0.95 — deliberately strict, because a loose threshold corrupts words the model already had right).

Reach for the spellings before the knobs. On an 85-minute German meeting, adding the pronounceable twins fixed three more terms for four extra changed words in the rest of the transcript, where raising boost to 1.5 bought two and disturbed forty-six — at 2.0 the correct compound "Medizintechnikgruppe" decayed into "Medizinischechnikgruppe", pulled apart by a listed term it half-matched.

Settings

Standing preferences live in settings.toml in the platform data dir (~/Library/Application Support/stenograf/ on macOS) so you stop re-typing them. A flag always beats an environment variable (STENOGRAF_ASR_BACKEND, STENOGRAF_NOTES_BACKEND, …), which beats the file, which beats the built-in default — steno settings show labels where every value came from.

steno settings show   # effective configuration + where each value comes from
steno settings edit   # open in $EDITOR (template on first run), validate on save

The first steno settings edit writes a fully commented template documenting every key. All keys are optional; the ones you're most likely to want:

[transcript]
formats = ["md", "json", "txt"]   # default --format list (srt/vtt for subtitles)

[vocab]                           # standing vocabulary — MERGED with per-run
glossary_file = "~/steno/glossary.txt"     # --glossary/--attendee flags
attendees = ["Anja Müller"]
glossary_threshold = 0.95

[output]
dir = "~/Documents/Meetings"      # where meeting folders are created (default:
                                  # Meetings/ in your documents folder)

[speakers]
diarization = true                # separate speakers within a channel (off by
                                  # default; a per-run flag or count also enables)
reid_threshold = 0.5              # cross-meeting voice match strictness (0–1)
profile_store = "~/steno/profiles.json"

[asr]
backend = "parakeet"
provider = "cpu"                  # ONNX execution provider for parakeet-onnx:
                                  # cpu | dml (DX12 GPUs, Windows) | cuda | auto

[notes]                           # see "Meeting notes" above
backend = "mlx"

Typos fail loudly: an unknown table or key is an error, and steno doctor validates the whole file.

Development

Requires uv and Python 3.12 or 3.13 (3.14 is not yet supported by the ASR stack).

uv sync
uv run pytest
uv run steno doctor

The test suite is label-free and runs without a meeting: model-gated and real-audio tests self-skip when their assets are absent. The desktop app's tests run headless (QT_QPA_PLATFORM=offscreen, no window is ever shown).

Two front-ends share one library: the CLI (stenograf/cli/) and the desktop app (stenograf/gui/). Neither holds pipeline logic — the workflows they drive live in stenograf/flow.py, so a behaviour change lands in both at once.

See PLAN.md for the remaining roadmap; native/README.md for the capture helper and its wire protocol; eval/README.md for the model-evaluation and AEC-scoring harnesses.

License

MIT

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

stenograf-0.6.0.tar.gz (1.8 MB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

stenograf-0.6.0-py3-none-win_amd64.whl (13.3 MB view details)

Uploaded Python 3Windows x86-64

stenograf-0.6.0-py3-none-manylinux_2_39_x86_64.whl (17.6 MB view details)

Uploaded Python 3manylinux: glibc 2.39+ x86-64

stenograf-0.6.0-py3-none-manylinux_2_28_x86_64.whl (1.4 MB view details)

Uploaded Python 3manylinux: glibc 2.28+ x86-64

stenograf-0.6.0-py3-none-macosx_14_0_arm64.whl (10.3 MB view details)

Uploaded Python 3macOS 14.0+ ARM64

stenograf-0.6.0-py3-none-any.whl (1.2 MB view details)

Uploaded Python 3

File details

Details for the file stenograf-0.6.0.tar.gz.

File metadata

  • Download URL: stenograf-0.6.0.tar.gz
  • Upload date:
  • Size: 1.8 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for stenograf-0.6.0.tar.gz
Algorithm Hash digest
SHA256 02151e1132cd1a5650a93e1206e0852a12ee481e4e50f428799bc584b052546f
MD5 f30aa7e6d0f77739b1afd0e5b55e34d3
BLAKE2b-256 2fcd238d4bf3ed42f09aff33de8a9366365ac33f3695404f71621bc21be4355b

See more details on using hashes here.

Provenance

The following attestation bundles were made for stenograf-0.6.0.tar.gz:

Publisher: release.yml on daniel-om-weber/stenograf

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file stenograf-0.6.0-py3-none-win_amd64.whl.

File metadata

  • Download URL: stenograf-0.6.0-py3-none-win_amd64.whl
  • Upload date:
  • Size: 13.3 MB
  • Tags: Python 3, Windows x86-64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for stenograf-0.6.0-py3-none-win_amd64.whl
Algorithm Hash digest
SHA256 240beb9180e7278cf950e873b51b2169dbebf381afe7ea7f154c0d63d3c56d2a
MD5 61d37a4bda77f7f57ed452d4ef84e0f8
BLAKE2b-256 b06142cfaf604527fe6db2fc8f7b8776a460a05b29930d3cd6ff3201b2de0ec8

See more details on using hashes here.

Provenance

The following attestation bundles were made for stenograf-0.6.0-py3-none-win_amd64.whl:

Publisher: release.yml on daniel-om-weber/stenograf

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file stenograf-0.6.0-py3-none-manylinux_2_39_x86_64.whl.

File metadata

File hashes

Hashes for stenograf-0.6.0-py3-none-manylinux_2_39_x86_64.whl
Algorithm Hash digest
SHA256 3b421e4cc9930d29258066434f78b3e4af5b5bd902b4982892f55cd73299608a
MD5 5fd149610e331f1ec32556316052e08a
BLAKE2b-256 8e7025ed7902fee62d5ed856820cdf34e074acaa871946a8c60b17b5dfa81648

See more details on using hashes here.

Provenance

The following attestation bundles were made for stenograf-0.6.0-py3-none-manylinux_2_39_x86_64.whl:

Publisher: release.yml on daniel-om-weber/stenograf

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file stenograf-0.6.0-py3-none-manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for stenograf-0.6.0-py3-none-manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 ccf81c76ceebbd23aa3aaf9d94aa82c60a0a116a881d1efb7879c294e4f8171c
MD5 5d16c0a0db97335e485c2c6ff5004d67
BLAKE2b-256 68ce2da65e9d251f953ccf4304235cdc1bd3e1e540c80b74b74710ad5f4f6ee4

See more details on using hashes here.

Provenance

The following attestation bundles were made for stenograf-0.6.0-py3-none-manylinux_2_28_x86_64.whl:

Publisher: release.yml on daniel-om-weber/stenograf

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file stenograf-0.6.0-py3-none-macosx_14_0_arm64.whl.

File metadata

File hashes

Hashes for stenograf-0.6.0-py3-none-macosx_14_0_arm64.whl
Algorithm Hash digest
SHA256 6ec1fd891805c9ae543ac978c9a97936863c6216c1948ca1fdeb29b114444696
MD5 6db6621e128b95fd40b0c06c0c8e0024
BLAKE2b-256 e11e644ee7fe84d6285546cf5fee0507597f3596c6dfa7f82578a2b31f2c5d82

See more details on using hashes here.

Provenance

The following attestation bundles were made for stenograf-0.6.0-py3-none-macosx_14_0_arm64.whl:

Publisher: release.yml on daniel-om-weber/stenograf

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file stenograf-0.6.0-py3-none-any.whl.

File metadata

  • Download URL: stenograf-0.6.0-py3-none-any.whl
  • Upload date:
  • Size: 1.2 MB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for stenograf-0.6.0-py3-none-any.whl
Algorithm Hash digest
SHA256 51b850c2ac6b749a8e32d3c48b0b827a09ba8bbb0d11e5038c8ebd117584270a
MD5 0a9e4506fb1269b4642716bb6ede10a2
BLAKE2b-256 e69963a2c535a6c3557706060ac10f04acfc4bb0fdaf52c0db119742f623656c

See more details on using hashes here.

Provenance

The following attestation bundles were made for stenograf-0.6.0-py3-none-any.whl:

Publisher: release.yml on daniel-om-weber/stenograf

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

0.7.0

6 files

This release

0.6.0 This release

6 files

0.5.0

5 files

0.4.0

5 files

0.3.0

3 files

0.2.2

3 files

0.2.1

3 files

0.2.0

3 files

0.1.0

3 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