mirror of
https://github.com/Serial-ATA/lofty-rs
synced 2024-11-10 06:34:18 +00:00
ParseOptions: Add read_cover_art
This commit is contained in:
parent
89dd85c3dc
commit
6e821b7e3e
22 changed files with 306 additions and 82 deletions
|
@ -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");
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)));
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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!(
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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");
|
||||
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue