Fast local transcription for large lectures with NVIDIA Parakeet ONNX
Project description
fast-transcript
fast-transcript is a local lecture transcription CLI built to beat the usual Apple Silicon tradeoff: either fast but flaky, or accurate but painfully slow.
On the development machine, this project handled 30 minutes in 2!* while staying around 2.51 GB RSS on the long run. In the same local test set, it beat mlx-whisper, insanely-fast-whisper, and parakeet-mlx.
* Benchmark run on a MacBook Pro M1. The exact long-run measurement was 29m47s of Portuguese lecture audio transcribed in about 2m14s (13.38x real-time).
The CLI binary is called fscript:
fscript lecture.mp3
fscript lecture.mp3 notes/
fscript lecture.mp3 --text
fscript lecture.mp3 --text=plain
fscript lecture.mp3 --raw
fscript lecture.mp3 --srt
fscript lecture.mp3 --vtt
fscript lecture.mp3 --json
fscript lecture.mp3 --diarize lseend-dihard3
fscript lecture.mp3 -D --json --raw
fscript lecture.mp3 -n 2
--srt and --vtt subtitle output are experimental.
That is the whole point of this project. One command. Large audio. No babysitting.
Why this exists
I wanted a tool for transcribing long classes and lectures quickly on a laptop while still using the computer for normal work.
The existing options I tested had clear problems for this use case:
insanely-fast-whisperwas far too slow on this Mac once it fell back to CPUmlx-whisperwas solid, but slower than I wanted for long lecture workflowsparakeet-mlxhad excellent memory numbers, but drifted into English on longer Portuguese segments unless heavily tuned
fast-transcript packages the ONNX Parakeet path that held up best in practice.
What it does
- downloads the default Parakeet TDT 0.6B v3 int8 model automatically if it is missing
- stores the extracted model in a persistent per-user application data directory
- keeps the downloaded tarball in the user cache directory
- accepts local audio/video files in formats supported by
ffmpeg - accepts remote
http(s)video/audio URLs supported byyt-dlp - prefers platform-provided manual subtitles for remote URLs when available
- falls back to downloading remote audio and transcribing locally when only auto-captions exist or no captions exist
- auto-converts unsupported audio to 16 kHz mono PCM16 WAV
- uses 120s chunks with 2s overlap by default
- runs local speaker diarization by default via
fluidaudiocli process --mode offline - writes
<input>.speakers.txtnext to the input unless you choose a different output path - can alternatively write raw transcript text via
--text, with timestamps on by default and--text=plainas the opt-out - can alternatively write experimental subtitle files via
--srtor--vtt - can alternatively write speaker-aware text via
--speakers, defaulting toHH:MM:SS - SPEAKER_01: ... - cleans pathological repeated-word runs such as
we we we weintowe... weby default, with--rawas the opt-out - stays quiet by default: concise progress in the terminal, transcript JSON on disk
- shows a spinner and chunk progress bar on interactive terminals
Install
Requirements
ffmpegffprobeyt-dlpfor remote URLs, oruvx yt-dlpfluidaudioclionPATHif you want speaker-aware diarization- when it is missing,
fscriptwarns and continues without diarization - use
-d,--diarize, or--diarize lseend-dihard3to require diarization explicitly
- when it is missing,
Install with Homebrew
brew tap brenorb/tap
brew install fast-transcript
On Apple Silicon macOS, the tap also installs fluidaudio-cli, so the default speaker-aware mode works out of the box.
If you prefer the fully-qualified formula name:
brew install brenorb/tap/fast-transcript
If you previously used the old tap name, migrate with:
brew uninstall brenorb/fast-transcript/fast-transcript
brew untap brenorb/fast-transcript
brew tap brenorb/tap
brew install fast-transcript
On Apple Silicon macOS, Homebrew now installs fast-transcript from a proper bottle.
On Linux x86_64, the formula still installs from the published release binary.
PyPI / uv
The PyPI package name for this project is fscript so the target UX is:
uvx fscript lecture.mp3
uv tool install fscript
The repo already includes platform wheel builds for:
- macOS arm64
- Linux x86_64
PyPI publishing is currently enabled for:
- macOS arm64
See docs/pypi-publishing.md for the release workflow details.
Install a prebuilt binary directly
Download the archive for your platform from the GitHub Releases page, then put fscript on your PATH.
Build from source
cargo install --git https://github.com/brenorb/fast-transcript
Or from a local clone:
cargo install --path .
On macOS, the build now auto-detects the active Xcode or Command Line Tools Clang runtime directories so cargo test keeps linking even if your Rust toolchain points at a stale libclang_rt.osx path.
Development
For local development, install the checked-in git hooks once per clone:
scripts/install-git-hooks.sh
The pre-commit hook blocks Rust commits that would fail cargo fmt --check in CI.
Quick start
fscript lecture.mp3
fscript https://www.youtube.com/watch?v=QSdh8Gj0mEg
This will:
- ensure the default model exists
- normalize the audio if needed
- transcribe with the default chunking strategy
- diarize with the default
coremlbackend - write
lecture.speakers.txt - print the final absolute transcript path to
stdout
For remote URLs, the default speaker-aware flow is:
- inspect the URL with
yt-dlp - download the remote audio
- run the normal local transcription + diarization pipeline
If you switch to -D or --no-diarization, fscript can still use platform-provided manual subtitles directly when they are available unless you also force --local.
Usage
fscript <media-or-url> [output-path]
fscript <media-or-url> -o output-path
fscript <media-or-url> --stdout
fscript <media-or-url> -
fscript <media-or-url> --speakers
fscript <media-or-url> --speakers=plain
fscript <media-or-url> --speakers=timestamps
fscript <media-or-url> --text
fscript <media-or-url> --text=plain
fscript <media-or-url> --raw
fscript <media-or-url> --json
fscript <media-or-url> --srt
fscript <media-or-url> --vtt
fscript <media-or-url> --diarize lseend-dihard3
fscript <media-or-url> -D --json --raw
fscript <media-or-url> -n 2
fscript --version
<media-or-url> can be a local audio file, a local video file, or a supported remote media URL.
When fscript writes the transcript to a file, it keeps progress and human-readable status on stderr and prints only the final absolute transcript path on stdout.
That makes it easy to compose in shell scripts:
out=$(fscript lecture.mp3)
open "$out"
If the explicit output-path already exists as a directory, fscript writes the default filename for the chosen mode inside that directory.
Optional overrides:
fscript lecture.wav custom-output.txt
fscript lecture.wav exports/
fscript lecture.wav -o custom-output.txt
fscript lecture.wav --stdout
fscript lecture.wav --speakers
fscript lecture.wav --speakers=plain
fscript lecture.wav --speakers=timestamps
fscript lecture.wav --text
fscript lecture.wav --text=plain
fscript lecture.wav --raw
fscript lecture.wav --json
fscript lecture.wav --srt
fscript lecture.wav --vtt
fscript lecture.wav --diarize lseend-dihard3
fscript lecture.wav -D --json --raw
fscript lecture.wav -n 2
fscript lecture.wav --chunk 180 --overlap 3
fscript lecture.wav --chunk 0
fscript lecture.wav --model-dir ./models/parakeet/custom-copy
fscript lecture.wav --model-package ./models/parakeet-v3-int8.tar.gz
fscript lecture.wav --model-url https://example.com/parakeet-v3-int8.tar.gz
fscript https://www.youtube.com/watch?v=QSdh8Gj0mEg
fscript https://www.youtube.com/watch?v=QSdh8Gj0mEg --local
Raw text output modes:
--text: transcript text with segment timestamps, one line per segment withHH:MM:SS - ...--text=plain: transcript text without timestamps or speaker labels- when
--textis active and you do not pass an explicit output path, the default file becomes<input>.transcript.txt
Cleaning mode:
- cleaning is on by default and affects only the output being written for that invocation
--raw: disables output cleaning for that invocation- it applies to JSON, speakers, text, SRT, and VTT outputs
- it is intentionally conservative and leaves ordinary repetition alone
Subtitle output modes:
--srt: experimental SubRip subtitle file--vtt: experimental WebVTT subtitle file- subtitle output is still experimental and may change
- if diarization is active, subtitle cues include normalized speaker labels such as
SPEAKER_01: ... - when
--srtis active and you do not pass an explicit output path, the default file becomes<input>.srt - when
--vttis active and you do not pass an explicit output path, the default file becomes<input>.vtt
Speaker-aware output modes:
--speakers: speaker-aware output with timestamps, for example00:12:34 - SPEAKER_01: ...--speakers=timestamps: explicit alias for the default speaker-aware timestamped output--speakers=plain: speaker-aware output without timestamps, for exampleSPEAKER_01: ...- if diarization is disabled or a segment has no speaker label, the line falls back to plain segment text without an
UNKNOWN:prefix - when no output mode is passed,
--speakersis the default - when
--speakersis active and you do not pass an explicit output path, the default file becomes<input>.speakers.txt
Environment overrides:
FSCRIPT_MODEL_DIRFSCRIPT_MODEL_PACKAGEFSCRIPT_MODEL_URLFSCRIPT_DIARIZATION_BINARY
Optional diarization
fscript automatically enables speaker diarization when fluidaudiocli is available.
If the helper is missing and you did not request diarization explicitly, fscript falls back to plain transcript segments after printing a warning on stderr.
By default, it:
- runs the normal Parakeet ASR flow first
- releases the ASR model
- runs a separate
fluidaudioclidiarization subprocess - merges diarization windows into ASR segments by temporal overlap
Modes:
-d/--diarize: enable diarization explicitly with the defaultcoremlmodel-d coreml/--diarize coreml:FluidInference/speaker-diarization-coremlpath viafluidaudiocli process --mode offline-d lseend-dihard3/--diarize lseend-dihard3: alternateFluidInference/ls-eend-coremlDIHARD III path viafluidaudiocli lseend --variant dihard3- defaults to
--threshold 0.3
- defaults to
-D/--no-diarization: skip diarization entirely--backend=coreml|lseend-dihard3|none: legacy alias kept for backwards compatibility
Controls:
-n N/--num-speakers Nis forwarded only to the defaultcoremlbackend-t N/--threshold Noverrides the default diarization threshold forlseend-dihard3lseend-dihard3does not support--num-speakers; use the default threshold or override it with-t/--threshold
If you explicitly request -d, --diarize, or a concrete diarization model and fluidaudiocli is missing, fscript returns a clear backend error instead of silently falling back.
Defaults
- model dir:
- macOS:
~/Library/Application Support/fast-transcript/models/parakeet-tdt-0.6b-v3-int8 - Linux:
~/.local/share/fast-transcript/models/parakeet-tdt-0.6b-v3-int8
- macOS:
- model package cache:
- macOS:
~/Library/Caches/fast-transcript/parakeet-v3-int8.tar.gz - Linux:
~/.cache/fast-transcript/parakeet-v3-int8.tar.gz
- macOS:
- model URL:
https://huggingface.co/brenorb/parakeet-tdt-0.6b-v3-int8-onnx-bundle/resolve/main/parakeet-v3-int8.tar.gz?download=1 - chunk seconds:
120 - chunk overlap seconds:
2 - default diarization mode:
coremlwhenfluidaudiocliis available - cleaning: on
- default output path:
<input>.speakers.txt - output path with
--json:<input>.transcript.json - output path with
--text:<input>.transcript.txt - output path with
--srt:<input>.srt - output path with
--vtt:<input>.vtt - output path with
--speakers:<input>.speakers.txt
Benchmarks
These are local development benchmarks, not universal claims. They were run on the same Apple Silicon Mac used during development, using a Portuguese lecture clip and the same broader workflow comparison.
2-minute lecture clip
| Engine | Setup | Speed | Peak RSS | Notes |
|---|---|---|---|---|
| fast-transcript | Parakeet ONNX | 13.06x real-time | 2.25 GB | Best balance of speed and reliability |
mlx-whisper |
whisper-large-v3-turbo |
5.25x |
1.70 GB |
Good quality, slower |
parakeet-mlx |
tuned for quality | 4.92x |
1.29 GB |
Needed substantial tuning |
parakeet-mlx |
raw greedy | 10.16x |
0.57 GB |
Faster on short audio, drifted into English on longer PT-BR |
insanely-fast-whisper |
whisper-large-v3 CPU |
0.30x |
6.18 GB |
Accurate, but too slow here |
insanely-fast-whisper |
MPS + fallback | 0.31x |
3.04 GB |
Small gain, same general problem |
Long lecture run
| Engine | Audio | Speed | Peak RSS | Notes |
|---|---|---|---|---|
| fast-transcript | 29m47s lecture |
13.38x real-time | 2.51 GB | Stable long run with default chunking |
Practical reading
fast-transcriptwas not the absolute fastest thing we saw in every synthetic case- it was the best result once long Portuguese lecture audio, transcript quality, and unattended runs all mattered at the same time
- that is the target workload for this repo
Output format
Default output is speaker-aware text and includes:
- segment timestamps
- speaker labels when diarization returns them
- cleaned repeated-word runs unless you pass
--raw
JSON output via --json includes:
- merged transcript text
- model path
- original input path
- prepared WAV path
- whether a remote URL used manual subtitles or the local model
- whether
ffmpegnormalization was used - load time
- transcribe time
- chunk configuration
- per-chunk timing
- transcript
segments - optional
speaker_diarizationmetadata
When diarization is enabled, each transcript segment may include:
speaker
Alternative output modes:
--speakers: speaker-aware text with timestamps--speakers=timestamps: explicit speaker-aware text with timestamps--speakers=plain: speaker-aware text without timestamps--text: transcript text with segment timestamps--text=plain: transcript text without timestamps--json: structured JSON benchmark/transcript payload--srt: experimental subtitle file--vtt: experimental subtitle file
Motivation
This project is optimized for large lectures and classes, including files in the 30-minute to 2-hour range, where:
- startup friction matters
- background CPU usage matters
- memory spikes matter
- brittle hand-tuned command lines become a tax
The design goal is not “highest benchmark on a cherry-picked GPU server”. The goal is “transcribe big local lecture audio fast enough that you actually keep using it”.
Inspiration
This project was heavily informed by:
In particular, the ONNX Parakeet path here was shaped by the packaging and implementation ideas used in Handy and GLaDOS.
Default model bundle
The default auto-download bundle is published in our own Hugging Face model repository:
This keeps the default install path tied to the exact validated tarball instead of an app-specific blob host.
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distributions
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file fscript-1.0.3-py3-none-macosx_10_13_universal2.whl.
File metadata
- Download URL: fscript-1.0.3-py3-none-macosx_10_13_universal2.whl
- Upload date:
- Size: 9.8 MB
- Tags: Python 3, macOS 10.13+ universal2 (ARM64, x86-64)
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3741f6f66383f066e39c55592ebde68b0039586d67febd01fe40754944df3b90
|
|
| MD5 |
90e644dce9f5bdc7e0cd839953f2dd79
|
|
| BLAKE2b-256 |
8ee9a980cff4b9d764336ef8f4cb0d8cb67a008efc7fc0f9a581e5a4d70388ca
|
Provenance
The following attestation bundles were made for fscript-1.0.3-py3-none-macosx_10_13_universal2.whl:
Publisher:
release.yml on brenorb/fast-transcript
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
fscript-1.0.3-py3-none-macosx_10_13_universal2.whl -
Subject digest:
3741f6f66383f066e39c55592ebde68b0039586d67febd01fe40754944df3b90 - Sigstore transparency entry: 1839312394
- Sigstore integration time:
-
Permalink:
brenorb/fast-transcript@4a31eb2b92bffaf1d8cdb82eadb602e53eda7a60 -
Branch / Tag:
refs/tags/v1.0.3 - Owner: https://github.com/brenorb
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@4a31eb2b92bffaf1d8cdb82eadb602e53eda7a60 -
Trigger Event:
push
-
Statement type: