1use memchr::memchr;
6use std::ops::Range;
7
8use super::{MAX_VOBSUB_IMAGE_PIXELS, VobSubPalette};
9
10#[derive(Debug, Clone)]
11pub enum SubtitlePacketData {
12 SharedRange { start: usize, end: usize },
13 Owned(Vec<u8>),
14}
15
16#[derive(Debug, Clone)]
18pub struct SubtitlePacket {
19 pub timestamp_ms: u32,
21 pub duration_ms: u32,
23 pub x: u16,
25 pub y: u16,
27 pub width: u16,
29 pub height: u16,
31 pub color_indices: [u8; 4],
33 pub alpha_values: [u8; 4],
35 pub(crate) packet_data: SubtitlePacketData,
37 pub(crate) even_field_range: Range<usize>,
39 pub(crate) odd_field_range: Range<usize>,
41}
42
43impl SubtitlePacket {
44 fn packet_slice<'a>(&'a self, sub_data: &'a [u8]) -> &'a [u8] {
45 match &self.packet_data {
46 SubtitlePacketData::SharedRange { start, end } => &sub_data[*start..*end],
47 SubtitlePacketData::Owned(data) => data,
48 }
49 }
50
51 pub fn even_field_data<'a>(&'a self, sub_data: &'a [u8]) -> &'a [u8] {
52 let packet = self.packet_slice(sub_data);
53 &packet[self.even_field_range.clone()]
54 }
55
56 pub fn odd_field_data<'a>(&'a self, sub_data: &'a [u8]) -> &'a [u8] {
57 let packet = self.packet_slice(sub_data);
58 &packet[self.odd_field_range.clone()]
59 }
60}
61
62pub fn parse_subtitle_packet(
64 data: &[u8],
65 start_offset: usize,
66 _palette: &VobSubPalette,
67) -> Option<(SubtitlePacket, usize)> {
68 let mut offset = start_offset;
69 let data_len = data.len();
70
71 let max_scan = (start_offset + 262144).min(data_len);
73
74 let mut pts: u32 = 0;
75 let mut data_chunks: Vec<(usize, usize)> = Vec::new();
76 let mut expected_size: usize = 0;
77 let mut collected_size: usize = 0;
78
79 while offset < max_scan.saturating_sub(4) {
81 if let Some(pos) = memchr(0x00, &data[offset..max_scan.saturating_sub(3)]) {
83 let candidate = offset + pos;
84
85 if data[candidate + 1] != 0x00 || data[candidate + 2] != 0x01 {
86 offset = candidate + 1;
87 continue;
88 }
89 offset = candidate;
90 } else {
91 break;
92 }
93
94 let stream_id = data[offset + 3];
95
96 if stream_id == 0xBA {
98 offset += 4;
99
100 if offset < data_len && (data[offset] & 0xC0) == 0x40 {
102 offset += 9;
104 if offset < data_len {
105 let stuffing = (data[offset] & 0x07) as usize;
106 offset += 1 + stuffing;
107 }
108 } else {
109 offset += 8;
111 }
112 continue;
113 }
114
115 if stream_id == 0xBD {
117 offset += 4;
118
119 if offset + 2 > data_len {
120 break;
121 }
122
123 let pes_length = ((data[offset] as usize) << 8) | (data[offset + 1] as usize);
124 offset += 2;
125
126 let packet_end = match offset.checked_add(pes_length) {
127 Some(packet_end) if packet_end <= data_len => packet_end,
128 _ => break,
129 };
130
131 if offset + 3 > packet_end {
132 break;
133 }
134
135 let pes_flags = data[offset + 1];
137 let header_data_length = data[offset + 2] as usize;
138 offset += 3;
139
140 if offset + header_data_length > packet_end {
141 break;
142 }
143
144 if (pes_flags & 0x80) != 0 && pts == 0 && offset + 5 <= packet_end {
146 pts = extract_pts(data, offset);
147 }
148
149 offset += header_data_length;
150
151 if offset + 1 > packet_end {
153 break;
154 }
155 offset += 1;
156
157 let payload_length = packet_end.saturating_sub(offset);
159
160 if payload_length > 0 {
161 if expected_size == 0 && payload_length >= 2 {
163 expected_size = ((data[offset] as usize) << 8) | (data[offset + 1] as usize);
164 }
165
166 data_chunks.push((offset, offset + payload_length));
167 collected_size += payload_length;
168 offset += payload_length;
169
170 if expected_size > 0 && collected_size >= expected_size {
172 break;
173 }
174
175 continue;
176 }
177 }
178
179 if stream_id == 0xBE {
181 offset += 4;
182 if offset + 2 > data_len {
183 break;
184 }
185 let length = ((data[offset] as usize) << 8) | (data[offset + 1] as usize);
186 offset += 2 + length;
187 continue;
188 }
189
190 if stream_id >= 0xBC {
192 if !data_chunks.is_empty() {
193 break;
194 }
195 offset += 4;
196 if offset + 2 > data_len {
197 break;
198 }
199 let length = ((data[offset] as usize) << 8) | (data[offset + 1] as usize);
200 offset += 2 + length;
201 continue;
202 }
203
204 offset += 1;
205 }
206
207 if data_chunks.is_empty() {
209 return None;
210 }
211
212 if data_chunks.len() == 1 {
213 let (start, end) = data_chunks.into_iter().next().unwrap();
214 let trimmed_end = if expected_size > 0 {
215 start + expected_size.min(end - start)
216 } else {
217 end
218 };
219 let packet_source = SubtitlePacketData::SharedRange {
220 start,
221 end: trimmed_end,
222 };
223 let subtitle_data = &data[start..trimmed_end];
224 if subtitle_data.len() < 4 {
225 return None;
226 }
227
228 return parse_subtitle_data(packet_source, data, pts).map(|packet| (packet, offset));
229 } else {
230 let final_size = if expected_size > 0 {
231 expected_size.min(collected_size)
232 } else {
233 collected_size
234 };
235 let mut merged = Vec::with_capacity(final_size);
236 for (start, end) in data_chunks {
237 if merged.len() >= final_size {
238 break;
239 }
240
241 let remaining = final_size - merged.len();
242 let chunk = &data[start..end];
243 let take = remaining.min(chunk.len());
244 merged.extend_from_slice(&chunk[..take]);
245 }
246
247 if merged.len() < 4 {
248 return None;
249 }
250
251 return parse_subtitle_data(SubtitlePacketData::Owned(merged), data, pts)
252 .map(|packet| (packet, offset));
253 }
254}
255
256fn extract_pts(data: &[u8], offset: usize) -> u32 {
258 if offset + 5 > data.len() {
259 return 0;
260 }
261
262 let pts32_30 = ((data[offset] >> 1) & 0x07) as u64;
263 let pts29_15 = ((data[offset + 1] as u64) << 7) | ((data[offset + 2] >> 1) as u64);
264 let pts14_0 = ((data[offset + 3] as u64) << 7) | ((data[offset + 4] >> 1) as u64);
265
266 let pts = (pts32_30 << 30) | (pts29_15 << 15) | pts14_0;
268
269 (pts / 90) as u32
271}
272
273fn parse_subtitle_data(
275 packet_data: SubtitlePacketData,
276 source_data: &[u8],
277 pts: u32,
278) -> Option<SubtitlePacket> {
279 let data = match &packet_data {
280 SubtitlePacketData::SharedRange { start, end } => &source_data[*start..*end],
281 SubtitlePacketData::Owned(data) => data.as_slice(),
282 };
283
284 if data.len() < 4 {
285 return None;
286 }
287
288 let packet_start = 0;
289 let end_offset = data.len();
290
291 let dcsq_offset = ((data[2] as usize) << 8) | (data[3] as usize);
296 if dcsq_offset < 4 || dcsq_offset > end_offset {
297 return None;
298 }
299
300 let mut x: u16 = 0;
302 let mut y: u16 = 0;
303 let mut width: u16 = 0;
304 let mut height: u16 = 0;
305 let mut duration: u32 = 0;
306 let mut found_stop: bool = false;
307 let mut color_indices = [0u8, 1, 2, 3];
308 let mut alpha_values = [0u8, 15, 15, 15];
309 let mut top_field_offset: usize = 0;
310 let mut bottom_field_offset: usize = 0;
311
312 let mut ctrl_offset = packet_start + dcsq_offset;
313 let mut iterations = 0;
314 const MAX_ITERATIONS: usize = 1000; while ctrl_offset < end_offset && iterations < MAX_ITERATIONS && !found_stop {
317 iterations += 1;
318
319 let block_start = ctrl_offset;
321
322 if ctrl_offset + 4 > end_offset {
324 break;
325 }
326
327 let delay = ((data[ctrl_offset] as u32) << 8) | (data[ctrl_offset + 1] as u32);
328 ctrl_offset += 2;
329
330 let next_ctrl_offset =
332 ((data[ctrl_offset] as usize) << 8) | (data[ctrl_offset + 1] as usize);
333 ctrl_offset += 2;
334
335 while ctrl_offset < end_offset {
337 let cmd = data[ctrl_offset];
338 ctrl_offset += 1;
339
340 match cmd {
341 0x00 => {} 0x01 => {} 0x02 => {
344 duration = (delay * 1024) / 90;
346 found_stop = true;
347 }
348 0x03 => {
349 if ctrl_offset + 2 <= end_offset {
351 color_indices[3] = (data[ctrl_offset] >> 4) & 0x0F;
352 color_indices[2] = data[ctrl_offset] & 0x0F;
353 color_indices[1] = (data[ctrl_offset + 1] >> 4) & 0x0F;
354 color_indices[0] = data[ctrl_offset + 1] & 0x0F;
355 ctrl_offset += 2;
356 }
357 }
358 0x04 => {
359 if ctrl_offset + 2 <= end_offset {
361 alpha_values[3] = (data[ctrl_offset] >> 4) & 0x0F;
362 alpha_values[2] = data[ctrl_offset] & 0x0F;
363 alpha_values[1] = (data[ctrl_offset + 1] >> 4) & 0x0F;
364 alpha_values[0] = data[ctrl_offset + 1] & 0x0F;
365 ctrl_offset += 2;
366 }
367 }
368 0x05 => {
369 if ctrl_offset + 6 <= end_offset {
371 let x1 = ((data[ctrl_offset] as u16) << 4)
372 | ((data[ctrl_offset + 1] >> 4) as u16);
373 let x2 = (((data[ctrl_offset + 1] & 0x0F) as u16) << 8)
374 | (data[ctrl_offset + 2] as u16);
375 let y1 = ((data[ctrl_offset + 3] as u16) << 4)
376 | ((data[ctrl_offset + 4] >> 4) as u16);
377 let y2 = (((data[ctrl_offset + 4] & 0x0F) as u16) << 8)
378 | (data[ctrl_offset + 5] as u16);
379 if x2 < x1 || y2 < y1 {
380 return None;
381 }
382
383 let width_usize = (x2 - x1) as usize + 1;
384 let height_usize = (y2 - y1) as usize + 1;
385 if width_usize.checked_mul(height_usize)? > MAX_VOBSUB_IMAGE_PIXELS {
386 return None;
387 }
388
389 x = x1;
390 y = y1;
391 width = width_usize as u16;
392 height = height_usize as u16;
393 ctrl_offset += 6;
394 }
395 }
396 0x06 => {
397 if ctrl_offset + 4 <= end_offset {
399 top_field_offset =
400 ((data[ctrl_offset] as usize) << 8) | (data[ctrl_offset + 1] as usize);
401 bottom_field_offset = ((data[ctrl_offset + 2] as usize) << 8)
402 | (data[ctrl_offset + 3] as usize);
403 ctrl_offset += 4;
404 }
405 }
406 0xFF => break, _ => {}
408 }
409
410 if cmd == 0xFF || cmd == 0x02 {
411 break;
412 }
413 }
414
415 let next_block_abs = packet_start + next_ctrl_offset;
418
419 if next_ctrl_offset < dcsq_offset || next_block_abs <= block_start {
423 break;
424 }
425
426 ctrl_offset = next_block_abs;
427 }
428
429 let even_start = if top_field_offset > 0 {
431 top_field_offset
432 } else {
433 4
434 };
435 let odd_start = if bottom_field_offset > 0 {
436 bottom_field_offset
437 } else {
438 even_start
439 };
440
441 let even_field_end = odd_start;
442 let odd_field_end = packet_start + dcsq_offset;
443
444 let even_field_range = if even_start < even_field_end.min(end_offset) {
445 even_start..even_field_end.min(end_offset)
446 } else {
447 0..0
448 };
449
450 let odd_field_range = if odd_start < odd_field_end.min(end_offset) {
451 odd_start..odd_field_end.min(end_offset)
452 } else {
453 0..0
454 };
455
456 Some(SubtitlePacket {
457 timestamp_ms: pts,
458 duration_ms: if duration > 0 { duration } else { 5000 },
459 x,
460 y,
461 width,
462 height,
463 color_indices,
464 alpha_values,
465 packet_data,
466 even_field_range,
467 odd_field_range,
468 })
469}
470
471#[cfg(test)]
472mod tests {
473 use super::*;
474
475 #[test]
476 fn test_parse_subtitle_packet_rejects_short_pes_header() {
477 let data = [0x00, 0x00, 0x01, 0xBD, 0x00, 0x01, 0x00];
478
479 assert!(parse_subtitle_packet(&data, 0, &VobSubPalette::default()).is_none());
480 }
481
482 #[test]
483 fn test_parse_subtitle_packet_rejects_invalid_control_offset() {
484 let data = [0x00, 0x08, 0x00, 0x09, 0x11, 0x22, 0x33, 0x44];
485
486 assert!(parse_subtitle_data(SubtitlePacketData::Owned(data.to_vec()), &data, 0).is_none());
487 }
488
489 #[test]
490 fn test_parse_style_commands_use_dvd_nibble_order() {
491 let data = [
492 0x00, 0x10, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x03, 0xfd, 0x3b, 0x04, 0xdf, 0xd0, 0x01, 0xff, ];
498
499 let packet =
500 parse_subtitle_data(SubtitlePacketData::Owned(data.to_vec()), &data, 0).unwrap();
501
502 assert_eq!(packet.color_indices, [11, 3, 13, 15]);
503 assert_eq!(packet.alpha_values, [0, 13, 15, 13]);
504 }
505}