1pub mod codes;
12
13use std::io::Read;
14use std::path::Path;
15use thiserror::Error;
16
17#[derive(Debug, Clone, PartialEq)]
22pub struct SampleRate {
23 pub numerator: i64,
24 pub denominator: i64,
25}
26
27#[derive(Debug, Error)]
30pub enum MxfParseError {
31 #[error("IO error: {0}")]
32 Io(#[from] std::io::Error),
33 #[error("Not a valid MXF file: invalid header partition pack key")]
34 NotMxf,
35 #[error("KLV parse error at byte offset {offset}: {message}")]
36 KlvError { offset: u64, message: String },
37 #[error("Header partition pack missing or too short (got {got} bytes, need ≥ {need})")]
38 PartitionPackTooShort { got: usize, need: usize },
39}
40
41type Result<T> = std::result::Result<T, MxfParseError>;
42
43#[derive(Debug, Clone)]
50pub struct MxfHeaderInfo {
51 pub version: (u16, u16),
53 pub operational_pattern: String,
57 pub essence_containers: Vec<String>,
59 pub descriptor: Option<MxfDescriptor>,
61}
62
63#[derive(Debug, Clone)]
68pub enum MxfDescriptor {
69 Video(MxfVideoDescriptor),
70 Audio(MxfAudioDescriptor),
71 TimedText(MxfTimedTextDescriptor),
72}
73
74#[derive(Debug, Clone)]
76pub struct MxfVideoDescriptor {
77 pub stored_width: u32,
78 pub stored_height: u32,
79 pub sample_rate: SampleRate,
80 pub picture_compression_ul: Option<String>,
82 pub color_primaries_ul: Option<String>,
84 pub transfer_characteristic_ul: Option<String>,
86}
87
88#[derive(Debug, Clone)]
90pub struct MxfAudioDescriptor {
91 pub sample_rate: SampleRate,
92 pub channel_count: u32,
93 pub quantization_bits: u32,
94}
95
96#[derive(Debug, Clone)]
98pub struct MxfTimedTextDescriptor {
99 pub namespace_uri: Option<String>,
100}
101
102pub fn parse_mxf_header_info(path: &Path) -> Result<MxfHeaderInfo> {
106 let file = std::fs::File::open(path)?;
107 let mut reader = std::io::BufReader::new(file);
108 parse_mxf_header_info_from_reader(&mut reader)
109}
110
111pub fn parse_mxf_header_info_from_reader<R: Read>(reader: &mut R) -> Result<MxfHeaderInfo> {
115 let mut key = [0u8; 16];
117 reader.read_exact(&mut key).map_err(|e| {
118 if e.kind() == std::io::ErrorKind::UnexpectedEof {
119 MxfParseError::NotMxf
120 } else {
121 MxfParseError::Io(e)
122 }
123 })?;
124
125 const MXF_PP_PREFIX: [u8; 12] = [
131 0x06, 0x0E, 0x2B, 0x34, 0x02, 0x05, 0x01, 0x01, 0x0D, 0x01, 0x02, 0x01,
132 ];
133 if key[..12] != MXF_PP_PREFIX || key[12] != 0x01 {
134 return Err(MxfParseError::NotMxf);
135 }
136
137 let length = read_ber_length(reader, 16)?;
139
140 const MIN_PP_BODY: u64 = 88;
142 if length < MIN_PP_BODY {
143 return Err(MxfParseError::PartitionPackTooShort {
144 got: length as usize,
145 need: MIN_PP_BODY as usize,
146 });
147 }
148
149 let body_len = length.min(4096) as usize; let mut body = vec![0u8; body_len];
152 reader.read_exact(&mut body)?;
153
154 let major_version = u16::from_be_bytes([body[0], body[1]]);
171 let minor_version = u16::from_be_bytes([body[2], body[3]]);
172
173 let operational_pattern = format_ul(&body[64..80]);
175
176 let mut essence_containers = Vec::new();
178 if body.len() >= 88 {
179 let count = u32::from_be_bytes([body[80], body[81], body[82], body[83]]) as usize;
181 let elem_size = u32::from_be_bytes([body[84], body[85], body[86], body[87]]) as usize;
182
183 if elem_size == 16 {
184 let mut offset = 88;
185 for _ in 0..count {
186 if offset + 16 <= body.len() {
187 essence_containers.push(format_ul(&body[offset..offset + 16]));
188 offset += 16;
189 } else {
190 break;
191 }
192 }
193 }
194 }
195
196 Ok(MxfHeaderInfo {
197 version: (major_version, minor_version),
198 operational_pattern,
199 essence_containers,
200 descriptor: None,
201 })
202}
203
204fn read_ber_length<R: Read>(reader: &mut R, key_offset: u64) -> Result<u64> {
209 let mut first = [0u8; 1];
210 reader.read_exact(&mut first)?;
211 let first = first[0];
212
213 if first < 0x80 {
214 return Ok(first as u64);
215 }
216
217 if first == 0x80 {
218 return Err(MxfParseError::KlvError {
219 offset: key_offset + 16,
220 message: "Indefinite BER length not supported in partition packs".to_string(),
221 });
222 }
223
224 let num_bytes = (first & 0x7F) as usize;
225 if num_bytes > 8 {
226 return Err(MxfParseError::KlvError {
227 offset: key_offset + 16,
228 message: format!("BER length too wide: {num_bytes} bytes"),
229 });
230 }
231
232 let mut buf = [0u8; 8];
233 reader.read_exact(&mut buf[8 - num_bytes..])?;
234 Ok(u64::from_be_bytes(buf))
235}
236
237fn format_ul(bytes: &[u8]) -> String {
239 if bytes.len() < 16 {
240 return format!("(invalid-ul:{}-bytes)", bytes.len());
241 }
242 format!(
243 "urn:smpte:ul:{:02x}{:02x}{:02x}{:02x}.{:02x}{:02x}{:02x}{:02x}.\
244 {:02x}{:02x}{:02x}{:02x}.{:02x}{:02x}{:02x}{:02x}",
245 bytes[0],
246 bytes[1],
247 bytes[2],
248 bytes[3],
249 bytes[4],
250 bytes[5],
251 bytes[6],
252 bytes[7],
253 bytes[8],
254 bytes[9],
255 bytes[10],
256 bytes[11],
257 bytes[12],
258 bytes[13],
259 bytes[14],
260 bytes[15],
261 )
262}
263
264#[cfg(test)]
267mod tests {
268 use super::*;
269 use std::io::Cursor;
270
271 fn make_minimal_mxf_stream(op_ul: [u8; 16]) -> Vec<u8> {
274 let mut stream = Vec::new();
275
276 stream.extend_from_slice(&[
278 0x06, 0x0E, 0x2B, 0x34, 0x02, 0x05, 0x01, 0x01, 0x0D, 0x01, 0x02, 0x01, 0x01, 0x02,
279 0x04, 0x00,
280 ]);
281 stream.push(88);
283
284 stream.extend_from_slice(&[0x00, 0x01]);
287 stream.extend_from_slice(&[0x00, 0x03]);
289 stream.extend_from_slice(&[0x00, 0x00, 0x02, 0x00]);
291 stream.extend_from_slice(&[0u8; 8]);
293 stream.extend_from_slice(&[0u8; 8]);
295 stream.extend_from_slice(&[0u8; 8]);
297 stream.extend_from_slice(&[0u8; 8]);
299 stream.extend_from_slice(&[0u8; 8]);
301 stream.extend_from_slice(&[0u8; 4]);
303 stream.extend_from_slice(&[0u8; 8]);
305 stream.extend_from_slice(&[0u8; 4]);
307 stream.extend_from_slice(&op_ul);
309 stream.extend_from_slice(&[0x00, 0x00, 0x00, 0x00]); stream.extend_from_slice(&[0x00, 0x00, 0x00, 0x10]); assert_eq!(stream.len(), 16 + 1 + 88);
314 stream
315 }
316
317 #[test]
319 fn valid_header_partition_pack_parsed() {
320 let op1a: [u8; 16] = [
322 0x06, 0x0E, 0x2B, 0x34, 0x04, 0x01, 0x01, 0x02, 0x0D, 0x01, 0x02, 0x01, 0x01, 0x01,
323 0x09, 0x00,
324 ];
325 let stream = make_minimal_mxf_stream(op1a);
326 let mut cursor = Cursor::new(stream);
327 let info = parse_mxf_header_info_from_reader(&mut cursor).unwrap();
328
329 assert_eq!(info.version, (1, 3));
330 assert_eq!(
331 info.operational_pattern,
332 "urn:smpte:ul:060e2b34.04010102.0d010201.01010900"
333 );
334 assert!(info.essence_containers.is_empty());
335 assert!(info.descriptor.is_none());
336 }
337
338 #[test]
340 fn non_mxf_data_rejected() {
341 let data = vec![0u8; 105];
342 let mut cursor = Cursor::new(data);
343 assert!(matches!(
344 parse_mxf_header_info_from_reader(&mut cursor),
345 Err(MxfParseError::NotMxf)
346 ));
347 }
348
349 #[test]
352 fn body_partition_pack_rejected() {
353 let mut key = vec![
354 0x06, 0x0E, 0x2B, 0x34, 0x02, 0x05, 0x01, 0x01, 0x0D, 0x01, 0x02, 0x01, 0x02, 0x02,
355 0x04, 0x00, ];
357 key.extend_from_slice(&[0u8; 89]);
358 let mut cursor = Cursor::new(key);
359 assert!(matches!(
360 parse_mxf_header_info_from_reader(&mut cursor),
361 Err(MxfParseError::NotMxf)
362 ));
363 }
364
365 #[test]
367 fn essence_containers_parsed() {
368 let op: [u8; 16] = [
369 0x06, 0x0E, 0x2B, 0x34, 0x04, 0x01, 0x01, 0x02, 0x0D, 0x01, 0x02, 0x01, 0x01, 0x01,
370 0x09, 0x00,
371 ];
372 let ec: [u8; 16] = [
374 0x06, 0x0E, 0x2B, 0x34, 0x04, 0x01, 0x01, 0x0D, 0x0D, 0x01, 0x03, 0x01, 0x02, 0x0C,
375 0x01, 0x00,
376 ];
377
378 let mut stream = Vec::new();
379 stream.extend_from_slice(&[
381 0x06, 0x0E, 0x2B, 0x34, 0x02, 0x05, 0x01, 0x01, 0x0D, 0x01, 0x02, 0x01, 0x01, 0x02,
382 0x04, 0x00,
383 ]);
384 stream.push(104);
386
387 stream.extend_from_slice(&[0x00, 0x01]); stream.extend_from_slice(&[0x00, 0x03]); stream.extend_from_slice(&[0x00, 0x00, 0x02, 0x00]); stream.extend_from_slice(&[0u8; 8 * 5 + 4 + 8 + 4]); stream.extend_from_slice(&op); stream.extend_from_slice(&[0x00, 0x00, 0x00, 0x01]); stream.extend_from_slice(&[0x00, 0x00, 0x00, 0x10]); stream.extend_from_slice(&ec);
397
398 let mut cursor = Cursor::new(stream);
399 let info = parse_mxf_header_info_from_reader(&mut cursor).unwrap();
400
401 assert_eq!(info.essence_containers.len(), 1);
402 assert_eq!(
403 info.essence_containers[0],
404 "urn:smpte:ul:060e2b34.0401010d.0d010301.020c0100"
405 );
406 }
407
408 #[test]
410 #[ignore = "requires test-data MXF files (large)"]
411 fn real_meridian_mxf_parses() {
412 let path = std::path::Path::new(
413 "../../test-data/MERIDIAN_Netflix_Photon_161006/MERIDIAN_Netflix_Photon_161006_00.mxf",
414 );
415 if !path.exists() {
416 return; }
418 let info = parse_mxf_header_info(path).unwrap();
419 assert!(!info.operational_pattern.is_empty());
420 println!("OP: {}", info.operational_pattern);
421 for ec in &info.essence_containers {
422 println!("EC: {ec}");
423 }
424 }
425}