Skip to main content

mp4_atom/meta/
iinf.rs

1use crate::*;
2
3// ItemInformationBox. ISO/IEC 14496-12:2022 Section 8.11.6
4// This is used to work out what the items are
5
6ext! {
7    name: Iinf,
8    versions: [0, 1],
9    flags: {}
10}
11
12ext! {
13    name: ItemInfoEntry,
14    versions: [0, 1, 2, 3],
15    flags: {item_not_in_presentation = 0,}
16}
17
18#[derive(Debug, Clone, PartialEq, Eq)]
19#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
20pub struct ItemInfoEntry {
21    pub item_id: u32,
22    pub item_protection_index: u16,
23    pub item_type: Option<FourCC>,
24    pub item_name: String,
25    pub content_type: Option<String>,
26    pub content_encoding: Option<String>,
27    pub item_uri_type: Option<String>,
28    pub item_not_in_presentation: bool,
29}
30
31impl AtomExt for ItemInfoEntry {
32    const KIND_EXT: FourCC = FourCC::new(b"infe");
33
34    type Ext = ItemInfoEntryExt;
35
36    fn encode_body_ext<B: BufMut>(&self, buf: &mut B) -> Result<Self::Ext> {
37        // TODO: maybe work harder at versioning
38        let version: ItemInfoEntryVersion = if self.item_id > u16::MAX as u32 {
39            ItemInfoEntryVersion::V3
40        } else {
41            // version 0 or 2, since we don't support version 1 yet
42            if self.item_type.is_some() {
43                ItemInfoEntryVersion::V2
44            } else {
45                ItemInfoEntryVersion::V0
46            }
47        };
48        if (version == ItemInfoEntryVersion::V0) || (version == ItemInfoEntryVersion::V1) {
49            (self.item_id as u16).encode(buf)?;
50            self.item_protection_index.encode(buf)?;
51            self.item_name.as_str().encode(buf)?;
52            self.content_type.clone().unwrap().as_str().encode(buf)?;
53            self.content_encoding
54                .clone()
55                .unwrap_or("".to_string())
56                .as_str()
57                .encode(buf)?;
58            if version == ItemInfoEntryVersion::V1 {
59                unimplemented!("infe extensions are not yet supported");
60            }
61        } else {
62            if version == ItemInfoEntryVersion::V2 {
63                (self.item_id as u16).encode(buf)?;
64            } else {
65                self.item_id.encode(buf)?;
66            }
67            self.item_protection_index.encode(buf)?;
68            Some(self.item_type).encode(buf)?;
69            self.item_name.as_str().encode(buf)?;
70            if self.item_type == Some(FourCC::new(b"mime")) {
71                self.content_type.clone().unwrap().as_str().encode(buf)?;
72                self.content_encoding
73                    .clone()
74                    .unwrap_or("".to_string())
75                    .as_str()
76                    .encode(buf)?;
77            } else if self.item_type == Some(FourCC::new(b"uri ")) {
78                let item_uri_type = self.item_uri_type.as_ref().ok_or(Error::MissingContent(
79                    "item_uri_type required with 'uri ' item_type",
80                ))?;
81                item_uri_type.as_str().encode(buf)?;
82            }
83        }
84        Ok(ItemInfoEntryExt {
85            version,
86            item_not_in_presentation: self.item_not_in_presentation,
87        })
88    }
89
90    fn decode_body_ext<B: Buf>(buf: &mut B, ext: Self::Ext) -> Result<Self> {
91        let item_id: u32;
92        let item_protection_index;
93        let mut item_type = None;
94        let item_name;
95        let mut content_type = None;
96        let mut content_encoding = None;
97        let mut item_uri_type = None;
98        if (ext.version == ItemInfoEntryVersion::V0) || (ext.version == ItemInfoEntryVersion::V1) {
99            item_id = u16::decode(buf)? as u32;
100            item_protection_index = u16::decode(buf)?;
101            item_name = String::decode(buf)?;
102            content_type = Some(String::decode(buf)?);
103            content_encoding = Some(String::decode(buf)?);
104            if ext.version == ItemInfoEntryVersion::V1 {
105                unimplemented!("infe extensions are not yet supported");
106            }
107        } else {
108            if ext.version == ItemInfoEntryVersion::V2 {
109                item_id = u16::decode(buf)? as u32;
110            } else {
111                item_id = u32::decode(buf)?;
112            }
113            item_protection_index = u16::decode(buf)?;
114            item_type = Some(FourCC::decode(buf)?);
115            item_name = String::decode(buf)?;
116            if item_type == Some(FourCC::new(b"mime")) {
117                content_type = Some(String::decode(buf)?);
118                content_encoding = Some(String::decode(buf)?);
119            } else if item_type == Some(FourCC::new(b"uri ")) {
120                item_uri_type = Some(String::decode(buf)?);
121            }
122        }
123        Ok(ItemInfoEntry {
124            item_id,
125            item_protection_index,
126            item_type,
127            item_name,
128            content_type,
129            content_encoding,
130            item_uri_type,
131            item_not_in_presentation: ext.item_not_in_presentation,
132        })
133    }
134}
135
136#[derive(Debug, Clone, PartialEq, Eq)]
137#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
138pub struct Iinf {
139    pub item_infos: Vec<ItemInfoEntry>,
140}
141
142impl AtomExt for Iinf {
143    type Ext = IinfExt;
144
145    const KIND_EXT: FourCC = FourCC::new(b"iinf");
146
147    fn decode_body_ext<B: Buf>(buf: &mut B, ext: IinfExt) -> Result<Self> {
148        let mut item_infos = vec![];
149        let entry_count = if ext.version == IinfVersion::V0 {
150            u16::decode(buf)? as usize
151        } else {
152            u32::decode(buf)? as usize
153        };
154        for _ in 0..entry_count {
155            item_infos.push(ItemInfoEntry::decode(buf)?);
156        }
157        Ok(Iinf { item_infos })
158    }
159
160    fn encode_body_ext<B: BufMut>(&self, buf: &mut B) -> Result<IinfExt> {
161        let version;
162        if self.item_infos.len() > u16::MAX as usize {
163            version = IinfVersion::V1;
164            (self.item_infos.len() as u32).encode(buf)?
165        } else {
166            version = IinfVersion::V0;
167            (self.item_infos.len() as u16).encode(buf)?
168        }
169        for item_info in &self.item_infos {
170            item_info.encode(buf)?;
171        }
172
173        Ok(IinfExt { version })
174    }
175}
176
177#[cfg(test)]
178mod tests {
179    use super::*;
180
181    const ENCODED_IINF_LIBAVIF_MIME: &[u8] = &[
182        0, 0, 0, 65, 105, 105, 110, 102, 0, 0, 0, 0, 0, 1, 0, 0, 0, 51, 105, 110, 102, 101, 2, 0,
183        0, 0, 0, 1, 0, 0, 109, 105, 109, 101, 73, 116, 101, 109, 0, 99, 111, 110, 116, 101, 110,
184        116, 45, 116, 121, 112, 101, 0, 117, 110, 107, 110, 111, 119, 110, 47, 109, 105, 109, 101,
185        0,
186    ];
187
188    #[test]
189    fn test_iinf_libavif_decode_mime() {
190        let buf: &mut std::io::Cursor<&&[u8]> =
191            &mut std::io::Cursor::new(&ENCODED_IINF_LIBAVIF_MIME);
192
193        let iinf: Iinf = Iinf {
194            item_infos: vec![ItemInfoEntry {
195                item_id: 1,
196                item_protection_index: 0,
197                item_type: Some(FourCC::new(b"mime")),
198                item_name: "Item".to_string(),
199                content_type: Some("content-type".to_string()),
200                content_encoding: Some("unknown/mime".to_string()),
201                item_uri_type: None,
202                item_not_in_presentation: false,
203            }],
204        };
205        let decoded = Iinf::decode(buf).unwrap();
206        assert_eq!(decoded, iinf);
207    }
208
209    #[test]
210    fn test_iinf_avif_encode_mime() {
211        let iinf: Iinf = Iinf {
212            item_infos: vec![ItemInfoEntry {
213                item_id: 1,
214                item_protection_index: 0,
215                item_type: Some(FourCC::new(b"mime")),
216                item_name: "Item".to_string(),
217                content_type: Some("content-type".to_string()),
218                content_encoding: Some("unknown/mime".to_string()),
219                item_uri_type: None,
220                item_not_in_presentation: false,
221            }],
222        };
223        let mut buf = Vec::new();
224        iinf.encode(&mut buf).unwrap();
225
226        assert_eq!(buf.as_slice(), ENCODED_IINF_LIBAVIF_MIME);
227    }
228
229    const ENCODED_IINF_LIBAVIF_URI: &[u8] = &[
230        0, 0, 0, 50, 105, 105, 110, 102, 0, 0, 0, 0, 0, 1, 0, 0, 0, 36, 105, 110, 102, 101, 2, 0,
231        0, 0, 0, 1, 0, 0, 117, 114, 105, 32, 73, 116, 101, 109, 0, 117, 114, 105, 58, 47, 47, 116,
232        101, 115, 116, 0,
233    ];
234
235    #[test]
236    fn test_iinf_libavif_decode_uri() {
237        let buf: &mut std::io::Cursor<&&[u8]> =
238            &mut std::io::Cursor::new(&ENCODED_IINF_LIBAVIF_URI);
239
240        let iinf: Iinf = Iinf {
241            item_infos: vec![ItemInfoEntry {
242                item_id: 1,
243                item_protection_index: 0,
244                item_type: Some(FourCC::new(b"uri ")),
245                item_name: "Item".to_string(),
246                content_type: None,
247                content_encoding: None,
248                item_uri_type: Some("uri://test".to_string()),
249                item_not_in_presentation: false,
250            }],
251        };
252        let decoded = Iinf::decode(buf).unwrap();
253        assert_eq!(decoded, iinf);
254    }
255
256    #[test]
257    fn test_iinf_avif_encode_uri() {
258        let iinf: Iinf = Iinf {
259            item_infos: vec![ItemInfoEntry {
260                item_id: 1,
261                item_protection_index: 0,
262                item_type: Some(FourCC::new(b"uri ")),
263                item_name: "Item".to_string(),
264                content_type: None,
265                content_encoding: None,
266                item_uri_type: Some("uri://test".to_string()),
267                item_not_in_presentation: false,
268            }],
269        };
270        let mut buf = Vec::new();
271        iinf.encode(&mut buf).unwrap();
272
273        assert_eq!(buf.as_slice(), ENCODED_IINF_LIBAVIF_URI);
274    }
275
276    #[test]
277    fn test_iinf_avif_encode_uri_invalid() {
278        let iinf: Iinf = Iinf {
279            item_infos: vec![ItemInfoEntry {
280                item_id: 1,
281                item_protection_index: 0,
282                item_type: Some(FourCC::new(b"uri ")),
283                item_name: "Item".to_string(),
284                content_type: None,
285                content_encoding: None,
286                item_uri_type: None, // encode will return an error because this is empty
287                item_not_in_presentation: false,
288            }],
289        };
290        let mut buf = Vec::new();
291        assert!(matches!(
292            iinf.encode(&mut buf),
293            Err(Error::MissingContent(_))
294        ));
295    }
296}