flowly_mp4/mp4box/
ilst.rs

1use std::borrow::Cow;
2use std::collections::HashMap;
3
4use byteorder::ByteOrder;
5use serde::Serialize;
6
7use crate::mp4box::data::DataBox;
8use crate::mp4box::*;
9
10#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)]
11pub struct IlstBox {
12    pub items: HashMap<MetadataKey, DataBox>,
13}
14
15impl IlstBox {
16    pub fn get_type(&self) -> BoxType {
17        BoxType::IlstBox
18    }
19
20    pub fn get_size(&self) -> u64 {
21        let mut size = HEADER_SIZE;
22        let ilst_item_header_size = HEADER_SIZE;
23        for item in self.items.values() {
24            size += ilst_item_header_size + item.get_size();
25        }
26        size
27    }
28}
29
30impl Mp4Box for IlstBox {
31    const TYPE: BoxType = BoxType::IlstBox;
32
33    fn box_size(&self) -> u64 {
34        self.get_size()
35    }
36
37    fn to_json(&self) -> Result<String, Error> {
38        Ok(serde_json::to_string(&self).unwrap())
39    }
40
41    fn summary(&self) -> Result<String, Error> {
42        let s = format!("item_count={}", self.items.len());
43        Ok(s)
44    }
45}
46
47impl BlockReader for IlstBox {
48    fn read_block<'a>(reader: &mut impl Reader<'a>) -> Result<Self, Error> {
49        let mut items = HashMap::new();
50
51        while let Some(mut bx) = reader.get_box()? {
52            match bx.kind {
53                BoxType::NameBox => {
54                    if let Some(title) = bx.inner.try_find_box::<DataBox>()? {
55                        items.insert(MetadataKey::Title, title);
56                    }
57                }
58
59                BoxType::DayBox => {
60                    if let Some(day) = bx.inner.try_find_box::<DataBox>()? {
61                        items.insert(MetadataKey::Year, day);
62                    }
63                }
64
65                BoxType::CovrBox => {
66                    if let Some(cover) = bx.inner.try_find_box::<DataBox>()? {
67                        items.insert(MetadataKey::Poster, cover);
68                    }
69                }
70
71                BoxType::DescBox => {
72                    if let Some(summary) = bx.inner.try_find_box::<DataBox>()? {
73                        items.insert(MetadataKey::Summary, summary);
74                    }
75                }
76
77                _ => continue,
78            }
79        }
80        // dbg!(&items);
81        Ok(IlstBox { items })
82    }
83
84    fn size_hint() -> usize {
85        0
86    }
87}
88
89impl<W: Write> WriteBox<&mut W> for IlstBox {
90    fn write_box(&self, writer: &mut W) -> Result<u64, Error> {
91        let size = self.box_size();
92        BoxHeader::new(Self::TYPE, size).write(writer)?;
93
94        for (key, value) in &self.items {
95            let name = match key {
96                MetadataKey::Title => BoxType::NameBox,
97                MetadataKey::Year => BoxType::DayBox,
98                MetadataKey::Poster => BoxType::CovrBox,
99                MetadataKey::Summary => BoxType::DescBox,
100            };
101
102            let size = HEADER_SIZE + value.box_size(); // Size of IlstItem + DataBox
103
104            BoxHeader::new(name, size).write(writer)?;
105            value.write_box(writer)?;
106        }
107        Ok(size)
108    }
109}
110
111impl<'a> Metadata<'a> for IlstBox {
112    fn title(&self) -> Option<Cow<'_, str>> {
113        self.items.get(&MetadataKey::Title).map(item_to_str)
114    }
115
116    fn year(&self) -> Option<u32> {
117        self.items.get(&MetadataKey::Year).and_then(item_to_u32)
118    }
119
120    fn poster(&self) -> Option<&[u8]> {
121        self.items.get(&MetadataKey::Poster).map(item_to_bytes)
122    }
123
124    fn summary(&self) -> Option<Cow<'_, str>> {
125        self.items.get(&MetadataKey::Summary).map(item_to_str)
126    }
127}
128
129fn item_to_bytes(item: &DataBox) -> &[u8] {
130    &item.data
131}
132
133fn item_to_str(item: &DataBox) -> Cow<'_, str> {
134    String::from_utf8_lossy(&item.data)
135}
136
137fn item_to_u32(item: &DataBox) -> Option<u32> {
138    match item.data_type {
139        DataType::Binary if item.data.len() == 4 => Some(BigEndian::read_u32(&item.data)),
140        DataType::Text => String::from_utf8_lossy(&item.data).parse::<u32>().ok(),
141        _ => None,
142    }
143}
144
145#[cfg(test)]
146mod tests {
147    use super::*;
148    use crate::mp4box::BoxHeader;
149
150    #[tokio::test]
151    async fn test_ilst() {
152        let src_year = DataBox {
153            data_type: DataType::Text,
154            data: b"test_year".to_vec(),
155        };
156
157        let src_box = IlstBox {
158            items: [
159                (MetadataKey::Title, DataBox::default()),
160                (MetadataKey::Year, src_year),
161                (MetadataKey::Poster, DataBox::default()),
162                (MetadataKey::Summary, DataBox::default()),
163            ]
164            .into(),
165        };
166        let mut buf = Vec::new();
167        src_box.write_box(&mut buf).unwrap();
168        assert_eq!(buf.len(), src_box.box_size() as usize);
169
170        let mut reader = buf.as_slice();
171        let header = BoxHeader::read(&mut reader, &mut 0).await.unwrap().unwrap();
172        assert_eq!(header.kind, BoxType::IlstBox);
173        assert_eq!(src_box.box_size(), header.size);
174
175        let dst_box = IlstBox::read_block(&mut reader).unwrap();
176        assert_eq!(src_box, dst_box);
177    }
178
179    #[tokio::test]
180    async fn test_ilst_empty() {
181        let src_box = IlstBox::default();
182        let mut buf = Vec::new();
183        src_box.write_box(&mut buf).unwrap();
184        assert_eq!(buf.len(), src_box.box_size() as usize);
185
186        let mut reader = buf.as_slice();
187        let header = BoxHeader::read(&mut reader, &mut 0).await.unwrap().unwrap();
188        assert_eq!(header.kind, BoxType::IlstBox);
189        assert_eq!(src_box.box_size(), header.size);
190
191        let dst_box = IlstBox::read_block(&mut reader).unwrap();
192        assert_eq!(src_box, dst_box);
193    }
194}