Please check the build logs for more information.
See Builds for ideas on how to fix a failed build, or Metadata for how to configure docs.rs builds.
If you believe this is docs.rs' fault, open an issue.
A blazing fast, zero-copy subtitle parser and writer for SRT, WebVTT and ASS/SSA in Rust.
Installation
[]
= "0.5"
Features
- Zero-copy, zero-allocation parsing — borrows directly from the input string
#![no_std]support (with optionalallocandstdfeatures)- Lazy iterator-based parsing — blocks are yielded on demand
- DFA-based lexing via
logosfor fast tokenization - Strongly-typed newtypes (
Hour,Minute,Second,Millisecond,Centisecond,Percentage) with compile-time validation - W3C WebVTT spec conformant — validated against Web Platform Tests
SRT
SubRip has no formal specification; the cue-body behaviour below is the common
ground between FFmpeg's subrip decoder (libavcodec/htmlsubtitles.c, which is
also what mpv and everything else built on libavcodec uses), VLC's subtitle
decoder and Aegisub's SRT reader, and each rule is documented against them.
- Strict and lossy parsing modes
- Timestamps (
HH:MM:SS,mmm) - Multiline cue bodies
- Writer (
stdfeature) - Cue body text parsing — two-layer design, both usable standalone on an
embedded
S_TEXT/UTF8packet:TextParser: logos DFA-backed, zero-alloc token stream (no_std)PlainText: clean-text extraction withOnceCell-cached lazy normalization, plus an allocation-freesegments()iterator- Tags:
<b>,<i>,<u>,<s>and<font>with its attributes readable, matched case-insensitively;<br>in every form is a line break - A
<that begins none of them is literal text, and so is the rest of the line —I <3 thisand the Japanese narration convention<セリフare subtitle text, not markup - Not HTML: character references are not decoded, because no SubRip reader decodes them
- SSA (
{\an8}) and MicroDVD ({Y:i}) inline codes left behind by a converter are dropped, and a brace that opens neither is text - Flat by construction — no tree, so no nesting depth can overflow the stack and there is no depth bound to configure
WebVTT
- WEBVTT signature and header text
- Timestamps (short
MM:SS.mmmand longHH:MM:SS.mmmforms, unbounded hours) - Cue identifiers (zero-copy
&str) - Cue settings (
vertical,line,position,size,align,region) - NOTE, STYLE, REGION blocks
- Full REGION definition parsing (
id,width,lines,regionanchor,viewportanchor,scroll) - Float percentages (e.g.,
50.5%) - CRLF, CR, LF line endings
- BOM handling
- Error recovery (
-->in cue body, malformed timing lines) - Writer with round-trip fidelity (
stdfeature) - Cue text parsing — two-layer design:
CueParser: logos DFA-backed, zero-alloc token stream (no_std)CueText: W3C spec-compliant DOM tree builder withNode/TagNodetypes (alloc/std)- Tags:
<b>,<i>,<u>,<c>,<ruby>,<rt>,<v>,<lang>, with classes and annotations - W3C tree building algorithm (§6.4):
<rt>scoped on the current node,</ruby>closing an open<rt>along with its<ruby>, unterminated tag handling - §6.4's list of applicable classes (
TagNode::classes), which excludes the empty classes between adjacent.separators —TagNode::classes_rawkeeps the source text - §6.4's applicable language, derived from the tree rather than stored on every node:
TagNode::declared_languagefor one step of a descent,CueText::nodes_with_languagefor the whole tree in document order - Bounded nesting depth (
cue::Options::max_depth, default 16), so a hostile cue cannot overflow the stack while the tree is built, walked or dropped — every walk at the default limit is held to a 128 KiB thread by the test suite;CueText::try_parserefuses such input rather than flattening it - Full HTML5 named character reference support (2,231 entities via
phfperfect hash map) - Numeric (
 ) and hexadecimal ( ) character references - Lazy text normalization via
CueStrwithOnceCell-cached decoding and NULL (U+0000 → U+FFFD) replacement
ASS/SSA
ASS/SSA has no formal specification; the behaviour below follows the SSA v4.00 specification document together with the VSFilter/libass renderers.
- Sections (
[Script Info],[V4 Styles],[V4+ Styles],[Events],[Fonts],[Graphics], and unknown sections verbatim) - Timestamps (
H:MM:SS.cc, centiseconds, unpadded and unbounded hours) - Event rows (
Dialogue:,Comment:,Picture:,Sound:,Movie:,Command:) -
Format:-driven field order —EventFormatpresets for ASS v4+, SSA v4 and Matroska packets - The
Name(a.k.a.Actor) speaker column surfaced as first-class data -
Style:rows exposed as zero-copy fields (style rendering semantics are out of scope) -
[Fonts]/[Graphics]payload lines preserved verbatim, never mistaken for properties - Strict and lossy parsing modes
- CRLF, CR, LF line endings, and BOM handling
- Writer with round-trip fidelity (
stdfeature) - Event text parsing — two-layer design, both usable standalone:
TextParser: logos DFA-backed, zero-alloc token stream (no_std, noallocneeded)PlainText: clean-text extraction withOnceCell-cached lazy normalization, plus an allocation-freesegments()iterator- Override blocks
{…}, with libass tag boundaries: longest-match names (\fscxis not\fs+cx), spaces skipped after the backslash, and argument lists ending at the first)(\t(0,500,\frz360)stays one tag) - libass brace semantics:
{opens a block only when a}follows,\{and\}are literal-brace escapes, so cleaning never deletes text a renderer would show \N,\nand\hescapes;\p<n>drawing mode is tokenized and skipped, never interpreted as geometry- Columns a
Format:line declares under an unrecognized name are preserved through a write, not dropped
- Standalone event parsing for embedded Matroska
S_TEXT/ASStracks, which arrive one event per packet with container timing authoritative
Optional dependencies
| Feature | Default | Description |
|---|---|---|
std |
Yes | Enables std::io writer and thiserror::Error impls |
alloc |
No | Enables CueText DOM tree and entity decoding without std |
memchr |
Yes (via alloc/std) |
SIMD-accelerated fast path for entity decoding |
Benchmarks
Measured on Apple Silicon with cargo bench (Criterion).
SRT
| Benchmark | Input | Time | Throughput |
|---|---|---|---|
| Parse (strict) | 2 cues, 89 B | ~170 ns | 520 MiB/s |
| Parse (strict) | 26 KB file | ~38 µs | 661 MiB/s |
| Parse (lossy) | 332 files, ~8 MB | ~12.1 ms | 646 MiB/s |
Collect into Vec |
26 KB file | ~40 µs | 616 MiB/s |
WebVTT
| Benchmark | Input | Time | Throughput |
|---|---|---|---|
| Parse | 2 cues, 96 B | ~318 ns | 291 MiB/s |
| Parse | Settings + region + style, 354 B | ~915 ns | 387 MiB/s |
| Parse | All WPT fixtures, ~34 KB | ~113 µs | 314 MiB/s |
Collect into Vec |
Settings + region + style, 354 B | ~973 ns | 364 MiB/s |
Cue Text
| Benchmark | Input | Time | Throughput |
|---|---|---|---|
| Parse | Tags only, 166 B | ~316 ns | 552 MiB/s |
| Parse | 500 timestamps, ~11 KB | ~14.1 µs | 776 MiB/s |
Run benchmarks yourself:
License
fasrt is under the terms of both the MIT license and the
Apache License (Version 2.0).
See LICENSE-APACHE, LICENSE-MIT for details.
Copyright (c) 2026 FinDIT Studio authors.