Skip to main content

mp4_edit/atom/leaf/
ilst.rs

1use bon::bon;
2use core::fmt;
3use derive_more::Deref;
4
5use crate::atom::util::{DebugList, DebugUpperHex};
6use crate::ParseError;
7use crate::{atom::FourCC, parser::ParseAtomData, writer::SerializeAtom};
8
9pub const ILST: FourCC = FourCC::new(b"ilst");
10
11const DATA_TYPE_TEXT: u32 = 1;
12const DATA_TYPE_JPEG: u32 = 13;
13
14#[derive(Clone, Deref)]
15pub struct RawData(Vec<u8>);
16
17impl RawData {
18    pub fn new(data: impl Into<Vec<u8>>) -> Self {
19        RawData(data.into())
20    }
21}
22
23impl fmt::Debug for RawData {
24    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
25        fmt::Debug::fmt(&DebugList::new(self.0.iter().map(DebugUpperHex), 10), f)
26    }
27}
28
29#[derive(Debug, Clone)]
30#[non_exhaustive]
31pub enum ListItemData {
32    Text(String),
33    Jpeg(RawData),
34    Raw(RawData),
35}
36
37impl ListItemData {
38    fn new(data_type: u32, data: Vec<u8>) -> Self {
39        match data_type {
40            DATA_TYPE_TEXT => String::from_utf8(data)
41                .map_or_else(|e| Self::Raw(RawData(e.into_bytes())), Self::Text),
42            DATA_TYPE_JPEG => Self::Jpeg(RawData(data)),
43            _ => Self::Raw(RawData(data)),
44        }
45    }
46
47    fn to_bytes(self: ListItemData) -> Vec<u8> {
48        use ListItemData::{Jpeg, Raw, Text};
49        match self {
50            Text(s) => s.into_bytes(),
51            Jpeg(data) | Raw(data) => data.0,
52        }
53    }
54}
55
56#[derive(Debug, Clone)]
57pub struct DataAtom {
58    pub data_type: u32,
59    pub reserved: u32,
60    pub data: ListItemData,
61}
62
63impl DataAtom {
64    pub fn new(data: ListItemData) -> Self {
65        Self {
66            data_type: 0,
67            reserved: 0,
68            data,
69        }
70    }
71}
72
73#[derive(Debug, Clone)]
74pub struct MetadataItem {
75    pub item_type: FourCC,
76    pub mean: Option<Vec<u8>>,
77    pub name: Option<Vec<u8>>,
78    pub data_atoms: Vec<DataAtom>,
79}
80
81#[bon]
82impl MetadataItem {
83    #[builder]
84    pub fn new(
85        #[builder(into, start_fn)] item_type: FourCC,
86        #[builder(into)] data_atoms: Vec<DataAtom>,
87    ) -> Self {
88        Self {
89            item_type,
90            mean: None,
91            name: None,
92            data_atoms,
93        }
94    }
95}
96
97#[derive(Debug, Clone)]
98pub struct ItemListAtom {
99    pub items: Vec<MetadataItem>,
100}
101
102impl ParseAtomData for ItemListAtom {
103    fn parse_atom_data(atom_type: FourCC, input: &[u8]) -> Result<Self, ParseError> {
104        crate::atom::util::parser::assert_atom_type!(atom_type, ILST);
105        use crate::atom::util::parser::stream;
106        use winnow::Parser;
107        Ok(parser::parse_ilst_data.parse(stream(input))?)
108    }
109}
110
111impl SerializeAtom for ItemListAtom {
112    fn atom_type(&self) -> FourCC {
113        ILST
114    }
115
116    fn into_body_bytes(self) -> Vec<u8> {
117        serializer::serialize_ilst_atom(self)
118    }
119}
120
121mod serializer {
122    use crate::atom::util::serializer::{prepend_size_inclusive, SizeU32OrU64};
123
124    use super::{
125        DataAtom, ItemListAtom, ListItemData, MetadataItem, DATA_TYPE_JPEG, DATA_TYPE_TEXT,
126    };
127
128    pub fn serialize_ilst_atom(atom: ItemListAtom) -> Vec<u8> {
129        atom.items.into_iter().flat_map(serialize_item).collect()
130    }
131
132    fn serialize_item(item: MetadataItem) -> Vec<u8> {
133        prepend_size_inclusive::<SizeU32OrU64, _>(move || {
134            let mut item_data = Vec::new();
135
136            item_data.extend(item.item_type.into_bytes());
137
138            if let Some(mean) = item.mean {
139                item_data.extend(prepend_size_inclusive::<SizeU32OrU64, _>(move || {
140                    let mut mean_data = Vec::new();
141                    mean_data.extend(b"mean");
142                    mean_data.extend(mean);
143                    mean_data
144                }));
145            }
146
147            if let Some(name) = item.name {
148                item_data.extend(prepend_size_inclusive::<SizeU32OrU64, _>(move || {
149                    let mut name_data = Vec::new();
150                    name_data.extend(b"name");
151                    name_data.extend(name);
152                    name_data
153                }));
154            }
155
156            for data_atom in item.data_atoms {
157                item_data.extend(prepend_size_inclusive::<SizeU32OrU64, _>(move || {
158                    let mut atom_data = Vec::new();
159                    atom_data.extend(b"data");
160                    atom_data.extend(serialize_data_type(&data_atom));
161                    atom_data.extend(data_atom.reserved.to_be_bytes());
162                    atom_data.extend(data_atom.data.to_bytes());
163                    atom_data
164                }));
165            }
166
167            item_data
168        })
169    }
170
171    fn serialize_data_type(data_atom: &DataAtom) -> Vec<u8> {
172        match &data_atom.data {
173            ListItemData::Text(_) => DATA_TYPE_TEXT,
174            ListItemData::Jpeg(_) => DATA_TYPE_JPEG,
175            _ => data_atom.data_type,
176        }
177        .to_be_bytes()
178        .to_vec()
179    }
180}
181
182mod parser {
183    use winnow::{
184        binary::be_u32,
185        combinator::{opt, preceded, repeat, seq, trace},
186        error::StrContext,
187        token::{literal, rest},
188        ModalResult, Parser,
189    };
190
191    use super::{DataAtom, ItemListAtom, ListItemData, MetadataItem};
192    use crate::atom::util::parser::{
193        atom_size, combinators::inclusive_length_and_then, fourcc, rest_vec, Stream,
194    };
195
196    pub fn parse_ilst_data(input: &mut Stream<'_>) -> ModalResult<ItemListAtom> {
197        trace(
198            "ilst",
199            seq!(ItemListAtom {
200                items: repeat(0.., item),
201            })
202            .context(StrContext::Label("ilst")),
203        )
204        .parse_next(input)
205    }
206
207    fn item(input: &mut Stream<'_>) -> ModalResult<MetadataItem> {
208        trace(
209            "item",
210            inclusive_length_and_then(atom_size, item_inner).context(StrContext::Label("item")),
211        )
212        .parse_next(input)
213    }
214
215    fn item_inner(input: &mut Stream<'_>) -> ModalResult<MetadataItem> {
216        seq!(MetadataItem {
217            item_type: fourcc,
218            mean: opt(inclusive_length_and_then(
219                atom_size,
220                preceded(literal(b"mean"), rest_vec)
221            ))
222            .context(StrContext::Label("mean")),
223            name: opt(inclusive_length_and_then(
224                atom_size,
225                preceded(literal(b"name"), rest_vec)
226            ))
227            .context(StrContext::Label("name")),
228            data_atoms: repeat(
229                0..,
230                inclusive_length_and_then(atom_size, preceded(literal(b"data"), data_atom))
231            ),
232        })
233        .parse_next(input)
234    }
235
236    fn data_atom(input: &mut Stream<'_>) -> ModalResult<DataAtom> {
237        trace(
238            "data_atom",
239            seq!(DataAtom {
240                data_type: be_u32,
241                reserved: be_u32,
242                data: rest.map(|data: &[u8]| ListItemData::new(data_type, data.to_vec())),
243            })
244            .context(StrContext::Label("data_atom")),
245        )
246        .parse_next(input)
247    }
248}
249
250#[cfg(test)]
251mod tests {
252    use super::*;
253    use crate::atom::test_utils::test_atom_roundtrip;
254
255    /// Test round-trip for all available ilst test data files
256    #[test]
257    fn test_ilst_roundtrip() {
258        test_atom_roundtrip::<ItemListAtom>(ILST);
259    }
260}