aux4/whisper

Transcribe audio files to text from the command line, with the backend of your choice: whisper.cpp or mlx-whisper running on your own machine, the OpenAI API, or any OpenAI-compatible endpoint such as Groq or a self-hosted Whisper server. Every provider takes the same options and produces the same output (plain text, JSON, SRT or WebVTT), so switching backends is a config change.

Installation

aux4 aux4 pkger install aux4/whisper

Requirements depend on the provider you use:

  • local - whisper.cpp (brew install whisper-cpp) and a ggml model file.
  • mlx - Apple Silicon and pip install mlx-whisper.
  • openai - an OpenAI API key.
  • openai-compatible - the base URL of the server (and its API key, if it needs one).
  • Audio conversion comes from the aux4/audio package, installed automatically as a dependency (it brings ffmpeg). The local provider uses it to turn any input into 16 kHz mono wav, and mlx-whisper needs its ffmpeg to decode audio.

Quick Start

export OPENAI_API_KEY=sk-...
aux4 whisper transcribe meeting.mp3
Welcome everyone, let's start with the quarterly numbers.

Check which providers are usable on this machine:

aux4 whisper providers
local              unavailable  whisper-cli not found
mlx                unavailable  mlx_whisper not found (pip install mlx-whisper)
openai             ready        API key set, model whisper-1
openai-compatible  unavailable  baseUrl not set

Configuration

All options can live in a config.yaml section and be selected with --config <name> (use --configFile <file> for a different file). Flags always override config values. A section per backend makes switching a one-word change:

config:
  local:
    provider: local
    binary: /opt/homebrew/bin/whisper-cli          # default: whisper-cli (from PATH)
    model: /Users/sally/models/ggml-large-v3-turbo.bin
    language: en                                    # default: auto-detect

  mlx:
    provider: mlx
    binary: mlx_whisper                             # default: mlx_whisper, else the Python module
    model: mlx-community/whisper-large-v3-turbo     # default

  openai:
    provider: openai
    apiKey: secret://1password/Work/openai/credential  # default: OPENAI_API_KEY
    model: whisper-1                                # default: whisper-1
    format: srt                                     # default: text

  groq:
    provider: openai-compatible
    baseUrl: https://api.groq.com/openai/v1
    apiKey: secret://1password/Work/groq/credential
    model: whisper-large-v3-turbo                   # default: whisper-1
    maxUploadSize: 25MB                             # default: no limit for openai-compatible

  speaches:
    provider: openai-compatible
    baseUrl: http://localhost:8000/v1
    model: Systran/faster-whisper-small
    prompt: aux4, whisper.cpp, mlx
aux4 whisper transcribe standup.m4a --config local
aux4 whisper transcribe standup.m4a --config groq --format vtt

| Option | Description | Default | |--------|-------------|---------| | provider | local, mlx, openai or openai-compatible | openai | | model | Model name; for local, the path to a ggml model file | provider default | | language | Spoken language (ISO-639-1 code) | auto-detect | | format | text, json, srt or vtt | text | | prompt | Hint text for names, jargon and spelling | none | | apiKey | API key for openai / openai-compatible | OPENAI_API_KEY (openai only) | | baseUrl | API base URL | https://api.openai.com/v1 (openai) | | binary | Provider binary | whisper-cli (local), mlx_whisper (mlx) | | extension | Audio type for stdin or files without a reliable extension | file extension, then content detection | | maxUploadSize | Upload size limit (25MB, 500KB, bytes; 1 MB = 1024 KB) | 25MB (openai, also the maximum), none (openai-compatible) |

Note: keep API keys in config.yaml (plain or as secret:// references, which aux4 resolves through your secret provider) or in OPENAI_API_KEY. A key passed with --apiKey ends up in your shell history and process list.

Commands

whisper transcribe

aux4 whisper transcribe [<file>] [--extension <type>] [--provider <name>] [--model <model>] [--language <code>] [--format <text|json|srt|vtt>] [--prompt <text>] [--config <name>]

Transcribes one audio file, or audio piped to stdin when no file is given, and prints the result to stdout. Supported input: flac, m4a, mp3, mp4, mpeg, mpga, oga, ogg, wav, webm.

Browser recordings and uploads

Recordings made with the browser MediaRecorder API work as they are: Chrome, Edge and Firefox produce webm (or ogg) with Opus, Safari produces mp4 with AAC. The audio type is taken from --extension when given, otherwise from the file extension, otherwise detected from the content, so uploads saved to a temporary file without a reliable extension still work:

aux4 whisper transcribe /tmp/upload-7f3a --extension webm --config local
cat /tmp/upload-7f3a | aux4 whisper transcribe --config groq
Please call me back after lunch.

Output formats

  • text - the transcript as plain text.
  • json - the transcript, detected language, duration and timed segments:
{
  "text": "Hello from the team. This is the weekly update.",
  "language": "english",
  "duration": 4.2,
  "segments": [
    {
      "start": 0,
      "end": 2,
      "text": "Hello from the team."
    },
    {
      "start": 2,
      "end": 4.2,
      "text": "This is the weekly update."
    }
  ]
}
  • srt / vtt - subtitles built from the timed segments:
aux4 whisper transcribe interview.mp3 --format srt > interview.srt
1
00:00:00,000 --> 00:00:02,000
Hello from the team.

2
00:00:02,000 --> 00:00:04,200
This is the weekly update.

whisper stream

aux4 whisper stream [--input <pcm|wav>] [--provider <name>] [--config <name>] [--silenceMs <ms>] [--maxPhraseSeconds <seconds>] [--partialInterval <ms>] [--threshold <dBFS>]

Live, phrase-by-phrase transcription. Pipe 16 kHz mono 16-bit PCM into stdin (the format a browser AudioWorklet sends) and read one JSON event per line from stdout:

ffmpeg -loglevel error -f avfoundation -i ":0" -ar 16000 -ac 1 -f s16le - | aux4 whisper stream --config local
{"type":"ready"}
{"type":"partial","id":1,"text":"Can you hear","start":0.7,"end":1.74}
{"type":"final","id":1,"text":"Can you hear me now?","start":0.7,"end":2.2,"segments":[{"start":0.7,"end":2.2,"text":"Can you hear me now?"}]}
{"type":"end"}
  • Speech is split into phrases by loudness: a phrase ends after --silenceMs of silence (default 600) or is cut at --maxPhraseSeconds (default 25). Very short sounds and silence are ignored, and a short pre-roll keeps the first word.
  • While someone is speaking, partial events re-transcribe the phrase so far every --partialInterval milliseconds of audio (default 1000, 0 turns them off). Partials are skipped while a transcription is still running, so a slow provider never falls behind.
  • When the phrase ends, a final event with the same id replaces the partials. Finals always arrive, in order, and each request gets the previous final as context after your --prompt.
  • Times are seconds from the start of the stream. Transcription failures become error events and the stream continues; configuration problems stop the command before it starts.
  • At end of input, SIGINT or SIGTERM, the phrase in progress is finalized, an end event is sent and temporary files are removed.
  • Use --input wav to read a WAV stream, for example straight from aux4/audio:
aux4 audio speech-prep --format wav < meeting.webm | aux4 whisper stream --input wav --config groq

All providers and config.yaml sections work as with transcribe. See aux4 aux4 man whisper stream for the full event schema.

whisper providers

aux4 whisper providers [--provider <name>] [--config <name>] [--format <text|json>]

Lists every provider with ready or unavailable and the reason (binary found, model set, API key set, base URL set). Use --config <name> or --provider with --model, --binary, --baseUrl and --apiKey to check a specific setup. API keys are never printed.

Providers

local (whisper.cpp)

Runs fully offline with whisper.cpp. Download a ggml model (for example ggml-base.en.bin or ggml-large-v3-turbo.bin) and point model at it:

aux4 whisper transcribe call.mp3 --provider local --model ~/models/ggml-base.en.bin

Every input is converted to 16 kHz mono wav with aux4 audio speech-prep before transcription. If the aux4/audio package is missing, the command fails with the install command.

mlx (mlx-whisper)

Fast local transcription on Apple Silicon with mlx-whisper. Models are Hugging Face repositories and are downloaded on first use:

aux4 whisper transcribe call.mp3 --provider mlx --model mlx-community/whisper-large-v3-turbo

If the mlx_whisper command is not in PATH, the package uses python3 when it can import the mlx_whisper module. mlx-whisper decodes audio with the ffmpeg installed by aux4/audio.

openai

The OpenAI audio transcriptions API. Models: whisper-1 (default, supports every output format), gpt-4o-transcribe and gpt-4o-mini-transcribe (text and json only, since they do not return timestamps).

aux4 whisper transcribe call.mp3 --provider openai --model gpt-4o-transcribe

The API accepts uploads up to 25 MB; bigger files are handled automatically, see Large files.

openai-compatible

Any server that implements the OpenAI /audio/transcriptions endpoint, such as Groq or a self-hosted Whisper server. baseUrl is required and apiKey is optional:

aux4 whisper transcribe call.mp3 --provider openai-compatible --baseUrl http://localhost:8000/v1 --model Systran/faster-whisper-small

Large files

The OpenAI API accepts uploads up to 25 MB. When a file is bigger, transcribe does not refuse it:

  1. It compresses the audio to a small mono 16 kHz mp3 sized to fit (24 MB for the OpenAI limit). Speech survives low bitrates well, so this alone covers recordings of several hours.
  2. If the audio is too long to fit even at the lowest bitrate, it is split at pauses (or into fixed-length parts when there are no pauses), and the parts are grouped into as few chunks as fit the limit.
  3. The chunks are transcribed in order. Each request gets the end of the previous chunk's transcript as context (after your own --prompt), so words and names stay consistent across chunk boundaries.
  4. The results are merged into one transcript: text is joined, segment timestamps are shifted to their position in the original audio, and json, srt and vtt output look exactly like a single request.

Temporary files are always removed, also when a request fails. The same applies to the openai-compatible provider when you set maxUploadSize (for example 25MB for Groq's free tier); without it there is no limit. gpt-4o-transcribe and gpt-4o-mini-transcribe also limit the audio length, so audio longer than 1400 seconds is split into chunks for them even when it is small enough. The local and mlx providers have no limit and never split.

aux4 whisper transcribe all-hands-3h.m4a --format srt > all-hands.srt
aux4 whisper transcribe all-hands-3h.m4a --config groq --maxUploadSize 25MB

Environment Variables

| Variable | Description | |----------|-------------| | OPENAI_API_KEY | API key for the openai provider when apiKey is not set |