Metadata-Version: 2.5
Name: subtitlemaker
Version: 0.2.0
Summary: Generate subtitles from audio and video files using faster-whisper.
Project-URL: Homepage, https://github.com/CodeByVibes/subtitlemaker
Project-URL: Issues, https://github.com/CodeByVibes/subtitlemaker/issues
Project-URL: Changelog, https://github.com/CodeByVibes/subtitlemaker/blob/main/CHANGELOG.md
Author-email: Johan Isevind <codebyvibes@isevind.se>
License-Expression: MIT
License-File: LICENSE
Keywords: faster-whisper,speech-to-text,srt,subtitles,transcription,vtt,whisper
Classifier: Development Status :: 3 - Alpha
Classifier: Environment :: Console
Classifier: Intended Audience :: End Users/Desktop
Classifier: Operating System :: POSIX :: Linux
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Multimedia :: Sound/Audio :: Speech
Classifier: Topic :: Multimedia :: Video
Requires-Python: >=3.10
Requires-Dist: av<17,>=13
Requires-Dist: ctranslate2<5,>=4.6
Requires-Dist: faster-whisper>=1.2.1
Requires-Dist: numpy>=1.22
Requires-Dist: rich>=13
Requires-Dist: tomli>=2; python_version < '3.11'
Provides-Extra: cuda
Requires-Dist: nvidia-cublas-cu12; extra == 'cuda'
Requires-Dist: nvidia-cudnn-cu12==9.*; extra == 'cuda'
Description-Content-Type: text/markdown

# SubtitleMaker

[![CI](https://github.com/CodeByVibes/subtitlemaker/actions/workflows/ci.yml/badge.svg)](https://github.com/CodeByVibes/subtitlemaker/actions/workflows/ci.yml)
[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)

Generate subtitles from audio and video files with [faster-whisper](https://github.com/SYSTRAN/faster-whisper).
It runs locally, on a GPU if you have one and on the CPU if you don't.

```console
$ subtitlemaker interview.mkv
$ ls
interview.mkv  interview.srt
```

- **SRT, WebVTT and plain text** output, any combination in one run.
- **Subtitles that follow the speech.** Cues start at the first word and end at the last, using word timestamps, and long sentences are split at sentence ends where possible.
- **Readable by default:** at most two lines of 42 characters, 20 characters per second, 1 to 7 seconds on screen. All of it is configurable.
- **Batch mode:** pass many files or whole folders; the model is loaded once and one bad file doesn't stop the rest.
- **GPU or CPU.** `--device auto` uses an NVIDIA GPU when there is one and falls back to the CPU when there isn't.
- **Mixed-language audio** (`-m`, or `-l en,sv`), and optional translation to English (`-t translate`).

## Install

You need Python 3.10 to 3.13. FFmpeg is **not** required.

SubtitleMaker is a command line tool, so [pipx](https://pipx.pypa.io/) or [uv](https://docs.astral.sh/uv/) is the easiest way to install it:

```console
# CPU only
pipx install subtitlemaker

# NVIDIA GPU (also installs the CUDA 12 libraries, see below)
pipx install "subtitlemaker[cuda]"
```

With uv: `uv tool install subtitlemaker` or `uv tool install "subtitlemaker[cuda]"`. Plain `pip install` works too, ideally inside a virtual environment.

> **Platforms:** developed and tested on Linux. It should work on macOS and Windows on the CPU, but that has not been tested yet. GPU support is only set up for Linux.

### The first run downloads a model

Whisper models are downloaded from Hugging Face the first time you use them and cached afterwards (`~/.cache/huggingface`, or wherever `HF_HOME` points). Sizes of the model files:

| `--model`    | Download | Notes                                          |
| ------------ | -------- | ---------------------------------------------- |
| `tiny`       | 73 MB    | Fast, rough. Good for trying things out.       |
| `small`      | 462 MB   | **Default on CPU.**                            |
| `turbo`      | 1.6 GB   | Close to `large-v3` quality, much faster.      |
| `large-v3`   | 2.9 GB   | **Default on GPU.** Most accurate.             |

`base`, `medium`, the `.en` variants, `distil-*` models, any Hugging Face repo id such as `user/model`, and local paths to converted models also work.

## Usage

```console
# Subtitles next to the file, in the spoken language
subtitlemaker talk.mp4

# Several formats at once: talk.srt, talk.vtt and talk.txt
subtitlemaker talk.mp4 -f srt,vtt,txt

# English subtitles for foreign-language audio
subtitlemaker film.mkv -t translate

# Tell it the language instead of detecting it
subtitlemaker film.mkv -l sv

# A whole folder (add -r for sub folders), skipping files that are already done
subtitlemaker ~/Videos --skip-existing -r

# Put all the results in one place
subtitlemaker ~/Videos/*.mp4 -d ~/subtitles -f srt,vtt

# Smaller and faster, CPU only
subtitlemaker talk.mp4 --device cpu --model tiny
```

Run `subtitlemaker --help` for every option.

### Output

| Format | Contents                                                                         |
| ------ | -------------------------------------------------------------------------------- |
| `srt`  | SubRip, the most widely supported. This is the default.                          |
| `vtt`  | WebVTT, for web players. Markup characters in the text are escaped.              |
| `txt`  | The text only, one subtitle per line, no timing.                                 |

Files are written next to each input and named after it (`talk.mp4` becomes `talk.srt`). Existing files are overwritten unless you pass `--skip-existing`.

- `-o FILE` names the output for a single input. With several formats it is a base name and the extension is replaced for each one.
- `-d DIR` writes all outputs into one folder (created if needed). Two inputs that would write the same file are refused before anything runs.

The exit code is `0` when everything succeeded, `1` if any file failed (the others are still processed), `2` for invalid options, and `130` if you stop it with Ctrl-C.

### Languages

By default Whisper detects the language from the start of each file. If it guesses wrong, or the start is music, name the language with `-l`.

For audio that switches language, add `-m`, or list several languages (`-l en,sv`). The language is then re-detected for every segment. The list only switches this on: detection is not limited to the languages you name. Be aware that `-t translate` turns every segment into English.

### Missing punctuation

When a recording starts in the middle of a sentence, Whisper sometimes writes everything without punctuation or capitals ("come on come on come on randall"), and every subtitle then ends mid-sentence. subtitlemaker warns when this happens. Run it again with `--prompt` and a sentence or two in the same language and style:

```bash
subtitlemaker clip.mp4 --prompt "Hello, everyone. Let's begin."
```

A prompt can change other words too, so only use one when you need it.

If Whisper writes text during long silences or music that nobody says, try `--hallucination-silence-threshold 2`. Whisper then skips silences longer than 2 seconds where its text looks invented. It needs word timestamps, which are on by default.

### Subtitle style

| Option              | Default | Meaning                                              |
| ------------------- | ------- | ---------------------------------------------------- |
| `--max-line-length` | 42      | Maximum characters per line.                         |
| `--max-lines`       | 2       | Lines per subtitle, 1 or 2.                          |
| `--max-cps`         | 20      | Reading speed: characters per second.                |
| `--min-duration`    | 1.0     | Shortest time a subtitle stays on screen (seconds).  |
| `--max-duration`    | 7.0     | Longest time a subtitle stays on screen (seconds).   |
| `--min-gap`         | 0.05    | Smallest gap between two subtitles (seconds).        |
| `--max-pause`       | 1.0     | Silence between two words that starts a new subtitle (seconds). |
| `--linger`          | 0.5     | Time a subtitle stays up after its speech ends, if the time is free (seconds). 0 turns it off. |
| `--close-gap`       | 0.5     | A gap between two subtitles shorter than this is closed, so text doesn't flicker off and on (seconds). 0 turns it off. |

Timing follows the speech. A subtitle appears when its first word is spoken and stays up a little after the last one (`--linger`). If it would be too short or too quick to read, it is held longer, but only into free time: it never delays or overlaps the next subtitle. When the next subtitle follows within `--close-gap`, the first one stays up until just before it (`--min-gap`) instead of blinking off.

Lines are broken at a sentence end where possible, then at a comma, and otherwise kept about equally long. Names stay on one line, and for English and Swedish a line doesn't end on a word like "the", "of" or "och" when there is a better place. In dialogue written with dashes ("–Ja? –Nej."), each speaker gets their own line, and a third speaker starts a new subtitle. If two lines are spoken too close together, the first one is allowed to be shorter than `--min-duration`. A long silence in the middle of a sentence (a laugh track, music) starts a new subtitle, so words are never shown long before they are spoken.

### Config file

Options you always use can go in `~/.config/subtitlemaker/config.toml` (or `$XDG_CONFIG_HOME/subtitlemaker/config.toml`), or in a file given with `--config`. Command line options override the file, and the file overrides the defaults.

```toml
[style]
max_line_length = 38
max_lines = 2
max_cps = 17
min_duration = 1.0
max_duration = 6.0
min_gap = 0.05
max_pause = 1.0
linger = 0.5
close_gap = 0.5

[model]
name = "turbo"            # auto, a size, a Hugging Face repo id or a path
device = "auto"           # auto, cuda or cpu
compute_type = "auto"     # auto, float16, int8_float16, int8, float32, ...
word_timestamps = true
vad_filter = true
condition_on_previous_text = true
initial_prompt = ""       # same as --prompt
hallucination_silence_threshold = 0   # seconds, 0 = off
```

Unknown keys and wrong types are reported with the file name, so typos don't pass silently.

## GPU and CPU

`--device auto` (the default) picks the GPU when CTranslate2 can see one and warns and uses the CPU otherwise.

| Where | Default model | Precision | Notes                                               |
| ----- | ------------- | --------- | --------------------------------------------------- |
| GPU   | `large-v3`    | `float16` | Needs an NVIDIA GPU, a current driver and CUDA 12 libraries. |
| CPU   | `small`       | `int8`    | Works everywhere, much slower than a GPU.           |

For a rough idea, on one machine (RTX 4070 Laptop GPU, 24 CPU cores) a two minute clip took about 7 seconds with `large-v3` on the GPU and about 9 seconds with `small` on the CPU. Your results will differ.

### CUDA libraries

CTranslate2 needs the CUDA 12 versions of cuBLAS and cuDNN 9. Install SubtitleMaker with the `cuda` extra and they come from PyPI (`nvidia-cublas-cu12`, `nvidia-cudnn-cu12`) and are loaded automatically, so a different system-wide CUDA version doesn't matter. These packages are licensed by NVIDIA, not under this project's license, and they are large.

If the GPU can't be used, `--device auto` falls back to the CPU and tells you why. With an explicit `--device cuda` it stops with an error that says what is missing.

## How it works

1. faster-whisper transcribes the audio with word timestamps (voice activity detection is on, so stretches without speech are skipped).
2. Words are grouped into subtitles that fit the line limits, preferably breaking at the end of a sentence.
3. Timing is adjusted for readability without moving speech, as described above.
4. The result is written in each requested format.

## Limitations

- At most two lines per subtitle.
- Lines are wrapped at spaces, so languages written without spaces (Chinese, Japanese, Thai) are not wrapped well. This is untested.
- Speakers are not identified, and no sound descriptions like `[music]` are added.

## Contributing

Bug reports and pull requests are welcome, see [CONTRIBUTING.md](CONTRIBUTING.md). Changes are listed in [CHANGELOG.md](CHANGELOG.md).

## License

[MIT](LICENSE). SubtitleMaker builds on [faster-whisper](https://github.com/SYSTRAN/faster-whisper) (MIT), [CTranslate2](https://github.com/OpenNMT/CTranslate2) (MIT) and OpenAI's [Whisper](https://github.com/openai/whisper) models (MIT).
