mod flags;
mod frame;
mod items;
pub(crate) mod read;
mod restrictions;
pub(crate) mod tag;
pub mod util;
pub(crate) mod write;
use crate::error::{ID3v2Error, ID3v2ErrorKind, Result};
use crate::macros::err;
use util::unsynch_u32;
use std::io::Read;
use byteorder::{BigEndian, ByteOrder, ReadBytesExt};
pub use flags::ID3v2TagFlags;
pub use util::upgrade::{upgrade_v2, upgrade_v3};
pub use tag::ID3v2Tag;
pub use items::encapsulated_object::{GEOBInformation, GeneralEncapsulatedObject};
pub use items::encoded_text_frame::EncodedTextFrame;
pub use items::language_frame::LanguageFrame;
pub use items::sync_text::{
SyncTextContentType, SyncTextInformation, SynchronizedText, TimestampFormat,
};
pub use frame::id::FrameID;
pub use frame::{Frame, FrameFlags, FrameValue};
pub use restrictions::{
ImageSizeRestrictions, TagRestrictions, TagSizeRestrictions, TextSizeRestrictions,
};
#[derive(PartialEq, Eq, Debug, Clone, Copy)]
pub enum ID3v2Version {
V2,
V3,
V4,
}
#[derive(Copy, Clone)]
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);
}
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];
if version == ID3v2Version::V2 && flags & 0x40 == 0x40 {
return Err(ID3v2Error::new(ID3v2ErrorKind::Other(
"Encountered a compressed ID3v2.2 tag",
))
.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, restrictions: None, };
let size = unsynch_u32(BigEndian::read_u32(&header[6..]));
let mut extended_size = 0;
let extended_header =
(version == ID3v2Version::V4 || version == ID3v2Version::V3) && flags & 0x40 == 0x40;
if extended_header {
extended_size = unsynch_u32(bytes.read_u32::<BigEndian>()?);
if extended_size < 6 {
return Err(ID3v2Error::new(ID3v2ErrorKind::Other(
"Found an extended header with an invalid size (< 6)",
))
.into());
}
let _num_flag_bytes = bytes.read_u8()?;
let extended_flags = bytes.read_u8()?;
if extended_flags & 0x20 == 0x20 {
flags_parsed.crc = true;
let mut crc = [0; 6];
bytes.read_exact(&mut crc)?;
}
if extended_flags & 0x10 == 0x10 {
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::Other("Tag has an invalid size")).into());
}
Ok(ID3v2Header {
version,
flags: flags_parsed,
size,
extended_size,
})
}