On-device meeting transcriber for macOS — auto-records Teams/Zoom/Webex, transcribes & separates speakers locally. No cloud. Open-source alternative to Otter/Granola/Fireflies.
167
stars
1,698
commits
Swift
primary language
Sep 11, 2026
updated
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.
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 transcribers | Meeting Transcriber | |
|---|---|---|
| Audio leaves your machine | Yes | No |
| Recurring cost | $10–30 / month | Free |
| Works offline | No | Yes |
| Choice of summarization LLM | Vendor-locked | Claude · Ollama · LM Studio · any OpenAI API |
| Per-source speaker separation | Mixed track | Dual-track diarization |
| Source available | No | MIT licensed |
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-transcriberbefore 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.
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
OfflineDiarizer) and overlap-aware (Sortformer)~/Library/Application Support/MeetingTranscriber/protocol_prompt.md) with {LANGUAGE}, {MEETING_DATE} (YYYY-MM-DD), and {MEETING_TIME} (HH:mm) variables; recordings include authoritative metadata, while imports and recovery jobs resolve time placeholders to Unknowndocs/automation-api.mddocs/stream-deck.mdclaude --version)No HuggingFace token needed — FluidAudio and WhisperKit download their models automatically on first run.
Install ffmpeg to enable MKV and WebM support:
brew install ffmpeg
The app detects ffmpeg automatically. Status is shown in Settings → About.
brew install ollamaollama pull llama3.1 (or any model that fits your hardware)ollama serve (runs on http://localhost:11434 by default)http://localhost:11434/v1/chat/completionsllama3.1 (must match the pulled model name)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_TRUSTand becomes mandatory in a later release. If your Homebrew enforces it, runbrew trust --tap pasrom/meeting-transcriberbeforebrew install --cask.
brew tap pasrom/meeting-transcriber
brew install --cask meeting-transcriber@beta
Note: The stable and beta casks conflict — uninstall one before installing the other.
git clone https://github.com/pasrom/meeting-transcriber
cd meeting-transcriber
./scripts/run_app.sh
| Permission | Required for | Notes |
|---|---|---|
| Screen Recording | Optional — sharpens the meeting title and acts as a fallback for the audio tap. Detection itself works without it | System Settings → Privacy & Security |
| Microphone | Mic recording | Prompted on first use |
| Accessibility | Mute detection, participant reading (Teams) | System Settings → Privacy & Security |
| App audio capture | — | No 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 (bars bounce) → Transcribing (bars morph to text) → Diarizing (bars split into groups) → Protocol (lines appear sequentially)
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:
CGWindowListCopyWindowInfo returns no window titles (TCC state out of sync)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.
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.
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:
The tint follows the levels, and Settings → Audio → Per-Channel Indicator is what turns it on or off (default: on). Dual dBFS thresholds with hysteresis keep transient dips between syllables from resetting the debounce timer.
Notifications do not follow the tint. A single channel is reported only once it stops delivering, either no buffers at all or buffers carrying nothing but digital zeroes, measured from what the capture layer records per buffer rather than from a level: a microphone whose owner is listening rather than talking is quiet, not broken, and is not reported. The separate "Recording Appears Silent" warning, for the case where both channels are quiet, is still decided from levels and can therefore still fire on a call in which nobody is speaking. How long the condition must last first is the Warn after slider (30–300 s, default 90 s), which applies whether or not the tint is switched on.
If a permission problem coexists, the red exclamation badge takes precedence over the channel-silent tint.
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.
Open Settings via the menu bar item or ⌘,.
| Tab | What's in it |
|---|---|
| General | Record-only mode, apps to watch (Teams/Zoom/Webex/Browser/WeChat/Tencent Meeting/FaceTime/WhatsApp), detection timing, update checks |
| Audio | Microphone device, voice activity detection (VAD), per-channel silence indicator, echo cancellation (off by default) |
| Transcribe | ASR engine (WhisperKit / Parakeet) and per-engine options (model, language, custom vocabulary), terminology normalization rules, live caption overlay (PoC) |
| Speakers | Diarization, mic speaker name, known voices, recognition stats |
| Output | LLM provider (Claude CLI / OpenAI-compatible / none), transcript-retention options, protocol language, output folder, custom prompt |
| Advanced | Permissions status, diagnostics, version info |
Files are saved to ~/Library/Application Support/MeetingTranscriber/protocols/:
| File | Content |
|---|---|
20260225_1400_meeting.txt | Raw transcript (when enabled) |
20260225_1400_meeting.md | Structured protocol (when an LLM provider is configured) |
In Settings → Output, both transcript options default to enabled for backward compatibility:
.txt file.Disable both to retain only the generated meeting minutes. The recording follows its own retention policy and is not deleted by either setting. To avoid data loss, a raw transcript is retained if protocol generation is disabled, fails, or the job ends with an error.
Protocol structure: Summary, Participants, Topics Discussed, Decisions, Tasks (with responsible person, deadline, priority), Open Questions, and optionally the Full Transcript.
| Problem | Solution |
|---|---|
claude not found | Install Claude Code CLI, run claude --version — or switch to OpenAI-compatible provider in Settings |
| No meeting detected | Check 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 audio | Requires macOS 14.2+ for CATapDescription audio capture |
| Empty transcription | Check that the file contains an audio track — the app converts to 16 kHz mono automatically |
| Models not loading | Models download on first run (WhisperKit ~1 GB, Parakeet ~50 MB); check internet connectivity |
| OpenAI-compatible API connection failed | Verify the endpoint URL and that the local model server is running |
If a recording's _app.wav is silent or unexpectedly quiet, enable verbose audio logging to capture forensic detail during the next attempt:
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] 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 captureThe toggle persists in UserDefaults and takes effect on the next recording without an app restart.
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.
Swift
87.4%
Shell
11.2%
On-device meeting transcriber for macOS — auto-records Teams/Zoom/Webex, transcribes & separates speakers locally. No cloud. Open-source alternative to Otter/Granola/Fireflies.
167
stars
1,698
commits
Swift
primary language
Sep 11, 2026
updated
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.
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 transcribers | Meeting Transcriber | |
|---|---|---|
| Audio leaves your machine | Yes | No |
| Recurring cost | $10–30 / month | Free |
| Works offline | No | Yes |
| Choice of summarization LLM | Vendor-locked | Claude · Ollama · LM Studio · any OpenAI API |
| Per-source speaker separation | Mixed track | Dual-track diarization |
| Source available | No | MIT licensed |
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-transcriberbefore 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.
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
OfflineDiarizer) and overlap-aware (Sortformer)~/Library/Application Support/MeetingTranscriber/protocol_prompt.md) with {LANGUAGE}, {MEETING_DATE} (YYYY-MM-DD), and {MEETING_TIME} (HH:mm) variables; recordings include authoritative metadata, while imports and recovery jobs resolve time placeholders to Unknowndocs/automation-api.mddocs/stream-deck.mdclaude --version)No HuggingFace token needed — FluidAudio and WhisperKit download their models automatically on first run.
Install ffmpeg to enable MKV and WebM support:
brew install ffmpeg
The app detects ffmpeg automatically. Status is shown in Settings → About.
brew install ollamaollama pull llama3.1 (or any model that fits your hardware)ollama serve (runs on http://localhost:11434 by default)http://localhost:11434/v1/chat/completionsllama3.1 (must match the pulled model name)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_TRUSTand becomes mandatory in a later release. If your Homebrew enforces it, runbrew trust --tap pasrom/meeting-transcriberbeforebrew install --cask.
brew tap pasrom/meeting-transcriber
brew install --cask meeting-transcriber@beta
Note: The stable and beta casks conflict — uninstall one before installing the other.
git clone https://github.com/pasrom/meeting-transcriber
cd meeting-transcriber
./scripts/run_app.sh
| Permission | Required for | Notes |
|---|---|---|
| Screen Recording | Optional — sharpens the meeting title and acts as a fallback for the audio tap. Detection itself works without it | System Settings → Privacy & Security |
| Microphone | Mic recording | Prompted on first use |
| Accessibility | Mute detection, participant reading (Teams) | System Settings → Privacy & Security |
| App audio capture | — | No 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 (bars bounce) → Transcribing (bars morph to text) → Diarizing (bars split into groups) → Protocol (lines appear sequentially)
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:
CGWindowListCopyWindowInfo returns no window titles (TCC state out of sync)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.
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.
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:
The tint follows the levels, and Settings → Audio → Per-Channel Indicator is what turns it on or off (default: on). Dual dBFS thresholds with hysteresis keep transient dips between syllables from resetting the debounce timer.
Notifications do not follow the tint. A single channel is reported only once it stops delivering, either no buffers at all or buffers carrying nothing but digital zeroes, measured from what the capture layer records per buffer rather than from a level: a microphone whose owner is listening rather than talking is quiet, not broken, and is not reported. The separate "Recording Appears Silent" warning, for the case where both channels are quiet, is still decided from levels and can therefore still fire on a call in which nobody is speaking. How long the condition must last first is the Warn after slider (30–300 s, default 90 s), which applies whether or not the tint is switched on.
If a permission problem coexists, the red exclamation badge takes precedence over the channel-silent tint.
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.
Open Settings via the menu bar item or ⌘,.
| Tab | What's in it |
|---|---|
| General | Record-only mode, apps to watch (Teams/Zoom/Webex/Browser/WeChat/Tencent Meeting/FaceTime/WhatsApp), detection timing, update checks |
| Audio | Microphone device, voice activity detection (VAD), per-channel silence indicator, echo cancellation (off by default) |
| Transcribe | ASR engine (WhisperKit / Parakeet) and per-engine options (model, language, custom vocabulary), terminology normalization rules, live caption overlay (PoC) |
| Speakers | Diarization, mic speaker name, known voices, recognition stats |
| Output | LLM provider (Claude CLI / OpenAI-compatible / none), transcript-retention options, protocol language, output folder, custom prompt |
| Advanced | Permissions status, diagnostics, version info |
Files are saved to ~/Library/Application Support/MeetingTranscriber/protocols/:
| File | Content |
|---|---|
20260225_1400_meeting.txt | Raw transcript (when enabled) |
20260225_1400_meeting.md | Structured protocol (when an LLM provider is configured) |
In Settings → Output, both transcript options default to enabled for backward compatibility:
.txt file.Disable both to retain only the generated meeting minutes. The recording follows its own retention policy and is not deleted by either setting. To avoid data loss, a raw transcript is retained if protocol generation is disabled, fails, or the job ends with an error.
Protocol structure: Summary, Participants, Topics Discussed, Decisions, Tasks (with responsible person, deadline, priority), Open Questions, and optionally the Full Transcript.
| Problem | Solution |
|---|---|
claude not found | Install Claude Code CLI, run claude --version — or switch to OpenAI-compatible provider in Settings |
| No meeting detected | Check 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 audio | Requires macOS 14.2+ for CATapDescription audio capture |
| Empty transcription | Check that the file contains an audio track — the app converts to 16 kHz mono automatically |
| Models not loading | Models download on first run (WhisperKit ~1 GB, Parakeet ~50 MB); check internet connectivity |
| OpenAI-compatible API connection failed | Verify the endpoint URL and that the local model server is running |
If a recording's _app.wav is silent or unexpectedly quiet, enable verbose audio logging to capture forensic detail during the next attempt:
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] 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 captureThe toggle persists in UserDefaults and takes effect on the next recording without an app restart.
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.
Swift
87.4%
Shell
11.2%