subreel
Turn speech audio into subtitle reels: green-screen H.264, transparent WebM/ProRes, with word-by-word karaoke highlight.
subreel is a small, dependency-free CLI that turns a voice-over into a subtitle clip you can drop straight onto a video timeline. It renders the subtitles onto a chroma-key green background (for editors that key out green) or onto a transparent canvas (for editors that take alpha directly), and it lights up each word as it is spoken — classic karaoke style.
Two ways to get the timeline:
subreel tts— the accurate path. Synthesize the speech with edge-tts and capture the per-word timestamps straight from the synthesis events. The subtitle timing is exact, because it comes from the same engine that produced the audio — zero ASR error.subreel render --asr— the any-audio path. Run faster-whisper on any existing audio file to get word-level timestamps.
Features
- Three output flavours from one command:
- Green-screen MP4 — H.264 /
yuv420p, ready for chroma key in 剪映, Premiere, DaVinci, Final Cut. - Transparent WebM — VP9 with an alpha channel (
yuva420p). - Transparent ProRes 4444 MOV — with alpha (
yuva444p10le), for Premiere / Final Cut / DaVinci.
- Green-screen MP4 — H.264 /
- Per-word karaoke highlight — each word is wrapped in an ASS
\kfsweep, so the current and already-sung words are drawn in the highlight colour. - edge-tts precise timelines — word timings captured from
WordBoundaryevents, no speech recognition involved. - Whisper ASR fallback — word-level recognition for audio you did not synthesize, with caching.
- Zero mandatory Python dependencies — ffmpeg is the only system
requirement;
edge-ttsandfaster-whisperare opt-in extras. - A documented, portable timeline format — the
words.jsonfile.
Installation
1. ffmpeg (required)
subreel shells out to ffmpeg / ffprobe and checks for them at startup.
| Platform | Command |
|---|---|
| Ubuntu / Debian | sudo apt install ffmpeg |
| macOS | brew install ffmpeg |
| Windows | winget install Gyan.FFmpeg |
| Conda | conda install -c conda-forge ffmpeg |
For CJK subtitles, install a CJK font as well (e.g. fonts-noto-cjk on Debian
/ Ubuntu); the default style uses Noto Sans CJK SC.
2. subreel
Packaging is CI-verified: pip install subreel works as soon as the first
release lands on PyPI (see docs/releasing.md). Until then
install straight from GitHub:
pip install "git+https://github.com/paxoscn/subreel.git"
With the optional features:
# edge-tts synthesis (subreel tts)
pip install "git+https://github.com/paxoscn/subreel.git#egg=subreel[tts]"
# faster-whisper ASR (subreel render --asr)
pip install "git+https://github.com/paxoscn/subreel.git#egg=subreel[asr]"
# both
pip install "git+https://github.com/paxoscn/subreel.git#egg=subreel[tts,asr]"
Quick start
A. Synthesize and render (most accurate)
# 1. make the voice-over plus its exact word timings
subreel tts \
--text script.txt \
--voice zh-CN-XiaoxiaoNeural \
--rate +15% \
--out voice.mp3
# -> voice.mp3 and voice.words.json
# 2. render all three flavours
subreel render voice.mp3 \
--timings voice.words.json \
--formats green,webm,mov \
--out-dir out
# -> out/subs-green.mp4, out/subs-alpha.webm, out/subs-alpha.mov
B. You already have audio — let Whisper transcribe it
subreel render narration.m4a --asr --asr-model small --formats green,webm
The recognised timings are cached to out/narration.words.json and reused on
the next run (pass --force-asr to re-run).
C. You already have timings
Any tool that emits the words.json format can
drive subreel:
subreel render voice.mp3 --timings my-alignment.json --formats mov
Decoding the transparent WebM from the command line
subs-alpha.webm carries its alpha channel as a VP9 BlockAdditional layer
(alpha_mode=1). NLEs and browsers handle this natively, but ffmpeg's
native vp9 decoder silently drops that layer, so a plain
ffmpeg -i subs-alpha.webm frame.png yields an opaque black frame. Decode
with libvpx instead:
ffmpeg -c:v libvpx-vp9 -i subs-alpha.webm -frames:v 1 frame.png # RGBA, alpha intact
The ProRes .mov needs no such flag — every ffmpeg/ProRes toolchain reads its
alpha directly.
Compositing guide
All three outputs are subtitle overlays: they contain only the text (and the audio), so you place them on top of your footage and key out the background.
剪映 (CapCut / JianYing)
- Import
subs-green.mp4and drop it on a track above your video. - Select the clip → 画面 (Picture) → 抠像 (Key) → 色度抠图 (Chroma key).
- Pick the key colour from the green background — it is
0x00B140(RGB0, 177, 64). Use the eyedropper on the green area. - Raise 强度 (Strength) until the green is fully gone, then adjust 阴影 (Shadow) to clean up the antialiased edges around the glyphs.
Adobe Premiere Pro
- Green MP4: apply Ultra Key, eyedrop the green, set Matte Generation → Tolerance ~ 30–40 % and Pedestal ~ 10 %.
- Transparent WebM / MOV: just drop
subs-alpha.webm(VP9 with alpha) orsubs-alpha.mov(ProRes 4444) onto the timeline — no keying needed.
DaVinci Resolve
- Green MP4: add a 3D Keyer node, eyedrop the green, then refine with the Clean Black / Clean White controls.
- Transparent MOV: import
subs-alpha.movand place it on a higher track; Resolve honours the ProRes 4444 alpha directly.
Final Cut Pro
- Green MP4: apply the Keyer effect and pick the green.
- Transparent MOV: add
subs-alpha.movabove your footage — the alpha is used automatically.
Any NLE
Transparent WebM (VP9 yuva420p) and transparent MOV (ProRes 4444) can be
dragged straight onto the timeline in Premiere, DaVinci and Final Cut with no
keying. If your editor is unhappy with VP9 alpha, use the MOV.
words.json timing format
subreel's public interchange format. Times are in seconds (float).
{
"language": "zh",
"source": "edge-tts",
"words": [
{ "text": "你", "start": 0.12, "end": 0.31 },
{ "text": "好", "start": 0.31, "end": 0.58 }
]
}
| Field | Type | Required | Notes |
|---|---|---|---|
words |
array | yes | Non-empty list of word objects. |
words[].text |
string | yes | The word or character as it should be displayed. |
words[].start |
number | yes | Start time in seconds. |
words[].end |
number | yes | End time in seconds; must be strictly greater than start. |
language |
string | no | Best-effort language tag (e.g. zh, en). |
source |
string | no | Free-form provenance (edge-tts, faster-whisper, …). |
Validation rules (enforced on load, with a clear error otherwise):
wordsmust be present and non-empty;- every word needs
text(string) and numericstart/end; start < endfor every word;startvalues are monotonically non-decreasing (equal is allowed, so two words may share a boundary).
CLI reference
subreel [-h] [--version] [-v] [-q] {tts,render} ...
Global options: --version, -v/--verbose (INFO logging), -q/--quiet.
subreel tts
subreel tts (--text FILE | --text-inline STR) [--voice VOICE]
[--rate RATE] [--pitch PITCH] --out AUDIO [--timings JSON]
| Option | Default | Description |
|---|---|---|
--text FILE |
— | Read the script from a UTF-8 file. |
--text-inline STR |
— | Use the given text directly. |
--voice |
zh-CN-XiaoxiaoNeural |
edge-tts voice name. |
--rate |
+0% |
Speaking rate, e.g. +30%. |
--pitch |
+0Hz |
Pitch shift, e.g. +8Hz. |
--out |
(required) | Output audio file. |
--timings |
next to --out |
words.json output path. |
subreel render
subreel render AUDIO [--timings JSON] [--asr] [--asr-model M] [--language L]
[--force-asr] [--formats F] [--out-dir DIR] [--size WxH]
[--fps N] [--green HEX] [--no-audio] [--fontsdir DIR]
[--font NAME] [--fontsize N] [--align POS] [--highlight HEX]
[--color HEX] [--no-fade] [--max-chars N] [--max-lines N]
[--hold SEC]
| Option | Default | Description |
|---|---|---|
AUDIO |
(required) | Source audio file. |
--timings |
— | words.json word timings (takes precedence over --asr). |
--asr |
off | Derive timings with faster-whisper. |
--asr-model |
base |
faster-whisper model size / name. |
--language |
auto | Force the ASR language (e.g. zh, en). |
--force-asr |
off | Re-run ASR even if a cached words.json exists. |
--formats |
green |
Comma-separated subset of green, webm, mov. |
--out-dir |
./out |
Output directory (created if missing). |
--size |
1280x720 |
Canvas size. |
--fps |
30 |
Frame rate. |
--green |
0x00B140 |
Chroma-key green colour for the green format. |
--no-audio |
off | Omit the audio track from the outputs. |
--fontsdir |
— | Extra font directory for libass. |
--font |
Noto Sans CJK SC |
Subtitle font name. |
--fontsize |
64 |
Subtitle font size. |
--align |
bottom |
bottom, center/middle or top. |
--highlight |
#FFE500 |
Karaoke highlight colour (sung words), #RRGGBB. |
--color |
#FFFFFF |
Base colour (not-yet-sung words). |
--no-fade |
off | Disable the per-line fade in/out. |
--max-chars |
22 |
Per-line width budget, in CJK cells. |
--max-lines |
2 |
Soft maximum lines per sentence. |
--hold |
0.3 |
Karaoke hold tail, in seconds. |
Exit codes: 0 on success, 2 for every recoverable error (missing
ffmpeg, missing audio, no timing source, bad words.json, ffmpeg failure).
Development
git clone https://github.com/paxoscn/subreel.git
cd subreel
pip install -e ".[dev,tts,asr]"
# run the offline test suite (no network, no model downloads)
python3 -m pytest tests/ -q
# byte-compile check
python3 -m compileall src/
The tests mock edge-tts, faster-whisper and ffmpeg, so they run anywhere.
CI (.github/workflows/ci.yml) runs the suite on
Python 3.9–3.13, then builds the sdist + wheel, twine check --stricts the
metadata and smoke-installs the wheel. Release automation (tag → PyPI via OIDC
trusted publishing) is documented in docs/releasing.md.
License
Apache-2.0 © 2026 paxoscn
Credits
subreel stands on the shoulders of these projects:
- ffmpeg — probing, filtering and encoding;
- libass — ASS subtitle rendering (via
ffmpeg's
assfilter); - edge-tts — neural text-to-speech and word-boundary timings;
- faster-whisper — fast word-level speech recognition.
Metadata
Release files for subreel 0.1.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 | |
|---|---|---|---|
| subreel-0.1.0.tar.gz | 50.8 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| subreel-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 83.0 kB
Release files / subreel-0.1.0.tar.gz
| Download URL | subreel-0.1.0.tar.gz |
|---|---|
| Size | 50.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
2434338e26624435f17c9dfab675d562776361c8ca7db176f264511d9ea3ea4b
|
|
BLAKE2b-256 checksum How to use checksums |
9d0abdc3262243ae78aa28ffb9229cdec894a2dc9bd14d4493e1116b3fca747b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 10, 2026.
Transparency logRelease files / subreel-0.1.0-py3-none-any.whl
| Download URL | subreel-0.1.0-py3-none-any.whl |
|---|---|
| Size | 32.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
2f4267867fdc5dca87edbfe7f06652c0ee7de5c59bc1bd09d0ed0efedb586882
|
|
BLAKE2b-256 checksum How to use checksums |
12d83c1e9687349814db6ee5732febfe7bbf18ca8946d2d23c464c291b34a5ee
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 10, 2026.
Transparency log