Skip to main content

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}