lofty/aac/
read.rs

1use super::header::{ADTSHeader, HEADER_MASK};
2use super::AacFile;
3use crate::config::{ParseOptions, ParsingMode};
4use crate::error::Result;
5use crate::id3::v2::header::Id3v2Header;
6use crate::id3::v2::read::parse_id3v2;
7use crate::id3::{find_id3v1, ID3FindResults};
8use crate::macros::{decode_err, err, parse_mode_choice};
9use crate::mpeg::header::{cmp_header, search_for_frame_sync, HeaderCmpResult};
10
11use std::io::{Read, Seek, SeekFrom};
12
13use byteorder::ReadBytesExt;
14
15#[allow(clippy::unnecessary_wraps)]
16pub(super) fn read_from<R>(reader: &mut R, parse_options: ParseOptions) -> Result<AacFile>
17where
18	R: Read + Seek,
19{
20	let parse_mode = parse_options.parsing_mode;
21
22	let mut file = AacFile::default();
23
24	let mut first_frame_header = None;
25	let mut first_frame_end = 0;
26
27	// Skip any invalid padding
28	while reader.read_u8()? == 0 {}
29
30	reader.seek(SeekFrom::Current(-1))?;
31
32	let pos = reader.stream_position()?;
33	let mut stream_len = reader.seek(SeekFrom::End(0))?;
34
35	reader.seek(SeekFrom::Start(pos))?;
36
37	let mut header = [0; 4];
38
39	while let Ok(()) = reader.read_exact(&mut header) {
40		match header {
41			// [I, D, 3, ver_major, ver_minor, flags, size (4 bytes)]
42			[b'I', b'D', b'3', ..] => {
43				// Seek back to read the tag in full
44				reader.seek(SeekFrom::Current(-4))?;
45
46				let header = Id3v2Header::parse(reader)?;
47				let skip_footer = header.flags.footer;
48
49				let Some(new_stream_len) = stream_len.checked_sub(u64::from(header.size)) else {
50					err!(SizeMismatch);
51				};
52
53				stream_len = new_stream_len;
54
55				if parse_options.read_tags {
56					let id3v2 = parse_id3v2(reader, header, parse_options)?;
57					if let Some(existing_tag) = &mut file.id3v2_tag {
58						log::warn!("Duplicate ID3v2 tag found, appending frames to previous tag");
59
60						// https://github.com/Serial-ATA/lofty-rs/issues/87
61						// Duplicate tags should have their frames appended to the previous
62						for frame in id3v2.frames {
63							existing_tag.insert(frame);
64						}
65						continue;
66					}
67					file.id3v2_tag = Some(id3v2);
68				}
69
70				// Skip over the footer
71				if skip_footer {
72					log::debug!("Skipping ID3v2 footer");
73
74					let Some(new_stream_len) = stream_len.checked_sub(10) else {
75						err!(SizeMismatch);
76					};
77
78					stream_len = new_stream_len;
79					reader.seek(SeekFrom::Current(10))?;
80				}
81
82				continue;
83			},
84			// Tags might be followed by junk bytes before the first ADTS frame begins
85			_ => {
86				log::debug!("Searching for first ADTS frame");
87
88				// Seek back the length of the temporary header buffer, to include them
89				// in the frame sync search
90				#[allow(clippy::neg_multiply)]
91				reader.seek(SeekFrom::Current(-1 * header.len() as i64))?;
92
93				if let Some((first_frame_header_, first_frame_end_)) =
94					find_next_frame(reader, parse_mode)?
95				{
96					log::debug!("Found first ADTS frame");
97
98					first_frame_header = Some(first_frame_header_);
99					first_frame_end = first_frame_end_;
100					break;
101				}
102			},
103		}
104	}
105
106	#[allow(unused_variables)]
107	let ID3FindResults(header, id3v1) = find_id3v1(reader, parse_options.read_tags)?;
108
109	if header.is_some() {
110		let Some(new_stream_len) = stream_len.checked_sub(128) else {
111			err!(SizeMismatch);
112		};
113
114		stream_len = new_stream_len;
115		file.id3v1_tag = id3v1;
116	}
117
118	if parse_options.read_properties {
119		let Some(mut first_frame_header) = first_frame_header else {
120			// The search for sync bits was unsuccessful
121			decode_err!(@BAIL Mpeg, "File contains an invalid frame");
122		};
123
124		if first_frame_header.sample_rate == 0 {
125			parse_mode_choice!(
126				parse_mode,
127				STRICT: decode_err!(@BAIL Mpeg, "Sample rate is 0"),
128			);
129		}
130
131		if first_frame_header.bitrate == 0 {
132			parse_mode_choice!(parse_mode, STRICT: decode_err!(@BAIL Mpeg, "Bitrate is 0"),);
133		}
134
135		// Read as many frames as we can to try and find the average bitrate
136		reader.seek(SeekFrom::Start(first_frame_end))?;
137
138		let mut frame_count = 1;
139
140		while let Some((header, _)) = find_next_frame(reader, parse_mode)? {
141			first_frame_header.bitrate += header.bitrate;
142			frame_count += 1u32;
143		}
144
145		first_frame_header.bitrate /= frame_count;
146
147		super::properties::read_properties(&mut file.properties, first_frame_header, stream_len);
148	}
149
150	Ok(file)
151}
152
153// Searches for the next frame, comparing it to the following one
154fn find_next_frame<R>(
155	reader: &mut R,
156	parsing_mode: ParsingMode,
157) -> Result<Option<(ADTSHeader, u64)>>
158where
159	R: Read + Seek,
160{
161	let mut pos = reader.stream_position()?;
162
163	while let Ok(Some(first_adts_frame_start_relative)) = search_for_frame_sync(reader) {
164		let first_adts_frame_start_absolute = pos + first_adts_frame_start_relative;
165
166		// Seek back to the start of the frame and read the header
167		reader.seek(SeekFrom::Start(first_adts_frame_start_absolute))?;
168
169		if let Some(first_header) = ADTSHeader::read(reader, parsing_mode)? {
170			let header_len = if first_header.has_crc { 9 } else { 7 };
171
172			match cmp_header(
173				reader,
174				header_len,
175				u32::from(first_header.len),
176				u32::from_be_bytes(first_header.bytes[..4].try_into().unwrap()),
177				HEADER_MASK,
178			) {
179				HeaderCmpResult::Equal => {
180					return Ok(Some((
181						first_header,
182						first_adts_frame_start_absolute + u64::from(header_len),
183					)))
184				},
185				HeaderCmpResult::Undetermined => return Ok(None),
186				HeaderCmpResult::NotEqual => {},
187			}
188		}
189
190		pos = reader.stream_position()?;
191	}
192
193	Ok(None)
194}