Skip to main content

lofty/mp4/ilst/
atom.rs

1use crate::error::Result;
2use crate::macros::err;
3use crate::mp4::AtomIdent;
4use crate::mp4::ilst::data_type::DataType;
5use crate::picture::Picture;
6
7use std::fmt::{Debug, Formatter};
8
9// Atoms with multiple values aren't all that common,
10// so there's no need to create a bunch of single-element Vecs
11#[derive(PartialEq, Clone)]
12pub(super) enum AtomDataStorage {
13	Single(AtomData),
14	Multiple(Vec<AtomData>),
15}
16
17impl AtomDataStorage {
18	pub(super) fn first_mut(&mut self) -> &mut AtomData {
19		match self {
20			AtomDataStorage::Single(val) => val,
21			AtomDataStorage::Multiple(data) => data.first_mut().expect("not empty"),
22		}
23	}
24
25	pub(super) fn is_pictures(&self) -> bool {
26		match self {
27			AtomDataStorage::Single(v) => matches!(v, AtomData::Picture(_)),
28			AtomDataStorage::Multiple(v) => v.iter().all(|p| matches!(p, AtomData::Picture(_))),
29		}
30	}
31
32	pub(super) fn from_vec(mut v: Vec<AtomData>) -> Option<Self> {
33		match v.len() {
34			0 => None,
35			1 => Some(AtomDataStorage::Single(v.remove(0))),
36			_ => Some(AtomDataStorage::Multiple(v)),
37		}
38	}
39}
40
41impl Debug for AtomDataStorage {
42	fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
43		match &self {
44			AtomDataStorage::Single(v) => write!(f, "{:?}", v),
45			AtomDataStorage::Multiple(v) => f.debug_list().entries(v.iter()).finish(),
46		}
47	}
48}
49
50impl IntoIterator for AtomDataStorage {
51	type Item = AtomData;
52	type IntoIter = std::vec::IntoIter<Self::Item>;
53
54	fn into_iter(self) -> Self::IntoIter {
55		match self {
56			AtomDataStorage::Single(s) => vec![s].into_iter(),
57			AtomDataStorage::Multiple(v) => v.into_iter(),
58		}
59	}
60}
61
62impl<'a> IntoIterator for &'a AtomDataStorage {
63	type Item = &'a AtomData;
64	type IntoIter = AtomDataStorageIter<'a>;
65
66	fn into_iter(self) -> Self::IntoIter {
67		let cap = match self {
68			AtomDataStorage::Single(_) => 0,
69			AtomDataStorage::Multiple(v) => v.len(),
70		};
71
72		Self::IntoIter {
73			storage: Some(self),
74			idx: 0,
75			cap,
76		}
77	}
78}
79
80pub(super) struct AtomDataStorageIter<'a> {
81	storage: Option<&'a AtomDataStorage>,
82	idx: usize,
83	cap: usize,
84}
85
86impl<'a> Iterator for AtomDataStorageIter<'a> {
87	type Item = &'a AtomData;
88
89	fn next(&mut self) -> Option<Self::Item> {
90		match self.storage {
91			Some(AtomDataStorage::Single(data)) => {
92				self.storage = None;
93				Some(data)
94			},
95			Some(AtomDataStorage::Multiple(data)) => {
96				if self.idx == self.cap {
97					self.storage = None;
98					return None;
99				}
100
101				let ret = &data[self.idx];
102				self.idx += 1;
103
104				Some(ret)
105			},
106			_ => None,
107		}
108	}
109}
110
111/// Represents an `MP4` atom
112#[derive(PartialEq, Clone)]
113pub struct Atom<'a> {
114	pub(crate) ident: AtomIdent<'a>,
115	pub(super) data: AtomDataStorage,
116}
117
118impl<'a> Atom<'a> {
119	/// Create a new [`Atom`]
120	#[must_use]
121	pub const fn new(ident: AtomIdent<'a>, data: AtomData) -> Self {
122		Self {
123			ident,
124			data: AtomDataStorage::Single(data),
125		}
126	}
127
128	/// Create a new [`Atom`] from a collection of [`AtomData`]s
129	///
130	/// This will return `None` if `data` is empty, as empty atoms are useless.
131	pub fn from_collection(ident: AtomIdent<'a>, mut data: Vec<AtomData>) -> Option<Self> {
132		let data = match data.len() {
133			0 => return None,
134			1 => AtomDataStorage::Single(data.swap_remove(0)),
135			_ => AtomDataStorage::Multiple(data),
136		};
137
138		Some(Self { ident, data })
139	}
140
141	/// Returns the atom's [`AtomIdent`]
142	pub fn ident(&self) -> &AtomIdent<'_> {
143		&self.ident
144	}
145
146	/// Returns the atom's [`AtomData`]
147	pub fn data(&self) -> impl Iterator<Item = &AtomData> {
148		(&self.data).into_iter()
149	}
150
151	/// Consumes the atom, returning its [`AtomData`]
152	///
153	/// # Examples
154	///
155	/// ```rust
156	/// use lofty::mp4::{Atom, AtomData, AtomIdent};
157	///
158	/// let atom = Atom::new(
159	/// 	AtomIdent::Fourcc(*b"\x49ART"),
160	/// 	AtomData::UTF8(String::from("Foo")),
161	/// );
162	/// assert_eq!(atom.into_data().count(), 1);
163	/// ```
164	pub fn into_data(self) -> impl Iterator<Item = AtomData> + use<> {
165		self.data.into_iter()
166	}
167
168	/// Append a value to the atom
169	pub fn push_data(&mut self, data: AtomData) {
170		match self.data {
171			AtomDataStorage::Single(ref s) => {
172				self.data = AtomDataStorage::Multiple(vec![s.clone(), data])
173			},
174			AtomDataStorage::Multiple(ref mut m) => m.push(data),
175		}
176	}
177
178	/// Merge the data of another atom into this one
179	///
180	/// NOTE: Both atoms must have the same identifier
181	///
182	/// # Errors
183	///
184	/// * `self.ident()` != `other.ident()`
185	///
186	/// # Examples
187	///
188	/// ```rust
189	/// use lofty::mp4::{Atom, AtomData, AtomIdent};
190	///
191	/// # fn main() -> lofty::error::Result<()> {
192	/// // Create an artist atom
193	/// let mut atom = Atom::new(
194	/// 	AtomIdent::Fourcc(*b"\x49ART"),
195	/// 	AtomData::UTF8(String::from("foo")),
196	/// );
197	///
198	/// // Create a second and merge it into the first
199	/// let atom2 = Atom::new(
200	/// 	AtomIdent::Fourcc(*b"\x49ART"),
201	/// 	AtomData::UTF8(String::from("bar")),
202	/// );
203	/// atom.merge(atom2)?;
204	///
205	/// // Our first atom now contains the second atom's content
206	/// assert_eq!(atom.data().count(), 2);
207	/// # Ok(()) }
208	/// ```
209	pub fn merge(&mut self, other: Atom<'_>) -> Result<()> {
210		if self.ident != other.ident {
211			err!(AtomMismatch);
212		}
213
214		for data in other.data {
215			self.push_data(data)
216		}
217
218		Ok(())
219	}
220
221	// Used internally, has no correctness checks
222	pub(crate) fn unknown_implicit(ident: AtomIdent<'_>, data: Vec<u8>) -> Self {
223		Self {
224			ident: ident.into_owned(),
225			data: AtomDataStorage::Single(AtomData::Unknown {
226				code: DataType::Reserved,
227				data,
228			}),
229		}
230	}
231
232	pub(crate) fn text(ident: AtomIdent<'_>, data: String) -> Self {
233		Self {
234			ident: ident.into_owned(),
235			data: AtomDataStorage::Single(AtomData::UTF8(data)),
236		}
237	}
238
239	pub(crate) fn into_owned(self) -> Atom<'static> {
240		let Self { ident, data } = self;
241		Atom {
242			ident: ident.into_owned(),
243			data,
244		}
245	}
246}
247
248impl Debug for Atom<'_> {
249	fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
250		f.debug_struct("Atom")
251			.field("ident", &self.ident)
252			.field("data", &self.data)
253			.finish()
254	}
255}
256
257/// The data of an atom
258///
259/// NOTES:
260///
261/// * This only covers the most common data types.
262///   See the list of [DataType] for all known types.
263/// * There are only two variants for integers, which
264///   will come from codes `21` and `22`. All other integer
265///   types will be stored as [`AtomData::Unknown`], refer
266///   to the link above for codes.
267#[derive(Debug, PartialEq, Eq, Clone)]
268pub enum AtomData {
269	/// A UTF-8 encoded string
270	UTF8(String),
271	/// A UTF-16 encoded string
272	UTF16(String),
273	/// A JPEG, PNG, GIF *(Deprecated)*, or BMP image
274	///
275	/// The type is read from the picture itself
276	Picture(Picture),
277	/// A big endian signed integer (1-4 bytes)
278	///
279	/// NOTE:
280	///
281	/// This will shrink the integer when writing
282	///
283	/// 255 will be written as `[255]` rather than `[0, 0, 0, 255]`
284	///
285	/// This behavior may be unexpected, use [`AtomData::Unknown`] if unsure
286	SignedInteger(i32),
287	/// A big endian unsigned integer (1-4 bytes)
288	///
289	/// NOTE: See [`AtomData::SignedInteger`]
290	UnsignedInteger(u32),
291	/// A boolean value
292	///
293	/// NOTE: This isn't an official data type, but multiple flag atoms exist,
294	///       so this makes them easier to represent. The *real* underlying type
295	///       is [`Self::SignedInteger`].
296	Bool(bool),
297	/// Unknown data
298	///
299	/// Due to the number of possible types, there are many
300	/// **specified** types that are going to fall into this
301	/// variant. See [`DataType`] for a list of known types.
302	Unknown {
303		/// The code, or type of the item
304		code: DataType,
305		/// The binary data of the atom
306		data: Vec<u8>,
307	},
308}
309
310impl AtomData {
311	/// Get the [`DataType`] of the atom
312	///
313	/// Note that for [`AtomData::Picture`], the type is determined by the picture's MIME type.
314	/// If the MIME type is unknown (or unset), the data type will be [`DataType::Reserved`].
315	///
316	/// # Examples
317	///
318	/// ```rust
319	/// use lofty::mp4::{AtomData, DataType};
320	/// use lofty::picture::{MimeType, Picture, PictureType};
321	///
322	/// let data = AtomData::UTF8(String::from("foo"));
323	/// assert_eq!(data.data_type(), DataType::Utf8);
324	///
325	/// let data = AtomData::SignedInteger(42);
326	/// assert_eq!(data.data_type(), DataType::BeSignedInteger);
327	///
328	/// let data = AtomData::Picture(
329	/// 	Picture::unchecked(Vec::new())
330	/// 		.pic_type(PictureType::CoverFront)
331	/// 		.mime_type(MimeType::Jpeg)
332	/// 		.build(),
333	/// );
334	/// assert_eq!(data.data_type(), DataType::Jpeg);
335	/// ```
336	pub fn data_type(&self) -> DataType {
337		match self {
338			AtomData::UTF8(_) => DataType::Utf8,
339			AtomData::UTF16(_) => DataType::Utf16,
340			AtomData::SignedInteger(_) | AtomData::Bool(_) => DataType::BeSignedInteger,
341			AtomData::UnsignedInteger(_) => DataType::BeUnsignedInteger,
342			AtomData::Picture(p) => {
343				let Some(mime) = p.mime_type() else {
344					return DataType::Reserved;
345				};
346
347				DataType::from(mime)
348			},
349			AtomData::Unknown { code, .. } => *code,
350		}
351	}
352}