mirror of
https://github.com/Serial-ATA/lofty-rs
synced 2024-12-13 14:12:31 +00:00
musepack: Support SV7 property reading
This commit is contained in:
parent
6888c17bad
commit
d89250eef7
7 changed files with 432 additions and 104 deletions
|
@ -1,3 +1,5 @@
|
|||
//! MusePack constants
|
||||
|
||||
// There are only 4 frequencies defined in the spec, but there are 8 possible indices in the header.
|
||||
//
|
||||
// The reference decoder defines the table as:
|
||||
|
|
|
@ -6,6 +6,7 @@ use crate::error::Result;
|
|||
use crate::id3::v2::read::parse_id3v2;
|
||||
use crate::id3::{find_id3v1, find_id3v2, find_lyrics3v2, ID3FindResults};
|
||||
use crate::probe::ParseOptions;
|
||||
use crate::traits::SeekStreamLen;
|
||||
|
||||
use std::io::{Read, Seek, SeekFrom};
|
||||
|
||||
|
@ -17,6 +18,8 @@ where
|
|||
let mut version = MpcStreamVersion::Sv4to6;
|
||||
let mut file = MpcFile::default();
|
||||
|
||||
let mut stream_length = reader.stream_len()?;
|
||||
|
||||
// ID3v2 tags are unsupported in MPC files, but still possible
|
||||
#[allow(unused_variables)]
|
||||
if let ID3FindResults(Some(header), Some(content)) = find_id3v2(reader, true)? {
|
||||
|
@ -24,8 +27,44 @@ where
|
|||
|
||||
let id3v2 = parse_id3v2(reader, header)?;
|
||||
file.id3v2_tag = Some(id3v2);
|
||||
|
||||
let mut size = header.size;
|
||||
if header.flags.footer {
|
||||
size += 10;
|
||||
}
|
||||
|
||||
stream_length -= size as u64;
|
||||
}
|
||||
|
||||
// Save the current position, so we can go back and read the properties after the tags
|
||||
let pos_past_id3v2 = reader.stream_position()?;
|
||||
|
||||
#[allow(unused_variables)]
|
||||
let ID3FindResults(header, id3v1) = find_id3v1(reader, true)?;
|
||||
|
||||
if header.is_some() {
|
||||
file.id3v1_tag = id3v1;
|
||||
stream_length -= 128;
|
||||
}
|
||||
|
||||
let ID3FindResults(_, lyrics3v2_size) = find_lyrics3v2(reader)?;
|
||||
stream_length -= lyrics3v2_size as u64;
|
||||
|
||||
reader.seek(SeekFrom::Current(-32))?;
|
||||
|
||||
if let Some((tag, header)) = crate::ape::tag::read::read_ape_tag(reader, true)? {
|
||||
file.ape_tag = Some(tag);
|
||||
|
||||
// Seek back to the start of the tag
|
||||
let pos = reader.stream_position()?;
|
||||
reader.seek(SeekFrom::Start(pos - u64::from(header.size)))?;
|
||||
|
||||
stream_length -= header.size as u64;
|
||||
}
|
||||
|
||||
// Restore the position of the magic signature
|
||||
reader.seek(SeekFrom::Start(pos_past_id3v2))?;
|
||||
|
||||
let mut header = [0; 4];
|
||||
reader.read_exact(&mut header)?;
|
||||
|
||||
|
@ -51,8 +90,7 @@ where
|
|||
MpcProperties::Sv8(MpcSv8Properties::read(reader, parse_options.parsing_mode)?)
|
||||
},
|
||||
MpcStreamVersion::Sv7 => {
|
||||
file.properties =
|
||||
MpcProperties::Sv7(MpcSv7Properties::read(reader, parse_options.parsing_mode)?)
|
||||
file.properties = MpcProperties::Sv7(MpcSv7Properties::read(reader, stream_length)?)
|
||||
},
|
||||
MpcStreamVersion::Sv4to6 => {
|
||||
file.properties = MpcProperties::Sv4to6(MpcSv4to6Properties::read(
|
||||
|
@ -63,30 +101,5 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
let ID3FindResults(header, id3v1) = find_id3v1(reader, true)?;
|
||||
|
||||
if header.is_some() {
|
||||
file.id3v1_tag = id3v1;
|
||||
}
|
||||
|
||||
let _ = find_lyrics3v2(reader)?;
|
||||
|
||||
reader.seek(SeekFrom::Current(-32))?;
|
||||
|
||||
match crate::ape::tag::read::read_ape_tag(reader, true)? {
|
||||
Some((tag, header)) => {
|
||||
file.ape_tag = Some(tag);
|
||||
|
||||
// Seek back to the start of the tag
|
||||
let pos = reader.stream_position()?;
|
||||
reader.seek(SeekFrom::Start(pos - u64::from(header.size)))?;
|
||||
},
|
||||
None => {
|
||||
// Correct the position (APE header - Preamble)
|
||||
reader.seek(SeekFrom::Current(24))?;
|
||||
},
|
||||
}
|
||||
|
||||
Ok(file)
|
||||
}
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
use crate::error::Result;
|
||||
use crate::probe::ParsingMode;
|
||||
use crate::macros::decode_err;
|
||||
use crate::musepack::constants::{FREQUENCY_TABLE, MPC_OLD_GAIN_REF};
|
||||
use crate::properties::FileProperties;
|
||||
|
||||
use std::io::Read;
|
||||
use std::time::Duration;
|
||||
|
||||
use byteorder::{BigEndian, LittleEndian, ReadBytesExt};
|
||||
|
||||
/// Used profile
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||
pub enum Profile {
|
||||
|
@ -39,6 +42,32 @@ pub enum Profile {
|
|||
AboveBrainDead10,
|
||||
}
|
||||
|
||||
impl Profile {
|
||||
/// Get a `Profile` from a u8
|
||||
///
|
||||
/// The mapping is available here: <http://trac.musepack.net/musepack/wiki/SV7Specification>
|
||||
#[rustfmt::skip]
|
||||
pub fn from_u8(value: u8) -> Option<Self> {
|
||||
match value {
|
||||
0 => Some(Self::None),
|
||||
1 => Some(Self::Unstable),
|
||||
2 | 3 | 4 => Some(Self::Unused),
|
||||
5 => Some(Self::BelowTelephone0),
|
||||
6 => Some(Self::BelowTelephone1),
|
||||
7 => Some(Self::Telephone),
|
||||
8 => Some(Self::Thumb),
|
||||
9 => Some(Self::Radio),
|
||||
10 => Some(Self::Standard),
|
||||
11 => Some(Self::Xtreme),
|
||||
12 => Some(Self::Insane),
|
||||
13 => Some(Self::BrainDead),
|
||||
14 => Some(Self::AboveBrainDead9),
|
||||
15 => Some(Self::AboveBrainDead10),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Volume description for the start and end of the title
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||
pub enum Link {
|
||||
|
@ -53,6 +82,21 @@ pub enum Link {
|
|||
LoudStartAndEnd,
|
||||
}
|
||||
|
||||
impl Link {
|
||||
/// Get a `Link` from a u8
|
||||
///
|
||||
/// The mapping is available here: <http://trac.musepack.net/musepack/wiki/SV7Specification>
|
||||
pub fn from_u8(value: u8) -> Option<Self> {
|
||||
match value {
|
||||
0 => Some(Self::VeryLowStartOrEnd),
|
||||
1 => Some(Self::LoudEnd),
|
||||
2 => Some(Self::LoudStart),
|
||||
3 => Some(Self::LoudStartAndEnd),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// http://trac.musepack.net/musepack/wiki/SV7Specification
|
||||
|
||||
/// MPC stream version 7 audio properties
|
||||
|
@ -64,27 +108,27 @@ pub struct MpcSv7Properties {
|
|||
pub(crate) audio_bitrate: u32,
|
||||
pub(crate) channels: u8, // NOTE: always 2
|
||||
// -- Section 1 --
|
||||
frame_count: u32,
|
||||
pub(crate) frame_count: u32,
|
||||
// -- Section 2 --
|
||||
intensity_stereo: bool,
|
||||
mid_side_stereo: bool,
|
||||
max_band: u8,
|
||||
profile: Profile,
|
||||
link: Link,
|
||||
sample_freq: u32,
|
||||
max_level: u16,
|
||||
pub(crate) intensity_stereo: bool,
|
||||
pub(crate) mid_side_stereo: bool,
|
||||
pub(crate) max_band: u8,
|
||||
pub(crate) profile: Profile,
|
||||
pub(crate) link: Link,
|
||||
pub(crate) sample_freq: u32,
|
||||
pub(crate) max_level: u16,
|
||||
// -- Section 3 --
|
||||
title_gain: i16,
|
||||
title_peak: u16,
|
||||
pub(crate) title_gain: i16,
|
||||
pub(crate) title_peak: u16,
|
||||
// -- Section 4 --
|
||||
album_gain: i16,
|
||||
album_peak: u16,
|
||||
pub(crate) album_gain: i16,
|
||||
pub(crate) album_peak: u16,
|
||||
// -- Section 5 --
|
||||
true_gapless: bool,
|
||||
last_frame_length: u16,
|
||||
fast_seeking_safe: bool,
|
||||
pub(crate) true_gapless: bool,
|
||||
pub(crate) last_frame_length: u16,
|
||||
pub(crate) fast_seeking_safe: bool,
|
||||
// -- Section 6 --
|
||||
encoder_version: u8,
|
||||
pub(crate) encoder_version: u8,
|
||||
}
|
||||
|
||||
impl From<MpcSv7Properties> for FileProperties {
|
||||
|
@ -102,10 +146,233 @@ impl From<MpcSv7Properties> for FileProperties {
|
|||
}
|
||||
|
||||
impl MpcSv7Properties {
|
||||
pub(crate) fn read<R>(_reader: &mut R, _parse_mode: ParsingMode) -> Result<Self>
|
||||
/// Duration of the audio
|
||||
pub fn duration(&self) -> Duration {
|
||||
self.duration
|
||||
}
|
||||
|
||||
/// Overall bitrate (kbps)
|
||||
pub fn overall_bitrate(&self) -> u32 {
|
||||
self.overall_bitrate
|
||||
}
|
||||
|
||||
/// Audio bitrate (kbps)
|
||||
pub fn audio_bitrate(&self) -> u32 {
|
||||
self.audio_bitrate
|
||||
}
|
||||
|
||||
/// Sample rate (Hz)
|
||||
pub fn sample_rate(&self) -> u32 {
|
||||
self.sample_freq
|
||||
}
|
||||
|
||||
/// Channel count
|
||||
pub fn channels(&self) -> u8 {
|
||||
self.channels
|
||||
}
|
||||
|
||||
/// Total number of audio frames
|
||||
pub fn frame_count(&self) -> u32 {
|
||||
self.frame_count
|
||||
}
|
||||
|
||||
/// Whether intensity stereo coding (IS) is used
|
||||
pub fn intensity_stereo(&self) -> bool {
|
||||
self.intensity_stereo
|
||||
}
|
||||
|
||||
/// Whether MidSideStereo is used
|
||||
pub fn mid_side_stereo(&self) -> bool {
|
||||
self.mid_side_stereo
|
||||
}
|
||||
|
||||
/// Last subband used in the whole file
|
||||
pub fn max_band(&self) -> u8 {
|
||||
self.max_band
|
||||
}
|
||||
|
||||
/// Profile used
|
||||
pub fn profile(&self) -> Profile {
|
||||
self.profile
|
||||
}
|
||||
|
||||
/// Volume description of the start and end
|
||||
pub fn link(&self) -> Link {
|
||||
self.link
|
||||
}
|
||||
|
||||
/// Maximum level of the coded PCM input signal
|
||||
pub fn max_level(&self) -> u16 {
|
||||
self.max_level
|
||||
}
|
||||
|
||||
/// Change in the replay level
|
||||
///
|
||||
/// The value is a signed 16 bit integer, with the level being attenuated by that many mB
|
||||
pub fn title_gain(&self) -> i16 {
|
||||
self.title_gain
|
||||
}
|
||||
|
||||
/// Maximum level of the decoded title
|
||||
///
|
||||
/// * 16422: -6 dB
|
||||
/// * 32767: 0 dB
|
||||
/// * 65379: +6 dB
|
||||
pub fn title_peak(&self) -> u16 {
|
||||
self.title_peak
|
||||
}
|
||||
|
||||
/// Change in the replay level if the whole CD is supposed to be played with the same level change
|
||||
///
|
||||
/// The value is a signed 16 bit integer, with the level being attenuated by that many mB
|
||||
pub fn album_gain(&self) -> i16 {
|
||||
self.album_gain
|
||||
}
|
||||
|
||||
/// Maximum level of the whole decoded CD
|
||||
///
|
||||
/// * 16422: -6 dB
|
||||
/// * 32767: 0 dB
|
||||
/// * 65379: +6 dB
|
||||
pub fn album_peak(&self) -> u16 {
|
||||
self.album_peak
|
||||
}
|
||||
|
||||
/// Whether true gapless is used
|
||||
pub fn true_gapless(&self) -> bool {
|
||||
self.true_gapless
|
||||
}
|
||||
|
||||
/// Used samples of the last frame
|
||||
///
|
||||
/// * TrueGapless = 0: always 0
|
||||
/// * TrueGapless = 1: 1...1152
|
||||
pub fn last_frame_length(&self) -> u16 {
|
||||
self.last_frame_length
|
||||
}
|
||||
|
||||
/// Whether fast seeking can be used safely
|
||||
pub fn fast_seeking_safe(&self) -> bool {
|
||||
self.fast_seeking_safe
|
||||
}
|
||||
|
||||
/// Encoder version
|
||||
///
|
||||
/// * Encoder version * 100 (106 = 1.06)
|
||||
/// * EncoderVersion % 10 == 0 Release (1.0)
|
||||
/// * EncoderVersion % 2 == 0 Beta (1.06)
|
||||
/// * EncoderVersion % 2 == 1 Alpha (1.05a...z)
|
||||
pub fn encoder_version(&self) -> u8 {
|
||||
self.encoder_version
|
||||
}
|
||||
|
||||
#[allow(clippy::field_reassign_with_default)]
|
||||
pub(crate) fn read<R>(reader: &mut R, stream_length: u64) -> Result<Self>
|
||||
where
|
||||
R: Read,
|
||||
{
|
||||
todo!()
|
||||
let version = reader.read_u8()?;
|
||||
if version & 0x0F != 7 {
|
||||
decode_err!(@BAIL Mpc, "Expected stream version 7");
|
||||
}
|
||||
|
||||
let mut properties = MpcSv7Properties {
|
||||
channels: 2, // Always 2 channels
|
||||
..Self::default()
|
||||
};
|
||||
|
||||
// -- Section 1 --
|
||||
properties.frame_count = reader.read_u32::<LittleEndian>()?;
|
||||
|
||||
// -- Section 2 --
|
||||
let chunk = reader.read_u32::<LittleEndian>()?;
|
||||
|
||||
let byte1 = ((chunk & 0xFF00_0000) >> 24) as u8;
|
||||
|
||||
properties.intensity_stereo = ((byte1 & 0x80) >> 7) == 1;
|
||||
properties.mid_side_stereo = ((byte1 & 0x40) >> 6) == 1;
|
||||
properties.max_band = byte1 & 0x3F;
|
||||
|
||||
let byte2 = ((chunk & 0xFF_0000) >> 16) as u8;
|
||||
|
||||
let profile_index = (byte2 & 0xF0) >> 4;
|
||||
properties.profile = Profile::from_u8(profile_index).unwrap(); // Infallible
|
||||
|
||||
let link_index = (byte2 & 0x0C) >> 2;
|
||||
properties.link = Link::from_u8(link_index).unwrap(); // Infallible
|
||||
|
||||
let sample_freq_index = byte2 & 0x03;
|
||||
properties.sample_freq = FREQUENCY_TABLE[sample_freq_index as usize];
|
||||
|
||||
let remaining_bytes = (chunk & 0xFFFF) as u16;
|
||||
properties.max_level = remaining_bytes;
|
||||
|
||||
// -- Section 3 --
|
||||
let title_gain = reader.read_i16::<BigEndian>()?;
|
||||
let title_peak = reader.read_u16::<BigEndian>()?;
|
||||
|
||||
// -- Section 4 --
|
||||
let album_gain = reader.read_i16::<BigEndian>()?;
|
||||
let album_peak = reader.read_u16::<BigEndian>()?;
|
||||
|
||||
// -- Section 5 --
|
||||
let chunk = reader.read_u32::<LittleEndian>()?;
|
||||
|
||||
let byte1 = ((chunk & 0xFF00_0000) >> 24) as u8;
|
||||
|
||||
properties.true_gapless = ((byte1 & 0x80) >> 7) == 1;
|
||||
|
||||
let byte2 = ((chunk & 0xFF_0000) >> 16) as u8;
|
||||
|
||||
if properties.true_gapless {
|
||||
properties.last_frame_length =
|
||||
(u16::from(byte1 & 0x7F) << 4) | u16::from((byte2 & 0xF0) >> 4);
|
||||
}
|
||||
|
||||
// NOTE: Rest of the chunk is zeroed and unused
|
||||
|
||||
// -- Section 6 --
|
||||
properties.encoder_version = reader.read_u8()?;
|
||||
|
||||
// -- End of parsing --
|
||||
|
||||
// Convert ReplayGain values
|
||||
let set_replay_gain = |gain: i16| -> i16 {
|
||||
let mut gain = (MPC_OLD_GAIN_REF - f32::from(gain) / 100.0) * 256.0 + 0.5;
|
||||
if gain >= ((1 << 16) as f32) || gain < 0.0 {
|
||||
gain = 0.0
|
||||
}
|
||||
gain as i16
|
||||
};
|
||||
let set_replay_peak = |peak: u16| -> u16 {
|
||||
if peak == 0 {
|
||||
return 0;
|
||||
}
|
||||
|
||||
((peak.ilog10() * 20 * 256) as f32 + 0.5) as u16
|
||||
};
|
||||
|
||||
properties.title_gain = set_replay_gain(title_gain);
|
||||
properties.title_peak = set_replay_peak(title_peak);
|
||||
properties.album_gain = set_replay_gain(album_gain);
|
||||
properties.album_peak = set_replay_peak(album_peak);
|
||||
|
||||
let total_samples;
|
||||
if properties.true_gapless {
|
||||
total_samples =
|
||||
(properties.frame_count * 1152) - u32::from(properties.last_frame_length);
|
||||
} else {
|
||||
total_samples = (properties.frame_count * 1152) - 576;
|
||||
}
|
||||
|
||||
if total_samples > 0 && properties.sample_freq > 0 {
|
||||
let length =
|
||||
(f64::from(total_samples) * 1000.0 / f64::from(properties.sample_freq)).ceil();
|
||||
properties.duration = Duration::from_millis(length as u64);
|
||||
properties.audio_bitrate = (stream_length * 8 / length as u64) as u32;
|
||||
properties.overall_bitrate = properties.audio_bitrate;
|
||||
}
|
||||
|
||||
Ok(properties)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -476,7 +476,7 @@ impl<R: Read + Seek> Probe<R> {
|
|||
let file_type_after_id3_block = match &ident {
|
||||
[b'M', b'A', b'C', ..] => Ok(Some(FileType::Ape)),
|
||||
b"fLaC" => Ok(Some(FileType::Flac)),
|
||||
b"MPCK" | [.., b'M', b'P', b'+'] => Ok(Some(FileType::Mpc)),
|
||||
b"MPCK" | [b'M', b'P', b'+', ..] => Ok(Some(FileType::Mpc)),
|
||||
// Search for a frame sync, which may be preceded by junk
|
||||
_ if search_for_frame_sync(&mut self.inner)?.is_some() => {
|
||||
// Seek back to the start of the frame sync to check if we are dealing with
|
||||
|
|
|
@ -127,6 +127,7 @@ mod tests {
|
|||
use crate::iff::wav::{WavFile, WavFormat, WavProperties};
|
||||
use crate::mp4::{AudioObjectType, Mp4Codec, Mp4File, Mp4Properties};
|
||||
use crate::mpeg::{ChannelMode, Emphasis, Layer, MpegFile, MpegProperties, MpegVersion};
|
||||
use crate::musepack::sv7::{Link, MpcSv7Properties, Profile};
|
||||
use crate::musepack::sv8::{EncoderInfo, MpcSv8Properties, ReplayGain, StreamHeader};
|
||||
use crate::musepack::{MpcFile, MpcProperties};
|
||||
use crate::ogg::{
|
||||
|
@ -274,9 +275,32 @@ mod tests {
|
|||
channels: 2,
|
||||
};
|
||||
|
||||
const MPC_SV7_PROPERTIES: MpcSv7Properties = MpcSv7Properties {
|
||||
duration: Duration::from_millis(1428),
|
||||
overall_bitrate: 86,
|
||||
audio_bitrate: 86,
|
||||
channels: 2,
|
||||
frame_count: 60,
|
||||
intensity_stereo: false,
|
||||
mid_side_stereo: true,
|
||||
max_band: 26,
|
||||
profile: Profile::Standard,
|
||||
link: Link::VeryLowStartOrEnd,
|
||||
sample_freq: 48000,
|
||||
max_level: 0,
|
||||
title_gain: 16594,
|
||||
title_peak: 0,
|
||||
album_gain: 16594,
|
||||
album_peak: 0,
|
||||
true_gapless: true,
|
||||
last_frame_length: 578,
|
||||
fast_seeking_safe: false,
|
||||
encoder_version: 192,
|
||||
};
|
||||
|
||||
const MPC_SV8_PROPERTIES: MpcSv8Properties = MpcSv8Properties {
|
||||
duration: Duration::from_millis(1428),
|
||||
overall_bitrate: 82, // TODO: Reference decoder reports 84
|
||||
overall_bitrate: 82,
|
||||
audio_bitrate: 82,
|
||||
stream_header: StreamHeader {
|
||||
crc: 4_252_559_415,
|
||||
|
@ -460,6 +484,14 @@ mod tests {
|
|||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mpc_sv7_properties() {
|
||||
assert_eq!(
|
||||
get_properties::<MpcFile>("tests/files/assets/minimal/mpc_sv7.mpc"),
|
||||
MpcProperties::Sv7(MPC_SV7_PROPERTIES)
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mpc_sv8_properties() {
|
||||
assert_eq!(
|
||||
|
|
BIN
tests/files/assets/minimal/mpc_sv7.mpc
Normal file
BIN
tests/files/assets/minimal/mpc_sv7.mpc
Normal file
Binary file not shown.
|
@ -4,66 +4,80 @@ use lofty::{
|
|||
};
|
||||
use std::io::{Seek, Write};
|
||||
|
||||
// Marker test so IntelliJ Rust recognizes this as a test module
|
||||
#[test]
|
||||
fn read() {
|
||||
// Here we have an MPC file with an ID3v2, ID3v1, and an APEv2 tag
|
||||
let file = Probe::open("tests/files/assets/minimal/mpc_sv8.mpc")
|
||||
.unwrap()
|
||||
.options(ParseOptions::new().read_properties(false))
|
||||
.read()
|
||||
.unwrap();
|
||||
fn fake() {}
|
||||
|
||||
assert_eq!(file.file_type(), FileType::Mpc);
|
||||
macro_rules! generate_tests {
|
||||
($stream_version:ident, $path:literal) => {
|
||||
paste::paste! {
|
||||
#[test]
|
||||
fn [<read_ $stream_version>]() {
|
||||
// Here we have an MPC file with an ID3v2, ID3v1, and an APEv2 tag
|
||||
let file = Probe::open($path)
|
||||
.unwrap()
|
||||
.options(ParseOptions::new().read_properties(false))
|
||||
.read()
|
||||
.unwrap();
|
||||
|
||||
// Verify the APE tag first
|
||||
crate::verify_artist!(file, primary_tag, "Foo artist", 1);
|
||||
assert_eq!(file.file_type(), FileType::Mpc);
|
||||
|
||||
// Now verify ID3v1 (read only)
|
||||
crate::verify_artist!(file, tag, TagType::Id3v1, "Bar artist", 1);
|
||||
// Verify the APE tag first
|
||||
crate::verify_artist!(file, primary_tag, "Foo artist", 1);
|
||||
|
||||
// Finally, verify ID3v2 (read only)
|
||||
crate::verify_artist!(file, tag, TagType::Id3v2, "Baz artist", 1);
|
||||
// Now verify ID3v1 (read only)
|
||||
crate::verify_artist!(file, tag, TagType::Id3v1, "Bar artist", 1);
|
||||
|
||||
// Finally, verify ID3v2 (read only)
|
||||
crate::verify_artist!(file, tag, TagType::Id3v2, "Baz artist", 1);
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn [<write_ $stream_version>]() {
|
||||
let mut file = temp_file!($path);
|
||||
|
||||
let mut tagged_file = Probe::new(&mut file)
|
||||
.options(ParseOptions::new().read_properties(false))
|
||||
.guess_file_type()
|
||||
.unwrap()
|
||||
.read()
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(tagged_file.file_type(), FileType::Mpc);
|
||||
|
||||
// APE
|
||||
crate::set_artist!(tagged_file, primary_tag_mut, "Foo artist", 1 => file, "Bar artist");
|
||||
|
||||
// Now reread the file
|
||||
file.rewind().unwrap();
|
||||
let mut tagged_file = Probe::new(&mut file)
|
||||
.options(ParseOptions::new().read_properties(false))
|
||||
.guess_file_type()
|
||||
.unwrap()
|
||||
.read()
|
||||
.unwrap();
|
||||
|
||||
crate::set_artist!(tagged_file, primary_tag_mut, "Bar artist", 1 => file, "Foo artist");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn [<remove_id3v2_ $stream_version>]() {
|
||||
crate::remove_tag!($path, TagType::Id3v2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn [<remove_id3v1_ $stream_version>]() {
|
||||
crate::remove_tag!($path, TagType::Id3v1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn [<remove_ape_ $stream_version>]() {
|
||||
crate::remove_tag!($path, TagType::Ape);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn write() {
|
||||
let mut file = temp_file!("tests/files/assets/minimal/mpc_sv8.mpc");
|
||||
|
||||
let mut tagged_file = Probe::new(&mut file)
|
||||
.options(ParseOptions::new().read_properties(false))
|
||||
.guess_file_type()
|
||||
.unwrap()
|
||||
.read()
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(tagged_file.file_type(), FileType::Mpc);
|
||||
|
||||
// APE
|
||||
crate::set_artist!(tagged_file, primary_tag_mut, "Foo artist", 1 => file, "Bar artist");
|
||||
|
||||
// Now reread the file
|
||||
file.rewind().unwrap();
|
||||
let mut tagged_file = Probe::new(&mut file)
|
||||
.options(ParseOptions::new().read_properties(false))
|
||||
.guess_file_type()
|
||||
.unwrap()
|
||||
.read()
|
||||
.unwrap();
|
||||
|
||||
crate::set_artist!(tagged_file, primary_tag_mut, "Bar artist", 1 => file, "Foo artist");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn remove_id3v2() {
|
||||
crate::remove_tag!("tests/files/assets/minimal/mpc_sv8.mpc", TagType::Id3v2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn remove_id3v1() {
|
||||
crate::remove_tag!("tests/files/assets/minimal/mpc_sv8.mpc", TagType::Id3v1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn remove_ape() {
|
||||
crate::remove_tag!("tests/files/assets/minimal/mpc_sv8.mpc", TagType::Ape);
|
||||
}
|
||||
generate_tests!(sv8, "tests/files/assets/minimal/mpc_sv8.mpc");
|
||||
generate_tests!(sv7, "tests/files/assets/minimal/mpc_sv7.mpc");
|
||||
|
|
Loading…
Reference in a new issue