Metadata-Version: 2.4
Name: omni-transcriber
Version: 0.1.7
Summary: Transcribe anything: YouTube/Instagram/any URL, local media files, or whole folders - locally via Whisper.
License-Expression: MIT
License-File: LICENSE
Requires-Dist: typer>=0.15
Requires-Dist: rich>=13.7
Requires-Dist: faster-whisper>=1.2.0
Requires-Dist: ctranslate2>=4.6.3
Requires-Dist: yt-dlp[default]>=2026.1
Requires-Dist: onnxruntime<1.24 ; python_full_version < '3.11'
Requires-Dist: static-ffmpeg>=3.0
Requires-Dist: nvidia-cublas-cu12>=12.8 ; sys_platform != 'darwin' and extra == 'gpu'
Requires-Dist: nvidia-cudnn-cu12>=9.8,<10 ; sys_platform != 'darwin' and extra == 'gpu'
Requires-Python: >=3.10
Project-URL: Homepage, https://github.com/AyushShah2/omni-transcriber
Project-URL: Issues, https://github.com/AyushShah2/omni-transcriber/issues
Project-URL: Repository, https://github.com/AyushShah2/omni-transcriber
Provides-Extra: gpu
Description-Content-Type: text/markdown

# 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][fw] — no API key, no
audio leaves your machine.

```console
$ 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][uv] is the smoothest route, but pipx and pip
work too.

```console
$ 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:**

```console
$ git clone https://github.com/AyushShah2/omni-transcriber
$ cd omni-transcriber && uv sync && uv run transcriber --help
```

**With NVIDIA GPU acceleration:**

```console
$ 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][deno] is
  yt-dlp's preferred runtime; Node.js also works. `transcriber` enables whichever
  it finds on your `PATH` automatically. 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][ots], 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` |
| `-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:

```console
$ 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.

### Feeding transcripts to an LLM

```console
$ 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.

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:

```console
$ export HF_HOME=/mnt/data/huggingface        # bash/zsh
```
```powershell
> $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:

```console
$ 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:

```console
$ 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:

```console
$ 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 a `cookies.txt` with 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:

```console
$ 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](LICENSE).

[fw]: https://github.com/SYSTRAN/faster-whisper
[uv]: https://docs.astral.sh/uv/
[deno]: https://deno.com/
[ots]: https://opentimestamps.org/
