mirror of
https://github.com/Serial-ATA/lofty-rs
synced 2024-12-12 21:52:33 +00:00
ParseOptions: Add ParseOptions::read_tags
This makes it possible to use Lofty exclusively for its property reading, which many projects do at this point. closes #251
This commit is contained in:
parent
b2c310d709
commit
89dd85c3dc
34 changed files with 388 additions and 122 deletions
|
@ -6,6 +6,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- **ParseOptions**: `ParseOptions::read_tags` to skip the parsing of tags ([issue](https://github.com/Serial-ATA/lofty-rs/issues/251)) ([PR](https://github.com/Serial-ATA/lofty-rs/pull/406))
|
||||||
|
|
||||||
## [0.20.1] - 2024-07-02
|
## [0.20.1] - 2024-07-02
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
|
@ -48,6 +48,7 @@ where
|
||||||
|
|
||||||
stream_len -= u64::from(header.size);
|
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_mode)?;
|
||||||
if let Some(existing_tag) = &mut file.id3v2_tag {
|
if let Some(existing_tag) = &mut file.id3v2_tag {
|
||||||
log::warn!("Duplicate ID3v2 tag found, appending frames to previous tag");
|
log::warn!("Duplicate ID3v2 tag found, appending frames to previous tag");
|
||||||
|
@ -60,6 +61,7 @@ where
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
file.id3v2_tag = Some(id3v2);
|
file.id3v2_tag = Some(id3v2);
|
||||||
|
}
|
||||||
|
|
||||||
// Skip over the footer
|
// Skip over the footer
|
||||||
if skip_footer {
|
if skip_footer {
|
||||||
|
@ -94,7 +96,7 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unused_variables)]
|
#[allow(unused_variables)]
|
||||||
let ID3FindResults(header, id3v1) = find_id3v1(reader, true)?;
|
let ID3FindResults(header, id3v1) = find_id3v1(reader, parse_options.read_tags)?;
|
||||||
|
|
||||||
if header.is_some() {
|
if header.is_some() {
|
||||||
stream_len -= 128;
|
stream_len -= 128;
|
||||||
|
|
|
@ -8,7 +8,7 @@ use crate::id3::v1::tag::Id3v1Tag;
|
||||||
use crate::id3::v2::read::parse_id3v2;
|
use crate::id3::v2::read::parse_id3v2;
|
||||||
use crate::id3::v2::tag::Id3v2Tag;
|
use crate::id3::v2::tag::Id3v2Tag;
|
||||||
use crate::id3::{find_id3v1, find_id3v2, find_lyrics3v2, FindId3v2Config, ID3FindResults};
|
use crate::id3::{find_id3v1, find_id3v2, find_lyrics3v2, FindId3v2Config, ID3FindResults};
|
||||||
use crate::macros::decode_err;
|
use crate::macros::{decode_err, err};
|
||||||
|
|
||||||
use std::io::{Read, Seek, SeekFrom};
|
use std::io::{Read, Seek, SeekFrom};
|
||||||
|
|
||||||
|
@ -27,10 +27,14 @@ where
|
||||||
let mut id3v1_tag: Option<Id3v1Tag> = None;
|
let mut id3v1_tag: Option<Id3v1Tag> = None;
|
||||||
let mut ape_tag: Option<ApeTag> = None;
|
let mut ape_tag: Option<ApeTag> = None;
|
||||||
|
|
||||||
|
let find_id3v2_config = if parse_options.read_tags {
|
||||||
|
FindId3v2Config::READ_TAG
|
||||||
|
} else {
|
||||||
|
FindId3v2Config::NO_READ_TAG
|
||||||
|
};
|
||||||
|
|
||||||
// ID3v2 tags are unsupported in APE files, but still possible
|
// ID3v2 tags are unsupported in APE files, but still possible
|
||||||
if let ID3FindResults(Some(header), Some(content)) =
|
if let ID3FindResults(Some(header), content) = find_id3v2(data, find_id3v2_config)? {
|
||||||
find_id3v2(data, FindId3v2Config::READ_TAG)?
|
|
||||||
{
|
|
||||||
log::warn!("Encountered an ID3v2 tag. This tag cannot be rewritten to the APE file!");
|
log::warn!("Encountered an ID3v2 tag. This tag cannot be rewritten to the APE file!");
|
||||||
|
|
||||||
stream_len -= u64::from(header.size);
|
stream_len -= u64::from(header.size);
|
||||||
|
@ -40,11 +44,13 @@ where
|
||||||
stream_len -= 10;
|
stream_len -= 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(content) = content {
|
||||||
let reader = &mut &*content;
|
let reader = &mut &*content;
|
||||||
|
|
||||||
let id3v2 = parse_id3v2(reader, header, parse_options.parsing_mode)?;
|
let id3v2 = parse_id3v2(reader, header, parse_options.parsing_mode)?;
|
||||||
id3v2_tag = Some(id3v2);
|
id3v2_tag = Some(id3v2);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let mut found_mac = false;
|
let mut found_mac = false;
|
||||||
let mut mac_start = 0;
|
let mut mac_start = 0;
|
||||||
|
@ -76,14 +82,16 @@ where
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
if &remaining[..4] != b"AGEX" {
|
if &remaining[..4] != b"AGEX" {
|
||||||
decode_err!(@BAIL Ape, "Found incomplete APE tag");
|
err!(FakeTag)
|
||||||
}
|
}
|
||||||
|
|
||||||
let ape_header = read_ape_header(data, false)?;
|
let ape_header = read_ape_header(data, false)?;
|
||||||
stream_len -= u64::from(ape_header.size);
|
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)?;
|
||||||
ape_tag = Some(ape);
|
ape_tag = Some(ape);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
_ => {
|
_ => {
|
||||||
decode_err!(@BAIL Ape, "Invalid data found while reading header, expected any of [\"MAC \", \"APETAGEX\", \"ID3\"]")
|
decode_err!(@BAIL Ape, "Invalid data found while reading header, expected any of [\"MAC \", \"APETAGEX\", \"ID3\"]")
|
||||||
|
@ -96,7 +104,7 @@ where
|
||||||
// Starts with ['T', 'A', 'G']
|
// Starts with ['T', 'A', 'G']
|
||||||
// Exactly 128 bytes long (including the identifier)
|
// Exactly 128 bytes long (including the identifier)
|
||||||
#[allow(unused_variables)]
|
#[allow(unused_variables)]
|
||||||
let ID3FindResults(id3v1_header, id3v1) = find_id3v1(data, true)?;
|
let ID3FindResults(id3v1_header, id3v1) = find_id3v1(data, parse_options.read_tags)?;
|
||||||
|
|
||||||
if id3v1_header.is_some() {
|
if id3v1_header.is_some() {
|
||||||
stream_len -= 128;
|
stream_len -= 128;
|
||||||
|
@ -117,9 +125,9 @@ where
|
||||||
// Strongly recommended to be at the end of the file
|
// Strongly recommended to be at the end of the file
|
||||||
data.seek(SeekFrom::Current(-32))?;
|
data.seek(SeekFrom::Current(-32))?;
|
||||||
|
|
||||||
if let Some((tag, header)) = read_ape_tag(data, true)? {
|
if let (tag, Some(header)) = read_ape_tag(data, true, parse_options.read_tags)? {
|
||||||
stream_len -= u64::from(header.size);
|
stream_len -= u64::from(header.size);
|
||||||
ape_tag = Some(tag);
|
ape_tag = tag;
|
||||||
}
|
}
|
||||||
|
|
||||||
let file_length = data.stream_position()?;
|
let file_length = data.stream_position()?;
|
||||||
|
|
|
@ -622,9 +622,11 @@ mod tests {
|
||||||
let tag = crate::tag::utils::test_utils::read_path("tests/tags/assets/test.apev2");
|
let tag = crate::tag::utils::test_utils::read_path("tests/tags/assets/test.apev2");
|
||||||
let mut reader = Cursor::new(tag);
|
let mut reader = Cursor::new(tag);
|
||||||
|
|
||||||
let (parsed_tag, _) = crate::ape::tag::read::read_ape_tag(&mut reader, false)
|
let (Some(parsed_tag), _) =
|
||||||
.unwrap()
|
crate::ape::tag::read::read_ape_tag(&mut reader, false, true).unwrap()
|
||||||
.unwrap();
|
else {
|
||||||
|
unreachable!();
|
||||||
|
};
|
||||||
|
|
||||||
assert_eq!(expected_tag.len(), parsed_tag.len());
|
assert_eq!(expected_tag.len(), parsed_tag.len());
|
||||||
|
|
||||||
|
@ -638,9 +640,11 @@ mod tests {
|
||||||
let tag_bytes = crate::tag::utils::test_utils::read_path("tests/tags/assets/test.apev2");
|
let tag_bytes = crate::tag::utils::test_utils::read_path("tests/tags/assets/test.apev2");
|
||||||
let mut reader = Cursor::new(tag_bytes);
|
let mut reader = Cursor::new(tag_bytes);
|
||||||
|
|
||||||
let (parsed_tag, _) = crate::ape::tag::read::read_ape_tag(&mut reader, false)
|
let (Some(parsed_tag), _) =
|
||||||
.unwrap()
|
crate::ape::tag::read::read_ape_tag(&mut reader, false, true).unwrap()
|
||||||
.unwrap();
|
else {
|
||||||
|
unreachable!();
|
||||||
|
};
|
||||||
|
|
||||||
let mut writer = Vec::new();
|
let mut writer = Vec::new();
|
||||||
parsed_tag
|
parsed_tag
|
||||||
|
@ -649,9 +653,11 @@ mod tests {
|
||||||
|
|
||||||
let mut temp_reader = Cursor::new(writer);
|
let mut temp_reader = Cursor::new(writer);
|
||||||
|
|
||||||
let (temp_parsed_tag, _) = crate::ape::tag::read::read_ape_tag(&mut temp_reader, false)
|
let (Some(temp_parsed_tag), _) =
|
||||||
.unwrap()
|
crate::ape::tag::read::read_ape_tag(&mut temp_reader, false, true).unwrap()
|
||||||
.unwrap();
|
else {
|
||||||
|
unreachable!()
|
||||||
|
};
|
||||||
|
|
||||||
assert_eq!(parsed_tag, temp_parsed_tag);
|
assert_eq!(parsed_tag, temp_parsed_tag);
|
||||||
}
|
}
|
||||||
|
@ -661,9 +667,10 @@ mod tests {
|
||||||
let tag_bytes = crate::tag::utils::test_utils::read_path("tests/tags/assets/test.apev2");
|
let tag_bytes = crate::tag::utils::test_utils::read_path("tests/tags/assets/test.apev2");
|
||||||
let mut reader = Cursor::new(tag_bytes);
|
let mut reader = Cursor::new(tag_bytes);
|
||||||
|
|
||||||
let (ape, _) = crate::ape::tag::read::read_ape_tag(&mut reader, false)
|
let (Some(ape), _) = crate::ape::tag::read::read_ape_tag(&mut reader, false, true).unwrap()
|
||||||
.unwrap()
|
else {
|
||||||
.unwrap();
|
unreachable!()
|
||||||
|
};
|
||||||
|
|
||||||
let tag: Tag = ape.into();
|
let tag: Tag = ape.into();
|
||||||
|
|
||||||
|
|
|
@ -86,16 +86,20 @@ where
|
||||||
pub(crate) fn read_ape_tag<R: Read + Seek>(
|
pub(crate) fn read_ape_tag<R: Read + Seek>(
|
||||||
reader: &mut R,
|
reader: &mut R,
|
||||||
footer: bool,
|
footer: bool,
|
||||||
) -> Result<Option<(ApeTag, ApeHeader)>> {
|
read_tag: bool,
|
||||||
|
) -> Result<(Option<ApeTag>, Option<ApeHeader>)> {
|
||||||
let mut ape_preamble = [0; 8];
|
let mut ape_preamble = [0; 8];
|
||||||
reader.read_exact(&mut ape_preamble)?;
|
reader.read_exact(&mut ape_preamble)?;
|
||||||
|
|
||||||
|
let mut ape_tag = None;
|
||||||
if &ape_preamble == APE_PREAMBLE {
|
if &ape_preamble == APE_PREAMBLE {
|
||||||
let ape_header = header::read_ape_header(reader, footer)?;
|
let ape_header = header::read_ape_header(reader, footer)?;
|
||||||
|
if read_tag {
|
||||||
let ape = read_ape_tag_with_header(reader, ape_header)?;
|
ape_tag = Some(read_ape_tag_with_header(reader, ape_header)?);
|
||||||
return Ok(Some((ape, ape_header)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(None)
|
return Ok((ape_tag, Some(ape_header)));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok((None, None))
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,8 +48,8 @@ where
|
||||||
let mut header_ape_tag = (false, (0, 0));
|
let mut header_ape_tag = (false, (0, 0));
|
||||||
|
|
||||||
let start = file.stream_position()?;
|
let start = file.stream_position()?;
|
||||||
match read::read_ape_tag(file, false)? {
|
match read::read_ape_tag(file, false, true)? {
|
||||||
Some((mut existing_tag, header)) => {
|
(Some(mut existing_tag), Some(header)) => {
|
||||||
if write_options.respect_read_only {
|
if write_options.respect_read_only {
|
||||||
// Only keep metadata around that's marked read only
|
// Only keep metadata around that's marked read only
|
||||||
existing_tag.items.retain(|i| i.read_only);
|
existing_tag.items.retain(|i| i.read_only);
|
||||||
|
@ -61,7 +61,7 @@ where
|
||||||
|
|
||||||
header_ape_tag = (true, (start, start + u64::from(header.size)))
|
header_ape_tag = (true, (start, start + u64::from(header.size)))
|
||||||
},
|
},
|
||||||
None => {
|
_ => {
|
||||||
file.seek(SeekFrom::Current(-8))?;
|
file.seek(SeekFrom::Current(-8))?;
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -80,7 +80,7 @@ where
|
||||||
|
|
||||||
// Also check this tag for any read only items
|
// Also check this tag for any read only items
|
||||||
let start = file.stream_position()? as usize + 32;
|
let start = file.stream_position()? as usize + 32;
|
||||||
if let Some((mut existing_tag, header)) = read::read_ape_tag(file, true)? {
|
if let (Some(mut existing_tag), Some(header)) = read::read_ape_tag(file, true, true)? {
|
||||||
if write_options.respect_read_only {
|
if write_options.respect_read_only {
|
||||||
existing_tag.items.retain(|i| i.read_only);
|
existing_tag.items.retain(|i| i.read_only);
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
pub struct ParseOptions {
|
pub struct ParseOptions {
|
||||||
pub(crate) read_properties: bool,
|
pub(crate) read_properties: bool,
|
||||||
|
pub(crate) read_tags: bool,
|
||||||
pub(crate) parsing_mode: ParsingMode,
|
pub(crate) parsing_mode: ParsingMode,
|
||||||
pub(crate) max_junk_bytes: usize,
|
pub(crate) max_junk_bytes: usize,
|
||||||
}
|
}
|
||||||
|
@ -15,6 +16,7 @@ impl Default for ParseOptions {
|
||||||
/// ```rust,ignore
|
/// ```rust,ignore
|
||||||
/// ParseOptions {
|
/// ParseOptions {
|
||||||
/// read_properties: true,
|
/// read_properties: true,
|
||||||
|
/// read_tags: true,
|
||||||
/// parsing_mode: ParsingMode::BestAttempt,
|
/// parsing_mode: ParsingMode::BestAttempt,
|
||||||
/// max_junk_bytes: 1024
|
/// max_junk_bytes: 1024
|
||||||
/// }
|
/// }
|
||||||
|
@ -46,6 +48,7 @@ impl ParseOptions {
|
||||||
pub const fn new() -> Self {
|
pub const fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
read_properties: true,
|
read_properties: true,
|
||||||
|
read_tags: true,
|
||||||
parsing_mode: Self::DEFAULT_PARSING_MODE,
|
parsing_mode: Self::DEFAULT_PARSING_MODE,
|
||||||
max_junk_bytes: Self::DEFAULT_MAX_JUNK_BYTES,
|
max_junk_bytes: Self::DEFAULT_MAX_JUNK_BYTES,
|
||||||
}
|
}
|
||||||
|
@ -66,6 +69,21 @@ impl ParseOptions {
|
||||||
*self
|
*self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Whether or not to read the tags
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use lofty::config::ParseOptions;
|
||||||
|
///
|
||||||
|
/// // By default, `read_tags` is enabled. Here, we don't want to read them.
|
||||||
|
/// let parsing_options = ParseOptions::new().read_tags(false);
|
||||||
|
/// ```
|
||||||
|
pub fn read_tags(&mut self, read_tags: bool) -> Self {
|
||||||
|
self.read_tags = read_tags;
|
||||||
|
*self
|
||||||
|
}
|
||||||
|
|
||||||
/// The parsing mode to use, see [`ParsingMode`] for details
|
/// The parsing mode to use, see [`ParsingMode`] for details
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
|
|
|
@ -47,10 +47,14 @@ where
|
||||||
properties: FlacProperties::default(),
|
properties: FlacProperties::default(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let find_id3v2_config = if parse_options.read_tags {
|
||||||
|
FindId3v2Config::READ_TAG
|
||||||
|
} else {
|
||||||
|
FindId3v2Config::NO_READ_TAG
|
||||||
|
};
|
||||||
|
|
||||||
// It is possible for a FLAC file to contain an ID3v2 tag
|
// It is possible for a FLAC file to contain an ID3v2 tag
|
||||||
if let ID3FindResults(Some(header), Some(content)) =
|
if let ID3FindResults(Some(header), Some(content)) = find_id3v2(data, find_id3v2_config)? {
|
||||||
find_id3v2(data, FindId3v2Config::READ_TAG)?
|
|
||||||
{
|
|
||||||
log::warn!("Encountered an ID3v2 tag. This tag cannot be rewritten to the FLAC file!");
|
log::warn!("Encountered an ID3v2 tag. This tag cannot be rewritten to the FLAC file!");
|
||||||
|
|
||||||
let reader = &mut &*content;
|
let reader = &mut &*content;
|
||||||
|
@ -78,7 +82,7 @@ where
|
||||||
decode_err!(@BAIL Flac, "Encountered a zero-sized metadata block");
|
decode_err!(@BAIL Flac, "Encountered a zero-sized metadata block");
|
||||||
}
|
}
|
||||||
|
|
||||||
if block.ty == BLOCK_ID_VORBIS_COMMENTS {
|
if block.ty == BLOCK_ID_VORBIS_COMMENTS && parse_options.read_tags {
|
||||||
log::debug!("Encountered a Vorbis Comments block, parsing");
|
log::debug!("Encountered a Vorbis Comments block, parsing");
|
||||||
|
|
||||||
// NOTE: According to the spec
|
// NOTE: According to the spec
|
||||||
|
@ -106,7 +110,7 @@ where
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if block.ty == BLOCK_ID_PICTURE {
|
if block.ty == BLOCK_ID_PICTURE && parse_options.read_tags {
|
||||||
log::debug!("Encountered a FLAC picture block, parsing");
|
log::debug!("Encountered a FLAC picture block, parsing");
|
||||||
|
|
||||||
match Picture::from_flac_bytes(&block.content, false, parse_options.parsing_mode) {
|
match Picture::from_flac_bytes(&block.content, false, parse_options.parsing_mode) {
|
||||||
|
|
|
@ -69,7 +69,7 @@ where
|
||||||
|
|
||||||
while chunks.next(data).is_ok() {
|
while chunks.next(data).is_ok() {
|
||||||
match &chunks.fourcc {
|
match &chunks.fourcc {
|
||||||
b"ID3 " | b"id3 " => {
|
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.parsing_mode)?;
|
||||||
if let Some(existing_tag) = id3v2_tag.as_mut() {
|
if let Some(existing_tag) = id3v2_tag.as_mut() {
|
||||||
log::warn!("Duplicate ID3v2 tag found, appending frames to previous tag");
|
log::warn!("Duplicate ID3v2 tag found, appending frames to previous tag");
|
||||||
|
@ -95,12 +95,12 @@ where
|
||||||
stream_len = chunks.size;
|
stream_len = chunks.size;
|
||||||
chunks.skip(data)?;
|
chunks.skip(data)?;
|
||||||
},
|
},
|
||||||
b"ANNO" => {
|
b"ANNO" if parse_options.read_tags => {
|
||||||
annotations.push(chunks.read_pstring(data, None)?);
|
annotations.push(chunks.read_pstring(data, None)?);
|
||||||
},
|
},
|
||||||
// These four chunks are expected to appear at most once per file,
|
// These four chunks are expected to appear at most once per file,
|
||||||
// so there's no need to replace anything we already read
|
// so there's no need to replace anything we already read
|
||||||
b"COMT" if comments.is_empty() => {
|
b"COMT" if comments.is_empty() && parse_options.read_tags => {
|
||||||
if chunks.size < 2 {
|
if chunks.size < 2 {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -123,13 +123,13 @@ where
|
||||||
|
|
||||||
chunks.correct_position(data)?;
|
chunks.correct_position(data)?;
|
||||||
},
|
},
|
||||||
b"NAME" if text_chunks.name.is_none() => {
|
b"NAME" if text_chunks.name.is_none() && parse_options.read_tags => {
|
||||||
text_chunks.name = Some(chunks.read_pstring(data, None)?);
|
text_chunks.name = Some(chunks.read_pstring(data, None)?);
|
||||||
},
|
},
|
||||||
b"AUTH" if text_chunks.author.is_none() => {
|
b"AUTH" if text_chunks.author.is_none() && parse_options.read_tags => {
|
||||||
text_chunks.author = Some(chunks.read_pstring(data, None)?);
|
text_chunks.author = Some(chunks.read_pstring(data, None)?);
|
||||||
},
|
},
|
||||||
b"(c) " if text_chunks.copyright.is_none() => {
|
b"(c) " if text_chunks.copyright.is_none() && parse_options.read_tags => {
|
||||||
text_chunks.copyright = Some(chunks.read_pstring(data, None)?);
|
text_chunks.copyright = Some(chunks.read_pstring(data, None)?);
|
||||||
},
|
},
|
||||||
_ => chunks.skip(data)?,
|
_ => chunks.skip(data)?,
|
||||||
|
|
|
@ -78,7 +78,7 @@ where
|
||||||
data.read_exact(&mut list_type)?;
|
data.read_exact(&mut list_type)?;
|
||||||
|
|
||||||
match &list_type {
|
match &list_type {
|
||||||
b"INFO" => {
|
b"INFO" if parse_options.read_tags => {
|
||||||
let end = data.stream_position()? + u64::from(chunks.size - 4);
|
let end = data.stream_position()? + u64::from(chunks.size - 4);
|
||||||
super::tag::read::parse_riff_info(data, &mut chunks, end, &mut riff_info)?;
|
super::tag::read::parse_riff_info(data, &mut chunks, end, &mut riff_info)?;
|
||||||
},
|
},
|
||||||
|
@ -88,7 +88,7 @@ where
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
b"ID3 " | b"id3 " => {
|
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.parsing_mode)?;
|
||||||
if let Some(existing_tag) = id3v2_tag.as_mut() {
|
if let Some(existing_tag) = id3v2_tag.as_mut() {
|
||||||
log::warn!("Duplicate ID3v2 tag found, appending frames to previous tag");
|
log::warn!("Duplicate ID3v2 tag found, appending frames to previous tag");
|
||||||
|
|
|
@ -2,7 +2,7 @@ use super::atom_info::{AtomIdent, AtomInfo};
|
||||||
use super::ilst::read::parse_ilst;
|
use super::ilst::read::parse_ilst;
|
||||||
use super::ilst::Ilst;
|
use super::ilst::Ilst;
|
||||||
use super::read::{meta_is_full, nested_atom, skip_unneeded, AtomReader};
|
use super::read::{meta_is_full, nested_atom, skip_unneeded, AtomReader};
|
||||||
use crate::config::ParsingMode;
|
use crate::config::{ParseOptions, ParsingMode};
|
||||||
use crate::error::Result;
|
use crate::error::Result;
|
||||||
use crate::macros::decode_err;
|
use crate::macros::decode_err;
|
||||||
|
|
||||||
|
@ -34,11 +34,7 @@ impl Moov {
|
||||||
moov.ok_or_else(|| decode_err!(Mp4, "No \"moov\" atom found"))
|
moov.ok_or_else(|| decode_err!(Mp4, "No \"moov\" atom found"))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn parse<R>(
|
pub(super) fn parse<R>(reader: &mut AtomReader<R>, parse_options: ParseOptions) -> Result<Self>
|
||||||
reader: &mut AtomReader<R>,
|
|
||||||
parse_mode: ParsingMode,
|
|
||||||
read_properties: bool,
|
|
||||||
) -> Result<Self>
|
|
||||||
where
|
where
|
||||||
R: Read + Seek,
|
R: Read + Seek,
|
||||||
{
|
{
|
||||||
|
@ -48,15 +44,18 @@ impl Moov {
|
||||||
while let Ok(Some(atom)) = reader.next() {
|
while let Ok(Some(atom)) = reader.next() {
|
||||||
if let AtomIdent::Fourcc(fourcc) = atom.ident {
|
if let AtomIdent::Fourcc(fourcc) = atom.ident {
|
||||||
match &fourcc {
|
match &fourcc {
|
||||||
b"trak" if read_properties => {
|
b"trak" if parse_options.read_properties => {
|
||||||
// All we need from here is trak.mdia
|
// All we need from here is trak.mdia
|
||||||
if let Some(mdia) = nested_atom(reader, atom.len, b"mdia", parse_mode)? {
|
if let Some(mdia) =
|
||||||
|
nested_atom(reader, atom.len, b"mdia", parse_options.parsing_mode)?
|
||||||
|
{
|
||||||
skip_unneeded(reader, mdia.extended, mdia.len)?;
|
skip_unneeded(reader, mdia.extended, mdia.len)?;
|
||||||
traks.push(mdia);
|
traks.push(mdia);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
b"udta" => {
|
b"udta" if parse_options.read_tags => {
|
||||||
let ilst_parsed = ilst_from_udta(reader, parse_mode, atom.len - 8)?;
|
let ilst_parsed =
|
||||||
|
ilst_from_udta(reader, parse_options.parsing_mode, atom.len - 8)?;
|
||||||
if let Some(ilst_parsed) = ilst_parsed {
|
if let Some(ilst_parsed) = ilst_parsed {
|
||||||
let Some(mut existing_ilst) = ilst else {
|
let Some(mut existing_ilst) = ilst else {
|
||||||
ilst = Some(ilst_parsed);
|
ilst = Some(ilst_parsed);
|
||||||
|
|
|
@ -192,11 +192,7 @@ where
|
||||||
let moov_info = Moov::find(&mut reader)?;
|
let moov_info = Moov::find(&mut reader)?;
|
||||||
reader.reset_bounds(moov_info.start + 8, moov_info.len - 8);
|
reader.reset_bounds(moov_info.start + 8, moov_info.len - 8);
|
||||||
|
|
||||||
let moov = Moov::parse(
|
let moov = Moov::parse(&mut reader, parse_options)?;
|
||||||
&mut reader,
|
|
||||||
parse_options.parsing_mode,
|
|
||||||
parse_options.read_properties,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
Ok(Mp4File {
|
Ok(Mp4File {
|
||||||
ftyp,
|
ftyp,
|
||||||
|
|
|
@ -43,6 +43,7 @@ where
|
||||||
let header = Id3v2Header::parse(reader)?;
|
let header = Id3v2Header::parse(reader)?;
|
||||||
let skip_footer = header.flags.footer;
|
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.parsing_mode)?;
|
||||||
if let Some(existing_tag) = &mut file.id3v2_tag {
|
if let Some(existing_tag) = &mut file.id3v2_tag {
|
||||||
// https://github.com/Serial-ATA/lofty-rs/issues/87
|
// https://github.com/Serial-ATA/lofty-rs/issues/87
|
||||||
|
@ -53,6 +54,9 @@ where
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
file.id3v2_tag = Some(id3v2);
|
file.id3v2_tag = Some(id3v2);
|
||||||
|
} else {
|
||||||
|
reader.seek(SeekFrom::Current(i64::from(header.size)))?;
|
||||||
|
}
|
||||||
|
|
||||||
// Skip over the footer
|
// Skip over the footer
|
||||||
if skip_footer {
|
if skip_footer {
|
||||||
|
@ -74,9 +78,13 @@ where
|
||||||
if &header_remaining == b"AGEX" {
|
if &header_remaining == b"AGEX" {
|
||||||
let ape_header = read_ape_header(reader, false)?;
|
let ape_header = read_ape_header(reader, false)?;
|
||||||
|
|
||||||
|
if parse_options.read_tags {
|
||||||
file.ape_tag = Some(crate::ape::tag::read::read_ape_tag_with_header(
|
file.ape_tag = Some(crate::ape::tag::read::read_ape_tag_with_header(
|
||||||
reader, ape_header,
|
reader, ape_header,
|
||||||
)?);
|
)?);
|
||||||
|
} else {
|
||||||
|
reader.seek(SeekFrom::Current(i64::from(ape_header.size)))?;
|
||||||
|
}
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -105,7 +113,7 @@ where
|
||||||
std::cmp::min(_first_frame_offset, parse_options.max_junk_bytes as u64);
|
std::cmp::min(_first_frame_offset, parse_options.max_junk_bytes as u64);
|
||||||
|
|
||||||
let config = FindId3v2Config {
|
let config = FindId3v2Config {
|
||||||
read: true,
|
read: parse_options.read_tags,
|
||||||
allowed_junk_window: Some(search_window_size),
|
allowed_junk_window: Some(search_window_size),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -137,7 +145,7 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unused_variables)]
|
#[allow(unused_variables)]
|
||||||
let ID3FindResults(header, id3v1) = find_id3v1(reader, true)?;
|
let ID3FindResults(header, id3v1) = find_id3v1(reader, parse_options.read_tags)?;
|
||||||
|
|
||||||
if header.is_some() {
|
if header.is_some() {
|
||||||
file.id3v1_tag = id3v1;
|
file.id3v1_tag = id3v1;
|
||||||
|
@ -147,15 +155,15 @@ where
|
||||||
|
|
||||||
reader.seek(SeekFrom::Current(-32))?;
|
reader.seek(SeekFrom::Current(-32))?;
|
||||||
|
|
||||||
match crate::ape::tag::read::read_ape_tag(reader, true)? {
|
match crate::ape::tag::read::read_ape_tag(reader, true, parse_options.read_tags)? {
|
||||||
Some((tag, header)) => {
|
(tag, Some(header)) => {
|
||||||
file.ape_tag = Some(tag);
|
file.ape_tag = tag;
|
||||||
|
|
||||||
// Seek back to the start of the tag
|
// Seek back to the start of the tag
|
||||||
let pos = reader.stream_position()?;
|
let pos = reader.stream_position()?;
|
||||||
reader.seek(SeekFrom::Start(pos - u64::from(header.size)))?;
|
reader.seek(SeekFrom::Start(pos - u64::from(header.size)))?;
|
||||||
},
|
},
|
||||||
None => {
|
_ => {
|
||||||
// Correct the position (APE header - Preamble)
|
// Correct the position (APE header - Preamble)
|
||||||
reader.seek(SeekFrom::Current(24))?;
|
reader.seek(SeekFrom::Current(24))?;
|
||||||
},
|
},
|
||||||
|
|
|
@ -22,11 +22,15 @@ where
|
||||||
|
|
||||||
let mut stream_length = reader.stream_len_hack()?;
|
let mut stream_length = reader.stream_len_hack()?;
|
||||||
|
|
||||||
|
let find_id3v2_config = if parse_options.read_tags {
|
||||||
|
FindId3v2Config::READ_TAG
|
||||||
|
} else {
|
||||||
|
FindId3v2Config::NO_READ_TAG
|
||||||
|
};
|
||||||
|
|
||||||
// ID3v2 tags are unsupported in MPC files, but still possible
|
// ID3v2 tags are unsupported in MPC files, but still possible
|
||||||
#[allow(unused_variables)]
|
#[allow(unused_variables)]
|
||||||
if let ID3FindResults(Some(header), Some(content)) =
|
if let ID3FindResults(Some(header), Some(content)) = find_id3v2(reader, find_id3v2_config)? {
|
||||||
find_id3v2(reader, FindId3v2Config::READ_TAG)?
|
|
||||||
{
|
|
||||||
let reader = &mut &*content;
|
let reader = &mut &*content;
|
||||||
|
|
||||||
let id3v2 = parse_id3v2(reader, header, parse_options.parsing_mode)?;
|
let id3v2 = parse_id3v2(reader, header, parse_options.parsing_mode)?;
|
||||||
|
@ -39,7 +43,7 @@ where
|
||||||
let pos_past_id3v2 = reader.stream_position()?;
|
let pos_past_id3v2 = reader.stream_position()?;
|
||||||
|
|
||||||
#[allow(unused_variables)]
|
#[allow(unused_variables)]
|
||||||
let ID3FindResults(header, id3v1) = find_id3v1(reader, true)?;
|
let ID3FindResults(header, id3v1) = find_id3v1(reader, parse_options.read_tags)?;
|
||||||
|
|
||||||
if header.is_some() {
|
if header.is_some() {
|
||||||
file.id3v1_tag = id3v1;
|
file.id3v1_tag = id3v1;
|
||||||
|
@ -51,8 +55,10 @@ where
|
||||||
|
|
||||||
reader.seek(SeekFrom::Current(-32))?;
|
reader.seek(SeekFrom::Current(-32))?;
|
||||||
|
|
||||||
if let Some((tag, header)) = crate::ape::tag::read::read_ape_tag(reader, true)? {
|
if let (tag, Some(header)) =
|
||||||
file.ape_tag = Some(tag);
|
crate::ape::tag::read::read_ape_tag(reader, true, parse_options.read_tags)?
|
||||||
|
{
|
||||||
|
file.ape_tag = tag;
|
||||||
|
|
||||||
// Seek back to the start of the tag
|
// Seek back to the start of the tag
|
||||||
let pos = reader.stream_position()?;
|
let pos = reader.stream_position()?;
|
||||||
|
|
|
@ -30,7 +30,7 @@ impl OpusFile {
|
||||||
R: Read + Seek,
|
R: Read + Seek,
|
||||||
{
|
{
|
||||||
let file_information =
|
let file_information =
|
||||||
super::read::read_from(reader, OPUSHEAD, OPUSTAGS, 2, parse_options.parsing_mode)?;
|
super::read::read_from(reader, OPUSHEAD, OPUSTAGS, 2, parse_options)?;
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
properties: if parse_options.read_properties {
|
properties: if parse_options.read_properties {
|
||||||
|
@ -38,8 +38,8 @@ impl OpusFile {
|
||||||
} else {
|
} else {
|
||||||
OpusProperties::default()
|
OpusProperties::default()
|
||||||
},
|
},
|
||||||
// Safe to unwrap, a metadata packet is mandatory in Opus
|
// A metadata packet is mandatory in Opus
|
||||||
vorbis_comments_tag: file_information.0.unwrap(),
|
vorbis_comments_tag: file_information.0.unwrap_or_default(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,7 +32,11 @@ impl From<OpusProperties> for FileProperties {
|
||||||
sample_rate: Some(input.input_sample_rate),
|
sample_rate: Some(input.input_sample_rate),
|
||||||
bit_depth: None,
|
bit_depth: None,
|
||||||
channels: Some(input.channels),
|
channels: Some(input.channels),
|
||||||
channel_mask: Some(input.channel_mask),
|
channel_mask: if input.channel_mask == ChannelMask(0) {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(input.channel_mask)
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use super::tag::VorbisComments;
|
use super::tag::VorbisComments;
|
||||||
use super::verify_signature;
|
use super::verify_signature;
|
||||||
use crate::config::ParsingMode;
|
use crate::config::{ParseOptions, ParsingMode};
|
||||||
use crate::error::{ErrorKind, LoftyError, Result};
|
use crate::error::{ErrorKind, LoftyError, Result};
|
||||||
use crate::macros::{decode_err, err, parse_mode_choice};
|
use crate::macros::{decode_err, err, parse_mode_choice};
|
||||||
use crate::picture::{MimeType, Picture, PictureInformation, PictureType};
|
use crate::picture::{MimeType, Picture, PictureInformation, PictureType};
|
||||||
|
@ -189,7 +189,7 @@ pub(crate) fn read_from<T>(
|
||||||
header_sig: &[u8],
|
header_sig: &[u8],
|
||||||
comment_sig: &[u8],
|
comment_sig: &[u8],
|
||||||
packets_to_read: isize,
|
packets_to_read: isize,
|
||||||
parse_mode: ParsingMode,
|
parse_options: ParseOptions,
|
||||||
) -> Result<OGGTags>
|
) -> Result<OGGTags>
|
||||||
where
|
where
|
||||||
T: Read + Seek,
|
T: Read + Seek,
|
||||||
|
@ -210,6 +210,10 @@ where
|
||||||
.ok_or_else(|| decode_err!("OGG: Expected identification packet"))?;
|
.ok_or_else(|| decode_err!("OGG: Expected identification packet"))?;
|
||||||
verify_signature(identification_packet, header_sig)?;
|
verify_signature(identification_packet, header_sig)?;
|
||||||
|
|
||||||
|
if !parse_options.read_tags {
|
||||||
|
return Ok((None, first_page_header, packets));
|
||||||
|
}
|
||||||
|
|
||||||
let mut metadata_packet = packets
|
let mut metadata_packet = packets
|
||||||
.get(1)
|
.get(1)
|
||||||
.ok_or_else(|| decode_err!("OGG: Expected comment packet"))?;
|
.ok_or_else(|| decode_err!("OGG: Expected comment packet"))?;
|
||||||
|
@ -219,7 +223,7 @@ where
|
||||||
metadata_packet = &metadata_packet[comment_sig.len()..];
|
metadata_packet = &metadata_packet[comment_sig.len()..];
|
||||||
|
|
||||||
let reader = &mut metadata_packet;
|
let reader = &mut metadata_packet;
|
||||||
let tag = read_comments(reader, reader.len() as u64, parse_mode)?;
|
let tag = read_comments(reader, reader.len() as u64, parse_options.parsing_mode)?;
|
||||||
|
|
||||||
Ok((Some(tag), first_page_header, packets))
|
Ok((Some(tag), first_page_header, packets))
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,8 +28,7 @@ impl SpeexFile {
|
||||||
where
|
where
|
||||||
R: Read + Seek,
|
R: Read + Seek,
|
||||||
{
|
{
|
||||||
let file_information =
|
let file_information = super::read::read_from(reader, SPEEXHEADER, &[], 2, parse_options)?;
|
||||||
super::read::read_from(reader, SPEEXHEADER, &[], 2, parse_options.parsing_mode)?;
|
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
properties: if parse_options.read_properties {
|
properties: if parse_options.read_properties {
|
||||||
|
@ -37,8 +36,8 @@ impl SpeexFile {
|
||||||
} else {
|
} else {
|
||||||
SpeexProperties::default()
|
SpeexProperties::default()
|
||||||
},
|
},
|
||||||
// Safe to unwrap, a metadata packet is mandatory in Speex
|
// A metadata packet is mandatory in Speex
|
||||||
vorbis_comments_tag: file_information.0.unwrap(),
|
vorbis_comments_tag: file_information.0.unwrap_or_default(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,7 +34,7 @@ impl VorbisFile {
|
||||||
VORBIS_IDENT_HEAD,
|
VORBIS_IDENT_HEAD,
|
||||||
VORBIS_COMMENT_HEAD,
|
VORBIS_COMMENT_HEAD,
|
||||||
3,
|
3,
|
||||||
parse_options.parsing_mode,
|
parse_options,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
|
@ -43,8 +43,8 @@ impl VorbisFile {
|
||||||
} else {
|
} else {
|
||||||
VorbisProperties::default()
|
VorbisProperties::default()
|
||||||
},
|
},
|
||||||
// Safe to unwrap, a metadata packet is mandatory in OGG Vorbis
|
// A metadata packet is mandatory in OGG Vorbis
|
||||||
vorbis_comments_tag: file_information.0.unwrap(),
|
vorbis_comments_tag: file_information.0.unwrap_or_default(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -459,6 +459,10 @@ impl<R: Read + Seek> Probe<R> {
|
||||||
let reader = &mut self.inner;
|
let reader = &mut self.inner;
|
||||||
let options = self.options.unwrap_or_default();
|
let options = self.options.unwrap_or_default();
|
||||||
|
|
||||||
|
if !options.read_tags && !options.read_properties {
|
||||||
|
log::warn!("Skipping both tag and property reading, file will be empty");
|
||||||
|
}
|
||||||
|
|
||||||
match self.f_ty {
|
match self.f_ty {
|
||||||
Some(f_type) => Ok(match f_type {
|
Some(f_type) => Ok(match f_type {
|
||||||
FileType::Aac => AacFile::read_from(reader, options)?.into(),
|
FileType::Aac => AacFile::read_from(reader, options)?.into(),
|
||||||
|
|
|
@ -85,4 +85,21 @@ impl FileProperties {
|
||||||
pub fn channel_mask(&self) -> Option<ChannelMask> {
|
pub fn channel_mask(&self) -> Option<ChannelMask> {
|
||||||
self.channel_mask
|
self.channel_mask
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Used for tests
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
matches!(
|
||||||
|
self,
|
||||||
|
Self {
|
||||||
|
duration: Duration::ZERO,
|
||||||
|
overall_bitrate: None | Some(0),
|
||||||
|
audio_bitrate: None | Some(0),
|
||||||
|
sample_rate: None | Some(0),
|
||||||
|
bit_depth: None | Some(0),
|
||||||
|
channels: None | Some(0),
|
||||||
|
channel_mask: None,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,7 +32,11 @@ impl From<WavPackProperties> for FileProperties {
|
||||||
sample_rate: Some(input.sample_rate),
|
sample_rate: Some(input.sample_rate),
|
||||||
bit_depth: Some(input.bit_depth),
|
bit_depth: Some(input.bit_depth),
|
||||||
channels: Some(input.channels as u8),
|
channels: Some(input.channels as u8),
|
||||||
channel_mask: Some(input.channel_mask),
|
channel_mask: if input.channel_mask == ChannelMask(0) {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(input.channel_mask)
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,7 @@ where
|
||||||
let mut id3v1_tag = None;
|
let mut id3v1_tag = None;
|
||||||
let mut ape_tag = None;
|
let mut ape_tag = None;
|
||||||
|
|
||||||
let ID3FindResults(id3v1_header, id3v1) = find_id3v1(reader, true)?;
|
let ID3FindResults(id3v1_header, id3v1) = find_id3v1(reader, parse_options.read_tags)?;
|
||||||
|
|
||||||
if id3v1_header.is_some() {
|
if id3v1_header.is_some() {
|
||||||
stream_length -= 128;
|
stream_length -= 128;
|
||||||
|
@ -38,9 +38,11 @@ where
|
||||||
// Strongly recommended to be at the end of the file
|
// Strongly recommended to be at the end of the file
|
||||||
reader.seek(SeekFrom::Current(-32))?;
|
reader.seek(SeekFrom::Current(-32))?;
|
||||||
|
|
||||||
if let Some((tag, header)) = crate::ape::tag::read::read_ape_tag(reader, true)? {
|
if let (tag, Some(header)) =
|
||||||
|
crate::ape::tag::read::read_ape_tag(reader, true, parse_options.read_tags)?
|
||||||
|
{
|
||||||
stream_length -= u64::from(header.size);
|
stream_length -= u64::from(header.size);
|
||||||
ape_tag = Some(tag);
|
ape_tag = tag;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(WavPackFile {
|
Ok(WavPackFile {
|
||||||
|
|
|
@ -5,7 +5,7 @@ use lofty::prelude::*;
|
||||||
use lofty::probe::Probe;
|
use lofty::probe::Probe;
|
||||||
use lofty::tag::TagType;
|
use lofty::tag::TagType;
|
||||||
|
|
||||||
use std::io::{Seek, Write};
|
use std::io::Seek;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn read() {
|
fn read() {
|
||||||
|
@ -96,3 +96,13 @@ fn remove_id3v2() {
|
||||||
fn remove_id3v1() {
|
fn remove_id3v1() {
|
||||||
crate::remove_tag!("tests/files/assets/minimal/full_test.aac", TagType::Id3v1);
|
crate::remove_tag!("tests/files/assets/minimal/full_test.aac", TagType::Id3v1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn read_no_properties() {
|
||||||
|
crate::no_properties_test!("tests/files/assets/minimal/full_test.aac");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn read_no_tags() {
|
||||||
|
crate::no_tag_test!("tests/files/assets/minimal/full_test.aac");
|
||||||
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ use lofty::prelude::*;
|
||||||
use lofty::probe::Probe;
|
use lofty::probe::Probe;
|
||||||
use lofty::tag::TagType;
|
use lofty::tag::TagType;
|
||||||
|
|
||||||
use std::io::{Seek, Write};
|
use std::io::Seek;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn read() {
|
fn read() {
|
||||||
|
@ -70,3 +70,13 @@ fn remove_text_chunks() {
|
||||||
fn remove_id3v2() {
|
fn remove_id3v2() {
|
||||||
crate::remove_tag!("tests/files/assets/minimal/full_test.aiff", TagType::Id3v2);
|
crate::remove_tag!("tests/files/assets/minimal/full_test.aiff", TagType::Id3v2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn read_no_properties() {
|
||||||
|
crate::no_properties_test!("tests/files/assets/minimal/full_test.aiff");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn read_no_tags() {
|
||||||
|
crate::no_tag_test!("tests/files/assets/minimal/full_test.aiff");
|
||||||
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ use lofty::prelude::*;
|
||||||
use lofty::probe::Probe;
|
use lofty::probe::Probe;
|
||||||
use lofty::tag::TagType;
|
use lofty::tag::TagType;
|
||||||
|
|
||||||
use std::io::{Seek, Write};
|
use std::io::Seek;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn read() {
|
fn read() {
|
||||||
|
@ -76,3 +76,13 @@ fn remove_id3v1() {
|
||||||
fn remove_id3v2() {
|
fn remove_id3v2() {
|
||||||
crate::remove_tag!("tests/files/assets/minimal/full_test.ape", TagType::Id3v2);
|
crate::remove_tag!("tests/files/assets/minimal/full_test.ape", TagType::Id3v2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn read_no_properties() {
|
||||||
|
crate::no_properties_test!("tests/files/assets/minimal/full_test.ape");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn read_no_tags() {
|
||||||
|
crate::no_tag_test!("tests/files/assets/minimal/full_test.ape");
|
||||||
|
}
|
||||||
|
|
|
@ -30,3 +30,13 @@ fn multiple_vorbis_comments() {
|
||||||
Some("Artist 2")
|
Some("Artist 2")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn read_no_properties() {
|
||||||
|
crate::no_properties_test!("tests/files/assets/minimal/full_test.flac");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn read_no_tags() {
|
||||||
|
crate::no_tag_test!("tests/files/assets/minimal/full_test.flac");
|
||||||
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ use lofty::prelude::*;
|
||||||
use lofty::probe::Probe;
|
use lofty::probe::Probe;
|
||||||
use lofty::tag::TagType;
|
use lofty::tag::TagType;
|
||||||
|
|
||||||
use std::io::{Seek, Write};
|
use std::io::Seek;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn read() {
|
fn read() {
|
||||||
|
@ -58,3 +58,13 @@ fn remove() {
|
||||||
TagType::Mp4Ilst
|
TagType::Mp4Ilst
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn read_no_properties() {
|
||||||
|
crate::no_properties_test!("tests/files/assets/minimal/m4a_codec_aac.m4a");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn read_no_tags() {
|
||||||
|
crate::no_tag_test!("tests/files/assets/minimal/m4a_codec_aac.m4a");
|
||||||
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ use lofty::prelude::*;
|
||||||
use lofty::probe::Probe;
|
use lofty::probe::Probe;
|
||||||
use lofty::tag::TagType;
|
use lofty::tag::TagType;
|
||||||
|
|
||||||
use std::io::{Seek, Write};
|
use std::io::Seek;
|
||||||
|
|
||||||
// Marker test so IntelliJ Rust recognizes this as a test module
|
// Marker test so IntelliJ Rust recognizes this as a test module
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -79,6 +79,16 @@ macro_rules! generate_tests {
|
||||||
fn [<remove_ape_ $stream_version>]() {
|
fn [<remove_ape_ $stream_version>]() {
|
||||||
crate::remove_tag!($path, TagType::Ape);
|
crate::remove_tag!($path, TagType::Ape);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn [<read_no_properties_ $stream_version>]() {
|
||||||
|
crate::no_properties_test!($path);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn [<read_no_tags_ $stream_version>]() {
|
||||||
|
crate::no_tag_test!($path);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ use lofty::probe::Probe;
|
||||||
use lofty::tag::{Tag, TagType};
|
use lofty::tag::{Tag, TagType};
|
||||||
|
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::io::{Seek, Write};
|
use std::io::Seek;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn read() {
|
fn read() {
|
||||||
|
@ -369,3 +369,26 @@ fn read_and_write_tpil_frame() {
|
||||||
|
|
||||||
assert_eq!(key_value_pairs, content.key_value_pairs);
|
assert_eq!(key_value_pairs, content.key_value_pairs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn read_no_properties() {
|
||||||
|
let mut file = crate::temp_file!("tests/files/assets/minimal/full_test.mp3");
|
||||||
|
let tagged_file = Probe::new(&mut file)
|
||||||
|
.options(ParseOptions::new().read_properties(false))
|
||||||
|
.guess_file_type()
|
||||||
|
.unwrap()
|
||||||
|
.read()
|
||||||
|
.unwrap();
|
||||||
|
let properties = tagged_file.properties();
|
||||||
|
assert!(properties.duration().is_zero());
|
||||||
|
assert_eq!(properties.overall_bitrate(), Some(0));
|
||||||
|
assert_eq!(properties.audio_bitrate(), Some(0));
|
||||||
|
assert_eq!(properties.sample_rate(), Some(0));
|
||||||
|
assert!(properties.bit_depth().is_none());
|
||||||
|
assert_eq!(properties.channels(), Some(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn read_no_tags() {
|
||||||
|
crate::no_tag_test!("tests/files/assets/minimal/full_test.mp3");
|
||||||
|
}
|
||||||
|
|
|
@ -5,9 +5,9 @@ use lofty::prelude::*;
|
||||||
use lofty::probe::Probe;
|
use lofty::probe::Probe;
|
||||||
use lofty::tag::TagType;
|
use lofty::tag::TagType;
|
||||||
|
|
||||||
use std::io::{Seek, Write};
|
use std::io::Seek;
|
||||||
|
|
||||||
// The tests for OGG Opus/Vorbis are nearly identical
|
// The tests for OGG Opus/Vorbis/Speex are nearly identical
|
||||||
// We have the vendor string and a title stored in the tag
|
// We have the vendor string and a title stored in the tag
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -186,3 +186,33 @@ fn flac_try_write_non_empty_id3v2() {
|
||||||
)
|
)
|
||||||
.is_err());
|
.is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn read_no_properties_opus() {
|
||||||
|
crate::no_properties_test!("tests/files/assets/minimal/full_test.opus");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn read_no_tags_opus() {
|
||||||
|
crate::no_tag_test!(@MANDATORY_TAG "tests/files/assets/minimal/full_test.opus", expected_len: 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn read_no_properties_vorbis() {
|
||||||
|
crate::no_properties_test!("tests/files/assets/minimal/full_test.ogg");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn read_no_tags_vorbis() {
|
||||||
|
crate::no_tag_test!(@MANDATORY_TAG "tests/files/assets/minimal/full_test.ogg", expected_len: 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn read_no_properties_speex() {
|
||||||
|
crate::no_properties_test!("tests/files/assets/minimal/full_test.spx");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn read_no_tags_speex() {
|
||||||
|
crate::no_tag_test!(@MANDATORY_TAG "tests/files/assets/minimal/full_test.spx", expected_len: 1);
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! temp_file {
|
macro_rules! temp_file {
|
||||||
($path:tt) => {{
|
($path:tt) => {{
|
||||||
|
use std::io::Write as _;
|
||||||
|
|
||||||
let mut file = tempfile::tempfile().unwrap();
|
let mut file = tempfile::tempfile().unwrap();
|
||||||
file.write_all(&std::fs::read($path).unwrap()).unwrap();
|
file.write_all(&std::fs::read($path).unwrap()).unwrap();
|
||||||
|
|
||||||
|
@ -10,6 +12,48 @@ macro_rules! temp_file {
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! no_tag_test {
|
||||||
|
($path:literal) => {{
|
||||||
|
let mut file = crate::temp_file!($path);
|
||||||
|
let tagged_file = lofty::probe::Probe::new(&mut file)
|
||||||
|
.options(lofty::config::ParseOptions::new().read_tags(false))
|
||||||
|
.guess_file_type()
|
||||||
|
.unwrap()
|
||||||
|
.read()
|
||||||
|
.unwrap();
|
||||||
|
assert!(!tagged_file.contains_tag());
|
||||||
|
}};
|
||||||
|
(@MANDATORY_TAG $path:literal, expected_len: $expected_len:literal) => {{
|
||||||
|
use lofty::tag::TagExt as _;
|
||||||
|
|
||||||
|
let mut file = crate::temp_file!($path);
|
||||||
|
let tagged_file = lofty::probe::Probe::new(&mut file)
|
||||||
|
.options(lofty::config::ParseOptions::new().read_tags(false))
|
||||||
|
.guess_file_type()
|
||||||
|
.unwrap()
|
||||||
|
.read()
|
||||||
|
.unwrap();
|
||||||
|
for tag in tagged_file.tags() {
|
||||||
|
assert_eq!(tag.len(), $expected_len);
|
||||||
|
}
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! no_properties_test {
|
||||||
|
($path:literal) => {{
|
||||||
|
let mut file = crate::temp_file!($path);
|
||||||
|
let tagged_file = lofty::probe::Probe::new(&mut file)
|
||||||
|
.options(lofty::config::ParseOptions::new().read_properties(false))
|
||||||
|
.guess_file_type()
|
||||||
|
.unwrap()
|
||||||
|
.read()
|
||||||
|
.unwrap();
|
||||||
|
assert!(tagged_file.properties().is_empty());
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! verify_artist {
|
macro_rules! verify_artist {
|
||||||
($file:ident, $method:ident, $expected_value:literal, $item_count:expr) => {{
|
($file:ident, $method:ident, $expected_value:literal, $item_count:expr) => {{
|
||||||
|
|
|
@ -5,7 +5,7 @@ use lofty::prelude::*;
|
||||||
use lofty::probe::Probe;
|
use lofty::probe::Probe;
|
||||||
use lofty::tag::TagType;
|
use lofty::tag::TagType;
|
||||||
|
|
||||||
use std::io::{Seek, Write};
|
use std::io::Seek;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn read() {
|
fn read() {
|
||||||
|
@ -85,3 +85,13 @@ fn issue_174_divide_by_zero() {
|
||||||
|
|
||||||
assert_eq!(file.file_type(), FileType::Wav);
|
assert_eq!(file.file_type(), FileType::Wav);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn read_no_properties() {
|
||||||
|
crate::no_properties_test!("tests/files/assets/minimal/wav_format_pcm.wav");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn read_no_tags() {
|
||||||
|
crate::no_tag_test!("tests/files/assets/minimal/wav_format_pcm.wav");
|
||||||
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ use lofty::prelude::*;
|
||||||
use lofty::probe::Probe;
|
use lofty::probe::Probe;
|
||||||
use lofty::tag::TagType;
|
use lofty::tag::TagType;
|
||||||
|
|
||||||
use std::io::{Seek, Write};
|
use std::io::Seek;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn read() {
|
fn read() {
|
||||||
|
@ -67,3 +67,13 @@ fn remove_id3v1() {
|
||||||
fn remove_ape() {
|
fn remove_ape() {
|
||||||
crate::remove_tag!("tests/files/assets/minimal/full_test.wv", TagType::Ape);
|
crate::remove_tag!("tests/files/assets/minimal/full_test.wv", TagType::Ape);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn read_no_properties() {
|
||||||
|
crate::no_properties_test!("tests/files/assets/minimal/full_test.wv");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn read_no_tags() {
|
||||||
|
crate::no_tag_test!("tests/files/assets/minimal/full_test.wv");
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue