diff --git a/tests/taglib/data/bladeenc.mp3 b/tests/taglib/data/bladeenc.mp3 new file mode 100644 index 000000000..e3d1a4b51 Binary files /dev/null and b/tests/taglib/data/bladeenc.mp3 differ diff --git a/tests/taglib/data/duplicate_id3v2.mp3 b/tests/taglib/data/duplicate_id3v2.mp3 new file mode 100644 index 000000000..34f4f158c Binary files /dev/null and b/tests/taglib/data/duplicate_id3v2.mp3 differ diff --git a/tests/taglib/data/garbage.mp3 b/tests/taglib/data/garbage.mp3 new file mode 100644 index 000000000..730b74e7c Binary files /dev/null and b/tests/taglib/data/garbage.mp3 differ diff --git a/tests/taglib/data/invalid-frames1.mp3 b/tests/taglib/data/invalid-frames1.mp3 new file mode 100644 index 000000000..c076712c0 Binary files /dev/null and b/tests/taglib/data/invalid-frames1.mp3 differ diff --git a/tests/taglib/data/invalid-frames2.mp3 b/tests/taglib/data/invalid-frames2.mp3 new file mode 100644 index 000000000..01976fc54 Binary files /dev/null and b/tests/taglib/data/invalid-frames2.mp3 differ diff --git a/tests/taglib/data/invalid-frames3.mp3 b/tests/taglib/data/invalid-frames3.mp3 new file mode 100644 index 000000000..6bbd2d397 Binary files /dev/null and b/tests/taglib/data/invalid-frames3.mp3 differ diff --git a/tests/taglib/data/lame_cbr.mp3 b/tests/taglib/data/lame_cbr.mp3 new file mode 100644 index 000000000..b7badeb05 Binary files /dev/null and b/tests/taglib/data/lame_cbr.mp3 differ diff --git a/tests/taglib/data/lame_vbr.mp3 b/tests/taglib/data/lame_vbr.mp3 new file mode 100644 index 000000000..643056ef8 Binary files /dev/null and b/tests/taglib/data/lame_vbr.mp3 differ diff --git a/tests/taglib/data/mpeg2.mp3 b/tests/taglib/data/mpeg2.mp3 new file mode 100644 index 000000000..13e8d53df Binary files /dev/null and b/tests/taglib/data/mpeg2.mp3 differ diff --git a/tests/taglib/data/rare_frames.mp3 b/tests/taglib/data/rare_frames.mp3 new file mode 100644 index 000000000..e485337f9 Binary files /dev/null and b/tests/taglib/data/rare_frames.mp3 differ diff --git a/tests/taglib/main.rs b/tests/taglib/main.rs index dd456d239..424ee197b 100644 --- a/tests/taglib/main.rs +++ b/tests/taglib/main.rs @@ -8,4 +8,5 @@ mod test_flac; mod test_flacpicture; mod test_id3v1; mod test_info; +mod test_mpeg; mod test_speex; diff --git a/tests/taglib/test_mpeg.rs b/tests/taglib/test_mpeg.rs new file mode 100644 index 000000000..42435717f --- /dev/null +++ b/tests/taglib/test_mpeg.rs @@ -0,0 +1,395 @@ +use crate::temp_file; + +use std::fs::File; +use std::io::Seek; + +use lofty::ape::ApeTag; +use lofty::id3::v1::ID3v1Tag; +use lofty::id3::v2::{ID3v2Tag, ID3v2Version}; +use lofty::mpeg::MPEGFile; +use lofty::{Accessor, AudioFile, ParseOptions}; + +#[test] +#[ignore] +fn test_audio_properties_xing_header_cbr() { + let mut file = File::open("tests/taglib/data/lame_cbr.mp3").unwrap(); + let f = MPEGFile::read_from(&mut file, ParseOptions::new()).unwrap(); + + assert_eq!(f.properties().duration().as_secs(), 1887); // TODO: Off by 9 + assert_eq!(f.properties().duration().as_millis(), 1887164); + assert_eq!(f.properties().audio_bitrate(), 64); + assert_eq!(f.properties().channels(), 1); + assert_eq!(f.properties().sample_rate(), 44100); + // TODO? + // CPPUNIT_ASSERT_EQUAL(MPEG::XingHeader::Xing, f.audioProperties()->xingHeader()->type()); +} + +#[test] +#[ignore] +fn test_audio_properties_xing_header_vbr() { + let mut file = File::open("tests/taglib/data/lame_vbr.mp3").unwrap(); + let f = MPEGFile::read_from(&mut file, ParseOptions::new()).unwrap(); + + assert_eq!(f.properties().duration().as_secs(), 1887); // TODO: Off by 9 + assert_eq!(f.properties().duration().as_millis(), 1887164); + assert_eq!(f.properties().audio_bitrate(), 70); + assert_eq!(f.properties().channels(), 1); + assert_eq!(f.properties().sample_rate(), 44100); + // TODO? + // CPPUNIT_ASSERT_EQUAL(MPEG::XingHeader::Xing, f.audioProperties()->xingHeader()->type()); +} + +#[test] +#[ignore] +fn test_audio_properties_vbri_header() { + let mut file = File::open("tests/taglib/data/rare_frames.mp3").unwrap(); + let f = MPEGFile::read_from(&mut file, ParseOptions::new()).unwrap(); + + assert_eq!(f.properties().duration().as_secs(), 222); // TODO: Off by 1 + assert_eq!(f.properties().duration().as_millis(), 222198); + assert_eq!(f.properties().audio_bitrate(), 233); + assert_eq!(f.properties().channels(), 2); + assert_eq!(f.properties().sample_rate(), 44100); + // TODO? + // CPPUNIT_ASSERT_EQUAL(MPEG::XingHeader::VBRI, f.audioProperties()->xingHeader()->type()); +} + +#[test] +#[ignore] +fn test_audio_properties_no_vbr_headers() { + let mut file = File::open("tests/taglib/data/bladeenc.mp3").unwrap(); + let f = MPEGFile::read_from(&mut file, ParseOptions::new()).unwrap(); + + assert_eq!(f.properties().duration().as_secs(), 3); // Off by 1 + assert_eq!(f.properties().duration().as_millis(), 3553); + assert_eq!(f.properties().audio_bitrate(), 64); + assert_eq!(f.properties().channels(), 1); + assert_eq!(f.properties().sample_rate(), 44100); + + // NOTE: This test also checks the last frame of the file. That information is not saved + // in Lofty, and it doesn't seem too useful to expose. +} + +#[test] +fn test_skip_invalid_frames_1() { + let mut file = File::open("tests/taglib/data/invalid-frames1.mp3").unwrap(); + let f = MPEGFile::read_from(&mut file, ParseOptions::new()).unwrap(); + + assert_eq!(f.properties().duration().as_secs(), 0); + assert_eq!(f.properties().duration().as_millis(), 392); + assert_eq!(f.properties().audio_bitrate(), 160); + assert_eq!(f.properties().channels(), 2); + assert_eq!(f.properties().sample_rate(), 44100); +} + +#[test] +#[ignore] +fn test_skip_invalid_frames_2() { + let mut file = File::open("tests/taglib/data/invalid-frames2.mp3").unwrap(); + let f = MPEGFile::read_from(&mut file, ParseOptions::new()).unwrap(); + + assert_eq!(f.properties().duration().as_secs(), 0); + assert_eq!(f.properties().duration().as_millis(), 314); // TODO: Off by 79 + assert_eq!(f.properties().audio_bitrate(), 192); + assert_eq!(f.properties().channels(), 2); + assert_eq!(f.properties().sample_rate(), 44100); +} + +#[test] +#[ignore] +fn test_skip_invalid_frames_3() { + let mut file = File::open("tests/taglib/data/invalid-frames3.mp3").unwrap(); + let f = MPEGFile::read_from(&mut file, ParseOptions::new()).unwrap(); + + assert_eq!(f.properties().duration().as_secs(), 0); + assert_eq!(f.properties().duration().as_millis(), 183); // TODO: Off by 26 + assert_eq!(f.properties().audio_bitrate(), 320); + assert_eq!(f.properties().channels(), 2); + assert_eq!(f.properties().sample_rate(), 44100); +} + +#[test] +#[ignore] +fn test_version_2_duration_with_xing_header() { + let mut file = File::open("tests/taglib/data/mpeg2.mp3").unwrap(); + let f = MPEGFile::read_from(&mut file, ParseOptions::new()).unwrap(); + assert_eq!(f.properties().duration().as_secs(), 5387); // TODO: Off by 15 + assert_eq!(f.properties().duration().as_millis(), 5387285); +} + +#[test] +fn test_save_id3v24() { + let mut file = temp_file!("tests/taglib/data/xing.mp3"); + + let xxx = "X".repeat(254); + { + let mut f = MPEGFile::read_from(&mut file, ParseOptions::new()).unwrap(); + file.rewind().unwrap(); + assert!(f.id3v2().is_none()); + + let mut tag = ID3v2Tag::default(); + tag.set_title(xxx.clone()); + tag.set_artist(String::from("Artist A")); + f.set_id3v2(tag); + f.save_to(&mut file).unwrap(); + } + file.rewind().unwrap(); + { + let f = MPEGFile::read_from(&mut file, ParseOptions::new()).unwrap(); + assert_eq!(f.id3v2().unwrap().original_version(), ID3v2Version::V4); + assert_eq!(f.id3v2().unwrap().artist().as_deref(), Some("Artist A")); + assert_eq!(f.id3v2().unwrap().title().as_deref(), Some(xxx.as_str())); + } +} + +#[test] +#[ignore] +fn test_save_id3v24_wrong_param() { + // Marker test, Lofty does not replicate the TagLib saving API +} + +#[test] +#[ignore] // TODO: We don't yet support writing an ID3v23 tag (#62) +fn test_save_id3v23() { + let mut file = temp_file!("tests/taglib/data/xing.mp3"); + + let xxx = "X".repeat(254); + { + let mut f = MPEGFile::read_from(&mut file, ParseOptions::new()).unwrap(); + file.rewind().unwrap(); + assert!(f.id3v2().is_none()); + + let mut tag = ID3v2Tag::default(); + tag.set_title(xxx.clone()); + tag.set_artist(String::from("Artist A")); + f.set_id3v2(tag); + f.save_to(&mut file).unwrap(); + } + file.rewind().unwrap(); + { + let f = MPEGFile::read_from(&mut file, ParseOptions::new()).unwrap(); + assert_eq!(f.id3v2().unwrap().original_version(), ID3v2Version::V3); + assert_eq!(f.id3v2().unwrap().artist().as_deref(), Some("Artist A")); + assert_eq!(f.id3v2().unwrap().title().as_deref(), Some(xxx.as_str())); + } +} + +#[test] +fn test_duplicate_id3v2() { + let mut file = File::open("tests/taglib/data/duplicate_id3v2.mp3").unwrap(); + let f = MPEGFile::read_from(&mut file, ParseOptions::new()).unwrap(); + assert_eq!(f.properties().sample_rate(), 44100); +} + +#[test] +fn test_fuzzed_file() { + let mut file = File::open("tests/taglib/data/invalid-frames3.mp3").unwrap(); + let _ = MPEGFile::read_from(&mut file, ParseOptions::new()).unwrap(); +} + +#[test] +#[ignore] +fn test_frame_offset() { + // Marker test, Lofty does not replicate this API. Doesn't seem useful to retain frame offsets. +} + +#[test] +fn test_strip_and_properties() { + let mut file = temp_file!("tests/taglib/data/xing.mp3"); + + { + let mut f = MPEGFile::read_from(&mut file, ParseOptions::new()).unwrap(); + file.rewind().unwrap(); + + let mut id3v2 = ID3v2Tag::default(); + id3v2.set_title(String::from("ID3v2")); + f.set_id3v2(id3v2); + let mut ape = ApeTag::default(); + ape.set_title(String::from("APE")); + f.set_ape(ape); + let mut id3v1 = ID3v1Tag::default(); + id3v1.set_title(String::from("ID3v1")); + f.set_id3v1(id3v1); + f.save_to(&mut file).unwrap(); + } + file.rewind().unwrap(); + { + let mut f = MPEGFile::read_from(&mut file, ParseOptions::new()).unwrap(); + assert_eq!(f.id3v2().unwrap().title().as_deref(), Some("ID3v2")); + f.remove_id3v2(); + assert_eq!(f.ape().unwrap().title().as_deref(), Some("APE")); + f.remove_ape(); + assert_eq!(f.id3v1().unwrap().title().as_deref(), Some("ID3v1")); + f.remove_id3v1(); + assert!(!f.contains_tag()); + } +} + +#[test] +fn test_properties() {} + +#[test] +#[ignore] +fn test_repeated_save_1() { + // Marker test, yet another case of checking frame offsets that Lofty does not expose. +} + +#[test] +#[ignore] +fn test_repeated_save_2() { + // Marker test, not entirely sure what's even being tested here? +} + +#[test] +fn test_repeated_save_3() { + let mut file = temp_file!("tests/taglib/data/xing.mp3"); + + { + let mut f = MPEGFile::read_from(&mut file, ParseOptions::new()).unwrap(); + file.rewind().unwrap(); + assert!(f.ape().is_none()); + assert!(f.id3v1().is_none()); + + { + let mut ape = ApeTag::default(); + ape.set_title(String::from("01234 56789 ABCDE FGHIJ")); + f.set_ape(ape); + f.save_to(&mut file).unwrap(); + } + file.rewind().unwrap(); + { + f.ape_mut().unwrap().set_title(String::from("0")); + f.save_to(&mut file).unwrap(); + } + { + let mut id3v1 = ID3v1Tag::default(); + id3v1.set_title(String::from("01234 56789 ABCDE FGHIJ")); + f.set_id3v1(id3v1); + } + file.rewind().unwrap(); + { + f.ape_mut().unwrap().set_title(String::from( + "01234 56789 ABCDE FGHIJ 01234 56789 ABCDE FGHIJ 01234 56789", + )); + f.save_to(&mut file).unwrap(); + } + } + file.rewind().unwrap(); + { + let f = MPEGFile::read_from(&mut file, ParseOptions::new()).unwrap(); + assert!(f.ape().is_some()); + assert!(f.id3v1().is_some()); + } +} + +#[test] +fn test_empty_id3v2() { + let mut file = temp_file!("tests/taglib/data/xing.mp3"); + + { + let mut f = MPEGFile::read_from(&mut file, ParseOptions::new()).unwrap(); + file.rewind().unwrap(); + + let mut id3v2 = ID3v2Tag::default(); + id3v2.set_title(String::from("0123456789")); + f.set_id3v2(id3v2); + f.save_to(&mut file).unwrap(); + } + file.rewind().unwrap(); + { + let mut f = MPEGFile::read_from(&mut file, ParseOptions::new()).unwrap(); + file.rewind().unwrap(); + + let mut id3v2 = ID3v2Tag::default(); + id3v2.set_title(String::new()); + f.set_id3v2(id3v2); + f.save_to(&mut file).unwrap(); + } + file.rewind().unwrap(); + { + let f = MPEGFile::read_from(&mut file, ParseOptions::new()).unwrap(); + assert!(f.id3v2().is_none()); + } +} + +#[test] +fn test_empty_id3v1() { + let mut file = temp_file!("tests/taglib/data/xing.mp3"); + + { + let mut f = MPEGFile::read_from(&mut file, ParseOptions::new()).unwrap(); + file.rewind().unwrap(); + + let mut id3v1 = ID3v1Tag::default(); + id3v1.set_title(String::from("0123456789")); + f.set_id3v1(id3v1); + f.save_to(&mut file).unwrap(); + } + file.rewind().unwrap(); + { + let mut f = MPEGFile::read_from(&mut file, ParseOptions::new()).unwrap(); + file.rewind().unwrap(); + + let mut id3v1 = ID3v1Tag::default(); + id3v1.set_title(String::new()); + f.set_id3v1(id3v1); + f.save_to(&mut file).unwrap(); + } + file.rewind().unwrap(); + { + let f = MPEGFile::read_from(&mut file, ParseOptions::new()).unwrap(); + assert!(f.id3v1().is_none()); + } +} + +#[test] +fn test_empty_ape() { + let mut file = temp_file!("tests/taglib/data/xing.mp3"); + + { + let mut f = MPEGFile::read_from(&mut file, ParseOptions::new()).unwrap(); + file.rewind().unwrap(); + + let mut ape = ApeTag::default(); + ape.set_title(String::from("0123456789")); + f.set_ape(ape); + f.save_to(&mut file).unwrap(); + } + file.rewind().unwrap(); + { + let mut f = MPEGFile::read_from(&mut file, ParseOptions::new()).unwrap(); + file.rewind().unwrap(); + + let mut ape = ApeTag::default(); + ape.set_title(String::new()); + f.set_ape(ape); + f.save_to(&mut file).unwrap(); + } + file.rewind().unwrap(); + { + let f = MPEGFile::read_from(&mut file, ParseOptions::new()).unwrap(); + assert!(f.ape().is_none()); + } +} + +#[test] +#[ignore] // TODO: We can't find an ID3v2 tag after saving with garbage +fn test_ignore_garbage() { + let mut file = temp_file!("tests/taglib/data/garbage.mp3"); + + { + let mut f = MPEGFile::read_from(&mut file, ParseOptions::new()).unwrap(); + file.rewind().unwrap(); + assert!(f.id3v2().is_some()); + + assert_eq!(f.id3v2().unwrap().title().as_deref(), Some("Title A")); + f.id3v2_mut().unwrap().set_title(String::from("Title B")); + f.save_to(&mut file).unwrap(); + } + file.rewind().unwrap(); + { + let f = MPEGFile::read_from(&mut file, ParseOptions::new()).unwrap(); + assert!(f.id3v2().is_some()); + assert_eq!(f.id3v2().unwrap().title().as_deref(), Some("Title B")); + } +}