use super::tag::ApeTag;
use super::{ApeFile, ApeProperties};
use crate::ape::error::ApeParseError;
use crate::ape::tag::header::read_ape_header;
use crate::ape::tag::read::{read_ape_tag, read_ape_tag_with_header};
use crate::config::ParseOptions;
use crate::error::{FakeTagError, SizeMismatchError, TagParseError};
use crate::id3::v1::tag::Id3v1Tag;
use crate::id3::v2::read::parse_id3v2;
use crate::id3::v2::tag::Id3v2Tag;
use crate::id3::{FindId3v2Config, ID3FindResults, find_id3v1, find_id3v2, find_lyrics3v2};
use std::io::{Read, Seek, SeekFrom};
pub(crate) fn read_from<R>(
data: &mut R,
parse_options: ParseOptions,
) -> Result<ApeFile, ApeParseError>
where
R: Read + Seek,
{
let start = data.stream_position()?;
let end = data.seek(SeekFrom::End(0))?;
data.seek(SeekFrom::Start(start))?;
let mut stream_len = end - start;
let mut id3v2_tag: Option<Id3v2Tag> = None;
let mut id3v1_tag: Option<Id3v1Tag> = 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
};
if let ID3FindResults(Some(header), content) =
find_id3v2(data, find_id3v2_config).map_err(TagParseError::from)?
{
log::warn!("Encountered an ID3v2 tag. This tag cannot be rewritten to the APE file!");
let Some(new_stream_length) = stream_len.checked_sub(u64::from(header.full_tag_size()))
else {
return Err(SizeMismatchError.into());
};
stream_len = new_stream_length;
if let Some(content) = content {
let reader = &mut &*content;
let id3v2 = parse_id3v2(reader, header, parse_options).map_err(TagParseError::from)?;
id3v2_tag = Some(id3v2);
}
}
let mut found_mac = false;
let mut mac_start = 0;
let mut header = [0; 4];
data.read_exact(&mut header)?;
while !found_mac {
match &header {
b"MAC " => {
mac_start = data.stream_position()?;
found_mac = true;
},
b"APET" => {
log::warn!(
"Encountered an APE tag at the beginning of the file, attempting to read"
);
let mut remaining = [0; 4];
data.read_exact(&mut remaining)?;
if &remaining[..4] != b"AGEX" {
return Err(FakeTagError.into());
}
let ape_header = read_ape_header(data, false).map_err(TagParseError::from)?;
let Some(new_stream_length) = stream_len.checked_sub(u64::from(ape_header.size))
else {
return Err(SizeMismatchError.into());
};
stream_len = new_stream_length;
if parse_options.read_tags {
let ape = read_ape_tag_with_header(data, ape_header, parse_options)
.map_err(TagParseError::from)?;
ape_tag = Some(ape);
}
},
_ => {
return Err(ApeParseError::message(
"Invalid data found while reading header, expected any of [\"MAC \", \
\"APETAGEX\", \"ID3\"]",
));
},
}
}
#[allow(unused_variables)]
let ID3FindResults(id3v1_header, id3v1) =
find_id3v1(data, parse_options.read_tags, parse_options.parsing_mode)
.map_err(TagParseError::from)?;
if id3v1_header.is_some() {
id3v1_tag = id3v1;
let Some(new_stream_length) = stream_len.checked_sub(128) else {
return Err(SizeMismatchError.into());
};
stream_len = new_stream_length;
}
let ID3FindResults(_, lyrics3v2_size) = find_lyrics3v2(data)?;
let Some(new_stream_length) = stream_len.checked_sub(u64::from(lyrics3v2_size)) else {
return Err(SizeMismatchError.into());
};
stream_len = new_stream_length;
data.seek(SeekFrom::Current(-32))?;
if let (tag, Some(header)) =
read_ape_tag(data, true, parse_options).map_err(TagParseError::from)?
{
stream_len -= u64::from(header.size);
ape_tag = tag;
}
let file_length = data.stream_position()?;
data.seek(SeekFrom::Start(mac_start))?;
Ok(ApeFile {
id3v1_tag,
id3v2_tag,
ape_tag,
properties: if parse_options.read_properties {
super::properties::read_properties(
data,
stream_len,
file_length,
parse_options.parsing_mode,
)?
} else {
ApeProperties::default()
},
})
}