ID3v2: Move all header related items to v2::header module

This commit is contained in:
Serial 2023-07-30 21:03:57 -04:00 committed by Alex
parent 553d16fa88
commit bdfe1a8cfc
18 changed files with 175 additions and 172 deletions

View file

@ -1,8 +1,8 @@
use super::header::{ADTSHeader, HEADER_MASK};
use super::AacFile;
use crate::error::Result;
use crate::id3::v2::header::Id3v2Header;
use crate::id3::v2::read::parse_id3v2;
use crate::id3::v2::read_id3v2_header;
use crate::id3::{find_id3v1, ID3FindResults};
use crate::macros::{decode_err, parse_mode_choice};
use crate::mpeg::header::{cmp_header, search_for_frame_sync, HeaderCmpResult};
@ -43,7 +43,7 @@ where
// Seek back to read the tag in full
reader.seek(SeekFrom::Current(-4))?;
let header = read_id3v2_header(reader)?;
let header = Id3v2Header::parse(reader)?;
let skip_footer = header.flags.footer;
stream_len -= u64::from(header.size);

View file

@ -8,7 +8,7 @@ pub mod v2;
use crate::error::{ErrorKind, LoftyError, Result};
use crate::macros::try_vec;
use v2::{read_id3v2_header, Id3v2Header};
use v2::header::Id3v2Header;
use std::io::{Read, Seek, SeekFrom};
use std::ops::Neg;
@ -92,7 +92,7 @@ where
let mut header = None;
let mut id3v2 = None;
if let Ok(id3v2_header) = read_id3v2_header(data) {
if let Ok(id3v2_header) = Id3v2Header::parse(data) {
if read {
let mut tag = try_vec![0; id3v2_header.size as usize];
data.read_exact(&mut tag)?;

View file

@ -1,23 +0,0 @@
use super::restrictions::TagRestrictions;
/// Flags that apply to the entire tag
#[derive(Default, Copy, Clone, Debug, PartialEq, Eq)]
#[allow(clippy::struct_excessive_bools)]
pub struct Id3v2TagFlags {
/// Whether or not all frames are unsynchronised. See [`FrameFlags::unsynchronisation`](crate::id3::v2::FrameFlags::unsynchronisation)
pub unsynchronisation: bool,
/// Indicates if the tag is in an experimental stage
pub experimental: bool,
/// Indicates that the tag includes a footer
///
/// A footer will be created if the tag is written
pub footer: bool,
/// Whether or not to include a CRC-32 in the extended header
///
/// This is calculated if the tag is written
pub crc: bool,
/// Restrictions on the tag, written in the extended header
///
/// In addition to being setting this flag, all restrictions must be provided. See [`TagRestrictions`]
pub restrictions: Option<TagRestrictions>,
}

View file

@ -1,11 +1,11 @@
use crate::error::{Id3v2Error, Id3v2ErrorKind, Result};
use crate::id3::v2::frame::FrameValue;
use crate::id3::v2::header::Id3v2Version;
use crate::id3::v2::items::{
AttachedPictureFrame, CommentFrame, EventTimingCodesFrame, ExtendedTextFrame, ExtendedUrlFrame,
KeyValueFrame, OwnershipFrame, Popularimeter, PrivateFrame, RelativeVolumeAdjustmentFrame,
TextInformationFrame, UniqueFileIdentifierFrame, UnsynchronizedTextFrame, UrlLinkFrame,
};
use crate::id3::v2::Id3v2Version;
use crate::macros::err;
use crate::probe::ParsingMode;
use crate::util::text::TextEncoding;

View file

@ -3,13 +3,13 @@ mod header;
pub(super) mod id;
pub(super) mod read;
use super::header::Id3v2Version;
use super::items::{
AttachedPictureFrame, CommentFrame, EventTimingCodesFrame, ExtendedTextFrame, ExtendedUrlFrame,
KeyValueFrame, OwnershipFrame, Popularimeter, PrivateFrame, RelativeVolumeAdjustmentFrame,
TextInformationFrame, UniqueFileIdentifierFrame, UnsynchronizedTextFrame, UrlLinkFrame,
};
use super::util::upgrade::{upgrade_v2, upgrade_v3};
use super::Id3v2Version;
use crate::error::{ErrorKind, Id3v2Error, Id3v2ErrorKind, LoftyError, Result};
use crate::tag::item::{ItemKey, ItemValue, TagItem};
use crate::tag::TagType;

View file

@ -2,8 +2,9 @@ use super::header::{parse_header, parse_v2_header};
use super::Frame;
use crate::error::{Id3v2Error, Id3v2ErrorKind, Result};
use crate::id3::v2::frame::content::parse_content;
use crate::id3::v2::header::Id3v2Version;
use crate::id3::v2::util::synchsafe::{SynchsafeInteger, UnsynchronizedStream};
use crate::id3::v2::{FrameFlags, FrameId, FrameValue, Id3v2Version};
use crate::id3::v2::{FrameFlags, FrameId, FrameValue};
use crate::macros::try_vec;
use crate::probe::ParsingMode;

141
src/id3/v2/header.rs Normal file
View file

@ -0,0 +1,141 @@
use crate::error::{Id3v2Error, Id3v2ErrorKind, Result};
use crate::id3::v2::restrictions::TagRestrictions;
use crate::id3::v2::util::synchsafe::SynchsafeInteger;
use crate::macros::err;
use std::io::Read;
use byteorder::{BigEndian, ByteOrder, ReadBytesExt};
/// The ID3v2 version
#[derive(PartialEq, Eq, Debug, Clone, Copy)]
pub enum Id3v2Version {
/// ID3v2.2
V2,
/// ID3v2.3
V3,
/// ID3v2.4
V4,
}
/// Flags that apply to the entire tag
#[derive(Default, Copy, Clone, Debug, PartialEq, Eq)]
#[allow(clippy::struct_excessive_bools)]
pub struct Id3v2TagFlags {
/// Whether or not all frames are unsynchronised. See [`FrameFlags::unsynchronisation`](crate::id3::v2::FrameFlags::unsynchronisation)
pub unsynchronisation: bool,
/// Indicates if the tag is in an experimental stage
pub experimental: bool,
/// Indicates that the tag includes a footer
///
/// A footer will be created if the tag is written
pub footer: bool,
/// Whether or not to include a CRC-32 in the extended header
///
/// This is calculated if the tag is written
pub crc: bool,
/// Restrictions on the tag, written in the extended header
///
/// In addition to being setting this flag, all restrictions must be provided. See [`TagRestrictions`]
pub restrictions: Option<TagRestrictions>,
}
#[derive(Copy, Clone, Debug)]
pub(crate) struct Id3v2Header {
pub version: Id3v2Version,
pub flags: Id3v2TagFlags,
pub size: u32,
pub extended_size: u32,
}
impl Id3v2Header {
pub(crate) fn parse<R>(bytes: &mut R) -> Result<Self>
where
R: Read,
{
let mut header = [0; 10];
bytes.read_exact(&mut header)?;
if &header[..3] != b"ID3" {
err!(FakeTag);
}
// Version is stored as [major, minor], but here we don't care about minor revisions unless there's an error.
let version = match header[3] {
2 => Id3v2Version::V2,
3 => Id3v2Version::V3,
4 => Id3v2Version::V4,
major => {
return Err(
Id3v2Error::new(Id3v2ErrorKind::BadId3v2Version(major, header[4])).into(),
)
},
};
let flags = header[5];
// Compression was a flag only used in ID3v2.2 (bit 2).
// At the time the ID3v2.2 specification was written, a compression scheme wasn't decided.
// The spec recommends just ignoring the tag in this case.
if version == Id3v2Version::V2 && flags & 0x40 == 0x40 {
return Err(Id3v2Error::new(Id3v2ErrorKind::V2Compression).into());
}
let mut flags_parsed = Id3v2TagFlags {
unsynchronisation: flags & 0x80 == 0x80,
experimental: (version == Id3v2Version::V4 || version == Id3v2Version::V3)
&& flags & 0x20 == 0x20,
footer: (version == Id3v2Version::V4 || version == Id3v2Version::V3)
&& flags & 0x10 == 0x10,
crc: false, // Retrieved later if applicable
restrictions: None, // Retrieved later if applicable
};
let size = BigEndian::read_u32(&header[6..]).unsynch();
let mut extended_size = 0;
let extended_header =
(version == Id3v2Version::V4 || version == Id3v2Version::V3) && flags & 0x40 == 0x40;
if extended_header {
extended_size = bytes.read_u32::<BigEndian>()?.unsynch();
if extended_size < 6 {
return Err(Id3v2Error::new(Id3v2ErrorKind::BadExtendedHeaderSize).into());
}
// Useless byte since there's only 1 byte for flags
let _num_flag_bytes = bytes.read_u8()?;
let extended_flags = bytes.read_u8()?;
// The only flags we care about here are the CRC and restrictions
if extended_flags & 0x20 == 0x20 {
flags_parsed.crc = true;
// We don't care about the existing CRC (5) or its length byte (1)
let mut crc = [0; 6];
bytes.read_exact(&mut crc)?;
}
if extended_flags & 0x10 == 0x10 {
// We don't care about the length byte, it is always 1
let _data_length = bytes.read_u8()?;
flags_parsed.restrictions = Some(TagRestrictions::from_byte(bytes.read_u8()?));
}
}
if extended_size > 0 && extended_size >= size {
return Err(Id3v2Error::new(Id3v2ErrorKind::BadExtendedHeaderSize).into());
}
Ok(Id3v2Header {
version,
flags: flags_parsed,
size,
extended_size,
})
}
}

View file

@ -1,5 +1,5 @@
use crate::error::{Id3v2Error, Id3v2ErrorKind, Result};
use crate::id3::v2::Id3v2Version;
use crate::id3::v2::header::Id3v2Version;
use crate::macros::err;
use crate::picture::{MimeType, Picture, PictureType};
use crate::util::text::{encode_text, TextEncoding};

View file

@ -1,6 +1,6 @@
use crate::error::{Id3v2Error, Id3v2ErrorKind, LoftyError, Result};
use crate::id3::v2::frame::content::verify_encoding;
use crate::id3::v2::Id3v2Version;
use crate::id3::v2::header::Id3v2Version;
use crate::util::text::{decode_text, encode_text, read_to_terminator, utf16_decode, TextEncoding};
use std::hash::{Hash, Hasher};

View file

@ -1,6 +1,6 @@
use crate::error::Result;
use crate::id3::v2::frame::content::verify_encoding;
use crate::id3::v2::Id3v2Version;
use crate::id3::v2::header::Id3v2Version;
use crate::util::text::{decode_text, encode_text, TextEncoding};
use std::hash::{Hash, Hasher};

View file

@ -1,6 +1,6 @@
use crate::error::Result;
use crate::id3::v2::frame::content::verify_encoding;
use crate::id3::v2::Id3v2Version;
use crate::id3::v2::header::Id3v2Version;
use crate::util::text::{decode_text, encode_text, TextEncoding};
use byteorder::ReadBytesExt;

View file

@ -1,6 +1,6 @@
use crate::error::{Id3v2Error, Id3v2ErrorKind, Result};
use crate::id3::v2::frame::content::verify_encoding;
use crate::id3::v2::Id3v2Version;
use crate::id3::v2::header::Id3v2Version;
use crate::util::text::{decode_text, encode_text, TextEncoding};
use std::hash::{Hash, Hasher};

View file

@ -1,6 +1,6 @@
use crate::error::Result;
use crate::id3::v2::frame::content::verify_encoding;
use crate::id3::v2::Id3v2Version;
use crate::id3::v2::header::Id3v2Version;
use crate::util::text::{decode_text, encode_text, TextEncoding};
use byteorder::ReadBytesExt;

View file

@ -7,8 +7,8 @@
//! * [`Id3v2Tag`]
//! * [`Frame`]
mod flags;
mod frame;
pub(crate) mod header;
mod items;
pub(crate) mod read;
mod restrictions;
@ -16,17 +16,9 @@ pub(crate) mod tag;
pub mod util;
pub(crate) mod write;
use crate::error::{Id3v2Error, Id3v2ErrorKind, Result};
use crate::macros::err;
use util::synchsafe::SynchsafeInteger;
use std::io::Read;
use byteorder::{BigEndian, ByteOrder, ReadBytesExt};
// Exports
pub use flags::Id3v2TagFlags;
pub use header::{Id3v2TagFlags, Id3v2Version};
pub use util::upgrade::{upgrade_v2, upgrade_v3};
pub use tag::Id3v2Tag;
@ -39,110 +31,3 @@ pub use frame::{Frame, FrameFlags, FrameValue};
pub use restrictions::{
ImageSizeRestrictions, TagRestrictions, TagSizeRestrictions, TextSizeRestrictions,
};
/// The ID3v2 version
#[derive(PartialEq, Eq, Debug, Clone, Copy)]
pub enum Id3v2Version {
/// ID3v2.2
V2,
/// ID3v2.3
V3,
/// ID3v2.4
V4,
}
#[derive(Copy, Clone, Debug)]
pub(crate) struct Id3v2Header {
pub version: Id3v2Version,
pub flags: Id3v2TagFlags,
pub size: u32,
pub extended_size: u32,
}
pub(crate) fn read_id3v2_header<R>(bytes: &mut R) -> Result<Id3v2Header>
where
R: Read,
{
let mut header = [0; 10];
bytes.read_exact(&mut header)?;
if &header[..3] != b"ID3" {
err!(FakeTag);
}
// Version is stored as [major, minor], but here we don't care about minor revisions unless there's an error.
let version = match header[3] {
2 => Id3v2Version::V2,
3 => Id3v2Version::V3,
4 => Id3v2Version::V4,
major => {
return Err(Id3v2Error::new(Id3v2ErrorKind::BadId3v2Version(major, header[4])).into())
},
};
let flags = header[5];
// Compression was a flag only used in ID3v2.2 (bit 2).
// At the time the ID3v2.2 specification was written, a compression scheme wasn't decided.
// The spec recommends just ignoring the tag in this case.
if version == Id3v2Version::V2 && flags & 0x40 == 0x40 {
return Err(Id3v2Error::new(Id3v2ErrorKind::V2Compression).into());
}
let mut flags_parsed = Id3v2TagFlags {
unsynchronisation: flags & 0x80 == 0x80,
experimental: (version == Id3v2Version::V4 || version == Id3v2Version::V3)
&& flags & 0x20 == 0x20,
footer: (version == Id3v2Version::V4 || version == Id3v2Version::V3)
&& flags & 0x10 == 0x10,
crc: false, // Retrieved later if applicable
restrictions: None, // Retrieved later if applicable
};
let size = BigEndian::read_u32(&header[6..]).unsynch();
let mut extended_size = 0;
let extended_header =
(version == Id3v2Version::V4 || version == Id3v2Version::V3) && flags & 0x40 == 0x40;
if extended_header {
extended_size = bytes.read_u32::<BigEndian>()?.unsynch();
if extended_size < 6 {
return Err(Id3v2Error::new(Id3v2ErrorKind::BadExtendedHeaderSize).into());
}
// Useless byte since there's only 1 byte for flags
let _num_flag_bytes = bytes.read_u8()?;
let extended_flags = bytes.read_u8()?;
// The only flags we care about here are the CRC and restrictions
if extended_flags & 0x20 == 0x20 {
flags_parsed.crc = true;
// We don't care about the existing CRC (5) or its length byte (1)
let mut crc = [0; 6];
bytes.read_exact(&mut crc)?;
}
if extended_flags & 0x10 == 0x10 {
// We don't care about the length byte, it is always 1
let _data_length = bytes.read_u8()?;
flags_parsed.restrictions = Some(TagRestrictions::from_byte(bytes.read_u8()?));
}
}
if extended_size > 0 && extended_size >= size {
return Err(Id3v2Error::new(Id3v2ErrorKind::BadExtendedHeaderSize).into());
}
Ok(Id3v2Header {
version,
flags: flags_parsed,
size,
extended_size,
})
}

View file

@ -1,6 +1,6 @@
use super::frame::read::ParsedFrame;
use super::header::Id3v2Header;
use super::tag::Id3v2Tag;
use super::Id3v2Header;
use crate::error::{Id3v2Error, Id3v2ErrorKind, Result};
use crate::id3::v2::util::synchsafe::UnsynchronizedStream;
use crate::probe::ParsingMode;
@ -74,18 +74,18 @@ where
#[test]
fn zero_size_id3v2() {
use crate::id3::v2::read_id3v2_header;
use crate::id3::v2::header::Id3v2Header;
use crate::ParsingMode;
use std::io::Cursor;
let mut f = Cursor::new(std::fs::read("tests/tags/assets/id3v2/zero.id3v2").unwrap());
let header = read_id3v2_header(&mut f).unwrap();
let header = Id3v2Header::parse(&mut f).unwrap();
assert!(parse_id3v2(&mut f, header, ParsingMode::Strict).is_ok());
}
#[test]
fn bad_frame_id_relaxed_id3v2() {
use crate::id3::v2::read_id3v2_header;
use crate::id3::v2::header::Id3v2Header;
use crate::{Accessor, ParsingMode, TagExt};
use std::io::Cursor;
@ -94,7 +94,7 @@ fn bad_frame_id_relaxed_id3v2() {
let mut f = Cursor::new(
std::fs::read("tests/tags/assets/id3v2/bad_frame_otherwise_valid.id3v24").unwrap(),
);
let header = read_id3v2_header(&mut f).unwrap();
let header = Id3v2Header::parse(&mut f).unwrap();
let id3v2 = parse_id3v2(&mut f, header, ParsingMode::Relaxed);
assert!(id3v2.is_ok());

View file

@ -1,7 +1,6 @@
use super::flags::Id3v2TagFlags;
use super::frame::id::FrameId;
use super::frame::{Frame, FrameFlags, FrameValue, EMPTY_CONTENT_DESCRIPTOR, UNKNOWN_LANGUAGE};
use super::Id3v2Version;
use super::header::{Id3v2TagFlags, Id3v2Version};
use crate::error::{LoftyError, Result};
use crate::id3::v2::frame::{FrameRef, MUSICBRAINZ_UFID_OWNER};
use crate::id3::v2::items::{
@ -1223,13 +1222,13 @@ mod tests {
use std::borrow::Cow;
use crate::id3::v2::frame::MUSICBRAINZ_UFID_OWNER;
use crate::id3::v2::header::{Id3v2Header, Id3v2Version};
use crate::id3::v2::items::{ExtendedUrlFrame, Popularimeter, UniqueFileIdentifierFrame};
use crate::id3::v2::tag::{filter_comment_frame_by_description, new_text_frame};
use crate::id3::v2::util::pairs::DEFAULT_NUMBER_IN_PAIR;
use crate::id3::v2::{
read_id3v2_header, AttachedPictureFrame, CommentFrame, ExtendedTextFrame, Frame,
FrameFlags, FrameId, FrameValue, Id3v2Tag, Id3v2Version, TextInformationFrame,
UrlLinkFrame,
AttachedPictureFrame, CommentFrame, ExtendedTextFrame, Frame, FrameFlags, FrameId,
FrameValue, Id3v2Tag, TextInformationFrame, UrlLinkFrame,
};
use crate::tag::utils::test_utils::read_path;
use crate::util::text::TextEncoding;
@ -1245,7 +1244,7 @@ mod tests {
let mut reader = std::io::Cursor::new(&tag_bytes[..]);
let header = read_id3v2_header(&mut reader).unwrap();
let header = Id3v2Header::parse(&mut reader).unwrap();
crate::id3::v2::read::parse_id3v2(&mut reader, header, ParsingMode::Strict).unwrap()
}
@ -1356,7 +1355,7 @@ mod tests {
let temp_reader = &mut &*writer;
let temp_header = read_id3v2_header(temp_reader).unwrap();
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();
@ -1608,7 +1607,7 @@ mod tests {
let mut reader = &mut &writer[..];
let header = read_id3v2_header(&mut reader).unwrap();
let header = Id3v2Header::parse(&mut reader).unwrap();
assert!(crate::id3::v2::read::parse_id3v2(reader, header, ParsingMode::Strict).is_ok());
assert_eq!(writer[3..10], writer[writer.len() - 7..])
@ -1633,7 +1632,7 @@ mod tests {
let mut reader = &mut &writer[..];
let header = read_id3v2_header(&mut reader).unwrap();
let header = Id3v2Header::parse(&mut reader).unwrap();
let tag = crate::id3::v2::read::parse_id3v2(reader, header, ParsingMode::Strict).unwrap();
assert_eq!(tag.len(), 1);
@ -1950,7 +1949,7 @@ mod tests {
// And verify we can reread the tag
let mut reader = std::io::Cursor::new(&content[..]);
let header = read_id3v2_header(&mut reader).unwrap();
let header = Id3v2Header::parse(&mut reader).unwrap();
let reparsed =
crate::id3::v2::read::parse_id3v2(&mut reader, header, ParsingMode::Strict).unwrap();

View file

@ -96,14 +96,14 @@ impl<B: ByteOrder> Chunks<B> {
where
R: Read + Seek,
{
use crate::id3::v2::header::Id3v2Header;
use crate::id3::v2::read::parse_id3v2;
use crate::id3::v2::read_id3v2_header;
let content = self.content(data)?;
let reader = &mut &*content;
let header = read_id3v2_header(reader)?;
let header = Id3v2Header::parse(reader)?;
let id3v2 = parse_id3v2(reader, header, parse_mode)?;
// Skip over the footer

View file

@ -2,8 +2,8 @@ use super::header::{cmp_header, search_for_frame_sync, Header, HeaderCmpResult,
use super::{MpegFile, MpegProperties};
use crate::ape::header::read_ape_header;
use crate::error::Result;
use crate::id3::v2::header::Id3v2Header;
use crate::id3::v2::read::parse_id3v2;
use crate::id3::v2::read_id3v2_header;
use crate::id3::{find_id3v1, find_lyrics3v2, ID3FindResults};
use crate::macros::{decode_err, err};
use crate::mpeg::header::HEADER_MASK;
@ -36,7 +36,7 @@ where
// Seek back to read the tag in full
reader.seek(SeekFrom::Current(-4))?;
let header = read_id3v2_header(reader)?;
let header = Id3v2Header::parse(reader)?;
let skip_footer = header.flags.footer;
let id3v2 = parse_id3v2(reader, header, parse_options.parsing_mode)?;