use super::AacFile;
use super::header::{ADTSHeader, HEADER_MASK};
use crate::aac::error::AacParseError;
use crate::config::{ParseOptions, ParsingMode};
use crate::error::{SizeMismatchError, TagParseError};
use crate::id3::v2::header::Id3v2Header;
use crate::id3::v2::read::parse_id3v2;
use crate::id3::{ID3FindResults, find_id3v1};
use crate::mpeg::header::{HeaderCmpResult, cmp_header, search_for_frame_sync};
use std::io::{Read, Seek, SeekFrom};
use byteorder::ReadBytesExt;
#[allow(clippy::unnecessary_wraps)]
pub(super) fn read_from<R>(
reader: &mut R,
parse_options: ParseOptions,
) -> Result<AacFile, AacParseError>
where
R: Read + Seek,
{
let parse_mode = parse_options.parsing_mode;
let mut file = AacFile::default();
let mut first_frame_header = None;
let mut first_frame_end = 0;
while reader.read_u8()? == 0 {}
reader.seek(SeekFrom::Current(-1))?;
let pos = reader.stream_position()?;
let mut stream_len = reader.seek(SeekFrom::End(0))?;
reader.seek(SeekFrom::Start(pos))?;
let mut header = [0; 4];
while let Ok(()) = reader.read_exact(&mut header) {
match header {
[b'I', b'D', b'3', ..] => {
reader.seek(SeekFrom::Current(-4))?;
let header = Id3v2Header::parse(reader).map_err(TagParseError::from)?;
let skip_footer = header.flags.footer;
let Some(new_stream_len) = stream_len.checked_sub(u64::from(header.size)) else {
return Err(SizeMismatchError.into());
};
stream_len = new_stream_len;
if parse_options.read_tags {
let id3v2 =
parse_id3v2(reader, header, parse_options).map_err(TagParseError::from)?;
if let Some(existing_tag) = &mut file.id3v2_tag {
log::warn!("Duplicate ID3v2 tag found, appending frames to previous tag");
for frame in id3v2.frames {
existing_tag.insert(frame);
}
continue;
}
file.id3v2_tag = Some(id3v2);
}
if skip_footer {
log::debug!("Skipping ID3v2 footer");
let Some(new_stream_len) = stream_len.checked_sub(10) else {
return Err(SizeMismatchError.into());
};
stream_len = new_stream_len;
reader.seek(SeekFrom::Current(10))?;
}
continue;
},
_ => {
log::debug!("Searching for first ADTS frame");
#[allow(clippy::neg_multiply)]
reader.seek(SeekFrom::Current(-1 * header.len() as i64))?;
if let Some((first_frame_header_, first_frame_end_)) =
find_next_frame(reader, parse_mode)?
{
log::debug!("Found first ADTS frame");
first_frame_header = Some(first_frame_header_);
first_frame_end = first_frame_end_;
break;
}
},
}
}
#[allow(unused_variables)]
if let Some(ID3FindResults {
header,
content,
range: _,
}) = find_id3v1(reader, parse_options.read_tags, parse_options.parsing_mode)
.map_err(TagParseError::from)?
{
let Some(new_stream_len) = stream_len.checked_sub(128) else {
return Err(SizeMismatchError.into());
};
stream_len = new_stream_len;
file.id3v1_tag = content;
}
if parse_options.read_properties {
let Some(mut first_frame_header) = first_frame_header else {
return Err(AacParseError::message("file contains an invalid frame"));
};
if first_frame_header.sample_rate == 0 && parse_mode == ParsingMode::Strict {
return Err(AacParseError::message("file specifies a sample rate of 0"));
}
if first_frame_header.bitrate == 0 {
return Err(AacParseError::message("file specifies a bitrate of 0"));
}
reader.seek(SeekFrom::Start(first_frame_end))?;
let mut frame_count = 1;
while let Some((header, frame_end)) = find_next_frame(reader, parse_mode)? {
first_frame_header.bitrate += header.bitrate;
frame_count += 1u32;
reader.seek(SeekFrom::Start(frame_end))?;
}
first_frame_header.bitrate /= frame_count;
super::properties::read_properties(&mut file.properties, first_frame_header, stream_len);
}
Ok(file)
}
fn find_next_frame<R>(
reader: &mut R,
parsing_mode: ParsingMode,
) -> Result<Option<(ADTSHeader, u64)>, AacParseError>
where
R: Read + Seek,
{
let mut pos = reader.stream_position()?;
while let Ok(Some(first_adts_frame_start_relative)) = search_for_frame_sync(reader) {
let first_adts_frame_start_absolute = pos + first_adts_frame_start_relative;
reader.seek(SeekFrom::Start(first_adts_frame_start_absolute))?;
if let Some(first_header) = ADTSHeader::read(reader, parsing_mode)? {
let header_len = if first_header.has_crc { 9 } else { 7 };
match cmp_header(
reader,
header_len,
u32::from(first_header.len),
u32::from_be_bytes(first_header.bytes[..4].try_into().unwrap()),
HEADER_MASK,
) {
HeaderCmpResult::Equal => {
return Ok(Some((
first_header,
first_adts_frame_start_absolute + u64::from(first_header.len),
)));
},
HeaderCmpResult::Undetermined => return Ok(None),
HeaderCmpResult::NotEqual => {},
}
}
pos = reader.stream_position()?;
}
Ok(None)
}