RecordCueAutomateGuideFAQSupport

Automate / Whisper

Transcribe meeting recordings with Whisper, locally.

Apple's recogniser for every call, in seconds. whisper.cpp for the ones that matter, in minutes. Both files stay on the Mac.

RecordCue's transcript comes from Apple's on-device speech recognition: instant, free, private, and rough on jargon, names and calls that switch language. For the recordings that deserve better, run the same .m4a or .mp4 through whisper.cpp with the large-v3-turbo model, still entirely on the Mac, and a second transcript appears beside the first, minutes later for an hour-long call. This page does that three ways: once by hand on the latest recording, automatically for every finished call through the hook, and as a batch over last month. Nothing is uploaded at any step, and nothing needs the paid version.

What you get

A third file in the recording's folder. RecordCue's own .txt is untouched, and its Mic/System labels still tell you which side said what; the Whisper file has the words right and no labels at all. Roughly:

~/Movies/RecordCue/Google Meet/
  2026-09-08 18.07-19.01 Google Meet.m4a
  2026-09-08 18.07-19.01 Google Meet.txt            RecordCue, on-device, labeled by side
  2026-09-08 18.07-19.01 Google Meet (whisper).txt  whisper.cpp, large-v3-turbo

$ sed -n 41,42p "2026-09-08 18.07-19.01 Google Meet.txt"
[14:02] System: so the cooper netties migration slips to q four unless soren signs off
[14:11] Mic: fine, I'll raise it at the sink on Thursday

$ sed -n 31,32p "2026-09-08 18.07-19.01 Google Meet (whisper).txt"
So the Kubernetes migration slips to Q4 unless Søren signs off.
Fine, I'll raise it at the sync on Thursday.

The Whisper file is named <recording> (whisper).txt on purpose: RecordCue's CLI keeps reporting its own transcript as transcript.path, so every other recipe keeps working unchanged, and two lines point any of them at the better text when it exists.

Prerequisites

brew install whisper-cpp ffmpeg jq
mkdir -p ~/models
curl -L -o ~/models/ggml-large-v3-turbo.bin \
  https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-large-v3-turbo.bin

Apple silicon is assumed; on it, large-v3-turbo transcribes an hour-long call in minutes. Exactly how many depends on the chip, so try it once by hand before deciding whether every call gets it or only the long ones.

Once, by hand, on the latest recording

Ask the CLI where the latest recording is, convert it to the 16 kHz mono WAV that whisper-cli expects, transcribe, delete the WAV. -otxt -of names the output file without its extension, so the result is … (whisper).txt next to the recording.

rc='/Applications/RecordCue.app/Contents/MacOS/RecordCue'
M=$("$rc" --agent-cli latest --json | jq -r '.media.path')

tmp=$(mktemp -d); W="$tmp/audio.wav"
ffmpeg -nostdin -loglevel error -y -i "$M" -vn -ar 16000 -ac 1 "$W"
whisper-cli -m ~/models/ggml-large-v3-turbo.bin -f "$W" -l en \
  -otxt -of "${M%.*} (whisper)"
rm -r "$tmp"

open -R "${M%.*} (whisper).txt"    # or: cat "${M%.*} (whisper).txt"

-l en is explicit because whisper-cli assumes English unless told; -l ja, -l de or -l auto for the rest. Prefer timestamps? Add -osrt and you get a .srt as well.

The action: every recording gets a Whisper transcript

The same steps as an action for the hook runner. It uses only RC_MEDIA, so it does not care whether RecordCue's own transcript exists, and it exits 0 without doing anything when the Whisper file is already there, so re-runs and the batch below are safe. It is numbered 60 so it sorts last: the Slack post and the note have already gone out by the time the minutes of transcription start.

#!/bin/zsh
# ~/.config/recordcue/actions/60-whisper.sh
# Re-transcribe the finished recording with whisper.cpp, on this Mac.
# Writes "<recording> (whisper).txt" beside the media. RecordCue's own
# transcript is not touched, and the app does not know this file exists.
set -u
model="$HOME/models/ggml-large-v3-turbo.bin"
lang=en                             # ja, de, fr… or auto for mixed calls
out="${RC_MEDIA%.*} (whisper)"

[[ -e "$RC_MEDIA" ]] || exit 0      # moved or deleted: nothing to do
[[ -e "$out.txt" ]]  && exit 0      # already done, by hand or by an earlier run
[[ -e "$model" ]]    || { print -u2 -- "60-whisper: no model at $model"; exit 1; }

tmp=$(mktemp -d); trap 'rm -rf "$tmp"' EXIT
ffmpeg -nostdin -loglevel error -y -i "$RC_MEDIA" -vn -ar 16000 -ac 1 \
  "$tmp/audio.wav" || exit 1
whisper-cli -m "$model" -f "$tmp/audio.wav" -l "$lang" -np \
  -otxt -of "$out" >/dev/null
[[ -e "$out.txt" ]]                 # non-zero here means: retry next minute

-nostdin matters: the runner passes the recording's JSON on standard input, and ffmpeg would otherwise read it as keystrokes. A missing model exits 1, so the recording is retried every minute and picked up as soon as the download lands; the reminder is in hooks.log until then.

Test it

chmod +x ~/.config/recordcue/actions/60-whisper.sh

# Run the action by hand on the latest recording. (If the by-hand section
# already made a "(whisper).txt" for it, delete that first or it exits at once.)
export RC_MEDIA=$('/Applications/RecordCue.app/Contents/MacOS/RecordCue' \
  --agent-cli latest --json | jq -r '.media.path')
~/.config/recordcue/actions/60-whisper.sh </dev/null && ls -l "${RC_MEDIA%.*}"*

# Then the real thing: record a call, stop it, and within a few minutes
# of the hook seeing it the file appears. Failures land in:
tail ~/.config/recordcue/hooks.log

Last month, in one go

The action only needs RC_MEDIA, so a loop over the CLI's list is the batch. Recordings that already have a Whisper file are skipped in a fraction of a second. Start it before you leave for the evening; it runs one recording at a time.

caffeinate -i -w $$ &            # keep the Mac awake until this shell exits
rc='/Applications/RecordCue.app/Contents/MacOS/RecordCue'
"$rc" --agent-cli list --since 30d --json |
  jq -r '.recordings[] | [.media.path, ((.durationMs // 0) / 60000 | round)] | @tsv' |
  while IFS=$'\t' read -r m min; do
    RC_MEDIA="$m" RC_MINUTES="$min" ~/.config/recordcue/actions/60-whisper.sh </dev/null \
      && echo "done    ${m##*/}" || echo "FAILED  ${m##*/}"
  done

RC_MINUTES is passed along so the long-calls-only line below applies to the batch too.

Variations

Only the long calls

A ten-minute check-in rarely needs a second transcript. One line near the top of the action, before the model check:

[[ $RC_MINUTES -ge 20 ]] || exit 0   # add near the top of 60-whisper.sh

Non-English and mixed calls

Set lang=ja (or de, fr, ko, zh…) in the action for calls in one language, or lang=auto to let the model detect it from the first seconds. This is where the difference from the on-device transcript is largest; Apple's recogniser is set to one language per recording and does not switch mid-call.

Use it from the other actions

Nothing in RecordCue reads the Whisper file, and RC_TRANSCRIPT keeps pointing at RecordCue's own text. Any action that should prefer the better transcript when it exists needs two lines:

W="${RC_MEDIA%.*} (whisper).txt"
[[ -e $W ]] && T=$W || T=$RC_TRANSCRIPT   # then use "$T" where you used "$RC_TRANSCRIPT"

For that to find the file, Whisper has to run first: renumber it 15-whisper.sh and accept that the Slack summary now arrives after the transcription, minutes rather than seconds after the call.

A smaller model, or a window

Swap the -m path for ggml-medium.bin or ggml-small.bin when speed matters more than the last few names. If you would rather not touch a terminal at all, MacWhisper does the same job on the same files: drop the .m4a from ~/Movies/RecordCue onto it. It is also local, and also produces no Mic/System labels.

Or ask your agent to do it

With the Agent Skill installed, Claude Code or Codex can find media.path itself, run the same four commands, and read the result. The transcription stays on the Mac; the reading is the moment the text reaches the model you chose, under your own account, because you asked.

Transcribe my latest RecordCue recording with whisper.cpp (large-v3-turbo, locally, saved as "<recording> (whisper).txt" beside the file) and give me the decisions and action items from it.

Have your agent set it up

Paste into Claude Code, Codex or any local agent that can fetch a page and run a shell on this Mac.

Read https://www.recordcue.app/automate/whisper/ and set it up on this Mac exactly as that page describes. Show me every file before writing it, then run the test and show me the result.

Questions

Can whisper.cpp transcribe an .m4a meeting recording directly?
whisper-cli wants 16 kHz mono WAV, so the dependable route is a one-line ffmpeg conversion into a temporary file first, which is what every script on this page does. It works the same for RecordCue's .mp4 screen recordings; ffmpeg takes the audio track and drops the video. The WAV is deleted afterwards and the original is never modified.
Is Whisper better than the macOS transcription RecordCue uses?
On jargon, names, accents and calls that switch language, a large Whisper model is usually clearly better. It is also much slower: Apple's on-device recogniser finishes about when the call does, while large-v3-turbo takes minutes for an hour-long call. And Whisper does not know which side was talking, so its transcript has no Mic and System labels. Keep both files; they answer different questions.
Does the audio leave my Mac?
No. whisper.cpp runs on the Mac, the model is a file you downloaded once, and RecordCue makes no network requests of its own. The only moment anything leaves is if you then hand the transcript to a cloud-backed agent for a summary, in a line you wrote. MacWhisper is the same story with a window instead of a terminal.
Which Whisper model should I download?
ggml-large-v3-turbo is the sensible default: close to large-v3 in accuracy and much faster. ggml-medium and ggml-small are smaller downloads and quicker still, at some cost in accuracy; small is fine for a quick English draft. All of them come from the ggerganov/whisper.cpp repository on Hugging Face and drop into the same command with a different -m path.