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}