subcue 0.1.1

Parse, compose, and convert SRT, WebVTT, and ASS/SSA subtitle files
Documentation
# subcue

`subcue` parses, composes, and converts SRT, WebVTT, and ASS/SSA subtitle files.
It accepts byte slices for complete files and `BufRead` values for cue iteration.

The data model keeps cue order, signed millisecond timestamps, raw cue text, and
format metadata. Parsing accepts UTF-8. A UTF-8 byte order mark is removed with a
warning. UTF-16 input and invalid UTF-8 return typed errors.

## Parse and compose

```rust
use subcue::{parse_srt, Time};

let input = b"1\n00:00:01,000 --> 00:00:02,500\nHello\n\n";
let subtitles = parse_srt(input)?;

assert_eq!(subtitles.cues.len(), 1);
assert_eq!(subtitles.cues[0].start, Time(1_000));
assert_eq!(subtitles.cues[0].text, "Hello");

let (bytes, warnings) = subtitles.compose();
assert_eq!(bytes, input);
assert!(warnings.is_empty());
# Ok::<(), subcue::Error>(())
```

`compose_with` can write CRLF line endings or add a UTF-8 byte order mark.
Default composition writes LF line endings without a mark.

## Convert formats

```rust
use subcue::{parse_srt, Format};

let input = b"1\n00:00:01,000 --> 00:00:02,000\n<i>Hello</i>\n\n";
let srt = parse_srt(input)?;
let vtt = srt.convert(Format::Vtt);
let (bytes, warnings) = vtt.compose();

assert!(bytes.starts_with(b"WEBVTT\n\n"));
assert!(warnings.is_empty());
# Ok::<(), subcue::Error>(())
```

Conversion keeps cue order and timing. ASS output truncates milliseconds to
centiseconds and reports `Warning::TimePrecisionLost`. Settings, identifiers,
blocks, events, and text tags that have no target representation produce a
specific warning.

Every conversion reads the source tags into a `text::Span` tree and writes
that tree in the target dialect. The `text` module is public, so a caller can
parse or render inline formatting without converting a whole file.

```rust
use subcue::text::{parse, render, Span};
use subcue::Format;

let parsed = parse("<b>bold <i>both</i></b>", Format::Srt);
assert!(matches!(parsed.spans[0], Span::Bold(_)));
let ass = render(&parsed.spans, Format::Ass);
assert_eq!(ass.text, "{\\b1}bold {\\i1}both{\\i0}{\\b0}");
```

## Iterate over cues

```rust
use std::io::Cursor;
use subcue::SrtCues;

let input = b"1\n00:00:01,000 --> 00:00:02,000\nFirst\n\n";
let mut cues = SrtCues::new(Cursor::new(input));
assert_eq!(cues.next().unwrap()?.text, "First");
assert!(cues.next().is_none());
# Ok::<(), subcue::Error>(())
```

`VttCues` reads the signature and header in its constructor. `AssCues` reads
script and style metadata there. Each iterator keeps one line buffer and one
text accumulator, allocates 1 time per cue for SRT, 3 for WebVTT, and 5 for ASS, and exposes its collected
warnings. A failed read from the source is returned as `Error::Io`.

## Errors and warnings

Fatal errors cover decoding failures, timestamp failures, a missing WebVTT
signature, and content before an ASS section. Recoverable input stays in the
model and adds a warning. Examples include mixed line endings, missing SRT
indices, unknown ASS styles, and a cue that ends before it starts.

The library uses checked integer arithmetic for every timestamp calculation.
All parser entry points return a result for arbitrary input bytes.

## Toolchain and dependencies

The library has no runtime dependencies. It supports Rust 1.70.0 and edition
2021.

## Relation to subparse

subcue is an independent implementation. It is not affiliated with subparse
or its author. Both read SRT, WebVTT, and ASS/SSA files. subcue also composes
each format, converts between the three, and streams cues from a reader.

## License

MIT