use std::path::PathBuf; use id3::TagLike; use crate::types::{AudioFileInfo, ReplayGainData, ReplayGainRawData, Tags}; use super::{AudioContainer, AudioContainerFormat, AudioFormatError, BoxedError}; pub struct ID3AudioFormat { container_type: AudioContainer, id3_tags: id3::Tag, path: Box<PathBuf>, } impl AudioContainerFormat for ID3AudioFormat { fn get_tags(&self, allow_missing: bool) -> Result<Tags, BoxedError> { let title = self.id3_tags.title(); let artist = self.id3_tags.artist(); if !allow_missing { if title.is_none() { return Err(Box::new(AudioFormatError::MissingTitle)); } if artist.is_none() { return Err(Box::new(AudioFormatError::MissingArtist)); } } Ok(Tags { title: String::from(title.unwrap()), artist: String::from(artist.unwrap()), }) } fn get_replaygain_data(&self) -> Option<ReplayGainData> { let frames = self.id3_tags.frames(); let mut contains_replaygain_tags = false; let mut replaygain_data = ReplayGainData { track_gain: "".to_string(), track_peak: "".to_string(), }; for frame in frames { if frame.id() == "TXXX" { if let Some(extended_text) = frame.content().extended_text() { match extended_text.description.as_str() { "REPLAYGAIN_TRACK_GAIN" => { contains_replaygain_tags = true; replaygain_data.track_gain = extended_text.value.clone() } "REPLAYGAIN_TRACK_PEAK" => { contains_replaygain_tags = true; replaygain_data.track_peak = extended_text.value.clone() } _ => {} } } } } if !contains_replaygain_tags { None } else { Some(replaygain_data) } } fn supports_replaygain(&self) -> bool { true } fn set_title(&mut self, title: String) -> Result<(), BoxedError> { self.id3_tags.set_title(title); Ok(()) } fn set_artist(&mut self, artist: String) -> Result<(), BoxedError> { self.id3_tags.set_artist(artist); Ok(()) } fn set_replaygain_data(&mut self, data: ReplayGainRawData) -> Result<(), BoxedError> { let frames = self.id3_tags.remove("TXXX"); for frame in frames { if let Some(extended_text) = frame.content().extended_text() { if extended_text.description.starts_with("REPLAYGAIN") { continue; } } self.id3_tags.add_frame(frame); } self.id3_tags.add_frame(id3::Frame::with_content( "TXXX", id3::Content::ExtendedText(id3::frame::ExtendedText { description: "REPLAYGAIN_TRACK_GAIN".to_string(), value: format!("{:.2} dB", data.track_gain), }), )); self.id3_tags.add_frame(id3::Frame::with_content( "TXXX", id3::Content::ExtendedText(id3::frame::ExtendedText { description: "REPLAYGAIN_TRACK_PEAK".to_string(), value: format!("{:.6}", data.track_peak), }), )); Ok(()) } fn save_changes(&mut self) -> Result<(), BoxedError> { self.id3_tags .write_to_path(self.path.as_path(), id3::Version::Id3v24)?; Ok(()) } fn get_audio_file_info(&self, allow_missing_tags: bool) -> Result<AudioFileInfo, BoxedError> { return Ok(AudioFileInfo { tags: self.get_tags(allow_missing_tags)?, replaygain: self.get_replaygain_data(), supports_replaygain: self.supports_replaygain(), container: self.container_type, }); } } pub fn new_id3_format_handler( path: &PathBuf, container_type: AudioContainer, ) -> Result<ID3AudioFormat, BoxedError> { let id3_tags = match container_type { // Only works on ID3 containing WAV files, but doesn't seem very widespread AudioContainer::WAV => id3::Tag::read_from_wav_path(path)?, AudioContainer::AIFF => id3::Tag::read_from_aiff_path(path)?, _ => id3::Tag::read_from_path(path)?, }; Ok(ID3AudioFormat { container_type, id3_tags, path: Box::new(path.clone()), }) }