use crate::{
AudexError, FileType, Result, StreamInfo,
apev2::{APEv2, APEv2Tags},
};
use std::io::{Read, Seek, SeekFrom};
use std::path::Path;
use std::time::Duration;
#[cfg(feature = "async")]
use crate::util::loadfile_read_async;
#[cfg(feature = "async")]
use tokio::fs::File as TokioFile;
#[cfg(feature = "async")]
use tokio::io::{AsyncReadExt, AsyncSeekExt};
const RATES: [u32; 4] = [44100, 48000, 37800, 32000];
pub fn parse_sv8_int<R: Read>(reader: &mut R, limit: usize) -> Result<(u64, usize)> {
let mut num = 0u64;
for i in 0..limit {
let mut buf = [0u8; 1];
reader
.read_exact(&mut buf)
.map_err(|_| AudexError::MusepackHeaderError("Unexpected end of file".to_string()))?;
let byte = buf[0];
if num > (u64::MAX >> 7) {
return Err(AudexError::MusepackHeaderError(
"SV8 variable-length integer overflow".to_string(),
));
}
num = (num << 7) | (byte as u64 & 0x7F);
if (byte & 0x80) == 0 {
return Ok((num, i + 1));
}
}
if limit > 0 {
Err(AudexError::MusepackHeaderError(
"Invalid SV8 integer".to_string(),
))
} else {
Ok((0, 0))
}
}
fn calc_sv8_gain(gain: i16) -> f64 {
64.82 - (gain as f64) / 256.0
}
fn calc_sv8_peak(peak: i16) -> f64 {
10_f64.powf((peak as f64) / (256.0 * 20.0)) / 65535.0
}
#[derive(Debug, Default)]
pub struct MusepackStreamInfo {
pub length: Option<Duration>,
pub bitrate: Option<u32>,
pub channels: u16,
pub sample_rate: u32,
pub version: u8,
pub samples: u64,
pub title_gain: Option<f64>,
pub title_peak: Option<f64>,
pub album_gain: Option<f64>,
pub album_peak: Option<f64>,
}
impl StreamInfo for MusepackStreamInfo {
fn length(&self) -> Option<Duration> {
self.length
}
fn bitrate(&self) -> Option<u32> {
self.bitrate
}
fn sample_rate(&self) -> Option<u32> {
Some(self.sample_rate)
}
fn channels(&self) -> Option<u16> {
Some(self.channels)
}
fn bits_per_sample(&self) -> Option<u16> {
None
}
}
impl MusepackStreamInfo {
pub fn from_reader<R: Read + Seek>(reader: &mut R) -> Result<Self> {
reader.seek(SeekFrom::Start(0))?;
let mut header = [0u8; 4];
reader
.read_exact(&mut header)
.map_err(|_| AudexError::MusepackHeaderError("Not a Musepack file".to_string()))?;
if &header[0..3] == b"ID3" {
let mut id3_header = [0u8; 6];
reader
.read_exact(&mut id3_header)
.map_err(|_| AudexError::MusepackHeaderError("Not a Musepack file".to_string()))?;
let size = (((id3_header[2] & 0x7F) as u32) << 21)
| (((id3_header[3] & 0x7F) as u32) << 14)
| (((id3_header[4] & 0x7F) as u32) << 7)
| ((id3_header[5] & 0x7F) as u32);
reader.seek(SeekFrom::Start(10 + size as u64))?;
reader
.read_exact(&mut header)
.map_err(|_| AudexError::MusepackHeaderError("Not a Musepack file".to_string()))?;
}
if &header == b"MPCK" {
Self::parse_sv8(reader)
} else {
Self::parse_sv467(reader, &header)
}
}
fn parse_sv8<R: Read + Seek>(reader: &mut R) -> Result<Self> {
let mut info = MusepackStreamInfo {
version: 8,
..Default::default()
};
let key_size = 2;
let mandatory_packets = vec![b"SH".to_vec(), b"RG".to_vec()];
let mut found_packets = Vec::new();
loop {
let mut frame_type = [0u8; 2];
match reader.read_exact(&mut frame_type) {
Ok(_) => {}
Err(_) => break, }
if frame_type[0] < b'A'
|| frame_type[0] > b'Z'
|| frame_type[1] < b'A'
|| frame_type[1] > b'Z'
{
return Err(AudexError::MusepackHeaderError(
"Invalid frame key".to_string(),
));
}
if &frame_type == b"AP" || &frame_type == b"SE" {
break;
}
let (frame_size, size_len) = parse_sv8_int(reader, 9)?;
if frame_size == 0 {
return Err(AudexError::MusepackHeaderError(
"Malformed packet: zero-length frame".to_string(),
));
}
let data_size = frame_size
.saturating_sub(key_size as u64)
.saturating_sub(size_len as u64);
if data_size == 0 {
return Err(AudexError::MusepackHeaderError(
"Malformed packet: frame_size smaller than header overhead".to_string(),
));
}
if data_size > 4 * 1024 * 1024 {
return Err(AudexError::MusepackHeaderError(
"Packet size too large".to_string(),
));
}
match &frame_type {
b"SH" => {
if found_packets.contains(&b"SH".to_vec()) {
return Err(AudexError::MusepackHeaderError(
"Duplicate SH packet".to_string(),
));
}
found_packets.push(b"SH".to_vec());
Self::parse_stream_header(reader, data_size, &mut info)?;
}
b"RG" => {
if found_packets.contains(&b"RG".to_vec()) {
return Err(AudexError::MusepackHeaderError(
"Duplicate RG packet".to_string(),
));
}
found_packets.push(b"RG".to_vec());
Self::parse_replaygain_packet(reader, data_size, &mut info)?;
}
_ => {
let seek_offset = i64::try_from(data_size).map_err(|_| {
AudexError::MusepackHeaderError(
"Packet data_size too large for seek offset".to_string(),
)
})?;
reader.seek(SeekFrom::Current(seek_offset))?;
}
}
}
for packet in &mandatory_packets {
if !found_packets.contains(packet) {
return Err(AudexError::MusepackHeaderError(format!(
"Missing mandatory packet: {:?}",
String::from_utf8_lossy(packet)
)));
}
}
if info.sample_rate > 0 {
info.length =
Duration::try_from_secs_f64(info.samples as f64 / info.sample_rate as f64).ok();
}
Ok(info)
}
fn parse_stream_header<R: Read>(
reader: &mut R,
data_size: u64,
info: &mut MusepackStreamInfo,
) -> Result<()> {
let mut crc = [0u8; 4];
reader.read_exact(&mut crc)?;
let remaining_size = data_size
.checked_sub(4)
.ok_or_else(|| AudexError::MusepackHeaderError("SH packet too small".to_string()))?;
let mut version_buf = [0u8; 1];
reader.read_exact(&mut version_buf).map_err(|_| {
AudexError::MusepackHeaderError("SH packet ended unexpectedly".to_string())
})?;
info.version = version_buf[0];
let remaining_size = remaining_size.checked_sub(1).ok_or_else(|| {
AudexError::MusepackHeaderError("SH packet ended unexpectedly".to_string())
})?;
let (samples, len1) = parse_sv8_int(reader, 9).map_err(|_| {
AudexError::MusepackHeaderError("SH packet: Invalid sample counts".to_string())
})?;
let (samples_skip, len2) = parse_sv8_int(reader, 9).map_err(|_| {
AudexError::MusepackHeaderError("SH packet: Invalid sample counts".to_string())
})?;
info.samples = samples.saturating_sub(samples_skip);
let remaining_size = remaining_size
.checked_sub(len1 as u64 + len2 as u64)
.ok_or_else(|| {
AudexError::MusepackHeaderError("SH packet ended unexpectedly".to_string())
})?;
if remaining_size < 2 {
return Err(AudexError::MusepackHeaderError(
"SH packet ended unexpectedly".to_string(),
));
}
let mut rate_chan_data = [0u8; 2];
reader.read_exact(&mut rate_chan_data).map_err(|_| {
AudexError::MusepackHeaderError("SH packet ended unexpectedly".to_string())
})?;
let mut to_skip = remaining_size.saturating_sub(2);
let mut skip_buf = [0u8; 1024];
while to_skip > 0 {
let n = (to_skip as usize).min(skip_buf.len());
reader.read_exact(&mut skip_buf[..n]).map_err(|_| {
AudexError::MusepackHeaderError("SH packet ended unexpectedly".to_string())
})?;
to_skip -= n as u64;
}
let rate_index = (rate_chan_data[0] >> 5) as usize;
if rate_index >= RATES.len() {
return Err(AudexError::MusepackHeaderError(
"Invalid sample rate".to_string(),
));
}
info.sample_rate = RATES[rate_index];
let channels = ((rate_chan_data[1] >> 4) + 1) as u16;
if channels > 2 {
return Err(AudexError::MusepackHeaderError(format!(
"Unsupported channel count {}: Musepack supports at most 2 channels",
channels
)));
}
info.channels = channels;
Ok(())
}
fn parse_replaygain_packet<R: Read>(
reader: &mut R,
data_size: u64,
info: &mut MusepackStreamInfo,
) -> Result<()> {
const MAX_RG_PACKET_SIZE: u64 = 4 * 1024 * 1024;
if data_size > MAX_RG_PACKET_SIZE {
return Err(AudexError::MusepackHeaderError(
"RG packet data_size too large".to_string(),
));
}
if data_size < 9 {
return Err(AudexError::MusepackHeaderError(
"Invalid RG packet size".to_string(),
));
}
let mut data = vec![0u8; data_size as usize];
reader.read_exact(&mut data).map_err(|_| {
AudexError::MusepackHeaderError("RG packet ended unexpectedly".to_string())
})?;
let title_gain = i16::from_be_bytes([data[1], data[2]]);
let title_peak = i16::from_be_bytes([data[3], data[4]]);
let album_gain = i16::from_be_bytes([data[5], data[6]]);
let album_peak = i16::from_be_bytes([data[7], data[8]]);
if title_gain != 0 {
info.title_gain = Some(calc_sv8_gain(title_gain));
}
if title_peak != 0 {
info.title_peak = Some(calc_sv8_peak(title_peak));
}
if album_gain != 0 {
info.album_gain = Some(calc_sv8_gain(album_gain));
}
if album_peak != 0 {
info.album_peak = Some(calc_sv8_peak(album_peak));
}
Ok(())
}
fn parse_sv467<R: Read + Seek>(reader: &mut R, _initial_header: &[u8]) -> Result<Self> {
reader.seek(SeekFrom::Current(-4))?;
let mut header = [0u8; 32];
reader
.read_exact(&mut header)
.map_err(|_| AudexError::MusepackHeaderError("Not a Musepack file".to_string()))?;
let mut info = MusepackStreamInfo {
channels: 2, ..Default::default()
};
if &header[0..3] == b"MP+" {
info.version = header[3] & 0xF;
if info.version < 7 {
return Err(AudexError::MusepackHeaderError(
"Not a Musepack file".to_string(),
));
}
let frames = u32::from_le_bytes([header[4], header[5], header[6], header[7]]);
let flags = u32::from_le_bytes([header[8], header[9], header[10], header[11]]);
let title_peak = u16::from_le_bytes([header[12], header[13]]);
let title_gain = i16::from_le_bytes([header[14], header[15]]);
let album_peak = u16::from_le_bytes([header[16], header[17]]);
let album_gain = i16::from_le_bytes([header[18], header[19]]);
if title_gain != 0 {
info.title_gain = Some(title_gain as f64 / 100.0);
}
if album_gain != 0 {
info.album_gain = Some(album_gain as f64 / 100.0);
}
if title_peak != 0 {
info.title_peak = Some(title_peak as f64 / 65535.0);
}
if album_peak != 0 {
info.album_peak = Some(album_peak as f64 / 65535.0);
}
let rate_index = ((flags >> 16) & 0x0003) as usize;
if rate_index >= RATES.len() {
return Err(AudexError::MusepackHeaderError(
"Invalid sample rate".to_string(),
));
}
info.sample_rate = RATES[rate_index];
info.bitrate = Some(0);
info.samples = (frames as u64).saturating_mul(1152).saturating_sub(576);
} else {
let header_dword = u32::from_le_bytes([header[0], header[1], header[2], header[3]]);
let raw_version = (header_dword >> 11) & 0x03FF;
if !(4..=6).contains(&raw_version) {
return Err(AudexError::MusepackHeaderError(
"Not a Musepack file".to_string(),
));
}
info.version = raw_version as u8;
let raw_kbps = (header_dword >> 23) & 0x01FF;
info.bitrate = Some(raw_kbps.saturating_mul(1000));
info.sample_rate = 44100;
let frames = if info.version >= 5 {
u32::from_le_bytes([header[4], header[5], header[6], header[7]])
} else {
u16::from_le_bytes([header[6], header[7]]) as u32
};
let actual_frames = if info.version < 6 {
frames.saturating_sub(1)
} else {
frames
};
info.samples = (actual_frames as u64)
.saturating_mul(1152)
.saturating_sub(576);
}
if info.sample_rate > 0 {
info.length =
Duration::try_from_secs_f64(info.samples as f64 / info.sample_rate as f64).ok();
}
Ok(info)
}
pub fn pprint(&self) -> String {
let mut rg_data = Vec::new();
if let Some(title_gain) = self.title_gain {
rg_data.push(format!("{:+0.2} (title)", title_gain));
}
if let Some(album_gain) = self.album_gain {
rg_data.push(format!("{:+0.2} (album)", album_gain));
}
let rg_str = if rg_data.is_empty() {
String::new()
} else {
format!(", Gain: {}", rg_data.join(", "))
};
format!(
"Musepack SV{}, {:.2} seconds, {} Hz, {} bps{}",
self.version,
self.length.map(|d| d.as_secs_f64()).unwrap_or(0.0),
self.sample_rate,
self.bitrate.unwrap_or(0),
rg_str
)
}
}
#[derive(Debug)]
pub struct Musepack {
pub info: MusepackStreamInfo,
pub tags: Option<APEv2Tags>,
pub filename: Option<String>,
}
impl Musepack {
pub fn new() -> Self {
Self {
info: MusepackStreamInfo::default(),
tags: None,
filename: None,
}
}
fn parse_file<R: Read + Seek>(&mut self, reader: &mut R) -> Result<()> {
self.info = MusepackStreamInfo::from_reader(reader)?;
if self.info.bitrate.is_none() || self.info.bitrate == Some(0) {
let current_pos = reader.stream_position()?;
reader.seek(SeekFrom::End(0))?;
let file_size = reader.stream_position()?;
reader.seek(SeekFrom::Start(current_pos))?;
if let Some(length) = self.info.length {
if length.as_secs_f64() > 0.0 {
let bitrate_f64 =
(file_size.saturating_mul(8) as f64 / length.as_secs_f64()).round();
let bitrate = if bitrate_f64 > u32::MAX as f64 {
u32::MAX
} else {
bitrate_f64 as u32
};
self.info.bitrate = Some(bitrate);
}
}
}
if let Some(filename) = &self.filename {
match APEv2::load(filename) {
Ok(ape) => self.tags = Some(ape.tags),
Err(_) => self.tags = None, }
}
Ok(())
}
pub fn add_tags(&mut self) -> Result<()> {
if self.tags.is_some() {
return Err(AudexError::MusepackHeaderError(
"APEv2 tag already exists".to_string(),
));
}
self.tags = Some(APEv2Tags::new());
Ok(())
}
pub fn clear(&mut self) -> Result<()> {
if let Some(ref filename) = self.filename {
crate::apev2::clear(filename)?;
}
self.tags = None;
Ok(())
}
pub fn mime(&self) -> Vec<&'static str> {
vec!["audio/x-musepack", "audio/x-mpc"]
}
pub fn pprint(&self) -> String {
self.info.pprint()
}
#[cfg(feature = "async")]
pub async fn load_async<P: AsRef<Path>>(path: P) -> Result<Self> {
let mut file = loadfile_read_async(&path).await?;
let mut mpc = Musepack::new();
mpc.filename = Some(path.as_ref().to_string_lossy().to_string());
mpc.info = Self::parse_info_async(&mut file).await?;
match crate::apev2::APEv2::load_async(&path).await {
Ok(ape) => mpc.tags = Some(ape.tags),
Err(AudexError::APENoHeader) => mpc.tags = None,
Err(e) => return Err(e),
}
Ok(mpc)
}
#[cfg(feature = "async")]
async fn parse_info_async(file: &mut TokioFile) -> Result<MusepackStreamInfo> {
let file_size = file.seek(SeekFrom::End(0)).await?;
const MAX_HEADER_READ: u64 = 64 * 1024;
let read_size = std::cmp::min(file_size, MAX_HEADER_READ) as usize;
file.seek(SeekFrom::Start(0)).await?;
let mut data = vec![0u8; read_size];
file.read_exact(&mut data).await?;
let mut cursor = std::io::Cursor::new(&data[..]);
let mut info = MusepackStreamInfo::from_reader(&mut cursor)?;
if info.bitrate.is_none() || info.bitrate == Some(0) {
if let Some(length) = info.length {
if length.as_secs_f64() > 0.0 {
let bitrate_f64 =
(file_size.saturating_mul(8) as f64 / length.as_secs_f64()).round();
let bitrate = if bitrate_f64 > u32::MAX as f64 {
u32::MAX
} else {
bitrate_f64 as u32
};
info.bitrate = Some(bitrate);
}
}
}
Ok(info)
}
#[cfg(feature = "async")]
pub async fn save_async(&mut self) -> Result<()> {
let filename = self
.filename
.clone()
.ok_or(AudexError::InvalidData("No filename set".to_string()))?;
if let Some(ref tags) = self.tags {
let mut ape = crate::apev2::APEv2::new();
ape.filename = Some(filename);
ape.tags = tags.clone();
ape.save_async().await
} else {
Ok(())
}
}
#[cfg(feature = "async")]
pub async fn clear_async(&mut self) -> Result<()> {
if let Some(filename) = &self.filename {
crate::apev2::clear_async(filename).await?;
}
self.tags = None;
Ok(())
}
}
impl Default for Musepack {
fn default() -> Self {
Self::new()
}
}
impl FileType for Musepack {
type Tags = APEv2Tags;
type Info = MusepackStreamInfo;
fn format_id() -> &'static str {
"Musepack"
}
fn load<P: AsRef<Path>>(path: P) -> Result<Self> {
debug_event!("parsing Musepack file");
let mut file = std::fs::File::open(&path)?;
let mut musepack = Musepack::new();
musepack.filename = Some(path.as_ref().to_string_lossy().to_string());
musepack.parse_file(&mut file)?;
Ok(musepack)
}
fn load_from_reader(reader: &mut dyn crate::ReadSeek) -> Result<Self> {
let mut instance = Self::new();
let mut reader = reader;
instance.parse_file(&mut reader)?;
reader.seek(std::io::SeekFrom::Start(0))?;
if let Ok(ape) = <crate::apev2::APEv2 as FileType>::load_from_reader(&mut reader) {
instance.tags = Some(ape.tags);
}
Ok(instance)
}
fn save(&mut self) -> Result<()> {
let filename = self
.filename
.as_ref()
.ok_or(AudexError::InvalidData("No filename set".to_string()))?;
if let Some(ref tags) = self.tags {
let mut apev2 = crate::apev2::APEv2::new();
apev2.filename = Some(filename.clone());
for (key, value) in tags.items() {
let _ = apev2.tags.set(&key, value.clone());
}
apev2.save()?;
}
Ok(())
}
fn clear(&mut self) -> Result<()> {
if let Some(ref filename) = self.filename {
crate::apev2::clear(filename)?;
}
self.tags = None;
Ok(())
}
fn save_to_writer(&mut self, writer: &mut dyn crate::ReadWriteSeek) -> Result<()> {
if let Some(ref tags) = self.tags {
let mut apev2 = crate::apev2::APEv2::new();
for (key, value) in tags.items() {
let _ = apev2.tags.set(&key, value.clone());
}
apev2.save_to_writer(writer)?;
}
Ok(())
}
fn clear_writer(&mut self, writer: &mut dyn crate::ReadWriteSeek) -> Result<()> {
let mut apev2 = crate::apev2::APEv2::new();
apev2.clear_writer(writer)?;
self.tags = None;
Ok(())
}
fn save_to_path(&mut self, path: &Path) -> Result<()> {
if let Some(ref tags) = self.tags {
let mut apev2 = crate::apev2::APEv2::new();
apev2.filename = Some(path.to_string_lossy().to_string());
for (key, value) in tags.items() {
let _ = apev2.tags.set(&key, value.clone());
}
apev2.save()?;
}
Ok(())
}
fn add_tags(&mut self) -> Result<()> {
if self.tags.is_some() {
return Err(AudexError::InvalidOperation(
"APE tags already exist".to_string(),
));
}
self.tags = Some(APEv2Tags::new());
Ok(())
}
fn get(&self, key: &str) -> Option<Vec<String>> {
self.tags.as_ref()?.get(key)?.as_text_list().ok()
}
fn tags(&self) -> Option<&Self::Tags> {
self.tags.as_ref()
}
fn tags_mut(&mut self) -> Option<&mut Self::Tags> {
self.tags.as_mut()
}
fn info(&self) -> &Self::Info {
&self.info
}
fn score(filename: &str, header: &[u8]) -> i32 {
let mut score = 0;
if header.len() >= 4 {
if &header[0..3] == b"MP+" {
score += 2;
}
if &header[0..4] == b"MPCK" {
score += 2;
}
}
let lower_filename = filename.to_lowercase();
if lower_filename.ends_with(".mpc") {
score += 1;
}
score
}
fn mime_types() -> &'static [&'static str] {
&["audio/x-musepack", "audio/x-mpc"]
}
}
pub fn clear<P: AsRef<Path>>(path: P) -> Result<()> {
let mut musepack = Musepack::load(path)?;
musepack.clear()
}