subtitle-engine
Generate .srt subtitle files from audio or video files using WhisperX. Optionally generate a caption from the transcript with a local Ollama LLM.
Installation
Requires Python 3.12 or newer.
pip install subtitle-engine
Or install from source:
git clone https://github.com/leevipuntanen/subtitle-engine.git
cd subtitle-engine
python -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"
Usage
# Basic usage — writes <input>.srt next to the source file
subeng video.mp4
# Specify output file
subeng video.mp4 --output subtitles.srt
# Use a different model or language
subeng video.mp4 --model medium --language fi
# Force CPU / CUDA
subeng video.mp4 --device cpu
# Speaker diarization (requires a Hugging Face token)
subeng video.mp4 --diarize --hf-token $HF_TOKEN
# Generate a caption from the transcript using Ollama
subeng video.mp4 --caption --ollama-model qwen3.5:0.6b
# Generate a caption from an existing SRT file
subeng caption subtitles.srt
# Short-form subtitles (2-5 words per line, default)
subeng video.mp4 --preset shortform
# Long-form subtitles (10-14 words per line)
subeng video.mp4 --preset longform
# Burn subtitles into the video (requires ffmpeg on PATH)
subeng video.mp4 --burn
# Burn with a custom .ass style file
subeng video.mp4 --burn --style-file my-style.ass
Subtitle quality
The segmenter applies several quality passes by default. Each can be turned off or tuned via a CLI flag.
| Pass | Default | Flag to disable | Flag to tune |
|---|---|---|---|
Hallucination cleanup (Thanks for watching, [Music], Subtitles by …) |
on | --no-cleanup |
— |
Clause-aware line breaking (prefer splits at , . ? ! ; :) |
on | --no-clause-boundaries |
— |
Sentence-boundary splitting (split at . ? ! when at least one of: next word capitalized, ≥ 0.2s pause after, or speaker change) |
on | --no-sentence-split |
--sentence-pause-threshold 0.3 |
| Two-line balancing (longform only — splits long lines at the midpoint clause) | on (longform) | --no-line-balance |
— |
| Characters-per-second limit | 21 cps | --no-cps-limit |
--max-cps 17 |
| Minimum on-screen duration | 1.0 s | --no-min-duration |
--min-duration 1.5 |
The sentence splitter now considers context before splitting, so a mid-thought
period like I found a bug. reports say... stays in the same subtitle
(no capital, no pause, no speaker change → no split). To get the old
aggressive behavior back, set --sentence-pause-threshold 0.
Disable every quality pass to reproduce the legacy behavior:
subeng video.mp4 \
--no-cleanup \
--no-clause-boundaries \
--no-sentence-split \
--no-line-balance \
--no-cps-limit \
--no-min-duration
Burning subtitles
Pass --burn to also re-encode the input video with the generated SRT
rasterized into the frames (hardsub). The result is written next to the
original as <input>.subtitled.<ext>; the source file is never modified.
subeng video.mp4 --burn
Burn requires ffmpeg on your PATH. Audio is
stream-copied where possible, so only the video is re-encoded. To apply
custom styling, point --style-file at an .ass file; the first
Style: line is converted to ffmpeg's force_style argument:
subeng video.mp4 --burn --style-file my-style.ass
If the burn step fails (missing ffmpeg, no video stream, ffmpeg error), the SRT that was already written is kept on disk and its path is shown in the error message.
Options
| Option | Description |
|---|---|
--output, -o |
Output SRT file path |
--model, -m |
WhisperX model: tiny, base, small (default), medium, large-v2, large-v3 |
--language, -l |
ISO language code, e.g. en, fi. Auto-detected if omitted. |
--device, -d |
cpu or cuda. Auto-detected if omitted. |
--batch-size, -b |
Inference batch size (default: 16) |
--compute-type, -c |
int8 or float16. Auto-selected if omitted. |
--diarize |
Enable speaker diarization |
--hf-token |
Hugging Face token for diarization (or set HF_TOKEN env var) |
--caption |
Generate a caption from the transcript via Ollama |
--ollama-model |
Ollama model name. If omitted, installed models are listed and you can pick one. |
--ollama-host |
Ollama API host (default: http://localhost:11434) |
caption |
Generate a caption from an existing SRT file (e.g. subeng caption file.srt) |
--preset, -p |
Subtitle style: shortform (2-5 words, default) or longform (10-14 words) |
--no-cleanup |
Disable hallucination cleanup (e.g. Thanks for watching, [Music]) |
--no-clause-boundaries |
Disable clause-aware line breaking |
--no-sentence-split |
Disable sentence-boundary splitting (so mission. Building in public stays as one subtitle) |
--sentence-pause-threshold <float> |
Minimum pause (seconds) after a period for the gap to count as evidence of a real sentence boundary (default: 0.2). Set to 0 to rely on capital letters / speaker change only. |
--no-line-balance |
Disable two-line balancing (enabled by default for longform) |
--max-cps <float> |
Maximum characters per second per subtitle (default: 21). Use 0 to disable. |
--no-cps-limit |
Disable the CPS post-processor |
--min-duration <float> |
Minimum on-screen duration in seconds (default: 1.0). Use 0 to disable. |
--no-min-duration |
Disable the minimum-duration post-processor |
--burn |
Re-encode the input video with the generated SRT burned into the frames. Writes <input>.subtitled.<ext> next to the source. Requires ffmpeg on PATH. |
--style-file |
Path to a .ass style file used when --burn is set. The first Style: line is applied via ffmpeg's force_style. |
Development
Run the test suite:
pytest
License
MIT
Release files for subtitle-engine 0.3.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| subtitle_engine-0.3.0.tar.gz | 49.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| subtitle_engine-0.3.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 81.7 kB
Release files / subtitle_engine-0.3.0.tar.gz
| Download URL | subtitle_engine-0.3.0.tar.gz |
|---|---|
| Size | 49.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
1e1d634a085528bf158c84ebd90895e3f9e2f14969b547b37ecaeb39051bf2f9
|
|
BLAKE2b-256 checksum How to use checksums |
045030c42d14b1158acdd26eb51ce2577b234138541db67726256c5e3db7a7e8
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.11
|
Release files / subtitle_engine-0.3.0-py3-none-any.whl
| Download URL | subtitle_engine-0.3.0-py3-none-any.whl |
|---|---|
| Size | 32.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
2a0e6afe9d47812e2e93331187b01a3732f8ae2647c9033693794d93bf17f94d
|
|
BLAKE2b-256 checksum How to use checksums |
6698b87cdf11e12c632c12a193c6115194bf197ca294d4cb5d05f1ba26d7a009
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.11
|