Meeting Transcriber

August 15, 2026 · View on GitHub

The local-first meeting transcriber for macOS. Records Teams, Zoom, and Webex calls, transcribes them on-device with Whisper / Parakeet, separates speakers, and turns the result into a Markdown protocol using your own Claude CLI or any local LLM. No cloud. No subscription. No audio ever leaves your Mac.

Meeting Transcriber turning a Teams call into a Markdown protocol on-device

Build status Latest release macOS 14.2+ Swift 6.2 MIT License GitHub stars

Why this exists

Cloud meeting recorders (Otter, Fireflies, Granola, tl;dv) work great, until you remember that every word from every meeting goes to a third-party server. For a lot of teams (legal, healthcare, M&A, anything under NDA, or just folks who'd rather not) that's a non-starter.

Meeting Transcriber runs the entire pipeline (recording, transcription, speaker diarization, summarization) on your Mac. No account, no upload, no monthly bill.

Cloud transcribersMeeting Transcriber
Audio leaves your machineYesNo
Recurring cost$10–30 / monthFree
Works offlineNoYes
Choice of summarization LLMVendor-lockedClaude · Ollama · LM Studio · any OpenAI API
Per-source speaker separationMixed trackDual-track diarization
Source availableNoMIT licensed

Install

brew tap pasrom/meeting-transcriber
brew install --cask meeting-transcriber

Homebrew 6.0+ may flag the third-party tap as untrusted. If so, run brew trust --tap pasrom/meeting-transcriber before installing.

The app lives in your menu bar — open it, grant microphone + screen-recording permission, and the first detected Teams/Zoom/Webex call records automatically.


How it works

flowchart TD
    A["Meeting Detected<br/>Teams · Zoom · Webex"]
    A2["File Import<br/>WAV · MP3 · M4A · MP4 · FLAC · AMR · 3GP · OPUS · OGG<br/>MKV · WebM (ffmpeg)"]
    B["Dual Recording<br/>App audio + Mic · 16 kHz per track"]
    C["16 kHz Mono Convert<br/>AVAudioFile → AVAsset → ffmpeg"]
    D{"Transcription Engine<br/>CoreML / ANE"}
    D1["WhisperKit<br/>99 languages"]
    D2["Parakeet TDT v3<br/>25 EU languages"]
    E["Speaker Diarization<br/>FluidAudio · dual-track + recognition"]
    F["Protocol Generation<br/>Claude CLI · OpenAI-compatible · none"]
    G["Markdown Protocol<br/>Summary · Decisions · Tasks · Transcript"]

    A --> B
    A2 --> C
    B --> D
    C --> D
    D --> D1
    D --> D2
    D1 --> E
    D2 --> E
    E --> F
    F --> G

    classDef input fill:#5B8DEF,stroke:#3F6FD5,color:#fff
    classDef engine fill:#8B5CF6,stroke:#7C3AED,color:#fff
    classDef output fill:#22C55E,stroke:#16A34A,color:#fff
    class A,A2 input
    class D engine
    class G output

Features

  • Automatic meeting detection — Recognizes Teams, Zoom, and Webex meetings via window title polling, opt-in browser meeting detection (Google Meet, Whereby, web Zoom/Teams in any Chromium browser) gated behind a recording-consent prompt, and opt-in mic-input detection for call apps without a reliable meeting signal (WeChat, Tencent Meeting, FaceTime, WhatsApp) — per app, off by default, and unlike the browser path it starts recording without a prompt
  • Dual audio recording — App audio (CATapDescription) + microphone simultaneously
  • On-device transcription — Two engines, selectable in Settings:
    • WhisperKit — 99+ languages, ~1 GB model
    • Parakeet TDT v3 (NVIDIA) — 25 EU languages, ~50 MB model, ~10× faster, custom vocabulary support (CTC boosting)
  • On-device speaker diarizationFluidAudio via CoreML/ANE — no HuggingFace token needed; two modes: standard (OfflineDiarizer) and overlap-aware (Sortformer)
  • Dual-track diarization — App and mic tracks diarized separately for clean speaker separation without echo interference
  • Speaker recognition — Voice embeddings stored across meetings, matched via cosine similarity
  • VAD preprocessing — Optional silence trimming via FluidAudio Silero v6 before transcription, with automatic timestamp remapping
  • AI protocol generation — Structured Markdown via Claude Code CLI, OpenAI-compatible APIs (Ollama, LM Studio, etc.), or disabled (save transcript only)
  • Configurable protocol prompt — Custom prompt file support (~/Library/Application Support/MeetingTranscriber/protocol_prompt.md)
  • Manual recording — Record any app via app picker, not just detected meetings
  • Multi-format input — Supports WAV, MP3, M4A, MP4, FLAC, plus the phone and messenger voice formats AMR, 3GP/3G2 and OPUS/OGG; MKV and WebM additionally need ffmpeg
  • Update checker — Notifies when a new version is available
  • Background processing — PipelineQueue runs transcription and protocol generation independently from recording
  • Record-only mode — Skip the entire post-recording pipeline and drop dual-source recordings + a metadata sidecar into the output folder, for external/fleet processing (e.g. a separate GPU host)
  • Local automation API (Homebrew build): drive the pipeline headlessly over localhost HTTP. POST an audio file, get a diarized transcript back. See docs/automation-api.md
  • Stream Deck and hotkey control (Homebrew build): start/stop watching from a Stream Deck key, Shortcut, Raycast or any launcher. See docs/stream-deck.md
  • Distribution — Install via Homebrew Cask or build from source

Prerequisites

  • macOS 14.2+ (required for CATapDescription audio capture)
  • One of:
    • Claude Code CLI — installed and logged in (claude --version)
    • An OpenAI-compatible API endpoint (e.g. Ollama, LM Studio, llama.cpp) — configure in Settings

No HuggingFace token needed — FluidAudio and WhisperKit download their models automatically on first run.

Optional: ffmpeg for extra formats

Install ffmpeg to enable MKV and WebM support:

brew install ffmpeg

The app detects ffmpeg automatically. Status is shown in Settings → About.

Using Ollama as provider

  1. Install Ollama: brew install ollama
  2. Pull a model: ollama pull llama3.1 (or any model that fits your hardware)
  3. Start the server: ollama serve (runs on http://localhost:11434 by default)
  4. In the app's Settings, select OpenAI-Compatible API as provider and set:
    • Endpoint: http://localhost:11434/v1/chat/completions
    • Model: llama3.1 (must match the pulled model name)

Installation

brew tap pasrom/meeting-transcriber
brew install --cask meeting-transcriber

Tap trust: Homebrew 6.0 added tap trust for third-party taps. During the 6.0.x transition non-official taps are still allowed by default (you may just see a warning); enforcement is opt-in via HOMEBREW_REQUIRE_TAP_TRUST and becomes mandatory in a later release. If your Homebrew enforces it, run brew trust --tap pasrom/meeting-transcriber before brew install --cask.

Pre-release (RC) via Homebrew

brew tap pasrom/meeting-transcriber
brew install --cask meeting-transcriber@beta

Note: The stable and beta casks conflict — uninstall one before installing the other.

Build from source

git clone https://github.com/pasrom/meeting-transcriber
cd meeting-transcriber
./scripts/run_app.sh

Permissions

PermissionRequired forNotes
Screen RecordingOptional — sharpens the meeting title and acts as a fallback for the audio tap. Detection itself works without itSystem Settings → Privacy & Security
MicrophoneMic recordingPrompted on first use
AccessibilityMute detection, participant reading (Teams)System Settings → Privacy & Security
App audio captureNo permission needed (purple dot indicator only)

The app uses an animated waveform icon in the menu bar that reflects the current pipeline stage:

Idle   Recording   Transcribing   Diarizing   Protocol

IdleRecording (bars bounce) → Transcribing (bars morph to text) → Diarizing (bars split into groups) → Protocol (lines appear sequentially)

Permission problem badge

Permission problem

A red exclamation mark in the bottom-right corner is overlaid on top of the current icon (idle, recording, transcribing, …) whenever one of the required permissions is missing or broken. It means at least one of the following is not in a working state:

  • Microphone — denied, or granted but the capture engine can't open the device
  • Screen Recording — denied, or granted but CGWindowListCopyWindowInfo returns no window titles (TCC state out of sync)
  • Accessibility — denied, or granted but the AX API refuses to read Teams participant/mute info

The health check distinguishes denied from broken. "Broken" usually means the permission is toggled on in System Settings but macOS hasn't actually wired it through — the fix is to toggle the permission off and on again for Meeting Transcriber under System Settings → Privacy & Security. Open the menu bar dropdown to see which specific permission is affected; a notification is also posted when the state changes.

Record-only mode badge

Record-only mode

A small red dot in the bottom-right corner is overlaid on top of the current icon (idle, recording, transcribing, …) whenever Record-only mode is enabled (Settings → General → "Record-only mode"). In this mode the app keeps detecting meetings and producing dual-source recordings, but skips the entire post-recording pipeline (VAD, transcription, diarization, protocol generation). Recordings + a per-meeting <timestamp>_meta.json sidecar are dropped into your configured Output Folder for an external pipeline (e.g. a Linux GPU host via Syncthing) to pick up. The dot stays visible across all states so the mode is always clearly indicated; if a permission problem coexists, the red exclamation badge takes precedence.

Per-channel asymmetric-silence indicator

App-audio channel silent — bottom half red   Mic channel silent — top half red

When one capture channel goes silent while the other is still carrying audio for longer than the configured debounce window, the waveform bars are tinted red to surface the half-broken capture at a glance:

  • Bottom half red — app-audio channel is dead (you're speaking but nothing from the meeting app is being captured — bad tap, broken routing, system output muted)
  • Top half red — mic channel is dead (the meeting is audible but your voice isn't being captured — wrong input device, mic muted at system level)
  • Both halves red — both channels are silent while in recording state

A "Capture Channel Silent" notification fires once per episode at the moment the tint kicks in. Configurable in Settings → Audio → Per-Channel Indicator (default: on, 90 s debounce, range 30–300 s). Designed to surface real routing failures without false-positiving during normal speech pauses: the detector uses dual dBFS thresholds with hysteresis so transient dips between syllables don't reset the debounce timer.

If a permission problem coexists, the red exclamation badge takes precedence over the channel-silent tint.


Usage

Launch the app — it sits in your menu bar. When a supported meeting is detected, recording starts automatically. When the meeting ends, the pipeline runs in the background: transcription → diarization → protocol generation.

You can also batch-process existing audio and video files via the menu (⌘P) — supported formats: WAV, MP3, M4A, MP4, FLAC, AMR, 3GP/3G2 and OPUS/OGG (and MKV, WebM when ffmpeg is installed). Smartphone call recordings (AMR, 3GP) and voice messages (OPUS) need no extra tools.


Configuration

Open Settings via the menu bar item or ⌘,.

Settings window
TabWhat's in it
GeneralRecord-only mode, apps to watch (Teams/Zoom/Webex/Browser/WeChat/Tencent Meeting/FaceTime/WhatsApp), detection timing, update checks
AudioMicrophone device, voice activity detection (VAD), per-channel silence indicator
TranscribeASR engine (WhisperKit / Parakeet) and per-engine options (model, language, custom vocabulary), live caption overlay (PoC)
SpeakersDiarization, mic speaker name, known voices, recognition stats
OutputLLM provider (Claude CLI / OpenAI-compatible / none), protocol language, output folder, custom prompt
AdvancedPermissions status, diagnostics, version info

Output

Files are saved to ~/Library/Application Support/MeetingTranscriber/protocols/:

FileContent
20260225_1400_meeting.txtRaw transcript
20260225_1400_meeting.mdStructured protocol

Protocol structure: Summary, Participants, Topics Discussed, Decisions, Tasks (with responsible person, deadline, priority), Open Questions, Full Transcript.


Troubleshooting

ProblemSolution
claude not foundInstall Claude Code CLI, run claude --version — or switch to OpenAI-compatible provider in Settings
No meeting detectedCheck the app is enabled under Settings → General → Apps to Watch. Screen Recording is not required for detection; it only sharpens the meeting title
No app audioRequires macOS 14.2+ for CATapDescription audio capture
Empty transcriptionCheck that the file contains an audio track — the app converts to 16 kHz mono automatically
Models not loadingModels download on first run (WhisperKit ~1 GB, Parakeet ~50 MB); check internet connectivity
OpenAI-compatible API connection failedVerify the endpoint URL and that the local model server is running

Diagnosing silent app-audio recordings

If a recording's _app.wav is silent or unexpectedly quiet, enable verbose audio logging to capture forensic detail during the next attempt:

  1. Open Settings → Diagnostics and turn on Verbose Audio Logging.
  2. Reproduce the failing recording.
  3. Open Console.app, filter by subsystem com.meetingtranscriber.audiotap, and look for [debug] lines:
    • [debug] Tap target: pid=… exe=… bundle=… audioObjectID=… — which process the tap targeted
    • [debug] Default output device: name=… uid=… transport=… rate=… — output device at start
    • [debug] Tap format: rate=… Hz, tapID=… — sample rate and tap ID after the tap is configured
    • [debug] App audio RMS (5s): …dBFS, samples=…, totalBytes=… — every 5 seconds; tells you live whether the tap is delivering real audio (-40 dBFS or higher) or near-silence (≤ -90 dBFS)
    • [debug] Output device change → name=… uid=… — emitted when the system output device changes mid-recording
    • [debug] Mic input device: name=… uid=… hwRate=… hwChannels=… — mic hardware device at capture start
    • [debug] Mic RMS (5s): …dBFS, samples=… — every 5 seconds during mic capture
  4. Turn the toggle off again when done — the per-5 s RMS log is moderately chatty.

The toggle persists in UserDefaults and takes effect on the next recording without an app restart.


Testing & CI

CI E2E E2E (App) Quality & Safety App Store Smoke codecov

Pull requests run unit tests, lint, and analyzer in ci.yml. Two complementary E2E layers run on a self-hosted Apple Silicon Mac mini against the real production models (no mocks): e2e.yml feeds fixture audio through each ASR engine + the WatchLoop pipeline, and e2e-app.yml builds and signs the actual .app, drives a simulated meeting via tools/meeting-simulator, and asserts on the resulting transcript over the embedded debug RPC server.


License

MIT