use crate::{AudexError, FileType, Result, StreamInfo, apev2::APEv2Tags};
use std::io::Read;
use std::path::Path;
use std::time::Duration;
#[cfg(feature = "async")]
use crate::util::loadfile_read_async;
#[cfg(feature = "async")]
use std::io::SeekFrom;
#[cfg(feature = "async")]
use tokio::fs::File as TokioFile;
#[cfg(feature = "async")]
use tokio::io::{AsyncReadExt, AsyncSeekExt};
#[derive(Debug, Default)]
pub struct MonkeysAudioStreamInfo {
pub length: Option<Duration>,
pub bitrate: Option<u32>,
pub channels: u32,
pub sample_rate: u32,
pub bits_per_sample: u32,
pub version: f64,
}
impl StreamInfo for MonkeysAudioStreamInfo {
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 MonkeysAudioStreamInfo {
pub fn from_reader<R: Read>(reader: &mut R) -> Result<Self> {
let mut header = [0u8; 76];
reader
.read_exact(&mut header)
.map_err(|_| AudexError::InvalidData("not enough data".to_string()))?;
if &header[0..4] != b"MAC " {
return Err(AudexError::InvalidData(
"not a Monkey's Audio file".to_string(),
));
}
let version_raw = u16::from_le_bytes([header[4], header[5]]);
let version = version_raw as f64 / 1000.0;
let mut info = MonkeysAudioStreamInfo {
version,
..Default::default()
};
if version_raw >= 3980 {
let blocks_per_frame =
u32::from_le_bytes([header[56], header[57], header[58], header[59]]);
let final_frame_blocks =
u32::from_le_bytes([header[60], header[61], header[62], header[63]]);
let total_frames = u32::from_le_bytes([header[64], header[65], header[66], header[67]]);
info.bits_per_sample = u16::from_le_bytes([header[68], header[69]]) as u32;
info.channels = u16::from_le_bytes([header[70], header[71]]) as u32;
info.sample_rate = u32::from_le_bytes([header[72], header[73], header[74], header[75]]);
if info.sample_rate > 0 && total_frames > 0 {
let total_blocks =
(total_frames as u64 - 1) * blocks_per_frame as u64 + final_frame_blocks as u64;
let seconds = total_blocks as f64 / info.sample_rate as f64;
info.length = Duration::try_from_secs_f64(seconds).ok();
} else {
info.length = Some(Duration::from_secs(0));
}
} else {
let compression_level = u16::from_le_bytes([header[6], header[7]]);
info.channels = u16::from_le_bytes([header[10], header[11]]) as u32;
info.sample_rate = u32::from_le_bytes([header[12], header[13], header[14], header[15]]);
let total_frames = u32::from_le_bytes([header[24], header[25], header[26], header[27]]);
let final_frame_blocks =
u32::from_le_bytes([header[28], header[29], header[30], header[31]]);
let blocks_per_frame: u32 = if version_raw >= 3950 {
73728 * 4
} else if version_raw >= 3900 || (version_raw >= 3800 && compression_level == 4) {
73728
} else {
9216
};
info.bits_per_sample = 0;
if &header[48..55] == b"WAVEfmt" {
info.bits_per_sample = u16::from_le_bytes([header[74], header[75]]) as u32;
}
if info.sample_rate > 0 && total_frames > 0 {
let total_blocks =
(total_frames as u64 - 1) * blocks_per_frame as u64 + final_frame_blocks as u64;
let seconds = total_blocks as f64 / info.sample_rate as f64;
info.length = Duration::try_from_secs_f64(seconds).ok();
} else {
info.length = Some(Duration::from_secs(0));
}
}
Ok(info)
}
pub fn pprint(&self) -> String {
format!(
"Monkey's Audio {:.2}, {:.2} seconds, {} Hz",
self.version,
self.length.map(|d| d.as_secs_f64()).unwrap_or(0.0),
self.sample_rate
)
}
}
#[derive(Debug)]
pub struct MonkeysAudio {
pub info: MonkeysAudioStreamInfo,
pub tags: Option<APEv2Tags>,
pub filename: Option<String>,
}
impl MonkeysAudio {
pub fn new() -> Self {
Self {
info: MonkeysAudioStreamInfo::default(),
tags: None,
filename: None,
}
}
fn parse_file<R: Read>(&mut self, reader: &mut R) -> Result<()> {
self.info = MonkeysAudioStreamInfo::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/ape", "audio/x-ape"]
}
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 ma = MonkeysAudio::new();
ma.filename = Some(path.as_ref().to_string_lossy().to_string());
ma.info = Self::parse_info_async(&mut file).await?;
match crate::apev2::APEv2::load_async(&path).await {
Ok(ape) => ma.tags = Some(ape.tags),
Err(AudexError::APENoHeader) => ma.tags = None,
Err(e) => return Err(e),
}
Ok(ma)
}
#[cfg(feature = "async")]
async fn parse_info_async(file: &mut TokioFile) -> Result<MonkeysAudioStreamInfo> {
file.seek(SeekFrom::Start(0)).await?;
let mut header = [0u8; 76];
file.read_exact(&mut header).await?;
let mut cursor = std::io::Cursor::new(&header[..]);
MonkeysAudioStreamInfo::from_reader(&mut cursor)
}
#[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 MonkeysAudio {
fn default() -> Self {
Self::new()
}
}
impl FileType for MonkeysAudio {
type Tags = APEv2Tags;
type Info = MonkeysAudioStreamInfo;
fn format_id() -> &'static str {
"MonkeysAudio"
}
fn load<P: AsRef<Path>>(path: P) -> Result<Self> {
debug_event!("parsing Monkey's Audio file");
let mut file = std::fs::File::open(&path)?;
let mut monkeys_audio = MonkeysAudio::new();
monkeys_audio.filename = Some(path.as_ref().to_string_lossy().to_string());
monkeys_audio.parse_file(&mut file)?;
Ok(monkeys_audio)
}
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 && header.starts_with(b"MAC ") {
score += 1;
}
let lower_filename = filename.to_lowercase();
if lower_filename.ends_with(".ape") {
score += 11;
}
score
}
fn mime_types() -> &'static [&'static str] {
&["audio/ape", "audio/x-ape"]
}
}
pub fn clear<P: AsRef<Path>>(path: P) -> Result<()> {
let mut monkeys_audio = MonkeysAudio::load(path)?;
monkeys_audio.clear()
}
pub fn open<P: AsRef<Path>>(path: P) -> Result<MonkeysAudio> {
MonkeysAudio::load(path)
}