mp4_atom/meta/ilst/
mod.rs1mod covr;
2mod cprt;
3mod data;
4mod desc;
5mod name;
6mod tool;
7mod year;
8
9pub use covr::*;
10pub use cprt::*;
11pub use data::*;
12pub use desc::*;
13pub use name::*;
14pub use tool::*;
15pub use year::*;
16
17use crate::*;
18
19const MDTA_INDEX_LIMIT: u32 = 0x0100_0000;
23
24#[derive(Debug, Clone, PartialEq, Default)]
25#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
26pub struct Ilst {
27 pub name: Option<Name>,
28 pub year: Option<Year>, pub covr: Option<Covr>,
30 pub desc: Option<Desc>,
31 pub ctoo: Option<Tool>, pub cprt: Option<Copyright>, pub mdta: Vec<(u32, IlstData)>,
37}
38
39impl Atom for Ilst {
40 const KIND: FourCC = FourCC::new(b"ilst");
41
42 fn decode_body<B: Buf>(buf: &mut B) -> Result<Self> {
43 let mut name = None;
44 let mut year = None;
45 let mut covr = None;
46 let mut desc = None;
47 let mut ctoo = None;
48 let mut cprt = None;
49 let mut mdta = vec![];
50
51 while let Some(header) = Header::decode_maybe(buf)? {
57 let size = header.size.unwrap_or(buf.remaining());
58 if size > buf.remaining() {
59 break;
62 }
63 match header.kind {
64 Name::KIND => name = Some(Name::decode_atom(&header, buf)?),
65 Year::KIND => year = Some(Year::decode_atom(&header, buf)?),
66 Covr::KIND => covr = Some(Covr::decode_atom(&header, buf)?),
67 Desc::KIND => desc = Some(Desc::decode_atom(&header, buf)?),
68 Tool::KIND => ctoo = Some(Tool::decode_atom(&header, buf)?),
69 Copyright::KIND => cprt = Some(Copyright::decode_atom(&header, buf)?),
70 kind => {
71 let index = u32::from(kind);
72
73 let mdta_item = if index < MDTA_INDEX_LIMIT {
74 IlstData::decode(&mut buf.slice(size)).ok()
75 } else {
76 None
77 };
78
79 if let Some(data) = mdta_item {
80 mdta.push((index, data))
81 } else {
82 let body = Vec::decode(&mut buf.slice(size))?;
83 Self::decode_unknown(&Any::Unknown(kind, body))?;
84 }
85
86 buf.advance(size);
87 }
88 }
89 }
90
91 Ok(Ilst {
92 name,
93 year,
94 covr,
95 desc,
96 ctoo,
97 cprt,
98 mdta,
99 })
100 }
101
102 fn encode_body<B: BufMut>(&self, buf: &mut B) -> Result<()> {
103 self.name.encode(buf)?;
104 self.year.encode(buf)?;
105 self.covr.encode(buf)?;
106 self.desc.encode(buf)?;
107 self.ctoo.encode(buf)?;
108 self.cprt.encode(buf)?;
109
110 for (index, data) in &self.mdta {
111 if *index == 0 || *index >= MDTA_INDEX_LIMIT {
112 return Err(Error::Unsupported("mdta index out of range"));
116 }
117
118 let start = buf.len();
119 0u32.encode(buf)?; FourCC::from(*index).encode(buf)?;
121 data.encode(buf)?;
122
123 let size: u32 = (buf.len() - start)
124 .try_into()
125 .map_err(|_| Error::TooLarge(FourCC::from(*index)))?;
126 buf.set_slice(start, &size.to_be_bytes());
127 }
128
129 Ok(())
130 }
131}
132
133#[cfg(test)]
134mod tests {
135 use super::*;
136
137 #[test]
138 fn test_ilst() {
139 let expected = Ilst {
140 year: Year("src_year".to_string()).into(),
141 ..Default::default()
142 };
143
144 let mut buf = Vec::new();
145 expected.encode(&mut buf).unwrap();
146
147 let mut buf = buf.as_ref();
148 let decoded = Ilst::decode(&mut buf).unwrap();
149 assert_eq!(decoded, expected);
150 }
151
152 #[test]
153 fn test_ilst_empty() {
154 let expected = Ilst::default();
155 let mut buf = Vec::new();
156 expected.encode(&mut buf).unwrap();
157
158 let mut buf = buf.as_ref();
159 let decoded = Ilst::decode(&mut buf).unwrap();
160 assert_eq!(decoded, expected);
161 }
162
163 fn atom_box(fourcc: &[u8; 4], body: &[u8]) -> Vec<u8> {
165 let mut v = Vec::with_capacity(8 + body.len());
166 v.extend_from_slice(&((8 + body.len()) as u32).to_be_bytes());
167 v.extend_from_slice(fourcc);
168 v.extend_from_slice(body);
169 v
170 }
171
172 #[test]
177 fn test_ilst_itunes_cprt() {
178 let text = b"(c) 2026 Example";
179 let mut data_body = Vec::new();
180 data_body.extend_from_slice(&1u32.to_be_bytes()); data_body.extend_from_slice(&0u32.to_be_bytes()); data_body.extend_from_slice(text);
183 let cprt_item = atom_box(b"cprt", &atom_box(b"data", &data_body));
184 let encoded = atom_box(b"ilst", &cprt_item);
185
186 let decoded = Ilst::decode(&mut encoded.as_slice()).unwrap();
187 assert_eq!(
188 decoded,
189 Ilst {
190 cprt: Some(Copyright {
191 country_indicator: 0,
192 language_indicator: 0,
193 text: "(c) 2026 Example".into(),
194 }),
195 ..Default::default()
196 }
197 );
198
199 let mut reencoded = Vec::new();
201 decoded.encode(&mut reencoded).unwrap();
202 assert_eq!(reencoded, encoded);
203 }
204
205 #[test]
208 fn test_ilst_unknown_item() {
209 let mut data_body = Vec::new();
210 data_body.extend_from_slice(&1u32.to_be_bytes());
211 data_body.extend_from_slice(&0u32.to_be_bytes());
212 data_body.extend_from_slice(b"Some Artist");
213 let art_item = atom_box(b"\xa9ART", &atom_box(b"data", &data_body));
214 let encoded = atom_box(b"ilst", &art_item);
215
216 let result = Ilst::decode(&mut encoded.as_slice());
217 match result {
218 Err(Error::UnexpectedBox(kind)) => assert_eq!(kind, FourCC::new(b"\xa9ART")),
219 other => panic!("expected UnexpectedBox, got {other:?}"),
220 }
221 }
222
223 #[test]
228 fn test_ilst_mdta_item() {
229 let mut data_body = Vec::new();
230 data_body.extend_from_slice(&1u32.to_be_bytes()); data_body.extend_from_slice(&0u32.to_be_bytes()); data_body.extend_from_slice(b"Apple");
233 let item = atom_box(&1u32.to_be_bytes(), &atom_box(b"data", &data_body));
234 let encoded = atom_box(b"ilst", &item);
235
236 let decoded = Ilst::decode(&mut encoded.as_slice()).unwrap();
237 assert_eq!(
238 decoded,
239 Ilst {
240 mdta: vec![(
241 1,
242 IlstData {
243 country_indicator: 0,
244 language_indicator: 0,
245 value: IlstDataValue::Utf8("Apple".into()),
246 }
247 )],
248 ..Default::default()
249 }
250 );
251
252 let mut reencoded = Vec::new();
254 decoded.encode(&mut reencoded).unwrap();
255 assert_eq!(reencoded, encoded);
256 }
257
258 #[test]
262 fn test_ilst_mdta_index_out_of_range_rejected() {
263 let ilst = Ilst {
264 mdta: vec![(
265 MDTA_INDEX_LIMIT,
266 IlstData {
267 country_indicator: 0,
268 language_indicator: 0,
269 value: IlstDataValue::Utf8("Apple".into()),
270 },
271 )],
272 ..Default::default()
273 };
274
275 let mut buf = Vec::new();
276 let err = ilst.encode(&mut buf).unwrap_err();
277 assert!(matches!(err, Error::Unsupported(_)));
278 }
279}