# Migration Guide
## 2.0 → 2.1
### Behavior changes
- **SCC drop-frame timecodes**: previously SCC used the non-drop formula for
all timecodes, causing NTSC long-form video (`;` separator) to drift ~3.6
seconds per hour. 2.1 correctly implements SMPTE 12M-1-2014 drop-frame.
If you relied on the (incorrect) numerical values, re-parse your SCC files.
Key invariant: `01:00:00;00` (drop) now equals exactly 3600000ms (was
~3603604ms under the buggy non-drop handling).
- **EBU STL serialization**: previously `ebu_stl::to_string` wrote corrupted
TTI timecodes (`ms / 40` instead of milliseconds). 2.1 produces correct
files that round-trip through `parse_bytes`. Old corrupted files are not
parseable by 2.1, but were unusable anyway.
- **UTF-16 decoding**: UTF-16-encoded subtitle files now correctly strip the
U+FEFF BOM (matching UTF-8 path behavior). Downstream code that depended
on a leading BOM character must be adjusted.
- **`split_long` duration**: when a subtitle's duration is shorter than the
number of chunks it splits into, the effective end is now stretched so
every chunk has at least 1ms duration. Subtitles that previously had
`start == end` (zero-duration, failing `validate()`) are now valid.
### Additions
- `encoding::decode_to_string` now skips the 2-byte BOM before UTF-16 decoding
and returns an empty string (not a panic/error) for inputs under 2 bytes.
- `scc_timecode_to_ms` now takes a `drop_frame: bool` 6th argument.
---
# Migrating from 1.x to 2.0.0
2.0.0 completes the v2 API unification — all `parse_*` functions now return
`SubtitleFile`, errors are structured, and the internals have been modularized.
## All `parse_content` / `parse_bytes` / `parse_file` return `SubtitleFile`
Every format module (SRT, VTT, ASS, TTML, SBV, LRC, SAMI, MicroDVD,
SubViewer, MPL2, SCC, EBU STL) now consistently returns `SubtitleFile`
from all public parse entry points. Previously some modules returned
`Vec<Subtitle>` (e.g. VTT, MPL2 parse_bytes).
```rust
// Before (1.x)
let subs: Vec<Subtitle> = subtitler::vtt::parse_content(&text)?;
let subs: Vec<Subtitle> = subtitler::mpl2::parse_bytes(data)?;
// After (2.0)
let file: SubtitleFile = subtitler::vtt::parse_content(&text)?;
let file: SubtitleFile = subtitler::mpl2::parse_bytes(data)?;
// Access subtitles via trait:
let subs: &[Subtitle] = file.subtitles();
```
## Accessing subtitles from `SubtitleFile`
`SubtitleFile` is a parsed file, not a `Vec`. Use the `SubtitleFormat`
trait to access shared methods:
```rust
use subtitler::SubtitleFormat; // re-exported from model
let file = subtitler::srt::parse_content(&content)?;
let count = file.subtitles().len(); // not file.len()
let first = &file.subtitles()[0]; // not &file[0]
file.validate(); // trait method
file.sort(); // trait method
```
## `parse_timestamp` / `parse_timestamps` now require `Format`
```rust
// Before
subtitler::utils::parse_timestamp("00:00:01,000")?;
// After
subtitler::utils::parse_timestamp("00:00:01,000", Format::Srt)?;
subtitler::utils::parse_timestamps("... --> ...", Format::Vtt)?;
```
## Structured errors: `SubtitleError` replaces `anyhow` in internals
Format modules now use structured `SubtitleError` variants instead of
`anyhow!()` macros. Public API still returns `AnyResult` via `?` coercion.
New error variants give you format-aware context:
```rust
match subtitler::srt::parse_content(&text) {
Ok(file) => { /* ... */ }
Err(e) => {
// e is anyhow::Error, but the source is a SubtitleError:
if let Some(se) = e.downcast_ref::<subtitler::error::SubtitleError>() {
match se {
SubtitleError::InvalidTimestamp { format, value } => { /* ... */ }
SubtitleError::UnexpectedLine { format, row, expected, got } => { /* ... */ }
_ => {}
}
}
}
}
```
## `encoding::decode_to_string` returns `Result<_, SubtitleError>`
```rust
// Before
let text: anyhow::Result<String> = subtitler::encoding::decode_to_string(data);
// After
let text: Result<String, subtitler::error::SubtitleError> =
subtitler::encoding::decode_to_string(data);
```
## `SCC::to_string` now accepts `drop_frame` parameter
```rust
// Before
let scc = subtitler::scc::to_string(&subs);
// After
let scc = subtitler::scc::to_string(&subs, true); // drop-frame
let scc = subtitler::scc::to_string(&subs, false); // non-drop-frame
```
## Data type `to_string()` renamed to `render()`
`LrcData::to_string`, `SamiData::to_string`, `Mpl2Data::to_string`,
`SccData::to_string` renamed to `render()` to avoid shadowing
`std::string::ToString`.
```rust
// Before
data.to_string();
// After
data.render();
```
## Available feature flags
```toml
subtitler = { version = "2.0", default-features = false,
features = ["srt", "vtt", "ass", "ssa", "microdvd", "subviewer",
"ttml", "sbv", "lrc", "sami", "mpl2", "scc", "ebu_stl", "http"] }
```
---
# Migrating from 0.1.x to 1.0.0
1.0.0 unifies the subtitle architecture. Here's how to update.
## `SubtitleFormat` enum renamed to `Format`
```rust
// Before
use subtitler::model::SubtitleFormat;
let f: SubtitleFormat = file.format();
// After
use subtitler::model::Format;
let f: Format = file.format();
```
(`SubtitleFormat` is now the name of a **trait** — see below.)
## New `SubtitleFormat` trait
Methods like `validate()`, `shift_all()`, `merge_adjacent()`, `sort()`,
`map()`, `filter()` etc. moved from inherent methods on `SubtitleFile` to the
`SubtitleFormat` trait (with default implementations). Bring the trait into
scope at call sites:
```rust
use subtitler::model::SubtitleFormat; // or: use subtitler::SubtitleFormat;
file.validate(); // works
file.shift_all(1000);
```
## New `SubtitleFile` variants: `MicroDvd`, `SubViewer`, `Ssa`
MicroDVD and SubViewer no longer collapse into `Srt`. If you pattern-match on
`SubtitleFile`, add arms (and note they now preserve data that was previously
silently lost):
```rust
// Before: MicroDVD parsed as SubtitleFile::Srt(...), fps discarded.
// After:
match file {
subtitler::model::SubtitleFile::MicroDvd { fps, subtitles } => { /* fps preserved */ }
subtitler::model::SubtitleFile::SubViewer { header, subtitles } => { /* header preserved */ }
subtitler::model::SubtitleFile::Ssa(data) => { /* shares AssData shape with Ass */ }
_ => {}
}
```
## Parsing cores are now sync
`srt::parse_content`, `srt::parse_bytes`, and the `vtt::` equivalents are no
longer `async`:
```rust
// Before
let subs = subtitler::srt::parse_content(&text).await?;
// After
let subs = subtitler::srt::parse_content(&text)?;
```
`parse_file` and `parse_url` remain `async`.
## New unified entry points (recommended)
```rust
let file = subtitler::parse_bytes(&data)?;
let file = subtitler::parse_file("path.sub").await?;
#[cfg(feature = "http")]
let file = subtitler::parse_url("https://example.com/sub.vtt").await?;
```
## Removed Subtitle fields
The `Subtitle` struct no longer has `layer`, `margin_l`, `margin_r`, `margin_v`, or `effect` fields.
## Per-format feature flags
```toml
subtitler = { version = "1.0", default-features = false, features = ["srt", "vtt"] }
```
Available flags: `srt`, `vtt`, `ass`, `ssa`, `microdvd`, `subviewer`, `ttml`, `sbv`, `lrc`, `sami`, `mpl2`, `scc`, `ebu_stl`, `http`.