1use crate::{
12 Error, Reader, Result, bail,
13 boxes::{MdhdBox, TfdtBox, TfhdBox, TrunBox, TrunSample},
14 data,
15 parser::{self, Mp4Parser},
16 sub::builder::{Cue, Subtitles},
17};
18use std::{cell::RefCell, rc::Rc};
19
20pub struct WvttSubsParser {
22 pub timescale: u32,
23}
24
25impl WvttSubsParser {
26 pub fn from_init(data: &[u8]) -> Result<Self> {
28 let saw_wvtt = data!(false);
29 let timescale = data!();
30
31 let saw_wvtt_c = saw_wvtt.clone();
32 let timescale_c = timescale.clone();
33
34 Mp4Parser::new()
35 .base_box("moov", parser::children)
36 .base_box("trak", parser::children)
37 .base_box("mdia", parser::children)
38 .full_box("mdhd", move |mut box_| {
39 let box_version = box_.version.unwrap();
40
41 if box_version != 0 && box_version != 1 {
42 bail!("MDHD box version can only be 0 or 1.");
43 }
44
45 let mdhd_box = MdhdBox::new(&mut box_)?;
46 *timescale_c.borrow_mut() = Some(mdhd_box.timescale);
47 Ok(())
48 })
49 .base_box("minf", parser::children)
50 .base_box("stbl", parser::children)
51 .full_box("stsd", parser::sample_description)
52 .base_box("wvtt", move |_| {
53 *saw_wvtt_c.borrow_mut() = true;
55 Ok(())
56 })
57 .parse(data, false, false)?;
58
59 if !saw_wvtt.take() {
60 bail!("WVTT box not found.");
61 }
62
63 if let Some(timescale) = timescale.take() {
64 Ok(Self { timescale })
65 } else {
66 Err(Error::Other(
67 "Missing timescale (should exist inside MDHD box).".to_owned(),
68 ))
69 }
70 }
71
72 pub fn parse(&self, data: &[u8], period_start: Option<f32>) -> Result<Subtitles> {
74 let period_start = period_start.unwrap_or_default();
75 let timescale = self.timescale;
76
77 let base_time = data!(0_u64);
78 let default_duration = data!();
79 let presentations = data!(Vec::new());
80 let saw_tfdt = data!(false);
81 let saw_trun = data!(false);
82 let subtitles = data!(Subtitles::new());
83
84 let base_time_c = base_time.clone();
85 let default_duration_c = default_duration.clone();
86 let presentations_c = presentations.clone();
87 let saw_tfdt_c = saw_tfdt.clone();
88 let saw_trun_c = saw_trun.clone();
89 let subtitles_c = subtitles.clone();
90
91 Mp4Parser::new()
92 .base_box("moof", parser::children)
93 .base_box("traf", parser::children)
94 .full_box("tfdt", move |mut box_| {
95 *saw_tfdt_c.borrow_mut() = true;
96
97 let box_version = box_.version.unwrap();
98
99 if box_version != 0 && box_version != 1 {
100 bail!("TFDT box version can only be 0 or 1.");
101 }
102
103 let tfdt_box = TfdtBox::new(&mut box_)?;
104 *base_time_c.borrow_mut() = tfdt_box.base_media_decode_time;
105 Ok(())
106 })
107 .full_box("tfhd", move |mut box_| {
108 if box_.flags.is_none() {
109 bail!("TFHD box should have a valid flags value.");
110 }
111
112 let tfhd_box = TfhdBox::new(&mut box_)?;
113 *default_duration_c.borrow_mut() = tfhd_box.default_sample_duration;
114 Ok(())
115 })
116 .full_box("trun", move |mut box_| {
117 *saw_trun_c.borrow_mut() = true;
118
119 if box_.version.is_none() {
120 bail!("TRUN box should have a valid version value.");
121 }
122
123 if box_.flags.is_none() {
124 bail!("TRUN box should have a valid flags value.");
125 }
126
127 let trun_box = TrunBox::new(&mut box_)?;
128 *presentations_c.borrow_mut() = trun_box.sample_data;
129 Ok(())
130 })
131 .base_box(
132 "mdat",
133 parser::alldata(move |data| {
134 if !*saw_tfdt.borrow() && !*saw_trun.borrow() {
135 bail!("Some required boxes (either TFDT or TRUN) are missing.");
136 }
137
138 let cues = Self::parse_mdat(
139 *base_time.borrow(),
140 *default_duration.borrow(),
141 period_start,
142 &presentations.borrow(),
143 &data,
144 timescale,
145 )?;
146 subtitles_c.borrow_mut().extend_cues(cues);
147 Ok(())
148 }),
149 )
150 .parse(data, false, false)?;
151
152 Ok(subtitles.take())
153 }
154
155 fn parse_mdat(
156 base_time: u64,
157 default_duration: Option<u32>,
158 period_start: f32,
159 presentations: &[TrunSample],
160 raw_payload: &[u8],
161 timescale: u32,
162 ) -> Result<Vec<Cue>> {
163 let mut cues = Vec::new();
164 let mut current_time = base_time;
165 let mut reader = Reader::new_big_endian(raw_payload);
166
167 for presentation in presentations {
168 let duration = presentation.sample_duration.or(default_duration);
171 let start_time = if let Some(sample_composition_time_offset) =
172 presentation.sample_composition_time_offset
173 {
174 base_time + sample_composition_time_offset as u64
175 } else {
176 current_time
177 };
178
179 current_time = start_time + duration.unwrap_or_default() as u64;
180
181 let mut total_size = 0;
183 loop {
184 let payload_size = reader.read_u32()? as i32;
186 total_size += payload_size;
187
188 let payload_type = reader.read_u32()?;
190 let payload_name = parser::type_to_string(payload_type as usize)?;
191
192 let mut payload = None;
194
195 match payload_name.as_str() {
196 "vttc" => {
197 if payload_size > 8 {
198 payload = Some(reader.read_bytes_u8((payload_size - 8) as usize)?);
199 }
200 }
201 "vtte" => {
202 reader.skip((payload_size - 8) as u64)?;
205 }
206 _ => {
207 reader.skip((payload_size - 8) as u64)?;
209 }
210 }
211
212 if duration.is_some() {
213 if let Some(payload) = payload {
214 let cue = Self::parse_vttc(
215 &payload,
216 period_start + start_time as f32 / timescale as f32,
217 period_start + current_time as f32 / timescale as f32,
218 )?;
219 cues.push(cue);
220 }
221 } else {
222 bail!("WVTT sample duration unknown, and no default found.");
223 }
224
225 if !(presentation.sample_size.is_none()
226 || total_size <= presentation.sample_size.unwrap_or_default() as i32)
227 {
228 bail!(
229 "The samples do not fit evenly into the sample sizes given in the TRUN box."
230 );
231 };
232
233 if !(presentation.sample_size.is_some()
236 && (total_size < presentation.sample_size.unwrap_or_default() as i32))
237 {
238 break;
239 }
240 }
241 }
242
243 if reader.has_more_data() {
244 bail!("MDAT which contain VTT cues and non-VTT data are not currently supported.");
245 };
246
247 Ok(cues.into_iter().flatten().collect())
248 }
249
250 fn parse_vttc(data: &[u8], start_time: f32, end_time: f32) -> Result<Option<Cue>> {
252 let payload = Rc::new(RefCell::new(String::new()));
253 let settings = Rc::new(RefCell::new(String::new()));
254
255 let payload_c = payload.clone();
256 let settings_c = settings.clone();
257
258 Mp4Parser::new()
259 .base_box(
260 "payl",
261 parser::alldata(move |data| {
262 *payload_c.borrow_mut() = String::from_utf8(data)?;
263 Ok(())
264 }),
265 )
266 .base_box(
274 "sttg",
275 parser::alldata(move |data| {
276 *settings_c.borrow_mut() = String::from_utf8(data)?;
277 Ok(())
278 }),
279 )
280 .parse(data, false, false)?;
281
282 let payload = payload.take();
283
284 if !payload.is_empty() {
285 return Ok(Some(Cue {
286 payload,
287 settings: settings.take(),
288 start_time,
289 end_time,
290 }));
291 }
292
293 Ok(None)
294 }
295}