MP4: Support atoms with multiple values

This commit is contained in:
Serial 2022-05-13 17:06:55 -04:00
parent 3788a436af
commit b5478d1f1d
No known key found for this signature in database
GPG key ID: DA95198DC17C4568
9 changed files with 426 additions and 249 deletions

View file

@ -6,6 +6,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
### Added
- **MP4**:
- Support atoms with multiple values ([issue](https://github.com/Serial-ATA/lofty-rs/issues/48))
- `Atom::from_collection`
### Changed
- **ID3v2**: Discard empty frames, rather than error
- **APE**: Allow empty tag items

View file

@ -162,7 +162,8 @@
clippy::similar_names,
clippy::tabs_in_doc_comments,
clippy::len_without_is_empty,
clippy::needless_late_init
clippy::needless_late_init,
clippy::type_complexity
)]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]

View file

@ -1,17 +1,107 @@
use crate::mp4::AtomIdent;
use crate::picture::Picture;
#[derive(Debug, PartialEq, Clone)]
use std::fmt::{Debug, Formatter};
// Atoms with multiple values aren't all that common,
// so there's no need to create a bunch of single-element Vecs
#[derive(PartialEq, Clone)]
pub(super) enum AtomDataStorage {
Single(AtomData),
Multiple(Vec<AtomData>),
}
impl AtomDataStorage {
pub(super) fn take_first(self) -> AtomData {
match self {
AtomDataStorage::Single(val) => val,
AtomDataStorage::Multiple(mut data) => data.swap_remove(0),
}
}
}
impl Debug for AtomDataStorage {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match &self {
AtomDataStorage::Single(v) => write!(f, "{:?}", v),
AtomDataStorage::Multiple(v) => f.debug_list().entries(v.iter()).finish(),
}
}
}
impl<'a> IntoIterator for &'a AtomDataStorage {
type Item = &'a AtomData;
type IntoIter = AtomDataStorageIter<'a>;
fn into_iter(self) -> Self::IntoIter {
let cap = match self {
AtomDataStorage::Single(_) => 0,
AtomDataStorage::Multiple(v) => v.len(),
};
Self::IntoIter {
storage: Some(self),
idx: 0,
cap,
}
}
}
pub(super) struct AtomDataStorageIter<'a> {
storage: Option<&'a AtomDataStorage>,
idx: usize,
cap: usize,
}
impl<'a> Iterator for AtomDataStorageIter<'a> {
type Item = &'a AtomData;
fn next(&mut self) -> Option<Self::Item> {
match self.storage {
Some(AtomDataStorage::Single(data)) => {
self.storage = None;
Some(data)
},
Some(AtomDataStorage::Multiple(data)) => {
if self.idx == self.cap {
self.storage = None;
}
self.idx += 1;
Some(&data[self.idx])
},
_ => None,
}
}
}
#[derive(PartialEq, Clone)]
/// Represents an `MP4` atom
pub struct Atom {
pub(crate) ident: AtomIdent,
pub(crate) data: AtomData,
pub(super) data: AtomDataStorage,
}
impl Atom {
/// Create a new [`Atom`]
pub fn new(ident: AtomIdent, data: AtomData) -> Self {
Self { ident, data }
Self {
ident,
data: AtomDataStorage::Single(data),
}
}
/// Create a new [`Atom`] from a collection of [`AtomData`]s
///
/// This will return `None` if `data` is empty, as empty atoms are useless.
pub fn from_collection(ident: AtomIdent, mut data: Vec<AtomData>) -> Option<Self> {
let data = match data.len() {
0 => return None,
1 => AtomDataStorage::Single(data.swap_remove(0)),
_ => AtomDataStorage::Multiple(data),
};
Some(Self { ident, data })
}
/// Returns the atom's [`AtomIdent`]
@ -20,8 +110,24 @@ impl Atom {
}
/// Returns the atom's [`AtomData`]
// TODO: Do this properly to return all values
pub fn data(&self) -> &AtomData {
&self.data
match &self.data {
AtomDataStorage::Single(val) => val,
// There must be at least 1 element in here
AtomDataStorage::Multiple(data) => &data[0],
}
}
// TODO: push_data
}
impl Debug for Atom {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Atom")
.field("ident", &self.ident)
.field("data", &self.data)
.finish()
}
}
@ -105,65 +211,3 @@ impl From<u8> for AdvisoryRating {
}
}
}
pub(crate) struct AtomRef<'a> {
pub(crate) ident: AtomIdentRef<'a>,
pub(crate) data: AtomDataRef<'a>,
}
impl<'a> Into<AtomRef<'a>> for &'a Atom {
fn into(self) -> AtomRef<'a> {
AtomRef {
ident: (&self.ident).into(),
data: (&self.data).into(),
}
}
}
pub(crate) enum AtomIdentRef<'a> {
Fourcc([u8; 4]),
Freeform { mean: &'a str, name: &'a str },
}
impl<'a> Into<AtomIdentRef<'a>> for &'a AtomIdent {
fn into(self) -> AtomIdentRef<'a> {
match self {
AtomIdent::Fourcc(fourcc) => AtomIdentRef::Fourcc(*fourcc),
AtomIdent::Freeform { mean, name } => AtomIdentRef::Freeform { mean, name },
}
}
}
impl<'a> From<AtomIdentRef<'a>> for AtomIdent {
fn from(input: AtomIdentRef<'a>) -> Self {
match input {
AtomIdentRef::Fourcc(fourcc) => AtomIdent::Fourcc(fourcc),
AtomIdentRef::Freeform { mean, name } => AtomIdent::Freeform {
mean: mean.to_string(),
name: name.to_string(),
},
}
}
}
pub(crate) enum AtomDataRef<'a> {
UTF8(&'a str),
UTF16(&'a str),
Picture(&'a Picture),
SignedInteger(i32),
UnsignedInteger(u32),
Unknown { code: u32, data: &'a [u8] },
}
impl<'a> Into<AtomDataRef<'a>> for &'a AtomData {
fn into(self) -> AtomDataRef<'a> {
match self {
AtomData::UTF8(utf8) => AtomDataRef::UTF8(utf8),
AtomData::UTF16(utf16) => AtomDataRef::UTF16(utf16),
AtomData::Picture(pic) => AtomDataRef::Picture(pic),
AtomData::SignedInteger(int) => AtomDataRef::SignedInteger(*int),
AtomData::UnsignedInteger(uint) => AtomDataRef::UnsignedInteger(*uint),
AtomData::Unknown { code, data } => AtomDataRef::Unknown { code: *code, data },
}
}
}

View file

@ -1,17 +1,19 @@
pub(super) mod atom;
pub(super) mod constants;
pub(super) mod read;
mod r#ref;
pub(crate) mod write;
use super::AtomIdent;
use crate::error::{LoftyError, Result};
use crate::error::LoftyError;
use crate::mp4::ilst::atom::AtomDataStorage;
use crate::picture::{Picture, PictureType};
use crate::tag::item::{ItemKey, ItemValue, TagItem};
use crate::tag::{Tag, TagType};
use crate::traits::{Accessor, TagExt};
use atom::{AdvisoryRating, Atom, AtomData, AtomDataRef, AtomIdentRef, AtomRef};
use atom::{AdvisoryRating, Atom, AtomData};
use r#ref::AtomIdentRef;
use std::convert::TryInto;
use std::fs::{File, OpenOptions};
use std::io::Write;
use std::path::Path;
@ -39,7 +41,7 @@ macro_rules! impl_accessor {
fn [<set_ $name>](&mut self, value: String) {
self.replace_atom(Atom {
ident: $const,
data: AtomData::UTF8(value),
data: AtomDataStorage::Single(AtomData::UTF8(value)),
})
}
@ -134,11 +136,14 @@ impl Ilst {
pub fn pictures(&self) -> impl Iterator<Item = &Picture> {
const COVR: AtomIdent = AtomIdent::Fourcc(*b"covr");
self.atoms.iter().filter_map(|a| match a {
Atom {
ident: COVR,
data: AtomData::Picture(pic),
} => Some(pic),
self.atoms.iter().filter_map(|a| match a.ident {
COVR => {
if let AtomData::Picture(pic) = a.data() {
Some(pic)
} else {
None
}
},
_ => None,
})
}
@ -150,7 +155,7 @@ impl Ilst {
self.atoms.push(Atom {
ident: AtomIdent::Fourcc(*b"covr"),
data: AtomData::Picture(picture),
data: AtomDataStorage::Single(AtomData::Picture(picture)),
})
}
@ -162,8 +167,8 @@ impl Ilst {
/// Returns the parental advisory rating according to the `rtng` atom
pub fn advisory_rating(&self) -> Option<AdvisoryRating> {
if let Some(Atom { data, .. }) = self.atom(&AtomIdent::Fourcc(*b"rtng")) {
let rating = match data {
if let Some(atom) = self.atom(&AtomIdent::Fourcc(*b"rtng")) {
let rating = match atom.data() {
AtomData::SignedInteger(si) => *si as u8,
AtomData::Unknown { data: c, .. } if !c.is_empty() => c[0],
_ => return None,
@ -181,7 +186,7 @@ impl Ilst {
self.replace_atom(Atom {
ident: AtomIdent::Fourcc(*b"rtng"),
data: AtomData::SignedInteger(i32::from(byte)),
data: AtomDataStorage::Single(AtomData::SignedInteger(i32::from(byte))),
})
}
@ -236,11 +241,11 @@ impl TagExt for Ilst {
}
fn save_to(&self, file: &mut File) -> std::result::Result<(), Self::Err> {
Into::<IlstRef<'_>>::into(self).write_to(file)
self.as_ref().write_to(file)
}
fn dump_to<W: Write>(&self, writer: &mut W) -> std::result::Result<(), Self::Err> {
Into::<IlstRef<'_>>::into(self).dump_to(writer)
self.as_ref().dump_to(writer)
}
fn remove_from_path<P: AsRef<Path>>(&self, path: P) -> std::result::Result<(), Self::Err> {
@ -261,7 +266,8 @@ impl From<Ilst> for Tag {
let mut tag = Self::new(TagType::Mp4Ilst);
for atom in input.atoms {
let value = match atom.data {
let Atom { ident, data } = atom;
let value = match data.take_first() {
AtomData::UTF8(text) | AtomData::UTF16(text) => ItemValue::Text(text),
AtomData::Picture(pic) => {
tag.pictures.push(pic);
@ -269,7 +275,7 @@ impl From<Ilst> for Tag {
},
// We have to special case track/disc numbers since they are stored together
AtomData::Unknown { code: 0, data } if data.len() >= 6 => {
if let AtomIdent::Fourcc(ref fourcc) = atom.ident {
if let AtomIdent::Fourcc(ref fourcc) = ident {
match fourcc {
b"trkn" => {
let current = u16::from_be_bytes([data[2], data[3]]);
@ -296,7 +302,7 @@ impl From<Ilst> for Tag {
let key = ItemKey::from_key(
TagType::Mp4Ilst,
&match atom.ident {
&match ident {
AtomIdent::Fourcc(fourcc) => {
fourcc.iter().map(|b| *b as char).collect::<String>()
},
@ -330,10 +336,10 @@ impl From<Tag> for Ilst {
tag.atoms.push(Atom {
ident: AtomIdent::Fourcc(ident),
data: AtomData::Unknown {
data: AtomDataStorage::Single(AtomData::Unknown {
code: 0,
data: vec![0, 0, current[0], current[1], total[0], total[1], 0, 0],
},
}),
})
},
}
@ -361,7 +367,7 @@ impl From<Tag> for Ilst {
ItemKey::DiscTotal => convert_to_uint(&mut discs.1, data.as_str()),
_ => ilst.atoms.push(Atom {
ident,
data: AtomData::UTF8(data),
data: AtomDataStorage::Single(AtomData::UTF8(data)),
}),
}
}
@ -374,7 +380,7 @@ impl From<Tag> for Ilst {
ilst.atoms.push(Atom {
ident: AtomIdent::Fourcc([b'c', b'o', b'v', b'r']),
data: AtomData::Picture(picture),
data: AtomDataStorage::Single(AtomData::Picture(picture)),
})
}
@ -385,50 +391,6 @@ impl From<Tag> for Ilst {
}
}
pub(crate) struct IlstRef<'a> {
atoms: Box<dyn Iterator<Item = AtomRef<'a>> + 'a>,
}
impl<'a> IlstRef<'a> {
pub(crate) fn write_to(&mut self, file: &mut File) -> Result<()> {
write::write_to(file, self)
}
pub(crate) fn dump_to<W: Write>(&mut self, writer: &mut W) -> Result<()> {
let temp = write::build_ilst(&mut self.atoms)?;
writer.write_all(&*temp)?;
Ok(())
}
}
impl<'a> Into<IlstRef<'a>> for &'a Ilst {
fn into(self) -> IlstRef<'a> {
IlstRef {
atoms: Box::new(self.atoms.iter().map(Into::into)),
}
}
}
impl<'a> Into<IlstRef<'a>> for &'a Tag {
fn into(self) -> IlstRef<'a> {
let iter =
self.items
.iter()
.filter_map(|i| match (item_key_to_ident(i.key()), i.value()) {
(Some(ident), ItemValue::Text(text)) => Some(AtomRef {
ident,
data: AtomDataRef::UTF8(text),
}),
_ => None,
});
IlstRef {
atoms: Box::new(iter),
}
}
}
fn item_key_to_ident(key: &ItemKey) -> Option<AtomIdentRef<'_>> {
key.map_key(TagType::Mp4Ilst, true).and_then(|ident| {
if ident.starts_with("----") {
@ -458,6 +420,7 @@ fn item_key_to_ident(key: &ItemKey) -> Option<AtomIdentRef<'_>> {
#[cfg(test)]
mod tests {
use crate::mp4::ilst::atom::AtomDataStorage;
use crate::mp4::{AdvisoryRating, Atom, AtomData, AtomIdent, Ilst, Mp4File};
use crate::tag::utils::test_utils::read_path;
use crate::{Accessor, AudioFile, ItemKey, Tag, TagExt, TagType};
@ -717,7 +680,7 @@ mod tests {
let mut tag = Ilst::default();
tag.insert_atom(Atom {
ident: AtomIdent::Fourcc(*b"\xa9ART"),
data: AtomData::UTF8(String::from("Foo artist")),
data: AtomDataStorage::Single(AtomData::UTF8(String::from("Foo artist"))),
});
assert!(tag.save_to(&mut file).is_ok());
@ -732,4 +695,25 @@ mod tests {
&AtomData::UTF8(String::from("Foo artist")),
);
}
#[test]
fn multi_value_atom() {
let ilst = read_ilst("tests/tags/assets/ilst/multi_value_atom.ilst");
let artist_atom = ilst.atom(&AtomIdent::Fourcc(*b"\xa9ART")).unwrap();
assert_eq!(
artist_atom.data,
AtomDataStorage::Multiple(vec![
AtomData::UTF8(String::from("Foo artist")),
AtomData::UTF8(String::from("Bar artist")),
])
);
// Sanity single value atom
verify_atom(
&ilst,
*b"\xa9gen",
&AtomData::UTF8(String::from("Classical")),
);
}
}

View file

@ -14,6 +14,7 @@ use crate::picture::{MimeType, Picture, PictureType};
use std::borrow::Cow;
use std::io::{Cursor, Read, Seek, SeekFrom};
use crate::mp4::ilst::atom::AtomDataStorage;
use byteorder::ReadBytesExt;
pub(in crate::mp4) fn parse_ilst<R>(reader: &mut R, len: u64) -> Result<Ilst>
@ -28,35 +29,34 @@ where
let mut tag = Ilst::default();
while let Ok(atom) = AtomInfo::read(&mut cursor) {
let ident = match atom.ident {
AtomIdent::Fourcc(ref fourcc) => match fourcc {
if let AtomIdent::Fourcc(ref fourcc) = atom.ident {
match fourcc {
b"free" | b"skip" => {
skip_unneeded(&mut cursor, atom.extended, atom.len)?;
continue;
},
b"covr" => {
handle_covr(&mut cursor, &mut tag)?;
handle_covr(&mut cursor, &mut tag, &atom)?;
continue;
},
// Upgrade this to a \xa9gen atom
b"gnre" => {
let content = parse_data(&mut cursor)?;
if let Some(atom_data) = parse_data_inner(&mut cursor, &atom)? {
let mut data = Vec::new();
if let Some(AtomData::Unknown {
code: BE_UNSIGNED_INTEGER | 0,
data,
}) = content
{
if data.len() >= 2 {
let index = data[1] as usize;
if index > 0 && index <= GENRES.len() {
tag.atoms.push(Atom {
ident: AtomIdent::Fourcc(*b"\xa9gen"),
data: AtomData::UTF8(String::from(GENRES[index - 1])),
})
for (flags, content) in atom_data {
if (flags == BE_SIGNED_INTEGER || flags == 0) && content.len() >= 2 {
let index = content[1] as usize;
if index > 0 && index <= GENRES.len() {
data.push(AtomData::UTF8(String::from(GENRES[index - 1])));
}
}
}
tag.atoms.push(Atom {
ident: AtomIdent::Fourcc(*b"\xa9gen"),
data: AtomDataStorage::Multiple(data),
})
}
continue;
@ -64,94 +64,117 @@ where
// Special case the "Album ID", as it has the code "BE signed integer" (21), but
// must be interpreted as a "BE 64-bit Signed Integer" (74)
b"plID" => {
if let Some((code, content)) = parse_data_inner(&mut cursor)? {
if (code == BE_SIGNED_INTEGER || code == BE_64BIT_SIGNED_INTEGER)
&& content.len() == 8
{
tag.atoms.push(Atom {
ident: AtomIdent::Fourcc(*b"plID"),
data: AtomData::Unknown {
if let Some(atom_data) = parse_data_inner(&mut cursor, &atom)? {
let mut data = Vec::new();
for (code, content) in atom_data {
if (code == BE_SIGNED_INTEGER || code == BE_64BIT_SIGNED_INTEGER)
&& content.len() == 8
{
data.push(AtomData::Unknown {
code,
data: content,
},
})
})
}
}
tag.atoms.push(Atom {
ident: AtomIdent::Fourcc(*b"plID"),
data: AtomDataStorage::Multiple(data),
})
}
continue;
},
_ => atom.ident,
},
ident => ident,
};
if let Some(data) = parse_data(&mut cursor)? {
tag.atoms.push(Atom { ident, data })
_ => {},
}
}
parse_data(&mut cursor, &mut tag, atom)?;
}
Ok(tag)
}
fn parse_data<R>(data: &mut R) -> Result<Option<AtomData>>
fn parse_data<R>(data: &mut R, tag: &mut Ilst, atom_info: AtomInfo) -> Result<()>
where
R: Read + Seek,
{
if let Some((flags, content)) = parse_data_inner(data)? {
// https://developer.apple.com/library/archive/documentation/QuickTime/QTFF/Metadata/Metadata.html#//apple_ref/doc/uid/TP40000939-CH1-SW35
let value = match flags {
UTF8 => AtomData::UTF8(String::from_utf8(content)?),
UTF16 => AtomData::UTF16(utf16_decode(&*content, u16::from_be_bytes)?),
BE_SIGNED_INTEGER => AtomData::SignedInteger(parse_int(&content)?),
BE_UNSIGNED_INTEGER => AtomData::UnsignedInteger(parse_uint(&content)?),
code => AtomData::Unknown {
code,
data: content,
},
};
if let Some(mut atom_data) = parse_data_inner(data, &atom_info)? {
// Most atoms we encounter are only going to have 1 value, so store them as such
if atom_data.len() == 1 {
let (flags, content) = atom_data.remove(0);
let data = interpret_atom_content(flags, content)?;
return Ok(Some(value));
tag.atoms.push(Atom {
ident: atom_info.ident,
data: AtomDataStorage::Single(data),
});
return Ok(());
}
let mut data = Vec::new();
for (flags, content) in atom_data {
let value = interpret_atom_content(flags, content)?;
data.push(value);
}
tag.atoms.push(Atom {
ident: atom_info.ident,
data: AtomDataStorage::Multiple(data),
});
}
Ok(None)
Ok(())
}
fn parse_data_inner<R>(data: &mut R) -> Result<Option<(u32, Vec<u8>)>>
fn parse_data_inner<R>(data: &mut R, atom_info: &AtomInfo) -> Result<Option<Vec<(u32, Vec<u8>)>>>
where
R: Read + Seek,
{
let atom = AtomInfo::read(data)?;
// An atom can contain multiple data atoms
let mut ret = Vec::new();
match atom.ident {
AtomIdent::Fourcc(ref name) if name == b"data" => {},
_ => {
return Err(LoftyError::new(ErrorKind::BadAtom(
"Expected atom \"data\" to follow name",
)))
},
let to_read = (atom_info.start + atom_info.len) - data.stream_position()?;
let mut pos = 0;
while pos < to_read {
let data_atom = AtomInfo::read(data)?;
match data_atom.ident {
AtomIdent::Fourcc(ref name) if name == b"data" => {},
_ => {
return Err(LoftyError::new(ErrorKind::BadAtom(
"Expected atom \"data\" to follow name",
)))
},
}
// We don't care about the version
let _version = data.read_u8()?;
let mut flags = [0; 3];
data.read_exact(&mut flags)?;
let flags = u32::from_be_bytes([0, flags[0], flags[1], flags[2]]);
// We don't care about the locale
data.seek(SeekFrom::Current(4))?;
let content_len = (data_atom.len - 16) as usize;
if content_len == 0 {
// We won't add empty atoms
return Ok(None);
}
let mut content = try_vec![0; content_len];
data.read_exact(&mut content)?;
pos += data_atom.len;
ret.push((flags, content));
}
// We don't care about the version
let _version = data.read_u8()?;
let mut flags = [0; 3];
data.read_exact(&mut flags)?;
let flags = u32::from_be_bytes([0, flags[0], flags[1], flags[2]]);
// We don't care about the locale
data.seek(SeekFrom::Current(4))?;
let content_len = (atom.len - 16) as usize;
if content_len == 0 {
// We won't add empty atoms
return Ok(None);
}
let mut content = try_vec![0; content_len];
data.read_exact(&mut content)?;
Ok(Some((flags, content)))
let ret = if ret.is_empty() { None } else { Some(ret) };
Ok(ret)
}
fn parse_uint(bytes: &[u8]) -> Result<u32> {
@ -182,40 +205,65 @@ fn parse_int(bytes: &[u8]) -> Result<i32> {
})
}
fn handle_covr(reader: &mut Cursor<Vec<u8>>, tag: &mut Ilst) -> Result<()> {
if let Some(value) = parse_data(reader)? {
let (mime_type, data) = match value {
AtomData::Unknown { code, data } => match code {
fn handle_covr(reader: &mut Cursor<Vec<u8>>, tag: &mut Ilst, atom_info: &AtomInfo) -> Result<()> {
if let Some(atom_data) = parse_data_inner(reader, atom_info)? {
let mut data = Vec::new();
let len = atom_data.len();
for (flags, value) in atom_data {
let mime_type = match flags {
// Type 0 is implicit
RESERVED => (MimeType::None, data),
RESERVED => MimeType::None,
// GIF is deprecated
12 => (MimeType::Gif, data),
JPEG => (MimeType::Jpeg, data),
PNG => (MimeType::Png, data),
BMP => (MimeType::Bmp, data),
12 => MimeType::Gif,
JPEG => MimeType::Jpeg,
PNG => MimeType::Png,
BMP => MimeType::Bmp,
_ => {
return Err(LoftyError::new(ErrorKind::BadAtom(
"\"covr\" atom has an unknown type",
)))
},
},
_ => {
return Err(LoftyError::new(ErrorKind::BadAtom(
"\"covr\" atom has an unknown type",
)))
},
};
};
tag.atoms.push(Atom {
ident: AtomIdent::Fourcc(*b"covr"),
data: AtomData::Picture(Picture {
let picture_data = AtomData::Picture(Picture {
pic_type: PictureType::Other,
mime_type,
description: None,
data: Cow::from(data),
}),
data: Cow::from(value),
});
if len == 1 {
tag.atoms.push(Atom {
ident: AtomIdent::Fourcc(*b"covr"),
data: AtomDataStorage::Single(picture_data),
});
return Ok(());
}
data.push(picture_data);
}
tag.atoms.push(Atom {
ident: AtomIdent::Fourcc(*b"covr"),
data: AtomDataStorage::Multiple(data),
});
}
Ok(())
}
fn interpret_atom_content(flags: u32, content: Vec<u8>) -> Result<AtomData> {
// https://developer.apple.com/library/archive/documentation/QuickTime/QTFF/Metadata/Metadata.html#//apple_ref/doc/uid/TP40000939-CH1-SW35
Ok(match flags {
UTF8 => AtomData::UTF8(String::from_utf8(content)?),
UTF16 => AtomData::UTF16(utf16_decode(&*content, u16::from_be_bytes)?),
BE_SIGNED_INTEGER => AtomData::SignedInteger(parse_int(&content)?),
BE_UNSIGNED_INTEGER => AtomData::UnsignedInteger(parse_uint(&content)?),
code => AtomData::Unknown {
code,
data: content,
},
})
}

77
src/mp4/ilst/ref.rs Normal file
View file

@ -0,0 +1,77 @@
// *********************
// Reference Conversions
// *********************
use crate::error::Result;
use crate::mp4::{Atom, AtomData, AtomIdent, Ilst};
use std::fs::File;
use std::io::Write;
impl Ilst {
pub(crate) fn as_ref(&self) -> IlstRef<'_, impl IntoIterator<Item = &AtomData>> {
IlstRef {
atoms: Box::new(self.atoms.iter().map(Atom::as_ref)),
}
}
}
pub(crate) struct IlstRef<'a, I> {
pub(super) atoms: Box<dyn Iterator<Item = AtomRef<'a, I>> + 'a>,
}
impl<'a, I: 'a> IlstRef<'a, I>
where
I: IntoIterator<Item = &'a AtomData>,
{
pub(crate) fn write_to(&mut self, file: &mut File) -> Result<()> {
super::write::write_to(file, self)
}
pub(crate) fn dump_to<W: Write>(&mut self, writer: &mut W) -> Result<()> {
let temp = super::write::build_ilst(&mut self.atoms)?;
writer.write_all(&*temp)?;
Ok(())
}
}
impl Atom {
pub(super) fn as_ref(&self) -> AtomRef<'_, impl IntoIterator<Item = &AtomData>> {
AtomRef {
ident: (&self.ident).into(),
data: (&self.data).into_iter(),
}
}
}
pub(crate) struct AtomRef<'a, I> {
pub(crate) ident: AtomIdentRef<'a>,
pub(crate) data: I,
}
pub(crate) enum AtomIdentRef<'a> {
Fourcc([u8; 4]),
Freeform { mean: &'a str, name: &'a str },
}
impl<'a> Into<AtomIdentRef<'a>> for &'a AtomIdent {
fn into(self) -> AtomIdentRef<'a> {
match self {
AtomIdent::Fourcc(fourcc) => AtomIdentRef::Fourcc(*fourcc),
AtomIdent::Freeform { mean, name } => AtomIdentRef::Freeform { mean, name },
}
}
}
impl<'a> From<AtomIdentRef<'a>> for AtomIdent {
fn from(input: AtomIdentRef<'a>) -> Self {
match input {
AtomIdentRef::Fourcc(fourcc) => AtomIdent::Fourcc(fourcc),
AtomIdentRef::Freeform { mean, name } => AtomIdent::Freeform {
mean: mean.to_string(),
name: name.to_string(),
},
}
}
}

View file

@ -1,11 +1,12 @@
use super::{AtomDataRef, IlstRef};
use super::r#ref::IlstRef;
use crate::error::{ErrorKind, FileEncodingError, LoftyError, Result};
use crate::file::FileType;
use crate::macros::try_vec;
use crate::mp4::atom_info::{AtomIdent, AtomInfo};
use crate::mp4::ilst::{AtomIdentRef, AtomRef};
use crate::mp4::ilst::r#ref::{AtomIdentRef, AtomRef};
use crate::mp4::moov::Moov;
use crate::mp4::read::{atom_tree, meta_is_full, nested_atom, verify_mp4};
use crate::mp4::AtomData;
use crate::picture::{MimeType, Picture};
use std::fs::File;
@ -13,7 +14,10 @@ use std::io::{Cursor, Read, Seek, SeekFrom, Write};
use byteorder::{BigEndian, WriteBytesExt};
pub(in crate) fn write_to(data: &mut File, tag: &mut IlstRef<'_>) -> Result<()> {
pub(in crate) fn write_to<'a, I: 'a>(data: &mut File, tag: &mut IlstRef<'a, I>) -> Result<()>
where
I: IntoIterator<Item = &'a AtomData>,
{
verify_mp4(data)?;
let moov = Moov::find(data)?;
@ -292,7 +296,12 @@ fn write_size(start: u64, size: u64, extended: bool, writer: &mut Cursor<Vec<u8>
Ok(())
}
pub(super) fn build_ilst(atoms: &mut dyn Iterator<Item = AtomRef<'_>>) -> Result<Vec<u8>> {
pub(super) fn build_ilst<'a, I: 'a>(
atoms: &mut dyn Iterator<Item = AtomRef<'a, I>>,
) -> Result<Vec<u8>>
where
I: IntoIterator<Item = &'a AtomData>,
{
let mut peek = atoms.peekable();
if peek.peek().is_none() {
@ -313,7 +322,7 @@ pub(super) fn build_ilst(atoms: &mut dyn Iterator<Item = AtomRef<'_>>) -> Result
AtomIdentRef::Freeform { mean, name } => write_freeform(mean, name, &mut writer)?,
}
write_atom_data(&atom.data, &mut writer)?;
write_atom_data(atom.data, &mut writer)?;
let end = writer.stream_position()?;
@ -357,15 +366,22 @@ fn write_freeform(mean: &str, name: &str, writer: &mut Cursor<Vec<u8>>) -> Resul
Ok(())
}
fn write_atom_data(value: &AtomDataRef<'_>, writer: &mut Cursor<Vec<u8>>) -> Result<()> {
match value {
AtomDataRef::UTF8(text) => write_data(1, text.as_bytes(), writer),
AtomDataRef::UTF16(text) => write_data(2, text.as_bytes(), writer),
AtomDataRef::Picture(pic) => write_picture(pic, writer),
AtomDataRef::SignedInteger(int) => write_signed_int(*int, writer),
AtomDataRef::UnsignedInteger(uint) => write_unsigned_int(*uint, writer),
AtomDataRef::Unknown { code, data } => write_data(*code, data, writer),
fn write_atom_data<'a, I: 'a>(data: I, writer: &mut Cursor<Vec<u8>>) -> Result<()>
where
I: IntoIterator<Item = &'a AtomData>,
{
for value in data {
match value {
AtomData::UTF8(text) => write_data(1, text.as_bytes(), writer)?,
AtomData::UTF16(text) => write_data(2, text.as_bytes(), writer)?,
AtomData::Picture(ref pic) => write_picture(pic, writer)?,
AtomData::SignedInteger(int) => write_signed_int(*int, writer)?,
AtomData::UnsignedInteger(uint) => write_unsigned_int(*uint, writer)?,
AtomData::Unknown { code, ref data } => write_data(*code, data, writer)?,
};
}
Ok(())
}
fn write_signed_int(int: i32, writer: &mut Cursor<Vec<u8>>) -> Result<()> {

View file

@ -8,7 +8,7 @@ use crate::id3::v1::tag::Id3v1TagRef;
#[cfg(feature = "id3v2")]
use crate::id3::v2::{self, tag::Id3v2TagRef, Id3v2TagFlags};
#[cfg(feature = "mp4_ilst")]
use crate::mp4::ilst::IlstRef;
use crate::mp4::Ilst;
#[cfg(feature = "vorbis_comments")]
use crate::ogg::tag::{create_vorbis_comments_ref, VorbisCommentsRef};
#[cfg(feature = "ape")]
@ -32,7 +32,9 @@ pub(crate) fn write_tag(tag: &Tag, file: &mut File, file_type: FileType) -> Resu
},
FileType::MP3 => mp3::write::write_to(file, tag),
#[cfg(feature = "mp4_ilst")]
FileType::MP4 => crate::mp4::ilst::write::write_to(file, &mut Into::<IlstRef<'_>>::into(tag)),
FileType::MP4 => {
crate::mp4::ilst::write::write_to(file, &mut Into::<Ilst>::into(tag.clone()).as_ref())
},
FileType::WAV => iff::wav::write::write_to(file, tag),
_ => Err(LoftyError::new(ErrorKind::UnsupportedTag)),
}
@ -56,7 +58,7 @@ pub(crate) fn dump_tag<W: Write>(tag: &Tag, writer: &mut W) -> Result<()> {
}
.dump_to(writer),
#[cfg(feature = "mp4_ilst")]
TagType::Mp4Ilst => Into::<IlstRef<'_>>::into(tag).dump_to(writer),
TagType::Mp4Ilst => Into::<Ilst>::into(tag.clone()).as_ref().dump_to(writer),
#[cfg(feature = "vorbis_comments")]
TagType::VorbisComments => {
let (vendor, items, pictures) = create_vorbis_comments_ref(tag);

Binary file not shown.