ParseOptions: Add read_cover_art

This commit is contained in:
Serial 2024-07-02 21:19:23 -04:00 committed by Alex
parent 89dd85c3dc
commit 6e821b7e3e
22 changed files with 306 additions and 82 deletions

View file

@ -49,7 +49,7 @@ where
stream_len -= u64::from(header.size);
if parse_options.read_tags {
let id3v2 = parse_id3v2(reader, header, parse_mode)?;
let id3v2 = parse_id3v2(reader, header, parse_options)?;
if let Some(existing_tag) = &mut file.id3v2_tag {
log::warn!("Duplicate ID3v2 tag found, appending frames to previous tag");

View file

@ -47,7 +47,7 @@ where
if let Some(content) = content {
let reader = &mut &*content;
let id3v2 = parse_id3v2(reader, header, parse_options.parsing_mode)?;
let id3v2 = parse_id3v2(reader, header, parse_options)?;
id3v2_tag = Some(id3v2);
}
}
@ -89,7 +89,7 @@ where
stream_len -= u64::from(ape_header.size);
if parse_options.read_tags {
let ape = read_ape_tag_with_header(data, ape_header)?;
let ape = read_ape_tag_with_header(data, ape_header, parse_options)?;
ape_tag = Some(ape);
}
},
@ -125,7 +125,7 @@ where
// Strongly recommended to be at the end of the file
data.seek(SeekFrom::Current(-32))?;
if let (tag, Some(header)) = read_ape_tag(data, true, parse_options.read_tags)? {
if let (tag, Some(header)) = read_ape_tag(data, true, parse_options)? {
stream_len -= u64::from(header.size);
ape_tag = tag;
}

View file

@ -564,11 +564,12 @@ pub(crate) fn tagitems_into_ape(tag: &Tag) -> impl Iterator<Item = ApeItemRef<'_
#[cfg(test)]
mod tests {
use crate::ape::{ApeItem, ApeTag};
use crate::config::WriteOptions;
use crate::config::{ParseOptions, WriteOptions};
use crate::id3::v2::util::pairs::DEFAULT_NUMBER_IN_PAIR;
use crate::prelude::*;
use crate::tag::{ItemValue, Tag, TagItem, TagType};
use crate::picture::{MimeType, Picture, PictureType};
use std::io::Cursor;
#[test]
@ -623,7 +624,7 @@ mod tests {
let mut reader = Cursor::new(tag);
let (Some(parsed_tag), _) =
crate::ape::tag::read::read_ape_tag(&mut reader, false, true).unwrap()
crate::ape::tag::read::read_ape_tag(&mut reader, false, ParseOptions::new()).unwrap()
else {
unreachable!();
};
@ -641,7 +642,7 @@ mod tests {
let mut reader = Cursor::new(tag_bytes);
let (Some(parsed_tag), _) =
crate::ape::tag::read::read_ape_tag(&mut reader, false, true).unwrap()
crate::ape::tag::read::read_ape_tag(&mut reader, false, ParseOptions::new()).unwrap()
else {
unreachable!();
};
@ -654,9 +655,10 @@ mod tests {
let mut temp_reader = Cursor::new(writer);
let (Some(temp_parsed_tag), _) =
crate::ape::tag::read::read_ape_tag(&mut temp_reader, false, true).unwrap()
crate::ape::tag::read::read_ape_tag(&mut temp_reader, false, ParseOptions::new())
.unwrap()
else {
unreachable!()
unreachable!();
};
assert_eq!(parsed_tag, temp_parsed_tag);
@ -667,9 +669,10 @@ mod tests {
let tag_bytes = crate::tag::utils::test_utils::read_path("tests/tags/assets/test.apev2");
let mut reader = Cursor::new(tag_bytes);
let (Some(ape), _) = crate::ape::tag::read::read_ape_tag(&mut reader, false, true).unwrap()
let (Some(ape), _) =
crate::ape::tag::read::read_ape_tag(&mut reader, false, ParseOptions::new()).unwrap()
else {
unreachable!()
unreachable!();
};
let tag: Tag = ape.into();
@ -907,4 +910,34 @@ mod tests {
assert_eq!(tag.disk().unwrap(), disk_number);
assert_eq!(tag.disk_total().unwrap(), disk_total);
}
#[test]
fn skip_reading_cover_art() {
let p = Picture::new_unchecked(
PictureType::CoverFront,
Some(MimeType::Jpeg),
None,
std::iter::repeat(0).take(50).collect::<Vec<u8>>(),
);
let mut tag = Tag::new(TagType::Ape);
tag.push_picture(p);
tag.set_artist(String::from("Foo artist"));
let mut writer = Vec::new();
tag.dump_to(&mut writer, WriteOptions::new()).unwrap();
let mut reader = Cursor::new(writer);
let (Some(ape), _) = crate::ape::tag::read::read_ape_tag(
&mut reader,
false,
ParseOptions::new().read_cover_art(false),
)
.unwrap() else {
unreachable!()
};
assert_eq!(ape.len(), 1);
}
}

View file

@ -2,6 +2,8 @@ use super::item::ApeItem;
use super::ApeTag;
use crate::ape::constants::{APE_PREAMBLE, INVALID_KEYS};
use crate::ape::header::{self, ApeHeader};
use crate::ape::APE_PICTURE_TYPES;
use crate::config::ParseOptions;
use crate::error::Result;
use crate::macros::{decode_err, err, try_vec};
use crate::tag::ItemValue;
@ -11,7 +13,11 @@ use std::io::{Read, Seek, SeekFrom};
use byteorder::{LittleEndian, ReadBytesExt};
pub(crate) fn read_ape_tag_with_header<R>(data: &mut R, header: ApeHeader) -> Result<ApeTag>
pub(crate) fn read_ape_tag_with_header<R>(
data: &mut R,
header: ApeHeader,
parse_options: ParseOptions,
) -> Result<ApeTag>
where
R: Read + Seek,
{
@ -46,11 +52,17 @@ where
decode_err!(@BAIL Ape, "APE tag item contains an illegal key");
}
if APE_PICTURE_TYPES.contains(&&*key) && !parse_options.read_cover_art {
data.seek(SeekFrom::Current(i64::from(value_size)))?;
continue;
}
let read_only = (flags & 1) == 1;
let item_type = (flags >> 1) & 3;
if value_size == 0 || key.len() < 2 || key.len() > 255 {
log::warn!("APE: Encountered invalid item key '{}'", key);
data.seek(SeekFrom::Current(i64::from(value_size)))?;
continue;
}
@ -86,7 +98,7 @@ where
pub(crate) fn read_ape_tag<R: Read + Seek>(
reader: &mut R,
footer: bool,
read_tag: bool,
parse_options: ParseOptions,
) -> Result<(Option<ApeTag>, Option<ApeHeader>)> {
let mut ape_preamble = [0; 8];
reader.read_exact(&mut ape_preamble)?;
@ -94,8 +106,8 @@ pub(crate) fn read_ape_tag<R: Read + Seek>(
let mut ape_tag = None;
if &ape_preamble == APE_PREAMBLE {
let ape_header = header::read_ape_header(reader, footer)?;
if read_tag {
ape_tag = Some(read_ape_tag_with_header(reader, ape_header)?);
if parse_options.read_tags {
ape_tag = Some(read_ape_tag_with_header(reader, ape_header, parse_options)?);
}
return Ok((ape_tag, Some(ape_header)));

View file

@ -2,7 +2,7 @@ use super::item::ApeItemRef;
use super::ApeTagRef;
use crate::ape::constants::APE_PREAMBLE;
use crate::ape::tag::read;
use crate::config::WriteOptions;
use crate::config::{ParseOptions, WriteOptions};
use crate::error::{LoftyError, Result};
use crate::id3::{find_id3v1, find_id3v2, find_lyrics3v2, FindId3v2Config};
use crate::macros::{decode_err, err};
@ -48,7 +48,8 @@ where
let mut header_ape_tag = (false, (0, 0));
let start = file.stream_position()?;
match read::read_ape_tag(file, false, true)? {
// TODO: Forcing the use of ParseOptions::default()
match read::read_ape_tag(file, false, ParseOptions::new())? {
(Some(mut existing_tag), Some(header)) => {
if write_options.respect_read_only {
// Only keep metadata around that's marked read only
@ -80,7 +81,10 @@ where
// Also check this tag for any read only items
let start = file.stream_position()? as usize + 32;
if let (Some(mut existing_tag), Some(header)) = read::read_ape_tag(file, true, true)? {
// TODO: Forcing the use of ParseOptions::default()
if let (Some(mut existing_tag), Some(header)) =
read::read_ape_tag(file, true, ParseOptions::new())?
{
if write_options.respect_read_only {
existing_tag.items.retain(|i| i.read_only);

View file

@ -6,6 +6,7 @@ pub struct ParseOptions {
pub(crate) read_tags: bool,
pub(crate) parsing_mode: ParsingMode,
pub(crate) max_junk_bytes: usize,
pub(crate) read_cover_art: bool,
}
impl Default for ParseOptions {
@ -18,7 +19,8 @@ impl Default for ParseOptions {
/// read_properties: true,
/// read_tags: true,
/// parsing_mode: ParsingMode::BestAttempt,
/// max_junk_bytes: 1024
/// max_junk_bytes: 1024,
/// read_cover_art: true,
/// }
/// ```
fn default() -> Self {
@ -51,6 +53,7 @@ impl ParseOptions {
read_tags: true,
parsing_mode: Self::DEFAULT_PARSING_MODE,
max_junk_bytes: Self::DEFAULT_MAX_JUNK_BYTES,
read_cover_art: true,
}
}
@ -116,6 +119,21 @@ impl ParseOptions {
self.max_junk_bytes = max_junk_bytes;
*self
}
/// Whether or not to read cover art
///
/// # Examples
///
/// ```rust
/// use lofty::config::ParseOptions;
///
/// // Reading cover art is expensive, and I do not need it!
/// let parsing_options = ParseOptions::new().read_cover_art(false);
/// ```
pub fn read_cover_art(&mut self, read_cover_art: bool) -> Self {
self.read_cover_art = read_cover_art;
*self
}
}
/// The parsing strictness mode

View file

@ -59,7 +59,7 @@ where
let reader = &mut &*content;
let id3v2 = parse_id3v2(reader, header, parse_options.parsing_mode)?;
let id3v2 = parse_id3v2(reader, header, parse_options)?;
flac_file.id3v2_tag = Some(id3v2);
}
@ -103,7 +103,7 @@ where
let vorbis_comments = read_comments(
&mut &*block.content,
block.content.len() as u64,
parse_options.parsing_mode,
parse_options,
)?;
flac_file.vorbis_comments_tag = Some(vorbis_comments);

View file

@ -1,6 +1,6 @@
use super::header::parse::{parse_header, parse_v2_header};
use super::Frame;
use crate::config::ParsingMode;
use crate::config::{ParseOptions, ParsingMode};
use crate::error::{Id3v2Error, Id3v2ErrorKind, Result};
use crate::id3::v2::frame::content::parse_content;
use crate::id3::v2::header::Id3v2Version;
@ -10,6 +10,7 @@ use crate::macros::try_vec;
use std::io::Read;
use crate::id3::v2::tag::ATTACHED_PICTURE_ID;
use byteorder::{BigEndian, ReadBytesExt};
pub(crate) enum ParsedFrame<'a> {
@ -22,12 +23,13 @@ impl<'a> ParsedFrame<'a> {
pub(crate) fn read<R>(
reader: &mut R,
version: Id3v2Version,
parse_mode: ParsingMode,
parse_options: ParseOptions,
) -> Result<Self>
where
R: Read,
{
let mut size = 0u32;
let parse_mode = parse_options.parsing_mode;
// The header will be upgraded to ID3v2.4 past this point, so they can all be treated the same
let parse_header_result = match version {
@ -53,6 +55,10 @@ impl<'a> ParsedFrame<'a> {
},
};
if !parse_options.read_cover_art && id == ATTACHED_PICTURE_ID {
return Ok(Self::Skip { size });
}
if size == 0 {
if parse_mode == ParsingMode::Strict {
return Err(Id3v2Error::new(Id3v2ErrorKind::EmptyFrame(id)).into());

View file

@ -1,7 +1,7 @@
use super::frame::read::ParsedFrame;
use super::header::Id3v2Header;
use super::tag::Id3v2Tag;
use crate::config::ParsingMode;
use crate::config::ParseOptions;
use crate::error::{Id3v2Error, Id3v2ErrorKind, Result};
use crate::id3::v2::util::synchsafe::UnsynchronizedStream;
@ -10,7 +10,7 @@ use std::io::Read;
pub(crate) fn parse_id3v2<R>(
bytes: &mut R,
header: Id3v2Header,
parse_mode: ParsingMode,
parse_options: ParseOptions,
) -> Result<Id3v2Tag>
where
R: Read,
@ -27,12 +27,12 @@ where
if header.flags.unsynchronisation {
// Unsynchronize the entire tag
let mut unsynchronized_reader = UnsynchronizedStream::new(tag_bytes);
ret = read_all_frames_into_tag(&mut unsynchronized_reader, header, parse_mode)?;
ret = read_all_frames_into_tag(&mut unsynchronized_reader, header, parse_options)?;
// Get the `Take` back from the `UnsynchronizedStream`
tag_bytes = unsynchronized_reader.into_inner();
} else {
ret = read_all_frames_into_tag(&mut tag_bytes, header, parse_mode)?;
ret = read_all_frames_into_tag(&mut tag_bytes, header, parse_options)?;
};
// Throw away the rest of the tag (padding, bad frames)
@ -56,7 +56,7 @@ fn skip_frame(reader: &mut impl Read, size: u32) -> Result<()> {
fn read_all_frames_into_tag<R>(
reader: &mut R,
header: Id3v2Header,
parse_mode: ParsingMode,
parse_options: ParseOptions,
) -> Result<Id3v2Tag>
where
R: Read,
@ -66,7 +66,7 @@ where
tag.set_flags(header.flags);
loop {
match ParsedFrame::read(reader, header.version, parse_mode)? {
match ParsedFrame::read(reader, header.version, parse_options)? {
ParsedFrame::Next(frame) => {
let frame_value_is_empty = frame.is_empty();
if let Some(replaced_frame) = tag.insert(frame) {
@ -111,7 +111,12 @@ fn zero_size_id3v2() {
let mut f = Cursor::new(std::fs::read("tests/tags/assets/id3v2/zero.id3v2").unwrap());
let header = Id3v2Header::parse(&mut f).unwrap();
assert!(parse_id3v2(&mut f, header, ParsingMode::Strict).is_ok());
assert!(parse_id3v2(
&mut f,
header,
ParseOptions::new().parsing_mode(ParsingMode::Strict)
)
.is_ok());
}
#[test]
@ -128,7 +133,11 @@ fn bad_frame_id_relaxed_id3v2() {
std::fs::read("tests/tags/assets/id3v2/bad_frame_otherwise_valid.id3v24").unwrap(),
);
let header = Id3v2Header::parse(&mut f).unwrap();
let id3v2 = parse_id3v2(&mut f, header, ParsingMode::Relaxed);
let id3v2 = parse_id3v2(
&mut f,
header,
ParseOptions::new().parsing_mode(ParsingMode::Relaxed),
);
assert!(id3v2.is_ok());
let id3v2 = id3v2.unwrap();

View file

@ -724,6 +724,7 @@ const GENRE_ID: FrameId<'static> = FrameId::Valid(Cow::Borrowed("TCON"));
const TRACK_ID: FrameId<'static> = FrameId::Valid(Cow::Borrowed("TRCK"));
const DISC_ID: FrameId<'static> = FrameId::Valid(Cow::Borrowed("TPOS"));
const RECORDING_TIME_ID: FrameId<'static> = FrameId::Valid(Cow::Borrowed("TDRC"));
pub(super) const ATTACHED_PICTURE_ID: FrameId<'static> = FrameId::Valid(Cow::Borrowed("APIC"));
impl Accessor for Id3v2Tag {
impl_accessor!(

View file

@ -1,4 +1,4 @@
use crate::config::ParsingMode;
use crate::config::{ParseOptions, ParsingMode};
use crate::id3::v2::header::Id3v2Header;
use crate::id3::v2::items::PopularimeterFrame;
use crate::id3::v2::util::pairs::DEFAULT_NUMBER_IN_PAIR;
@ -21,10 +21,15 @@ fn read_tag(path: &str) -> Id3v2Tag {
}
fn read_tag_raw(bytes: &[u8]) -> Id3v2Tag {
let options = ParseOptions::new().parsing_mode(ParsingMode::Strict);
read_tag_with_options(bytes, options)
}
fn read_tag_with_options(bytes: &[u8], parse_options: ParseOptions) -> Id3v2Tag {
let mut reader = Cursor::new(bytes);
let header = Id3v2Header::parse(&mut reader).unwrap();
crate::id3::v2::read::parse_id3v2(&mut reader, header, ParsingMode::Strict).unwrap()
crate::id3::v2::read::parse_id3v2(&mut reader, header, parse_options).unwrap()
}
#[test]
@ -93,8 +98,12 @@ fn id3v2_re_read() {
let temp_reader = &mut &*writer;
let temp_header = Id3v2Header::parse(temp_reader).unwrap();
let temp_parsed_tag =
crate::id3::v2::read::parse_id3v2(temp_reader, temp_header, ParsingMode::Strict).unwrap();
let temp_parsed_tag = crate::id3::v2::read::parse_id3v2(
temp_reader,
temp_header,
ParseOptions::new().parsing_mode(ParsingMode::Strict),
)
.unwrap();
assert_eq!(parsed_tag, temp_parsed_tag);
}
@ -267,7 +276,12 @@ fn id3v24_footer() {
let mut reader = &mut &writer[..];
let header = Id3v2Header::parse(&mut reader).unwrap();
let _ = crate::id3::v2::read::parse_id3v2(reader, header, ParsingMode::Strict).unwrap();
let _ = crate::id3::v2::read::parse_id3v2(
reader,
header,
ParseOptions::new().parsing_mode(ParsingMode::Strict),
)
.unwrap();
assert_eq!(writer[3..10], writer[writer.len() - 7..])
}
@ -292,7 +306,12 @@ fn issue_36() {
let mut reader = &mut &writer[..];
let header = Id3v2Header::parse(&mut reader).unwrap();
let tag = crate::id3::v2::read::parse_id3v2(reader, header, ParsingMode::Strict).unwrap();
let tag = crate::id3::v2::read::parse_id3v2(
reader,
header,
ParseOptions::new().parsing_mode(ParsingMode::Strict),
)
.unwrap();
assert_eq!(tag.len(), 1);
assert_eq!(
@ -567,8 +586,12 @@ fn user_defined_frames_conversion() {
let mut reader = std::io::Cursor::new(&content[..]);
let header = Id3v2Header::parse(&mut reader).unwrap();
let reparsed =
crate::id3::v2::read::parse_id3v2(&mut reader, header, ParsingMode::Strict).unwrap();
let reparsed = crate::id3::v2::read::parse_id3v2(
&mut reader,
header,
ParseOptions::new().parsing_mode(ParsingMode::Strict),
)
.unwrap();
assert_eq!(id3v2, reparsed);
}
@ -1305,3 +1328,25 @@ fn hold_back_4_character_txxx_description() {
let tag: Id3v2Tag = tag.into();
assert_eq!(tag.len(), 1);
}
#[test]
fn skip_reading_cover_art() {
let p = Picture::new_unchecked(
PictureType::CoverFront,
Some(MimeType::Jpeg),
None,
std::iter::repeat(0).take(50).collect::<Vec<u8>>(),
);
let mut tag = Tag::new(TagType::Id3v2);
tag.push_picture(p);
tag.set_artist(String::from("Foo artist"));
let mut writer = Vec::new();
tag.dump_to(&mut writer, WriteOptions::new()).unwrap();
let id3v2 = read_tag_with_options(&writer[..], ParseOptions::new().read_cover_art(false));
assert_eq!(id3v2.len(), 1); // Artist, no picture
assert!(id3v2.artist().is_some());
}

View file

@ -70,7 +70,7 @@ where
while chunks.next(data).is_ok() {
match &chunks.fourcc {
b"ID3 " | b"id3 " if parse_options.read_tags => {
let tag = chunks.id3_chunk(data, parse_options.parsing_mode)?;
let tag = chunks.id3_chunk(data, parse_options)?;
if let Some(existing_tag) = id3v2_tag.as_mut() {
log::warn!("Duplicate ID3v2 tag found, appending frames to previous tag");

View file

@ -1,4 +1,4 @@
use crate::config::ParsingMode;
use crate::config::ParseOptions;
use crate::error::Result;
use crate::id3::v2::tag::Id3v2Tag;
use crate::macros::{err, try_vec};
@ -91,7 +91,7 @@ impl<B: ByteOrder> Chunks<B> {
Ok(content)
}
pub fn id3_chunk<R>(&mut self, data: &mut R, parse_mode: ParsingMode) -> Result<Id3v2Tag>
pub fn id3_chunk<R>(&mut self, data: &mut R, parse_options: ParseOptions) -> Result<Id3v2Tag>
where
R: Read + Seek,
{
@ -103,7 +103,7 @@ impl<B: ByteOrder> Chunks<B> {
let reader = &mut &*content;
let header = Id3v2Header::parse(reader)?;
let id3v2 = parse_id3v2(reader, header, parse_mode)?;
let id3v2 = parse_id3v2(reader, header, parse_options)?;
// Skip over the footer
if id3v2.flags().footer {

View file

@ -89,7 +89,7 @@ where
}
},
b"ID3 " | b"id3 " if parse_options.read_tags => {
let tag = chunks.id3_chunk(data, parse_options.parsing_mode)?;
let tag = chunks.id3_chunk(data, parse_options)?;
if let Some(existing_tag) = id3v2_tag.as_mut() {
log::warn!("Duplicate ID3v2 tag found, appending frames to previous tag");

View file

@ -806,6 +806,7 @@ mod tests {
use crate::tag::utils::test_utils::read_path;
use crate::tag::{ItemValue, Tag, TagItem, TagType};
use crate::picture::{MimeType, Picture, PictureType};
use std::io::{Cursor, Read as _, Seek as _, Write as _};
fn read_ilst(path: &str, parse_mode: ParsingMode) -> Ilst {
@ -814,12 +815,8 @@ mod tests {
}
fn read_ilst_raw(bytes: &[u8], parse_mode: ParsingMode) -> Ilst {
let len = bytes.len();
let cursor = Cursor::new(bytes);
let mut reader = AtomReader::new(cursor, parse_mode).unwrap();
super::read::parse_ilst(&mut reader, parse_mode, len as u64).unwrap()
let options = ParseOptions::new().parsing_mode(parse_mode);
read_ilst_with_options(bytes, options)
}
fn read_ilst_strict(path: &str) -> Ilst {
@ -830,6 +827,15 @@ mod tests {
read_ilst(path, ParsingMode::BestAttempt)
}
fn read_ilst_with_options(bytes: &[u8], parse_options: ParseOptions) -> Ilst {
let len = bytes.len();
let cursor = Cursor::new(bytes);
let mut reader = AtomReader::new(cursor, parse_options.parsing_mode).unwrap();
super::read::parse_ilst(&mut reader, parse_options, len as u64).unwrap()
}
fn verify_atom(ilst: &Ilst, ident: [u8; 4], data: &AtomData) {
let atom = ilst.get(&AtomIdent::Fourcc(ident)).unwrap();
assert_eq!(atom.data().next().unwrap(), data);
@ -895,8 +901,12 @@ mod tests {
let cursor = Cursor::new(tag);
let mut reader = AtomReader::new(cursor, ParsingMode::Strict).unwrap();
let parsed_tag =
super::read::parse_ilst(&mut reader, ParsingMode::Strict, len as u64).unwrap();
let parsed_tag = super::read::parse_ilst(
&mut reader,
ParseOptions::new().parsing_mode(ParsingMode::Strict),
len as u64,
)
.unwrap();
assert_eq!(expected_tag, parsed_tag);
}
@ -914,9 +924,12 @@ mod tests {
let mut reader = AtomReader::new(cursor, ParsingMode::Strict).unwrap();
// Remove the ilst identifier and size
let temp_parsed_tag =
super::read::parse_ilst(&mut reader, ParsingMode::Strict, (writer.len() - 8) as u64)
.unwrap();
let temp_parsed_tag = super::read::parse_ilst(
&mut reader,
ParseOptions::new().parsing_mode(ParsingMode::Strict),
(writer.len() - 8) as u64,
)
.unwrap();
assert_eq!(parsed_tag, temp_parsed_tag);
}
@ -929,7 +942,12 @@ mod tests {
let cursor = Cursor::new(tag);
let mut reader = AtomReader::new(cursor, ParsingMode::Strict).unwrap();
let ilst = super::read::parse_ilst(&mut reader, ParsingMode::Strict, len as u64).unwrap();
let ilst = super::read::parse_ilst(
&mut reader,
ParseOptions::new().parsing_mode(ParsingMode::Strict),
len as u64,
)
.unwrap();
let tag: Tag = ilst.into();
@ -1048,9 +1066,12 @@ mod tests {
let cursor = Cursor::new(ilst_bytes);
let mut reader = AtomReader::new(cursor, ParsingMode::Strict).unwrap();
ilst =
super::read::parse_ilst(&mut reader, ParsingMode::Strict, ilst_bytes.len() as u64)
.unwrap();
ilst = super::read::parse_ilst(
&mut reader,
ParseOptions::new().parsing_mode(ParsingMode::Strict),
ilst_bytes.len() as u64,
)
.unwrap();
}
let mut file = tempfile::tempfile().unwrap();
@ -1338,4 +1359,27 @@ mod tests {
let generic_tag_re_read = read_ilst_raw(&tag_bytes[..], ParsingMode::Strict);
assert_eq!(tag_re_read, generic_tag_re_read);
}
#[test]
fn skip_reading_cover_art() {
let p = Picture::new_unchecked(
PictureType::CoverFront,
Some(MimeType::Jpeg),
None,
std::iter::repeat(0).take(50).collect::<Vec<u8>>(),
);
let mut tag = Tag::new(TagType::Mp4Ilst);
tag.push_picture(p);
tag.set_artist(String::from("Foo artist"));
let mut writer = Vec::new();
tag.dump_to(&mut writer, WriteOptions::new()).unwrap();
// Skip `ilst` header
let ilst = read_ilst_with_options(&writer[8..], ParseOptions::new().read_cover_art(false));
assert_eq!(ilst.len(), 1); // Artist, no picture
assert!(ilst.artist().is_some());
}
}

View file

@ -2,7 +2,7 @@ use super::constants::{
BE_SIGNED_INTEGER, BE_UNSIGNED_INTEGER, BMP, JPEG, PNG, RESERVED, UTF16, UTF8,
};
use super::{Atom, AtomData, AtomIdent, Ilst};
use crate::config::ParsingMode;
use crate::config::{ParseOptions, ParsingMode};
use crate::error::{LoftyError, Result};
use crate::id3::v1::constants::GENRES;
use crate::macros::{err, try_vec};
@ -17,12 +17,14 @@ use std::io::{Cursor, Read, Seek, SeekFrom};
pub(in crate::mp4) fn parse_ilst<R>(
reader: &mut AtomReader<R>,
parsing_mode: ParsingMode,
parse_options: ParseOptions,
len: u64,
) -> Result<Ilst>
where
R: Read + Seek,
{
let parsing_mode = parse_options.parsing_mode;
let mut contents = try_vec![0; len as usize];
reader.read_exact(&mut contents)?;
@ -40,7 +42,12 @@ where
continue;
},
b"covr" => {
handle_covr(&mut ilst_reader, parsing_mode, &mut tag, &atom)?;
if parse_options.read_cover_art {
handle_covr(&mut ilst_reader, parsing_mode, &mut tag, &atom)?;
} else {
skip_unneeded(&mut ilst_reader, atom.extended, atom.len)?;
}
continue;
},
// Upgrade this to a \xa9gen atom

View file

@ -2,7 +2,7 @@ use super::atom_info::{AtomIdent, AtomInfo};
use super::ilst::read::parse_ilst;
use super::ilst::Ilst;
use super::read::{meta_is_full, nested_atom, skip_unneeded, AtomReader};
use crate::config::{ParseOptions, ParsingMode};
use crate::config::ParseOptions;
use crate::error::Result;
use crate::macros::decode_err;
@ -54,8 +54,7 @@ impl Moov {
}
},
b"udta" if parse_options.read_tags => {
let ilst_parsed =
ilst_from_udta(reader, parse_options.parsing_mode, atom.len - 8)?;
let ilst_parsed = ilst_from_udta(reader, parse_options, atom.len - 8)?;
if let Some(ilst_parsed) = ilst_parsed {
let Some(mut existing_ilst) = ilst else {
ilst = Some(ilst_parsed);
@ -85,7 +84,7 @@ impl Moov {
fn ilst_from_udta<R>(
reader: &mut AtomReader<R>,
parsing_mode: ParsingMode,
parse_options: ParseOptions,
len: u64,
) -> Result<Option<Ilst>>
where
@ -143,7 +142,7 @@ where
}
if found_ilst {
return parse_ilst(reader, parsing_mode, ilst_atom_size - 8).map(Some);
return parse_ilst(reader, parse_options, ilst_atom_size - 8).map(Some);
}
Ok(None)

View file

@ -44,7 +44,7 @@ where
let skip_footer = header.flags.footer;
if parse_options.read_tags {
let id3v2 = parse_id3v2(reader, header, parse_options.parsing_mode)?;
let id3v2 = parse_id3v2(reader, header, parse_options)?;
if let Some(existing_tag) = &mut file.id3v2_tag {
// https://github.com/Serial-ATA/lofty-rs/issues/87
// Duplicate tags should have their frames appended to the previous
@ -80,7 +80,9 @@ where
if parse_options.read_tags {
file.ape_tag = Some(crate::ape::tag::read::read_ape_tag_with_header(
reader, ape_header,
reader,
ape_header,
parse_options,
)?);
} else {
reader.seek(SeekFrom::Current(i64::from(ape_header.size)))?;
@ -122,7 +124,7 @@ where
{
let reader = &mut &*id3v2_bytes;
let id3v2 = parse_id3v2(reader, header, parse_options.parsing_mode)?;
let id3v2 = parse_id3v2(reader, header, parse_options)?;
if let Some(existing_tag) = &mut file.id3v2_tag {
// https://github.com/Serial-ATA/lofty-rs/issues/87
@ -155,7 +157,7 @@ where
reader.seek(SeekFrom::Current(-32))?;
match crate::ape::tag::read::read_ape_tag(reader, true, parse_options.read_tags)? {
match crate::ape::tag::read::read_ape_tag(reader, true, parse_options)? {
(tag, Some(header)) => {
file.ape_tag = tag;

View file

@ -33,7 +33,7 @@ where
if let ID3FindResults(Some(header), Some(content)) = find_id3v2(reader, find_id3v2_config)? {
let reader = &mut &*content;
let id3v2 = parse_id3v2(reader, header, parse_options.parsing_mode)?;
let id3v2 = parse_id3v2(reader, header, parse_options)?;
file.id3v2_tag = Some(id3v2);
stream_length -= u64::from(header.full_tag_size());
@ -55,9 +55,7 @@ where
reader.seek(SeekFrom::Current(-32))?;
if let (tag, Some(header)) =
crate::ape::tag::read::read_ape_tag(reader, true, parse_options.read_tags)?
{
if let (tag, Some(header)) = crate::ape::tag::read::read_ape_tag(reader, true, parse_options)? {
file.ape_tag = tag;
// Seek back to the start of the tag

View file

@ -18,13 +18,15 @@ pub type OGGTags = (Option<VorbisComments>, PageHeader, Packets);
pub(crate) fn read_comments<R>(
data: &mut R,
mut len: u64,
parse_mode: ParsingMode,
parse_options: ParseOptions,
) -> Result<VorbisComments>
where
R: Read,
{
use crate::macros::try_vec;
let parse_mode = parse_options.parsing_mode;
let vendor_len = data.read_u32::<LittleEndian>()?;
if u64::from(vendor_len) > len {
err!(SizeMismatch);
@ -106,6 +108,10 @@ where
match key {
k if k.eq_ignore_ascii_case(b"METADATA_BLOCK_PICTURE") => {
if !parse_options.read_cover_art {
continue;
}
match Picture::from_flac_bytes(value, true, parse_mode) {
Ok(picture) => tag.pictures.push(picture),
Err(e) => {
@ -119,6 +125,10 @@ where
}
},
k if k.eq_ignore_ascii_case(b"COVERART") => {
if !parse_options.read_cover_art {
continue;
}
// `COVERART` is an old deprecated image storage format. We have to convert it
// to a `METADATA_BLOCK_PICTURE` for it to be useful.
//
@ -223,7 +233,7 @@ where
metadata_packet = &metadata_packet[comment_sig.len()..];
let reader = &mut metadata_packet;
let tag = read_comments(reader, reader.len() as u64, parse_options.parsing_mode)?;
let tag = read_comments(reader, reader.len() as u64, parse_options)?;
Ok((Some(tag), first_page_header, packets))
}

View file

@ -709,15 +709,22 @@ pub(crate) fn create_vorbis_comments_ref(
#[cfg(test)]
mod tests {
use crate::config::{ParsingMode, WriteOptions};
use crate::config::{ParseOptions, ParsingMode, WriteOptions};
use crate::ogg::{OggPictureStorage, VorbisComments};
use crate::picture::{MimeType, Picture, PictureType};
use crate::prelude::*;
use crate::tag::{ItemValue, Tag, TagItem, TagType};
use std::io::Cursor;
fn read_tag(tag: &[u8]) -> VorbisComments {
let mut reader = std::io::Cursor::new(tag);
crate::ogg::read::read_comments(&mut reader, tag.len() as u64, ParsingMode::Strict).unwrap()
crate::ogg::read::read_comments(
&mut reader,
tag.len() as u64,
ParseOptions::new().parsing_mode(ParsingMode::Strict),
)
.unwrap()
}
#[test]
@ -894,4 +901,35 @@ mod tests {
assert_eq!(Some("Cmin"), vorbis_comments.get("INITIALKEY"));
}
}
#[test]
fn skip_reading_cover_art() {
let p = Picture::new_unchecked(
PictureType::CoverFront,
Some(MimeType::Jpeg),
None,
std::iter::repeat(0).take(50).collect::<Vec<u8>>(),
);
let mut tag = Tag::new(TagType::VorbisComments);
tag.push_picture(p);
tag.set_artist(String::from("Foo artist"));
let mut writer = Vec::new();
tag.dump_to(&mut writer, WriteOptions::new()).unwrap();
let mut reader = Cursor::new(&writer);
let tag = crate::ogg::read::read_comments(
&mut reader,
writer.len() as u64,
ParseOptions::new()
.parsing_mode(ParsingMode::Strict)
.read_cover_art(false),
)
.unwrap();
assert_eq!(tag.pictures().len(), 0); // Artist, no picture
assert!(tag.artist().is_some());
}
}

View file

@ -38,9 +38,7 @@ where
// Strongly recommended to be at the end of the file
reader.seek(SeekFrom::Current(-32))?;
if let (tag, Some(header)) =
crate::ape::tag::read::read_ape_tag(reader, true, parse_options.read_tags)?
{
if let (tag, Some(header)) = crate::ape::tag::read::read_ape_tag(reader, true, parse_options)? {
stream_length -= u64::from(header.size);
ape_tag = tag;
}