use crate::{AudexError, FileType, Result, StreamInfo, 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; 15] = [
6000, 8000, 9600, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000, 64000, 88200, 96000,
192000,
];
#[derive(Debug)]
pub struct WavPackHeader {
pub block_size: u32,
pub version: u16,
pub track_no: u8,
pub index_no: u8,
pub total_samples: u32,
pub block_index: u32,
pub block_samples: u32,
pub flags: u32,
pub crc: u32,
}
impl WavPackHeader {
pub fn from_reader<R: Read>(reader: &mut R) -> Result<Self> {
let mut header = [0u8; 32];
reader
.read_exact(&mut header)
.map_err(|_| AudexError::WavPackHeaderError("not enough data".to_string()))?;
if &header[0..4] != b"wvpk" {
return Err(AudexError::WavPackHeaderError(format!(
"not a WavPack header: {:?}",
&header[0..4]
)));
}
let block_size = u32::from_le_bytes([header[4], header[5], header[6], header[7]]);
let version = u16::from_le_bytes([header[8], header[9]]);
let track_no = header[10];
let index_no = header[11];
let total_samples = u32::from_le_bytes([header[12], header[13], header[14], header[15]]);
let block_index = u32::from_le_bytes([header[16], header[17], header[18], header[19]]);
let block_samples = u32::from_le_bytes([header[20], header[21], header[22], header[23]]);
let flags = u32::from_le_bytes([header[24], header[25], header[26], header[27]]);
let crc = u32::from_le_bytes([header[28], header[29], header[30], header[31]]);
Ok(WavPackHeader {
block_size,
version,
track_no,
index_no,
total_samples,
block_index,
block_samples,
flags,
crc,
})
}
}
#[derive(Debug, Default)]
pub struct WavPackStreamInfo {
pub length: Option<Duration>,
pub bitrate: Option<u32>,
pub channels: u32,
pub sample_rate: u32,
pub bits_per_sample: u32,
pub version: u16,
}
impl StreamInfo for WavPackStreamInfo {
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> {
u16::try_from(self.channels).ok()
}
fn bits_per_sample(&self) -> Option<u16> {
u16::try_from(self.bits_per_sample).ok()
}
}
impl WavPackStreamInfo {
pub fn from_reader<R: Read + Seek>(reader: &mut R) -> Result<Self> {
reader.seek(SeekFrom::Start(0))?;
let mut header = WavPackHeader::from_reader(reader)?;
let mut info = WavPackStreamInfo {
version: header.version,
..Default::default()
};
info.channels = if (header.flags & 4) != 0 { 1 } else { 2 };
let rate_index = ((header.flags >> 23) & 0xF) as usize;
if rate_index >= RATES.len() {
return Err(AudexError::WavPackHeaderError(
"invalid sample rate index".to_string(),
));
}
info.sample_rate = RATES[rate_index];
info.bits_per_sample = ((header.flags & 3) + 1) * 8;
if (header.flags >> 31) & 1 != 0 {
info.sample_rate = info.sample_rate.saturating_mul(4);
info.bits_per_sample = 1;
}
let samples = if header.total_samples == 0xFFFFFFFF || header.block_index != 0 {
let mut total_samples = header.block_samples;
let initial_pos = reader.stream_position()?;
const MAX_BLOCK_SCAN: u32 = 1_000_000;
let mut blocks_scanned: u32 = 0;
loop {
blocks_scanned += 1;
if blocks_scanned > MAX_BLOCK_SCAN {
break;
}
if header.block_size < 24 {
break;
}
let skip_size = header.block_size - 24;
reader.seek(SeekFrom::Current(skip_size as i64))?;
match WavPackHeader::from_reader(reader) {
Ok(next_header) => {
total_samples = total_samples.saturating_add(next_header.block_samples);
header = next_header;
}
Err(_) => break,
}
}
reader.seek(SeekFrom::Start(initial_pos))?;
total_samples
} else {
header.total_samples
};
if info.sample_rate > 0 {
let seconds = samples as f64 / info.sample_rate as f64;
info.length = Duration::try_from_secs_f64(seconds).ok();
} else {
info.length = None;
}
Ok(info)
}
pub fn pprint(&self) -> String {
format!(
"WavPack, {:.2} seconds, {} Hz",
self.length.map(|d| d.as_secs_f64()).unwrap_or(0.0),
self.sample_rate
)
}
}
#[derive(Debug)]
pub struct WavPack {
pub info: WavPackStreamInfo,
pub tags: Option<APEv2Tags>,
pub filename: Option<String>,
}
impl WavPack {
pub fn new() -> Self {
Self {
info: WavPackStreamInfo::default(),
tags: None,
filename: None,
}
}
fn parse_file<R: Read + Seek>(&mut self, reader: &mut R) -> Result<()> {
self.info = WavPackStreamInfo::from_reader(reader)?;
if let Some(filename) = &self.filename {
match crate::apev2::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::InvalidOperation(
"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-wavpack"]
}
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 wv = WavPack::new();
wv.filename = Some(path.as_ref().to_string_lossy().to_string());
wv.info = Self::parse_info_async(&mut file).await?;
match crate::apev2::APEv2::load_async(&path).await {
Ok(ape) => wv.tags = Some(ape.tags),
Err(AudexError::APENoHeader) => wv.tags = None,
Err(e) => return Err(e),
}
Ok(wv)
}
#[cfg(feature = "async")]
async fn parse_info_async(file: &mut TokioFile) -> Result<WavPackStreamInfo> {
file.seek(SeekFrom::Start(0)).await?;
let mut header = Self::read_header_async(file).await?;
let mut info = WavPackStreamInfo {
version: header.version,
..Default::default()
};
info.channels = if (header.flags & 4) != 0 { 1 } else { 2 };
let rate_index = ((header.flags >> 23) & 0xF) as usize;
if rate_index >= RATES.len() {
return Err(AudexError::WavPackHeaderError(
"invalid sample rate index".to_string(),
));
}
info.sample_rate = RATES[rate_index];
info.bits_per_sample = ((header.flags & 3) + 1) * 8;
if (header.flags >> 31) & 1 != 0 {
info.sample_rate = info.sample_rate.saturating_mul(4);
info.bits_per_sample = 1;
}
let samples = if header.total_samples == 0xFFFFFFFF || header.block_index != 0 {
const MAX_BLOCK_SCAN: u32 = 1_000_000;
let mut blocks_scanned: u32 = 0;
let mut total_samples = header.block_samples;
let initial_pos = file.stream_position().await?;
loop {
blocks_scanned += 1;
if blocks_scanned > MAX_BLOCK_SCAN {
break;
}
if header.block_size < 24 {
break;
}
let skip_size = header.block_size - 24;
file.seek(SeekFrom::Current(skip_size as i64)).await?;
match Self::read_header_async(file).await {
Ok(next_header) => {
total_samples = total_samples.saturating_add(next_header.block_samples);
header = next_header;
}
Err(_) => break,
}
}
file.seek(SeekFrom::Start(initial_pos)).await?;
total_samples
} else {
header.total_samples
};
if info.sample_rate > 0 {
info.length = Some(Duration::from_secs_f64(
samples as f64 / info.sample_rate as f64,
));
} else {
info.length = None;
}
Ok(info)
}
#[cfg(feature = "async")]
async fn read_header_async(file: &mut TokioFile) -> Result<WavPackHeader> {
let mut buf = [0u8; 32];
file.read_exact(&mut buf)
.await
.map_err(|_| AudexError::WavPackHeaderError("not enough data".to_string()))?;
if &buf[0..4] != b"wvpk" {
return Err(AudexError::WavPackHeaderError(format!(
"not a WavPack header: {:?}",
&buf[0..4]
)));
}
Ok(WavPackHeader {
block_size: u32::from_le_bytes([buf[4], buf[5], buf[6], buf[7]]),
version: u16::from_le_bytes([buf[8], buf[9]]),
track_no: buf[10],
index_no: buf[11],
total_samples: u32::from_le_bytes([buf[12], buf[13], buf[14], buf[15]]),
block_index: u32::from_le_bytes([buf[16], buf[17], buf[18], buf[19]]),
block_samples: u32::from_le_bytes([buf[20], buf[21], buf[22], buf[23]]),
flags: u32::from_le_bytes([buf[24], buf[25], buf[26], buf[27]]),
crc: u32::from_le_bytes([buf[28], buf[29], buf[30], buf[31]]),
})
}
#[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 WavPack {
fn default() -> Self {
Self::new()
}
}
impl FileType for WavPack {
type Tags = APEv2Tags;
type Info = WavPackStreamInfo;
fn format_id() -> &'static str {
"WavPack"
}
fn load<P: AsRef<Path>>(path: P) -> Result<Self> {
debug_event!("parsing WavPack file");
let mut file = std::fs::File::open(&path)?;
let mut wavpack = WavPack::new();
wavpack.filename = Some(path.as_ref().to_string_lossy().to_string());
wavpack.parse_file(&mut file)?;
Ok(wavpack)
}
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(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(
"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 && &header[0..4] == b"wvpk" {
score += 2;
}
let lower_filename = filename.to_lowercase();
if lower_filename.ends_with(".wv") {
score += 1;
}
score
}
fn mime_types() -> &'static [&'static str] {
&["audio/x-wavpack"]
}
}
pub fn clear<P: AsRef<Path>>(path: P) -> Result<()> {
let mut wavpack = WavPack::load(path)?;
wavpack.clear()
}