Skip to main content

mp4_edit/atom/container/
meta.rs

1use std::io::Read;
2
3use crate::FourCC;
4
5pub const META: FourCC = FourCC::new(b"meta");
6
7pub const META_VERSION_FLAGS_SIZE: usize = 4;
8
9#[derive(Debug, Clone, PartialEq)]
10pub struct MetaHeader {
11    pub version: u8,
12    pub flags: [u8; 3],
13}
14
15impl MetaHeader {
16    /// Parse the META atom's 4-byte header from a byte slice
17    pub fn from_bytes(bytes: &[u8]) -> Result<Self, ParseMetaError> {
18        if bytes.len() < 4 {
19            return Err(ParseMetaError::InsufficientData);
20        }
21
22        Ok(MetaHeader {
23            version: bytes[0],
24            flags: [bytes[1], bytes[2], bytes[3]],
25        })
26    }
27
28    /// Parse the META atom's 4-byte header from a reader
29    pub fn from_reader<R: Read>(reader: &mut R) -> Result<Self, ParseMetaError> {
30        let mut header_bytes = [0u8; 4];
31        reader
32            .read_exact(&mut header_bytes)
33            .map_err(|_| ParseMetaError::ReadError)?;
34
35        Ok(MetaHeader {
36            version: header_bytes[0],
37            flags: [header_bytes[1], header_bytes[2], header_bytes[3]],
38        })
39    }
40
41    /// Get flags as a single u32 value (big-endian)
42    pub fn flags_as_u32(&self) -> u32 {
43        u32::from_be_bytes([0, self.flags[0], self.flags[1], self.flags[2]])
44    }
45
46    /// Check if this is a valid META header (version should be 0 for Apple compatibility)
47    pub fn is_valid_for_apple(&self) -> bool {
48        self.version == 0 && self.flags == [0, 0, 0]
49    }
50
51    /// Convert back to 4 bytes
52    pub fn to_bytes(&self) -> [u8; 4] {
53        [self.version, self.flags[0], self.flags[1], self.flags[2]]
54    }
55}
56
57#[derive(Debug, Clone, PartialEq)]
58pub enum ParseMetaError {
59    InsufficientData,
60    ReadError,
61}
62
63impl std::fmt::Display for ParseMetaError {
64    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
65        match self {
66            ParseMetaError::InsufficientData => {
67                write!(f, "Insufficient data to parse META header")
68            }
69            ParseMetaError::ReadError => write!(f, "Failed to read META header data"),
70        }
71    }
72}
73
74impl std::error::Error for ParseMetaError {}
75
76#[cfg(test)]
77mod tests {
78    use std::io::Cursor;
79
80    use super::*;
81
82    #[test]
83    fn test_parse_valid_apple_meta_header() {
84        let bytes = [0x00, 0x00, 0x00, 0x00]; // Version 0, flags all 0
85        let header = MetaHeader::from_bytes(&bytes).unwrap();
86
87        assert_eq!(header.version, 0);
88        assert_eq!(header.flags, [0, 0, 0]);
89        assert!(header.is_valid_for_apple());
90        assert_eq!(header.flags_as_u32(), 0);
91    }
92
93    #[test]
94    fn test_parse_non_apple_meta_header() {
95        let bytes = [0x01, 0x00, 0x00, 0x01]; // Version 1, some flags set
96        let header = MetaHeader::from_bytes(&bytes).unwrap();
97
98        assert_eq!(header.version, 1);
99        assert_eq!(header.flags, [0, 0, 1]);
100        assert!(!header.is_valid_for_apple());
101        assert_eq!(header.flags_as_u32(), 1);
102    }
103
104    #[test]
105    fn test_parse_from_reader() {
106        let data = [0x00, 0x00, 0x00, 0x00];
107        let mut cursor = Cursor::new(&data);
108        let header = MetaHeader::from_reader(&mut cursor).unwrap();
109
110        assert_eq!(header.version, 0);
111        assert_eq!(header.flags, [0, 0, 0]);
112    }
113
114    #[test]
115    fn test_insufficient_data() {
116        let bytes = [0x00, 0x00]; // Only 2 bytes
117        let result = MetaHeader::from_bytes(&bytes);
118
119        assert!(matches!(result, Err(ParseMetaError::InsufficientData)));
120    }
121
122    #[test]
123    fn test_round_trip() {
124        let original = MetaHeader {
125            version: 0,
126            flags: [0x12, 0x34, 0x56],
127        };
128
129        let bytes = original.to_bytes();
130        let parsed = MetaHeader::from_bytes(&bytes).unwrap();
131
132        assert_eq!(original, parsed);
133    }
134
135    #[test]
136    fn test_flags_as_u32() {
137        let header = MetaHeader {
138            version: 0,
139            flags: [0x01, 0x02, 0x03],
140        };
141
142        assert_eq!(header.flags_as_u32(), 0x010203);
143    }
144}