Skip to main content

Accuracy-first local meeting transcription for German and English. Audio stays in RAM; only text is written.

Project description

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 and Linux the pipeline is complete end to end: live system-audio + microphone capture, live captions, and the high-accuracy speaker-labelled finalize pass, plus meeting notes. Windows support is in progress. See PLAN.md.

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, double-click Stenograf on your Desktop (macOS) or start Stenograf from the application menu (Linux) to open the launcher — 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) and Linux with PipeWire or PulseAudio (capture uses parec, shipped with pipewire-pulse / pulseaudio-utils on every desktop distro; ASR runs ONNX on CPU).

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).

From a checkout

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

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

The launcher

Double-click Stenograf (the Desktop icon on macOS, the application-menu entry on Linux) — or run bare steno in a terminal — and a mouse-driven launcher opens:

  • 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 launcher does is also a plain CLI command — the rest of this document — so terminal users and scripts lose nothing.

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 — a full-screen TUI on a terminal, a plain line-by-line stream when piped — 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.

Useful flags:

steno start --plain                 # plain caption stream instead of the TUI
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 ~/Documents/Meetings:

~/Documents/Meetings/meeting-20260710-091500/
    transcript.md / .json / .txt        # the transcript (--format adds srt/vtt)
    transcript.notes.md / .notes.json   # 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. 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 per owner, 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

Notes land as sibling transcript.notes.md/.notes.json files. 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. Two more levers in [notes]: instructions = "~/style.md" appends your house style to the built-in prompt, and thinking = false skips the mlx model's reasoning pass (faster, less careful). Notes never run unless you ask (--notes or steno notes), and 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 are corrected in the finalized transcript (the ASR has no decode-time biasing, so this is a post-correction pass):

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

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.

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.82

[output]
dir = "~/Documents/Meetings"      # where meeting folders are created

[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.

See PLAN.md for the full architecture, model choices, and roadmap; PLAN-AEC.md for the echo-cancellation design and its measurements; native/README.md for the capture helper and its wire protocol; eval/README.md for the model-evaluation and AEC-scoring harnesses.

License

MIT

Project details


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.2.0.tar.gz (699.3 kB view details)

Uploaded Source

Built Distributions

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

stenograf-0.2.0-py3-none-macosx_14_0_arm64.whl (9.3 MB view details)

Uploaded Python 3macOS 14.0+ ARM64

stenograf-0.2.0-py3-none-any.whl (220.5 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: stenograf-0.2.0.tar.gz
  • Upload date:
  • Size: 699.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for stenograf-0.2.0.tar.gz
Algorithm Hash digest
SHA256 4a3545c583309284f33a5cb6b5cc6ce3c77194e72ecdd56b27c1ec3afd8fd64b
MD5 7d7104f1444fb50e9c43c1b6342f2e81
BLAKE2b-256 569ff3bd16e0cf43c0a386527e989aa0707076b49b0cba9c173801830a2cc413

See more details on using hashes here.

Provenance

The following attestation bundles were made for stenograf-0.2.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.2.0-py3-none-macosx_14_0_arm64.whl.

File metadata

File hashes

Hashes for stenograf-0.2.0-py3-none-macosx_14_0_arm64.whl
Algorithm Hash digest
SHA256 a1c013aac3802f0f6ed9b55af01831408a763d9ae1a6a314061db1dc41cc64b9
MD5 3509135e8a3e5b6a03ec192165085a3e
BLAKE2b-256 600796723f0a8c794ee9ff4fe2417735d03664339a704dd420cda6d95167fc77

See more details on using hashes here.

Provenance

The following attestation bundles were made for stenograf-0.2.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.2.0-py3-none-any.whl.

File metadata

  • Download URL: stenograf-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 220.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for stenograf-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 3005f90fe833ae2c6fadd457276f414b169a03a30701e8220d03d766252d3726
MD5 c5920fbf289600151da315181ef57d6f
BLAKE2b-256 9c88b60d905617eff93fc6bdffb7d3e42397502deb2f432b5712256efa407645

See more details on using hashes here.

Provenance

The following attestation bundles were made for stenograf-0.2.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.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page