added frame sync search for MP3 reading

The first MP3 frame behind metadata blocks is found by searching for frame sync bits.
This skips junk bytes between any metadata blocks and the first MP3 frame.
This commit is contained in:
localthomas 2022-01-21 14:52:32 +01:00
parent 0e8ad5759c
commit 47a28402db
No known key found for this signature in database
GPG key ID: 2F94960E894832A3
3 changed files with 47 additions and 11 deletions

View file

@ -1,4 +1,4 @@
use super::header::{verify_frame_sync, Header, XingHeader};
use super::header::{search_for_frame_sync, Header, XingHeader};
use super::{Mp3File, Mp3Properties};
use crate::ape::constants::APE_PREAMBLE;
#[cfg(feature = "ape")]
@ -12,7 +12,7 @@ use crate::id3::{find_id3v1, find_lyrics3v2, ID3FindResults};
use std::io::{Read, Seek, SeekFrom};
use byteorder::ReadBytesExt;
use byteorder::{BigEndian, ReadBytesExt};
pub(super) fn read_from<R>(
reader: &mut R,
@ -80,17 +80,30 @@ where
continue;
}
},
_ if verify_frame_sync([header[0], header[1]]) => {
let start = reader.seek(SeekFrom::Current(0))? - 4;
let header = Header::read(u32::from_be_bytes(header))?;
// metadata blocks might be followed by junk bytes before the first MP3 frame begins
_ => {
// seek back the length of the temporary header buffer
// so that all bytes are included in the search for a frame sync
let start_of_search_area =
reader.seek(SeekFrom::Current(-1 * header.len() as i64))?;
if let Some(first_mp3_frame_start_relative) = search_for_frame_sync(reader)? {
let first_mp3_frame_start_absolute =
start_of_search_area + first_mp3_frame_start_relative;
file.first_frame_offset = Some(start);
first_frame_header = Some(header);
// read the first four bytes of the found frame
reader.seek(SeekFrom::Start(first_mp3_frame_start_absolute))?;
let header = Header::read(reader.read_u32::<BigEndian>()?)?;
// We have found the first frame
break;
file.first_frame_offset = Some(first_mp3_frame_start_absolute);
first_frame_header = Some(header);
// We have found the first frame
break;
} else {
// the search for sync bits was unsuccessful
return Err(LoftyError::Mp3("File contains an invalid frame"));
}
},
_ => return Err(LoftyError::Mp3("File contains an invalid frame")),
}
}

BIN
tests/files/assets/b.mp3 Normal file

Binary file not shown.

View file

@ -1,5 +1,5 @@
use crate::{set_artist, temp_file, verify_artist};
use lofty::{FileType, ItemKey, ItemValue, TagItem, TagType};
use lofty::{Accessor, FileType, ItemKey, ItemValue, TagItem, TagType};
use std::io::{Seek, SeekFrom, Write};
#[test]
@ -19,6 +19,29 @@ fn read() {
crate::verify_artist!(file, tag, TagType::Ape, "Baz artist", 1);
}
#[test]
fn read_with_junk_bytes_between_frames() {
// Read a file that includes an ID3v2.3 data block followed by four bytes of junk data (0x20)
let file = lofty::read_from_path("tests/files/assets/b.mp3", true).unwrap();
// note that the file contains ID3v2 and ID3v1 data
assert_eq!(file.file_type(), &FileType::MP3);
let id3v2_tag = &file.tags()[0];
assert_eq!(id3v2_tag.artist(), Some("artist test"));
assert_eq!(id3v2_tag.album(), Some("album test"));
assert_eq!(id3v2_tag.title(), Some("title test"));
assert_eq!(
id3v2_tag.get_string(&ItemKey::EncoderSettings),
Some("Lavf58.62.100")
);
let id3v1_tag = &file.tags()[1];
assert_eq!(id3v1_tag.artist(), Some("artist test"));
assert_eq!(id3v1_tag.album(), Some("album test"));
assert_eq!(id3v1_tag.title(), Some("title test"));
}
#[test]
fn write() {
let mut file = temp_file!("tests/files/assets/a.mp3");