omni-transcriber
Transcribe anything from the command line: a YouTube link, an Instagram reel, any yt-dlp-supported URL, a local video or audio file, or a whole folder of media. Transcription runs locally via faster-whisper — no API key, no audio leaves your machine.
$ transcriber https://youtu.be/jNQXAC9IVRw
Alright, so here we are, one of the elephants...
$ transcriber lecture.mp4 -f srt -o lecture.srt
$ transcriber ~/podcasts -r -m turbo -o transcripts/
Install
Needs Python 3.10 or newer. uv is the smoothest route, but pipx and pip work too.
$ uv tool install omni-transcriber # or: pipx install omni-transcriber
$ uvx --from omni-transcriber ots --help # or run it without installing
That installs two commands for the same tool: transcriber and the shorter
ots. Use whichever you prefer.
From source:
$ git clone https://github.com/AyushShah2/omni-transcriber
$ cd omni-transcriber && uv sync && uv run transcriber --help
With NVIDIA GPU acceleration:
$ uv tool install "omni-transcriber[gpu]"
| Install | Download size | Notes |
|---|---|---|
| default (CPU) | ~450 MB | Works everywhere. Fine for short clips. |
[gpu] extra |
~2.7 GB | Adds the CUDA cuBLAS/cuDNN wheels. NVIDIA only. |
The [gpu] extra is never required. On a machine with an NVIDIA card but no
[gpu] install, --device cuda prints a warning and falls back to the CPU
rather than failing.
Optional companions
- A JavaScript runtime — YouTube increasingly needs one. Deno is
yt-dlp's preferred runtime; Node.js also works.
transcriberenables whichever it finds on yourPATHautomatically. Without one, some videos lose formats or fail outright. - ffmpeg — not used for transcription (the bundled PyAV decoder reads audio straight out of any container). yt-dlp needs it only for a few stream types; if it is missing when that happens, a static build is fetched automatically.
Usage
transcriber INPUTS... files, folders, and/or URLs — mix freely
ots INPUTS... the same command, less typing
Both names are installed and do exactly the same thing; every example below
works with either. (If you also use OpenTimestamps, which ships its own
ots command, one will shadow the other on your PATH — transcriber always
works.)
| Flag | Meaning |
|---|---|
-f, --format |
txt (default), srt, vtt, json, md |
-t, --timestamps |
Timestamps in txt/md (srt/vtt always have them) |
-m, --model |
Whisper model — see the table below (default small) |
-o, --output |
A file for one input, or a directory (-o out/) for many |
-l, --language |
Force a language (en, fr, ...); default is auto-detect |
-r, --recursive |
Recurse into subfolders |
--device |
auto (default), cpu, cuda |
--compute-type |
auto (default), int8, float16, int8_float16 |
--batch-size N |
Decoding batch size; default 8 on CUDA, sequential on CPU |
--no-vad |
Disable voice-activity filtering (keeps silence) |
--playlist / --limit N |
Expand a playlist link; cap how many entries |
--cookies-from-browser / --cookies |
Authenticate for private/login-walled media |
--keep-audio |
Keep the downloaded audio next to the transcript |
--overwrite |
Re-transcribe items that already have a transcript |
--clean |
Drop hallucinated repeats, [Music] tags, stutters, English fillers |
--llm |
LLM-ready markdown: metadata header + timestamped paragraphs; implies --clean |
--screen |
OCR what is shown on screen into timestamped snapshots (needs [screen]) |
-j, --jobs N |
Fetch N inputs in parallel while one transcribes; playlists split per entry |
-v, --verbose |
yt-dlp and engine detail on stderr |
--engine |
Transcription backend (local; cloud backends may follow) |
--version |
Print the version and exit |
Where output goes
- One input, no
-o→ the transcript goes to stdout, so it pipes:transcriber talk.mp4 | wc -w. Progress bars and warnings all go to stderr. -o FILE→ that exact file.- Several inputs, a folder, a playlist, or
-o DIR/→ one<title>.<format>file per item.
Playlists and channels
A link that is only a playlist or a channel expands to every video in it, one transcript file each:
$ transcriber "https://www.youtube.com/playlist?list=PL..." -o transcripts/
$ transcriber "https://www.youtube.com/@someone/videos" --limit 10 -o transcripts/
A link to a single video that also carries a &list= parameter is treated as
just that one video, which is almost always what you meant. Pass --playlist
when you want the whole list:
| Link | Default | With --playlist |
|---|---|---|
youtube.com/playlist?list=PL... |
whole playlist | whole playlist |
youtube.com/@name/videos |
every video | every video |
youtube.com/watch?v=X&list=PL... |
just video X |
whole playlist |
--limit N caps how many entries are fetched — worth using before you point it
at a 200-video channel. If one entry is private, deleted, or region-locked, it
is skipped with a warning saying why, the rest still run, and the closing
summary lists it as skipped. Unlisted videos need nothing special — having the
link is enough. Private videos your own account can watch are reachable with
--cookies-from-browser firefox.
Duplicates are skipped automatically: the same link or file given twice, a video listed twice in one playlist, or the same video reached through two different inputs all transcribe exactly once.
When the network is your bottleneck, add --jobs:
$ transcriber "https://www.youtube.com/playlist?list=PL..." -j 4 -m large-v3 -o out/
-j N turns a playlist into independent per-entry downloads, keeps up to N
of them running while a single worker transcribes (one loaded model — your GPU
never waits on the network), pauses downloading when a couple of finished
files are already queued so a fast connection cannot fill your disk, and waits
for free memory rather than crashing when the system is busy. Private or
deleted entries are skipped exactly as in sequential runs.
Feeding transcripts to an LLM
$ transcriber "https://youtu.be/xyz" --llm -o notes/
--llm produces markdown built for pasting into Claude, ChatGPT, or a RAG
pipeline: a metadata header (title, source, uploader, duration, language), then
the transcript in short paragraphs that each open with their [mm:ss]
timestamp — so the model can cite and deep-link any passage, and chunking
tools never split mid-thought. It implies --clean, which drops the noise
that trips models up: hallucinated repeated lines, [Music]/(applause)
tags, three-plus stutters, and English hesitation sounds (um, uh). Cleanup
never rewrites wording — everything else is exactly what was said. Use
--clean on its own to tidy any other format the same way.
Seeing the screen too (--screen)
When a speaker says "this function calls that object", the words alone are
useless — the meaning is on their screen. --screen reads it: video keyframes
are sampled, frames that actually changed are OCR'd locally (PP-OCR models on
ONNX Runtime — no cloud, no PyTorch), and each distinct screen state becomes a
timestamped snapshot woven into the --llm markdown right before the words
spoken over it:
**[On screen 12:26]**
```
class Portfolio:
def rebalance(self, weights):
```
[12:28] So this needs to have a function which calls that object...
It reads text — code, slides, terminals, docs. Pictures and diagrams without words stay opaque. Repeated content is emitted once: static IDE chrome appears in the first snapshot only, and scrolling through a file yields each line once.
$ uv tool install "omni-transcriber[screen]" # or [gpu,screen] for both
$ transcriber lecture.mp4 --screen -o notes/
$ transcriber "https://youtu.be/xyz" --screen -o notes/ # downloads video too
For links, --screen additionally downloads a video-only stream (up to 720p,
where on-screen code stays legible) — roughly 150–300 MB per half hour, which
is why it is never the default. In -f json output the snapshots appear as a
screen array instead.
Everything above works for any site yt-dlp supports, not just YouTube.
Existing transcripts are skipped with a notice rather than overwritten, so
re-running an interrupted batch resumes it for free. Pass --overwrite to redo
them. If two items in the same run would produce the same filename — two videos
with identical titles, or a/interview.mp4 and b/interview.mp4 — the later
ones get (2), (3) suffixes so no transcript is lost. A batch keeps going when one item fails and prints a summary at the end;
the exit code is 1 if anything failed.
Models
You never download or place a model by hand. The first time you use one it is fetched automatically and cached; every run after that reuses it. There is no models folder to manage and nothing to put anywhere.
The cache lives at ~/.cache/huggingface/hub on Linux and macOS, and at
C:\Users\<you>\.cache\huggingface\hub on Windows.
| Model | Download | Good for |
|---|---|---|
tiny |
75 MB | Quick drafts, testing |
base |
145 MB | Clear speech, low resources |
small |
484 MB | Default — the accuracy/speed sweet spot on CPU |
medium |
1.5 GB | Better accuracy, noticeably slower on CPU |
large-v3 |
3.1 GB | Best accuracy. GPU strongly recommended |
turbo |
1.6 GB | Near-large-v3 accuracy, much faster. Best GPU pick |
distil-large-v3 |
1.5 GB | Distilled, English-only, very fast |
Add .en to tiny/base/small/medium for slightly better English-only
accuracy.
Speed: on an RTX 5070 Ti, small transcribes 468 s of audio in 3.7 s;
the same job on CPU takes 122 s.
Moving the cache
Large models on a small home partition is a common squeeze. Point HF_HOME
somewhere roomier:
$ export HF_HOME=/mnt/data/huggingface # bash/zsh
> $env:HF_HOME = "D:\huggingface" # PowerShell
Pre-downloading and offline use
To fetch a model ahead of time, just run it once on any short clip — the download happens on first use, and a progress bar shows it:
$ transcriber some-short-clip.mp3 -m turbo
Once cached, transcription needs no network at all. Set HF_HUB_OFFLINE=1 to
guarantee it: cached models keep working, and an uncached one fails immediately
with a clear message instead of reaching out.
Using your own model
-m also takes a HuggingFace CTranslate2 repo id, or a path to a model
directory you converted or downloaded yourself:
$ transcriber talk.mp4 -m Systran/faster-whisper-large-v3
$ transcriber talk.mp4 -m /models/my-finetuned-whisper
Authentication for private media
Instagram reels, members-only videos, and anything else behind a login need your browser's cookies:
$ transcriber <reel-url> --cookies-from-browser firefox -f md -t
$ transcriber <reel-url> --cookies cookies.txt
Windows note: Chrome's app-bound cookie encryption usually defeats
--cookies-from-browser chrome. Use Firefox, or export acookies.txtwith a browser extension and pass--cookies.
Exit codes
| Code | Meaning |
|---|---|
| 0 | Everything transcribed (or was already done) |
| 1 | At least one item failed — the message says why |
| 2 | Bad command-line usage |
| 130 | Interrupted with Ctrl-C |
Development
From a checkout:
$ uv sync # dev environment
$ uv run pytest # fast tests: no network, no models
$ uv run pytest -m slow # real tiny-model smoke test (~75 MB, once)
$ uv run ruff check . && uv run ruff format .
CI runs the suite on Linux, macOS, and Windows across Python 3.10 and 3.12. Python 3.10 through 3.14 are verified locally on Linux.
Contributing
Source, issues, and pull requests: https://github.com/AyushShah2/omni-transcriber
License
MIT — see LICENSE.
Metadata
Release files for omni-transcriber 0.1.9
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| omni_transcriber-0.1.9.tar.gz | 41.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| omni_transcriber-0.1.9-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 91.4 kB
Release files / omni_transcriber-0.1.9.tar.gz
| Download URL | omni_transcriber-0.1.9.tar.gz |
|---|---|
| Size | 41.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
8203ac658dd394a937f76e9c7df3935cbca4f64917520aa26ed0142d8a8b2f45
|
|
BLAKE2b-256 checksum How to use checksums |
8c322601b3830624e34871ff660a45cd39118c3182e65d3b73853d5592af15f7
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.3 {"installer":{"name":"uv","version":"0.12.3","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"26.04","id":"resolute","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / omni_transcriber-0.1.9-py3-none-any.whl
| Download URL | omni_transcriber-0.1.9-py3-none-any.whl |
|---|---|
| Size | 49.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
295c23b56466af3021e83350e35e3a243b7d16df631f123d192a10c79f9aacf3
|
|
BLAKE2b-256 checksum How to use checksums |
a5e3778b2885873a6ec5ea2a4400458cea454f9de92ab4549763003b1f34634d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.3 {"installer":{"name":"uv","version":"0.12.3","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"26.04","id":"resolute","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|