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 buf.encode_atom(*index, |buf| data.encode(buf))?;
119 }
120
121 Ok(())
122 }
123}
124
125#[cfg(test)]
126mod tests {
127 use super::*;
128
129 #[test]
130 fn test_ilst() {
131 let expected = Ilst {
132 year: Year("src_year".to_string()).into(),
133 ..Default::default()
134 };
135
136 let mut buf = Vec::new();
137 expected.encode(&mut buf).unwrap();
138
139 let mut buf = buf.as_ref();
140 let decoded = Ilst::decode(&mut buf).unwrap();
141 assert_eq!(decoded, expected);
142 }
143
144 #[test]
145 fn test_ilst_empty() {
146 let expected = Ilst::default();
147 let mut buf = Vec::new();
148 expected.encode(&mut buf).unwrap();
149
150 let mut buf = buf.as_ref();
151 let decoded = Ilst::decode(&mut buf).unwrap();
152 assert_eq!(decoded, expected);
153 }
154
155 fn atom_box(fourcc: &[u8; 4], body: &[u8]) -> Vec<u8> {
157 let mut v = Vec::with_capacity(8 + body.len());
158 v.extend_from_slice(&((8 + body.len()) as u32).to_be_bytes());
159 v.extend_from_slice(fourcc);
160 v.extend_from_slice(body);
161 v
162 }
163
164 #[test]
169 fn test_ilst_itunes_cprt() {
170 let text = b"(c) 2026 Example";
171 let mut data_body = Vec::new();
172 data_body.extend_from_slice(&1u32.to_be_bytes()); data_body.extend_from_slice(&0u32.to_be_bytes()); data_body.extend_from_slice(text);
175 let cprt_item = atom_box(b"cprt", &atom_box(b"data", &data_body));
176 let encoded = atom_box(b"ilst", &cprt_item);
177
178 let decoded = Ilst::decode(&mut encoded.as_slice()).unwrap();
179 assert_eq!(
180 decoded,
181 Ilst {
182 cprt: Some(Copyright {
183 country_indicator: 0,
184 language_indicator: 0,
185 text: "(c) 2026 Example".into(),
186 }),
187 ..Default::default()
188 }
189 );
190
191 let mut reencoded = Vec::new();
193 decoded.encode(&mut reencoded).unwrap();
194 assert_eq!(reencoded, encoded);
195 }
196
197 #[test]
200 fn test_ilst_unknown_item() {
201 let mut data_body = Vec::new();
202 data_body.extend_from_slice(&1u32.to_be_bytes());
203 data_body.extend_from_slice(&0u32.to_be_bytes());
204 data_body.extend_from_slice(b"Some Artist");
205 let art_item = atom_box(b"\xa9ART", &atom_box(b"data", &data_body));
206 let encoded = atom_box(b"ilst", &art_item);
207
208 let result = Ilst::decode(&mut encoded.as_slice());
209 match result {
210 Err(Error::UnexpectedBox(kind)) => assert_eq!(kind, FourCC::new(b"\xa9ART")),
211 other => panic!("expected UnexpectedBox, got {other:?}"),
212 }
213 }
214
215 #[test]
220 fn test_ilst_mdta_item() {
221 let mut data_body = Vec::new();
222 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");
225 let item = atom_box(&1u32.to_be_bytes(), &atom_box(b"data", &data_body));
226 let encoded = atom_box(b"ilst", &item);
227
228 let decoded = Ilst::decode(&mut encoded.as_slice()).unwrap();
229 assert_eq!(
230 decoded,
231 Ilst {
232 mdta: vec![(
233 1,
234 IlstData {
235 country_indicator: 0,
236 language_indicator: 0,
237 value: IlstDataValue::Utf8("Apple".into()),
238 }
239 )],
240 ..Default::default()
241 }
242 );
243
244 let mut reencoded = Vec::new();
246 decoded.encode(&mut reencoded).unwrap();
247 assert_eq!(reencoded, encoded);
248 }
249
250 #[test]
254 fn test_ilst_mdta_index_out_of_range_rejected() {
255 let ilst = Ilst {
256 mdta: vec![(
257 MDTA_INDEX_LIMIT,
258 IlstData {
259 country_indicator: 0,
260 language_indicator: 0,
261 value: IlstDataValue::Utf8("Apple".into()),
262 },
263 )],
264 ..Default::default()
265 };
266
267 let mut buf = Vec::new();
268 let err = ilst.encode(&mut buf).unwrap_err();
269 assert!(matches!(err, Error::Unsupported(_)));
270 }
271}