{"id":50335314,"url":"https://github.com/bdombro/active-listener","last_synced_at":"2026-05-29T13:01:47.098Z","repository":{"id":350091505,"uuid":"1205273327","full_name":"bdombro/active-listener","owner":"bdombro","description":"Record and transcribe meetings using a CLI, fully local","archived":false,"fork":false,"pushed_at":"2026-04-20T10:12:47.000Z","size":658,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-20T11:34:32.616Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/bdombro.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-08T20:04:55.000Z","updated_at":"2026-04-20T10:12:50.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/bdombro/active-listener","commit_stats":null,"previous_names":["bdombro/active-listener"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/bdombro/active-listener","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bdombro%2Factive-listener","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bdombro%2Factive-listener/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bdombro%2Factive-listener/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bdombro%2Factive-listener/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/bdombro","download_url":"https://codeload.github.com/bdombro/active-listener/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bdombro%2Factive-listener/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33652986,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-05-29T02:00:06.066Z","response_time":107,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-05-29T13:01:46.316Z","updated_at":"2026-05-29T13:01:47.080Z","avatar_url":"https://github.com/bdombro.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Active Listener\n\nRecord and transcribe meetings using a CLI, fully local. Press Ctrl+C to stop — you get a WAV and a Whisper-transcribed markdown file. No cloud, no subscriptions.\n\nWhy use over WhisperX/Python? See [Architecture](#architecture)\n\n## Quick start\n\nDownload the binary for your platform from the [releases page](https://github.com/bdombro/active-listener/releases):\n\n| Platform | File |\n|---|---|\n| macOS Apple Silicon | `active-listener-*-aarch64-apple-darwin.tar.gz` |\n| macOS Intel | `active-listener-*-x86_64-apple-darwin.tar.gz` |\n| Linux x86_64 | `active-listener-*-x86_64-unknown-linux-gnu.tar.gz` |\n| Windows x86_64 | `active-listener-*-x86_64-pc-windows-gnu.tar.gz` |\n\nExtract and install (macOS / Linux):\n\n```bash\ntar -xzf active-listener-*.tar.gz\n./active-listener install\nsource ~/.zshrc\n```\n\n`install` copies the binary to `~/.local/bin`, adds zsh completions, and pre-downloads the default Whisper weights (`small`) from Hugging Face. Diarization models are also pre-fetched so your first `--diarize` run starts immediately.\n\n\u003e **Windows**: `install` is not supported — place `active-listener.exe` on your `PATH` manually and run it directly. Note that the Windows release binary is built without diarization support, so `--diarize` has no effect.\n\n**To build from source** (requires Rust 1.74+ and [just](https://github.com/casey/just)):\n\n```bash\njust install\n```\n\n## Usage\n\n```bash\n# Interactive: choose mic, system audio, or both (TTY only)\nactive-listener start\n\n# Record mic only; write to current directory\nactive-listener start --mic\n\n# Record mic + desktop audio (requires Screen Recording permission on macOS)\nactive-listener start --mic --system-audio\n\n# Custom output folder and filename stem\nactive-listener start --mic --dir ~/notes --name standup\n\n# Speaker labels in the output (adds ~10–30s post-processing)\nactive-listener start --mic --diarize\n\n# Transcribe an existing WAV (no re-record)\nactive-listener process recording.wav --dir ~/notes\nactive-listener process recording.wav --diarize\n```\n\n## Output\n\nEvery session produces two files with the same basename:\n\n- **`YYYY-MM-DD_HHMMSS.wav`** — raw 16 kHz mono recording\n- **`YYYY-MM-DD_HHMMSS.md`** — frontmatter + transcript\n\n### Default (no `--diarize`)\n\n```markdown\n---\ndate: 2026-04-08T14:30:00\nduration: 32m 15s\nwhisper_model: small\n---\n\n# Meeting notes — 2026-04-08 14:30\n\n## Transcript\n\n**[00:00]** Welcome everyone, let's get started with today's standup.\n\n**[00:12]** I finished the auth refactor yesterday and pushed it for review.\n\n**[00:28]** Looks good to me, I'll take a look after this call.\n\n**[01:04]** Any blockers? Nothing from my side, shipping the feature branch today.\n```\n\n### With `--diarize`\n\nHow it works:\n- Whisper and the speaker diarizer run in parallel; labels are merged into the transcript by timestamp overlap.\n\nTips:\n- Diarization isn't great. Can be improved by having an LLM \"improve diarization on this meeting transcript\"\n\n\n```markdown\n---\ndate: 2026-04-08T14:30:00\nduration: 32m 15s\nwhisper_model: small\n---\n\n# Meeting notes — 2026-04-08 14:30\n\n## Transcript\n\n**Speaker 1**\n\n**[00:00]** Welcome everyone, let's get started with today's standup.\n\n**Speaker 2**\n\n**[00:12]** I finished the auth refactor yesterday and pushed it for review.\n\n**Speaker 1**\n\n**[00:28]** Looks good to me, I'll take a look after this call.\n\n**Speaker 2**\n\n**[01:04]** No blockers, shipping the feature branch today.\n```\n\nLabels are `Speaker 1`, `Speaker 2`, … — not real names.\n\n## Options\n\nAll flags work on both `start` and `process` unless noted.\n\n| Flag | Default | Description |\n|---|---|---|\n| `--mic` | off | Capture microphone (`start` only; at least one source required) |\n| `--system-audio` | off | Mix in desktop audio (`start` only) |\n| `--dir PATH` | `.` | Output directory |\n| `--name STEM` | datetime | Output filename without extension |\n| `--whisper-model` | `small` | `tiny` / `base` / `small` / `medium` / `large` |\n| `--diarize` | off | Add speaker labels via sherpa-onnx |\n| `--num-speakers N` | auto | Fix speaker count (more reliable than threshold mode) |\n| `--diarize-threshold` | `0.9` | Cluster merge/split — higher → fewer speakers |\n| `--diarize-embedding PATH` | auto | Custom speaker embedding ONNX (see below) |\n| `--duration SECS` | unlimited | Auto-stop after N seconds (`start` only) |\n| `--device NAME` | default | Mic device name — see `--list-devices` |\n| `--cpu` | off | Force CPU; skip Metal |\n| `--verbose` | off | Print device and model details |\n\n`ACTIVE_LISTENER_DIARIZE_EMBEDDING` is the env-var equivalent of `--diarize-embedding`.\n\n## Speaker diarization\n\nPowered by [sherpa-onnx](https://github.com/k2-fsa/sherpa-onnx) (pyannote segmentation + NeMo Titanet small embedding). Models are downloaded once to `~/.cache/active-listener/diarize/` on first use (or pre-fetched by `install`).\n\nOffline diarization is fuzzy — one threshold cannot perfectly split every conversation. Practical tips:\n\n- **`--num-speakers N`** is the most reliable option when you know the count (two-person call, panel of three, etc.).\n- **`--diarize-threshold`** (default `0.9`) is the fallback knob: higher merges more aggressively (fewer speakers), lower splits more.\n- **`--diarize-embedding`** — swap in another sherpa-onnx embedding ONNX if you want to experiment (e.g. [Titanet large](https://github.com/k2-fsa/sherpa-onnx/releases/tag/speaker-recongition-models) for better accuracy at the cost of size and speed).\n\nExpect imperfect results in difficult audio (crosstalk, TV in the background, single mixed channel).\n\n## Privacy\n\n- Everything runs locally. No audio, transcript, or metadata leaves your machine.\n- Whisper weights are downloaded from Hugging Face once (model files only; no audio uploaded).\n- Diarization models are downloaded from GitHub releases once (no audio uploaded).\n- `active-listener uninstall` removes the binary, shell snippets from `~/.zshrc`, and all cached `openai/whisper-*` Hugging Face hub folders.\n- The `.wav` and `.md` files contain raw audio and full transcripts — treat them accordingly.\n- macOS requires **Microphone** permission for `--mic` and **Screen Recording** for `--system-audio`.\n\n## Platform notes\n\n| Platform | Audio capture | Diarization |\n|---|---|---|\n| macOS | Mic (cpal) + ScreenCaptureKit system audio | Yes (`diarize` feature, default) |\n| Linux | Mic (cpal) + PipeWire (`linux-system-audio` feature, default) | Yes (native build); No (cross-compiled `--no-default-features` build) |\n| Windows | Mic (cpal) + WASAPI loopback | Yes (native build); No (cross-compiled build) |\n\nLinux native builds need `libpipewire-0.3-dev` and `libspa-0.2-dev`.\n\n## Development\n\nRequires [just](https://github.com/casey/just) (`brew install just`) and Rust 1.74+.\n\n```bash\njust build        # cargo build --release (native host)\njust install      # build + install binary + shell config\njust build-cross  # cross-compile: macOS aarch64/x86_64, Linux x86_64, Windows x86_64 (needs Docker)\njust release      # build-cross + GitHub release (needs gh)\n```\n\nQuick dev loop: `cargo run -- start --mic` — records, transcribes, writes WAV + markdown.\n\nTo strip the diarization dependency for a smaller binary:\n```bash\ncargo build --release --no-default-features --features metal,linux-system-audio\n```\n\n## Requirements\n\n- Rust 1.74+\n- macOS: Metal is the default GPU backend (`metal` feature). On other platforms build with `--no-default-features` and re-add what you need.\n\n\n## Architecture\n\nIs built on top of:\n- **Candle** (`candle-transformers`) — Whisper transcription in Rust using Hugging Face `openai/whisper-*` weights; **`hf-hub`** fetches and caches those checkpoints.\n- **sherpa-onnx** — ONNX Runtime–backed offline diarization; default segmentation weights are **`sherpa-onnx-pyannote-segmentation-3-0`** (pyannote segmentation 3.0 exported to ONNX), plus a separate default **speaker-embedding** ONNX (NeMo Titanet small).\n\n**Compared to [WhisperX](https://github.com/m-bain/whisperX)**:\n\n- **active-listener** includes audio capture, is a **single native binary** optimized for MacOS with some Linux/Windows support, no hugging-face account or terms-agreement required.\n- **WhisperX** no audio capture, feature-bloated, and is glue-code for a collection of python projects which were never really meant to work well together. Is fragile, has several hidden host requirements (stale FFMpeg, python versions, hugging-face user account and usage/agreement for non-commercial models).\n- Quality: I, personally, did not get better quality with WhisperX over `active-listener`.\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbdombro%2Factive-listener","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbdombro%2Factive-listener","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbdombro%2Factive-listener/lists"}