- Rust 100%
srv3topysub2
Convert YouTube SRV3/.ytt captions to ASS, SRT, WebVTT or TTML.
A single static Rust binary. Reading and writing both stream, so memory scales with the number of events, not the input size. ASS is the highest-fidelity target — it alone preserves pen colours, fonts, sizes, opacities, background boxes, edges and window positions.
Install
$ cargo install --path .
Usage
$ srv3topysub2 captions.srv3 -o captions.ass
$ srv3topysub2 captions.srv3 -o captions.ttml # format from the extension
$ srv3topysub2 captions.srv3 -f srt -o - # stdout
$ srv3topysub2 *.srv3 -d out/ -f ass # batch
$ cat captions.srv3 | srv3topysub2 - -o - # stdin to stdout
| Option | Effect |
|---|---|
-f, --format |
ass, srt, vtt, ttml (default: from -o, else ass) |
-o, --output |
Output file; - writes to stdout |
-d, --outdir |
One output per input in a directory |
--ruby keep|drop |
Keep ruby annotations (default) or base text only |
--no-position |
Omit \an/\pos position tags |
--order time|document |
Sort by start time (default) or keep <p> order |
--report-irregular |
List anything outside the modelled vocabulary |
--default-duration MS |
Fallback length for a final event with no d (default 5000) |
Errors and findings go to stderr; stdout carries only converted subtitles.
How it works
The SRV3 vocabulary is data, not code: specs/srv3.toml (enums, constants, font names,
element vocabulary) and specs/formats.toml (output formats). build.rs turns them
into type-safe Rust at compile time, and a missing value fails the build.
The mapping follows FFmpeg's srv3dec.c, so output matches
ffmpeg -i captions.srv3 out.ass — 1280×720 canvas, 38px base font, ASS PenN styles,
&HBGR colours with inverted alpha, and \an/\pos window positions. Two deliberate
departures are documented in src/convert.rs (font style 4/5, and no stray outline
under hard shadows).
Real-world oddities — rollup overlap, teletype keystrokes, karaoke reveals, two-track files, bare tracks, padding paragraphs, literal newlines — are all handled and regression-tested.
--report-irregular lists unknown elements/attributes, out-of-range values, unreadable
colours, duplicate ids and dangling references, without changing the conversion.
What survives which format
| ASS | TTML | SRT | WebVTT | |
|---|---|---|---|---|
| Text, timing | ✅ | ✅ | ✅ | ✅ |
| Bold / italic / underline | ✅ | ✅ | ✅ | ✅ |
| Font name, colour | ✅ | ✅ | ✗ | ✗ |
| Font size | ✅ | ✗ | ✗ | ✗ |
| Background box, edges | ✅ | ✗ | ✗ | ✗ |
| Window position | ✅ | ✗ | ✗ | ✗ |
Vertical/rotated text, subscript/superscript and packed text have no SubStation equivalent; they emit a warning and are dropped.
Development
$ cargo build
$ cargo test
$ cargo clippy --all-targets --all-features -- -D warnings
cargo test runs the unit suite plus an end-to-end pass over every *.srv3 in the
repo root.