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]
257 fn test_ilst_roundtrip() {
258 test_atom_roundtrip::<ItemListAtom>(ILST);
259 }
260}