#[allow(clippy::wildcard_imports)] use crate::components::tags::*; use crate::{Album, AnyTag, Picture, Result, TagType}; use std::borrow::Cow; use std::fs::{File, OpenOptions}; use lofty_attr::{i32_accessor, str_accessor, u16_accessor, u32_accessor}; /// Combination of [`AudioTagEdit`], [`AudioTagWrite`], and [`ToAnyTag`] pub trait AudioTag: AudioTagEdit + AudioTagWrite + ToAnyTag {} /// Implementors of this trait are able to read and write audio metadata. /// /// Constructor methods e.g. `from_file` should be implemented separately. pub trait AudioTagEdit { str_accessor!(title); str_accessor!(artist); /// Splits the artist string into a `Vec` fn artists(&self, delimiter: &str) -> Option> { self.artist().map(|a| a.split(delimiter).collect()) } i32_accessor!(year); /// Returns the date fn date(&self) -> Option { self.year().map(|y| y.to_string()) } /// Sets the date fn set_date(&mut self, _date: &str) {} /// Removes the date fn remove_date(&mut self) {} str_accessor!(copyright); str_accessor!(genre); str_accessor!(lyrics); u16_accessor!(bpm); /// Returns the track's [`Album`] fn album(&self) -> Album<'_> { Album { title: self.album_title(), artist: self.album_artist(), covers: self.album_covers(), } } str_accessor!(album_title); str_accessor!(album_artist); /// Splits the artist string into a `Vec` fn album_artists(&self, delimiter: &str) -> Option> { self.album_artist().map(|a| a.split(delimiter).collect()) } /// Returns the front and back album covers fn album_covers(&self) -> (Option, Option) { (self.front_cover(), self.back_cover()) } /// Removes both album covers fn remove_album_covers(&mut self) { self.remove_front_cover(); self.remove_back_cover(); } /// Returns the front cover fn front_cover(&self) -> Option { None } /// Sets the front cover fn set_front_cover(&mut self, _cover: Picture) {} /// Removes the front cover fn remove_front_cover(&mut self) {} /// Returns the front cover fn back_cover(&self) -> Option { None } /// Sets the front cover fn set_back_cover(&mut self, _cover: Picture) {} /// Removes the front cover fn remove_back_cover(&mut self) {} /// Returns an `Iterator` over all pictures stored in the track fn pictures(&self) -> Option> { None } /// Returns the track number and total tracks fn track(&self) -> (Option, Option) { (self.track_number(), self.total_tracks()) } u32_accessor!(track_number); u32_accessor!(total_tracks); /// Returns the disc number and total discs fn disc(&self) -> (Option, Option) { (self.disc_number(), self.total_discs()) } /// Removes the disc number and total discs fn remove_disc(&mut self) { self.remove_disc_number(); self.remove_total_discs(); } u32_accessor!(disc_number); u32_accessor!(total_discs); } /// Functions for writing to a file pub trait AudioTagWrite { /// Write tag to a [`File`][std::fs::File] /// /// # Errors /// /// Will return `Err` if unable to write to the `File` fn write_to(&self, file: &mut File) -> Result<()>; /// Write tag to a path /// /// # Errors /// /// Will return `Err` if `path` doesn't exist fn write_to_path(&self, path: &str) -> Result<()> { self.write_to(&mut OpenOptions::new().read(true).write(true).open(path)?)?; Ok(()) } } /// Conversions between tag types pub trait ToAnyTag: ToAny { /// Converts the tag to [`AnyTag`] fn to_anytag(&self) -> AnyTag<'_>; /// Convert the tag type, which can be lossy. fn to_dyn_tag(&self, tag_type: TagType) -> Box { // TODO: write a macro or something that implement this method for every tag type so that if the // TODO: target type is the same, just return self match tag_type { #[cfg(feature = "format-ape")] TagType::Ape => Box::new(ApeTag::from(self.to_anytag())), #[cfg(feature = "format-id3")] TagType::Id3v2(_) => Box::new(Id3v2Tag::from(self.to_anytag())), #[cfg(feature = "format-mp4")] TagType::Mp4 => Box::new(Mp4Tag::from(self.to_anytag())), #[cfg(any( feature = "format-vorbis", feature = "format-flac", feature = "format-opus" ))] TagType::Ogg(_) => Box::new(OggTag::from(self.to_anytag())), #[cfg(feature = "format-riff")] TagType::RiffInfo => Box::new(RiffTag::from(self.to_anytag())), #[cfg(feature = "format-aiff")] TagType::AiffText => Box::new(AiffTag::from(self.to_anytag())), } } } /// Tag conversion to `Any` pub trait ToAny { /// Convert tag to `Any` fn to_any(&self) -> &dyn std::any::Any; /// Mutably convert tag to `Any` #[allow(clippy::wrong_self_convention)] fn to_any_mut(&mut self) -> &mut dyn std::any::Any; }