oxideav_core/subtitle.rs
1//! Unified subtitle cue representation.
2//!
3//! Produced by subtitle-format decoders (SRT, WebVTT, ASS/SSA) and consumed
4//! by the corresponding encoders. Timing is expressed in microseconds from
5//! the start of the stream so the IR is format-independent.
6
7/// A single displayable subtitle event.
8#[derive(Clone, Debug, Default)]
9pub struct SubtitleCue {
10 /// Cue start, microseconds from stream start.
11 pub start_us: i64,
12 /// Cue end, microseconds from stream start.
13 pub end_us: i64,
14 /// Optional style name this cue inherits from. References an entry in
15 /// the track-level style table (ASS `Style:` rows or WebVTT `::cue(.X)` rules).
16 pub style_ref: Option<String>,
17 /// Optional overriding position for this cue. `None` → use the style default.
18 pub positioning: Option<CuePosition>,
19 /// Cue body as a sequence of styled segments.
20 pub segments: Vec<Segment>,
21}
22
23/// Positioning information for a cue.
24///
25/// Interpretation differs by source format:
26/// * WebVTT — `x`/`y` are percentages of the viewport, `align` from cue settings.
27/// * ASS `\pos(x, y)` — absolute pixel coordinates in the `PlayResX`×`PlayResY` canvas.
28#[derive(Clone, Debug, Default)]
29pub struct CuePosition {
30 /// Horizontal position (WebVTT `position:N%` percentage, or ASS
31 /// `\pos` pixel X). `None` → format default.
32 pub x: Option<f32>,
33 /// Vertical position (WebVTT `line:N%` percentage, or ASS `\pos`
34 /// pixel Y). `None` → format default.
35 pub y: Option<f32>,
36 /// Horizontal text alignment for this cue.
37 pub align: TextAlign,
38 /// WebVTT `size:N%` cue setting. Irrelevant for ASS.
39 pub size: Option<f32>,
40}
41
42/// Horizontal alignment for a cue / a style row.
43#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
44pub enum TextAlign {
45 /// Aligned to the text-direction start edge (left in left-to-right
46 /// scripts). WebVTT default.
47 #[default]
48 Start,
49 /// Centered.
50 Center,
51 /// Aligned to the text-direction end edge (right in left-to-right
52 /// scripts).
53 End,
54 /// Aligned to the left edge regardless of text direction.
55 Left,
56 /// Aligned to the right edge regardless of text direction.
57 Right,
58}
59
60/// One inline element of a cue body.
61#[derive(Clone, Debug)]
62pub enum Segment {
63 /// Plain text run.
64 Text(String),
65 /// Explicit line break (SRT/WebVTT newline, ASS `\N`).
66 LineBreak,
67 /// Bold-styled children (`<b>`, ASS `\b1`).
68 Bold(Vec<Segment>),
69 /// Italic-styled children (`<i>`, ASS `\i1`).
70 Italic(Vec<Segment>),
71 /// Underlined children (`<u>`, ASS `\u1`).
72 Underline(Vec<Segment>),
73 /// Strikethrough children (`<s>`, ASS `\s1`).
74 Strike(Vec<Segment>),
75 /// Children rendered in a specific text color (SRT `<font color>`,
76 /// ASS `\c`).
77 Color {
78 /// Text color as an `(r, g, b)` triple, each channel `0..=255`.
79 rgb: (u8, u8, u8),
80 /// Segments the color applies to.
81 children: Vec<Segment>,
82 },
83 /// Children rendered with a font override (SRT `<font>`, ASS
84 /// `\fn` / `\fs`).
85 Font {
86 /// Font family name. `None` → inherit.
87 family: Option<String>,
88 /// Font size in the source format's units (ASS `\fs` points /
89 /// `<font size>` value). `None` → inherit.
90 size: Option<f32>,
91 /// Segments the font override applies to.
92 children: Vec<Segment>,
93 },
94 /// WebVTT `<v Speaker>...</v>`.
95 Voice {
96 /// Speaker name (the `<v>` annotation).
97 name: String,
98 /// Segments spoken by this voice.
99 children: Vec<Segment>,
100 },
101 /// WebVTT `<c.classname>...</c>`.
102 Class {
103 /// CSS class name (without the leading dot).
104 name: String,
105 /// Segments the class applies to.
106 children: Vec<Segment>,
107 },
108 /// ASS `{\k<cs>}` — the following text is highlighted for `cs` centiseconds.
109 /// The children slice is the text under this karaoke beat (until the next
110 /// `\k` override).
111 Karaoke {
112 /// Beat duration in centiseconds (the `\k` argument).
113 cs: u32,
114 /// Segments highlighted during this beat.
115 children: Vec<Segment>,
116 },
117 /// WebVTT inline timestamp `<00:00:01.500>`.
118 Timestamp {
119 /// Timestamp value, microseconds from stream start.
120 offset_us: i64,
121 },
122 /// Fallback for override tags we don't model explicitly. Carries the
123 /// textual source verbatim so a re-emit to the same format stays faithful.
124 Raw(String),
125}
126
127/// A named style definition — reusable across many cues.
128#[derive(Clone, Debug, Default)]
129pub struct SubtitleStyle {
130 /// Style name that cues reference via [`SubtitleCue::style_ref`]
131 /// (ASS `Style:` name or WebVTT cue class).
132 pub name: String,
133 /// Font family name. `None` → renderer default.
134 pub font_family: Option<String>,
135 /// Font size in the source format's units (ASS `Fontsize` points).
136 /// `None` → renderer default.
137 pub font_size: Option<f32>,
138 /// Main text fill color as `(r, g, b, a)`, each channel `0..=255`.
139 /// `None` → renderer default.
140 pub primary_color: Option<(u8, u8, u8, u8)>,
141 /// Text outline (border) color as `(r, g, b, a)`. `None` → default.
142 pub outline_color: Option<(u8, u8, u8, u8)>,
143 /// Background / shadow color as `(r, g, b, a)` (ASS `BackColour`).
144 /// `None` → default.
145 pub back_color: Option<(u8, u8, u8, u8)>,
146 /// Bold text.
147 pub bold: bool,
148 /// Italic text.
149 pub italic: bool,
150 /// Underlined text.
151 pub underline: bool,
152 /// Strikethrough text.
153 pub strike: bool,
154 /// Horizontal text alignment.
155 pub align: TextAlign,
156 /// Left margin in pixels (ASS `MarginL`). `None` → default.
157 pub margin_l: Option<i32>,
158 /// Right margin in pixels (ASS `MarginR`). `None` → default.
159 pub margin_r: Option<i32>,
160 /// Vertical margin in pixels (ASS `MarginV`). `None` → default.
161 pub margin_v: Option<i32>,
162 /// Outline (border) thickness in pixels (ASS `Outline`).
163 /// `None` → default.
164 pub outline: Option<f32>,
165 /// Drop-shadow offset in pixels (ASS `Shadow`). `None` → default.
166 pub shadow: Option<f32>,
167}
168
169impl SubtitleStyle {
170 /// Build a style with the given name and every other field at its
171 /// default (`None` / `false` / [`TextAlign::Start`]).
172 pub fn new(name: impl Into<String>) -> Self {
173 Self {
174 name: name.into(),
175 ..Default::default()
176 }
177 }
178}