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    pub x: Option<f32>,
31    pub y: Option<f32>,
32    pub align: TextAlign,
33    /// WebVTT `size:N%` cue setting. Irrelevant for ASS.
34    pub size: Option<f32>,
35}
36
37/// Horizontal alignment for a cue / a style row.
38#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
39pub enum TextAlign {
40    #[default]
41    Start,
42    Center,
43    End,
44    Left,
45    Right,
46}
47
48/// One inline element of a cue body.
49#[derive(Clone, Debug)]
50pub enum Segment {
51    Text(String),
52    LineBreak,
53    Bold(Vec<Segment>),
54    Italic(Vec<Segment>),
55    Underline(Vec<Segment>),
56    Strike(Vec<Segment>),
57    Color {
58        rgb: (u8, u8, u8),
59        children: Vec<Segment>,
60    },
61    Font {
62        family: Option<String>,
63        size: Option<f32>,
64        children: Vec<Segment>,
65    },
66    /// WebVTT `<v Speaker>...</v>`.
67    Voice {
68        name: String,
69        children: Vec<Segment>,
70    },
71    /// WebVTT `<c.classname>...</c>`.
72    Class {
73        name: String,
74        children: Vec<Segment>,
75    },
76    /// ASS `{\k<cs>}` — the following text is highlighted for `cs` centiseconds.
77    /// The children slice is the text under this karaoke beat (until the next
78    /// `\k` override).
79    Karaoke {
80        cs: u32,
81        children: Vec<Segment>,
82    },
83    /// WebVTT inline timestamp `<00:00:01.500>`.
84    Timestamp {
85        offset_us: i64,
86    },
87    /// Fallback for override tags we don't model explicitly. Carries the
88    /// textual source verbatim so a re-emit to the same format stays faithful.
89    Raw(String),
90}
91
92/// A named style definition — reusable across many cues.
93#[derive(Clone, Debug, Default)]
94pub struct SubtitleStyle {
95    pub name: String,
96    pub font_family: Option<String>,
97    pub font_size: Option<f32>,
98    pub primary_color: Option<(u8, u8, u8, u8)>,
99    pub outline_color: Option<(u8, u8, u8, u8)>,
100    pub back_color: Option<(u8, u8, u8, u8)>,
101    pub bold: bool,
102    pub italic: bool,
103    pub underline: bool,
104    pub strike: bool,
105    pub align: TextAlign,
106    pub margin_l: Option<i32>,
107    pub margin_r: Option<i32>,
108    pub margin_v: Option<i32>,
109    pub outline: Option<f32>,
110    pub shadow: Option<f32>,
111}
112
113impl SubtitleStyle {
114    pub fn new(name: impl Into<String>) -> Self {
115        Self {
116            name: name.into(),
117            ..Default::default()
118        }
119    }
120}