Skip to main content

mp4_edit/atom/leaf/
hdlr.rs

1use bon::Builder;
2use core::fmt;
3
4use crate::{atom::FourCC, parser::ParseAtomData, writer::SerializeAtom, ParseError};
5
6pub const HDLR: FourCC = FourCC::new(b"hdlr");
7
8// Common handler types
9pub const HANDLER_VIDEO: FourCC = FourCC::new(b"vide");
10pub const HANDLER_AUDIO: FourCC = FourCC::new(b"soun");
11pub const HANDLER_HINT: FourCC = FourCC::new(b"hint");
12pub const HANDLER_META: FourCC = FourCC::new(b"meta");
13pub const HANDLER_TEXT: FourCC = FourCC::new(b"text");
14pub const HANDLER_MDIR: FourCC = FourCC::new(b"mdir");
15pub const HANDLER_SUBTITLE: FourCC = FourCC::new(b"subt");
16pub const HANDLER_TIMECODE: FourCC = FourCC::new(b"tmcd");
17
18#[derive(Debug, Clone, PartialEq)]
19pub enum HandlerType {
20    Video,
21    Audio,
22    Hint,
23    Meta,
24    Text,
25    Mdir,
26    Subtitle,
27    Timecode,
28    Unknown(FourCC),
29}
30
31impl Default for HandlerType {
32    fn default() -> Self {
33        Self::Unknown(FourCC([0u8; 4]))
34    }
35}
36
37impl HandlerType {
38    pub fn from_bytes(bytes: &[u8; 4]) -> Self {
39        match FourCC::new(bytes) {
40            HANDLER_VIDEO => HandlerType::Video,
41            HANDLER_AUDIO => HandlerType::Audio,
42            HANDLER_HINT => HandlerType::Hint,
43            HANDLER_META => HandlerType::Meta,
44            HANDLER_TEXT => HandlerType::Text,
45            HANDLER_MDIR => HandlerType::Mdir,
46            HANDLER_SUBTITLE => HandlerType::Subtitle,
47            HANDLER_TIMECODE => HandlerType::Timecode,
48            _ => HandlerType::Unknown(FourCC::new(bytes)),
49        }
50    }
51
52    pub fn to_bytes(&self) -> [u8; 4] {
53        match self {
54            HandlerType::Video => *HANDLER_VIDEO,
55            HandlerType::Audio => *HANDLER_AUDIO,
56            HandlerType::Hint => *HANDLER_HINT,
57            HandlerType::Meta => *HANDLER_META,
58            HandlerType::Text => *HANDLER_TEXT,
59            HandlerType::Mdir => *HANDLER_MDIR,
60            HandlerType::Subtitle => *HANDLER_SUBTITLE,
61            HandlerType::Timecode => *HANDLER_TIMECODE,
62            HandlerType::Unknown(fourcc) => fourcc.into_bytes(),
63        }
64    }
65
66    pub fn as_str(&self) -> &str {
67        match self {
68            HandlerType::Video => "Video",
69            HandlerType::Audio => "Audio",
70            HandlerType::Hint => "Hint",
71            HandlerType::Meta => "Metadata",
72            HandlerType::Text => "Text",
73            HandlerType::Mdir => "Mdir",
74            HandlerType::Subtitle => "Subtitle",
75            HandlerType::Timecode => "Timecode",
76            HandlerType::Unknown(_) => "Unknown",
77        }
78    }
79
80    pub fn is_media_handler(&self) -> bool {
81        matches!(
82            self,
83            HandlerType::Video | HandlerType::Audio | HandlerType::Text | HandlerType::Subtitle
84        )
85    }
86}
87
88#[derive(Default, Debug, Clone, Builder)]
89pub struct HandlerReferenceAtom {
90    /// Version of the hdlr atom format (0)
91    #[builder(default = 0)]
92    pub version: u8,
93    /// Flags for the hdlr atom (usually all zeros)
94    #[builder(default = [0u8; 3])]
95    pub flags: [u8; 3],
96    /// Component type (pre-defined, usually 0)
97    #[builder(default = FourCC([0u8; 4]))]
98    pub component_type: FourCC,
99    /// Handler type (4CC code indicating the type of media handler)
100    pub handler_type: HandlerType,
101    /// Component manufacturer (usually 0)
102    #[builder(default = FourCC([0u8; 4]))]
103    pub component_manufacturer: FourCC,
104    /// Component flags (usually 0)
105    #[builder(default = 0)]
106    pub component_flags: u32,
107    /// Component flags mask (usually 0)
108    #[builder(default = 0)]
109    pub component_flags_mask: u32,
110    /// Human-readable name of the handler
111    #[builder(into)]
112    pub name: Option<HandlerName>,
113}
114
115#[derive(Debug, Clone, PartialEq, Eq)]
116pub enum HandlerName {
117    /// just string data
118    Raw(String),
119    /// length followed by string data
120    Pascal(String),
121    /// null terminated string
122    CString(String),
123    /// double-null terminated string
124    CString2(String),
125}
126
127impl Default for HandlerName {
128    fn default() -> Self {
129        Self::Raw(String::new())
130    }
131}
132
133impl fmt::Display for HandlerName {
134    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
135        fmt::Display::fmt(self.as_string(), f)
136    }
137}
138
139impl From<String> for HandlerName {
140    fn from(value: String) -> Self {
141        Self::CString(value)
142    }
143}
144
145impl From<&String> for HandlerName {
146    fn from(value: &String) -> Self {
147        Self::CString(value.to_owned())
148    }
149}
150
151impl From<&str> for HandlerName {
152    fn from(value: &str) -> Self {
153        Self::CString(value.to_owned())
154    }
155}
156
157impl HandlerName {
158    fn as_string(&self) -> &String {
159        match self {
160            Self::Raw(str) => str,
161            Self::Pascal(str) => str,
162            Self::CString(str) => str,
163            Self::CString2(str) => str,
164        }
165    }
166
167    pub fn as_str(&self) -> &str {
168        self.as_string().as_str()
169    }
170}
171
172impl ParseAtomData for HandlerReferenceAtom {
173    fn parse_atom_data(atom_type: FourCC, input: &[u8]) -> Result<Self, ParseError> {
174        crate::atom::util::parser::assert_atom_type!(atom_type, HDLR);
175        use crate::atom::util::parser::stream;
176        use winnow::Parser;
177        Ok(parser::parse_hdlr_data.parse(stream(input))?)
178    }
179}
180
181impl SerializeAtom for HandlerReferenceAtom {
182    fn atom_type(&self) -> FourCC {
183        HDLR
184    }
185
186    fn into_body_bytes(self) -> Vec<u8> {
187        serializer::serialize_hdlr_atom(self)
188    }
189}
190
191mod serializer {
192    use crate::FourCC;
193
194    use super::{HandlerName, HandlerReferenceAtom, HandlerType};
195
196    pub fn serialize_hdlr_atom(atom: HandlerReferenceAtom) -> Vec<u8> {
197        vec![
198            version(atom.version),
199            flags(atom.flags),
200            component_type(atom.component_type),
201            handler_type(atom.handler_type),
202            component_manufacturer(atom.component_manufacturer),
203            component_flags(atom.component_flags),
204            component_flags_mask(atom.component_flags_mask),
205            atom.name.map(name).unwrap_or_default(),
206        ]
207        .into_iter()
208        .flatten()
209        .collect()
210    }
211
212    fn version(version: u8) -> Vec<u8> {
213        vec![version]
214    }
215
216    fn flags(flags: [u8; 3]) -> Vec<u8> {
217        flags.to_vec()
218    }
219
220    fn component_type(component_type: FourCC) -> Vec<u8> {
221        component_type.to_vec()
222    }
223
224    fn handler_type(handler_type: HandlerType) -> Vec<u8> {
225        handler_type.to_bytes().to_vec()
226    }
227
228    fn component_manufacturer(manufacturer: FourCC) -> Vec<u8> {
229        manufacturer.to_vec()
230    }
231
232    fn component_flags(flags: u32) -> Vec<u8> {
233        flags.to_be_bytes().to_vec()
234    }
235
236    fn component_flags_mask(flags_mask: u32) -> Vec<u8> {
237        flags_mask.to_be_bytes().to_vec()
238    }
239
240    fn name(name: HandlerName) -> Vec<u8> {
241        let mut data = Vec::new();
242        match name {
243            HandlerName::Pascal(name) => {
244                let name_bytes = name.as_bytes();
245                let len = u8::try_from(name_bytes.len())
246                    .expect("HandlerName::Pascal length must not exceed u8::MAX");
247                data.push(len);
248                data.extend_from_slice(name_bytes);
249            }
250            HandlerName::CString(name) => {
251                data.extend_from_slice(name.as_bytes());
252                data.push(0); // Null terminator
253            }
254            HandlerName::CString2(name) => {
255                data.extend_from_slice(name.as_bytes());
256                data.push(0); // 1st null terminator
257                data.push(0); // 2nd null terminator
258            }
259            HandlerName::Raw(name) => {
260                data.extend_from_slice(name.as_bytes());
261            }
262        }
263        data
264    }
265}
266
267mod parser {
268    use winnow::{
269        binary::{be_u32, length_take, u8},
270        combinator::{alt, opt, repeat_till, seq, trace},
271        error::StrContext,
272        token::{literal, rest},
273        ModalResult, Parser,
274    };
275
276    use super::{HandlerName, HandlerReferenceAtom, HandlerType};
277    use crate::{
278        atom::util::parser::{byte_array, flags3, fourcc, version, Stream},
279        FourCC,
280    };
281
282    pub fn parse_hdlr_data(input: &mut Stream<'_>) -> ModalResult<HandlerReferenceAtom> {
283        trace(
284            "hdlr",
285            seq!(HandlerReferenceAtom {
286                version: version,
287                flags: flags3,
288                component_type: component_type,
289                handler_type: handler_type,
290                component_manufacturer: component_manufacturer,
291                component_flags: component_flags,
292                component_flags_mask: component_flags_mask,
293                name: opt(name),
294            })
295            .context(StrContext::Label("hdlr")),
296        )
297        .parse_next(input)
298    }
299
300    fn component_type(input: &mut Stream<'_>) -> ModalResult<FourCC> {
301        trace(
302            "component_type",
303            fourcc.context(StrContext::Label("component_type")),
304        )
305        .parse_next(input)
306    }
307
308    fn handler_type(input: &mut Stream<'_>) -> ModalResult<HandlerType> {
309        trace(
310            "handler_type",
311            byte_array
312                .map(|fourcc| HandlerType::from_bytes(&fourcc))
313                .context(StrContext::Label("handler_type")),
314        )
315        .parse_next(input)
316    }
317
318    fn component_manufacturer(input: &mut Stream<'_>) -> ModalResult<FourCC> {
319        trace(
320            "component_manufacturer",
321            fourcc.context(StrContext::Label("component_manufacturer")),
322        )
323        .parse_next(input)
324    }
325
326    fn component_flags(input: &mut Stream<'_>) -> ModalResult<u32> {
327        trace(
328            "component_flags",
329            be_u32.context(StrContext::Label("component_flags")),
330        )
331        .parse_next(input)
332    }
333
334    fn component_flags_mask(input: &mut Stream<'_>) -> ModalResult<u32> {
335        trace(
336            "component_flags_mask",
337            be_u32.context(StrContext::Label("component_flags_mask")),
338        )
339        .parse_next(input)
340    }
341
342    fn name(input: &mut Stream<'_>) -> ModalResult<HandlerName> {
343        trace(
344            "name",
345            alt((name_cstr, name_pascal, name_raw)).context(StrContext::Label("name")),
346        )
347        .parse_next(input)
348    }
349
350    fn name_cstr(input: &mut Stream<'_>) -> ModalResult<HandlerName> {
351        trace(
352            "name_cstr",
353            repeat_till(1.., u8, null_term).map(|(data, null2): (Vec<u8>, Option<()>)| {
354                let str = String::from_utf8_lossy(&data).to_string();
355                match null2 {
356                    Some(_) => HandlerName::CString2(str),
357                    None => HandlerName::CString(str),
358                }
359            }),
360        )
361        .parse_next(input)
362    }
363
364    fn null_term(input: &mut Stream<'_>) -> ModalResult<Option<()>> {
365        trace(
366            "null_term",
367            (literal(0x00), opt(literal(0x00))).map(|(_, null2)| null2.map(|_| ())),
368        )
369        .parse_next(input)
370    }
371
372    fn name_pascal(input: &mut Stream<'_>) -> ModalResult<HandlerName> {
373        trace(
374            "name_pascal",
375            length_take(u8)
376                .map(|data| HandlerName::Pascal(String::from_utf8_lossy(data).to_string())),
377        )
378        .parse_next(input)
379    }
380
381    fn name_raw(input: &mut Stream<'_>) -> ModalResult<HandlerName> {
382        trace(
383            "name_raw",
384            rest.map(|data| HandlerName::Raw(String::from_utf8_lossy(data).to_string())),
385        )
386        .parse_next(input)
387    }
388
389    #[cfg(test)]
390    mod tests {
391
392        use super::*;
393        use crate::{atom::util::parser::stream, FourCC};
394
395        #[test]
396        fn test_handler_type_from_bytes() {
397            assert_eq!(HandlerType::from_bytes(b"vide"), HandlerType::Video);
398            assert_eq!(HandlerType::from_bytes(b"soun"), HandlerType::Audio);
399            assert_eq!(HandlerType::from_bytes(b"text"), HandlerType::Text);
400            assert_eq!(HandlerType::from_bytes(b"meta"), HandlerType::Meta);
401            assert_eq!(
402                HandlerType::from_bytes(b"abcd"),
403                HandlerType::Unknown(FourCC::new(b"abcd"))
404            );
405        }
406
407        #[test]
408        fn test_handler_type_methods() {
409            let video_handler = HandlerType::Video;
410            assert!(video_handler.is_media_handler());
411            assert_eq!(video_handler.as_str(), "Video");
412            assert_eq!(video_handler.to_bytes(), *b"vide");
413
414            let unknown_handler = HandlerType::Unknown(FourCC::new(b"test"));
415            assert!(!unknown_handler.is_media_handler());
416            assert_eq!(unknown_handler.as_str(), "Unknown");
417            assert_eq!(unknown_handler.to_bytes(), *b"test");
418        }
419
420        #[test]
421        fn test_parse_handler_name_pascal() {
422            // Pascal string: length byte followed by string
423            let pascal_name = b"\x0CHello World!";
424            let result = name.parse(stream(pascal_name)).unwrap();
425            assert_eq!(result, HandlerName::Pascal("Hello World!".to_owned()));
426        }
427
428        #[test]
429        fn test_parse_handler_name_null_terminated() {
430            // C-style null-terminated string
431            let c_name = b"Hello World!\0";
432            let result = name.parse(stream(c_name)).unwrap();
433            assert_eq!(result, HandlerName::CString("Hello World!".to_owned()));
434        }
435
436        #[test]
437        fn test_parse_handler_name_double_null_terminated() {
438            // C-style null-terminated string
439            let c_name = b"Hello World!\0\0";
440            let result = name.parse(stream(c_name)).unwrap();
441            assert_eq!(result, HandlerName::CString2("Hello World!".to_owned()));
442        }
443
444        #[test]
445        fn test_parse_handler_name_raw() {
446            // Raw string without null terminator
447            let raw_name = b"Hello World!";
448            let result = name.parse(stream(raw_name)).unwrap();
449            assert_eq!(result, HandlerName::Raw("Hello World!".to_owned()));
450        }
451
452        #[test]
453        fn test_parse_handler_name_empty() {
454            let empty_name = b"";
455            let result = name.parse(stream(empty_name)).unwrap();
456            assert_eq!(result, HandlerName::Raw(String::new()));
457        }
458    }
459}
460
461#[cfg(test)]
462mod tests {
463    use super::*;
464    use crate::atom::test_utils::test_atom_roundtrip;
465
466    /// Test round-trip for all available hdlr test data files
467    #[test]
468    fn test_hdlr_roundtrip() {
469        test_atom_roundtrip::<HandlerReferenceAtom>(HDLR);
470    }
471}