1use anyhow::{bail, Context, Result};
8use std::fs::File;
9use std::io::{Read, Seek, SeekFrom};
10use std::path::Path;
11
12#[derive(Debug, Clone, Copy)]
13pub struct MpegSeqHeader {
14 pub width: u16,
15 pub height: u16,
16 pub aspect_ratio_code: u8,
17 pub frame_rate_code: u8,
18 pub bit_rate: u32,
19 pub vbv_buffer_size: u16,
20 pub constrained_parameters_flag: bool,
21}
22
23pub fn fps_from_frame_rate_code(code: u8) -> Option<f32> {
27 match code {
28 1 => Some(24000.0 / 1001.0),
29 2 => Some(24.0),
30 3 => Some(25.0),
31 4 => Some(30000.0 / 1001.0),
32 5 => Some(30.0),
33 6 => Some(50.0),
34 7 => Some(60000.0 / 1001.0),
35 8 => Some(60.0),
36 _ => None,
37 }
38}
39
40pub fn probe_sequence_header(
42 path: impl AsRef<Path>,
43 max_scan_bytes: usize,
44) -> Result<Option<MpegSeqHeader>> {
45 let mut f =
46 File::open(&path).with_context(|| format!("open MPEG: {}", path.as_ref().display()))?;
47 let file_len = f.seek(SeekFrom::End(0))?;
48 f.seek(SeekFrom::Start(0))?;
49 let to_read = std::cmp::min(max_scan_bytes as u64, file_len) as usize;
50 let mut buf = vec![0u8; to_read];
51 f.read_exact(&mut buf)?;
52 Ok(find_sequence_header(&buf))
53}
54
55pub fn find_sequence_header(data: &[u8]) -> Option<MpegSeqHeader> {
56 let mut i = 0usize;
58 while i + 4 < data.len() {
59 if data[i] == 0 && data[i + 1] == 0 && data[i + 2] == 1 && data[i + 3] == 0xB3 {
60 if i + 12 <= data.len() {
62 return parse_sequence_header(&data[i + 4..]);
63 }
64 return None;
65 }
66 i += 1;
67 }
68 None
69}
70
71fn parse_sequence_header(p: &[u8]) -> Option<MpegSeqHeader> {
72 if p.len() < 8 {
76 return None;
77 }
78 let width = ((p[0] as u16) << 4) | ((p[1] as u16) >> 4);
79 let height = (((p[1] as u16) & 0x0F) << 8) | (p[2] as u16);
80 let aspect_ratio_code = (p[3] >> 4) & 0x0F;
81 let frame_rate_code = p[3] & 0x0F;
82
83 let bit_rate = ((p[4] as u32) << 10) | ((p[5] as u32) << 2) | ((p[6] as u32) >> 6);
84 let marker_bit = (p[6] >> 5) & 0x01;
85 if marker_bit != 1 {
86 return None;
88 }
89 let vbv_buffer_size = (((p[6] as u16) & 0x1F) << 5) | ((p[7] as u16) >> 3);
90 let constrained_parameters_flag = ((p[7] >> 2) & 0x01) != 0;
91
92 Some(MpegSeqHeader {
93 width,
94 height,
95 aspect_ratio_code,
96 frame_rate_code,
97 bit_rate,
98 vbv_buffer_size,
99 constrained_parameters_flag,
100 })
101}
102
103pub fn ensure_mpeg_like(path: impl AsRef<Path>) -> Result<MpegSeqHeader> {
105 match probe_sequence_header(path, 1 << 20)? {
106 Some(h) => Ok(h),
107 None => bail!("no MPEG sequence header found"),
108 }
109}
110
111#[derive(Debug, Clone)]
117pub struct VideoFrameRgba {
118 pub width: u32,
119 pub height: u32,
120 pub pts: Option<i64>,
121 pub rgba: Vec<u8>,
122}
123
124pub fn decode_mpeg2_to_rgba_frames(
125 path: impl AsRef<Path>,
126 max_frames: Option<usize>,
127) -> Result<Vec<VideoFrameRgba>> {
128 let path = path.as_ref();
129 let bytes = std::fs::read(path).with_context(|| format!("read MPEG: {}", path.display()))?;
130 let mut out = Vec::<VideoFrameRgba>::new();
131 let mut pipeline = na_mpeg2_decoder::MpegVideoPipeline::new();
132 pipeline
133 .push_with(&bytes, None, |f| {
134 if max_frames.map_or(false, |limit| out.len() >= limit) {
135 return;
136 }
137 let w = f.width as u32;
138 let h = f.height as u32;
139 let mut rgba = vec![0u8; (w as usize).saturating_mul(h as usize).saturating_mul(4)];
140 na_mpeg2_decoder::frame_to_rgba_bt601_limited(&f, &mut rgba);
141 out.push(VideoFrameRgba {
142 width: w,
143 height: h,
144 pts: None,
145 rgba,
146 });
147 })
148 .context("mpeg2 decode")?;
149 pipeline.flush_with(|f| {
150 if max_frames.map_or(false, |limit| out.len() >= limit) {
151 return;
152 }
153 let w = f.width as u32;
154 let h = f.height as u32;
155 let mut rgba = vec![0u8; (w as usize).saturating_mul(h as usize).saturating_mul(4)];
156 na_mpeg2_decoder::frame_to_rgba_bt601_limited(&f, &mut rgba);
157 out.push(VideoFrameRgba {
158 width: w,
159 height: h,
160 pts: None,
161 rgba,
162 });
163 })?;
164 if let Some(limit) = max_frames {
165 out.truncate(limit);
166 }
167 Ok(out)
168}