lofty-rs/src/traits.rs
Serial 3e2d9abcae Add lyricist and composer methods
Signed-off-by: Serial <69764315+Serial-ATA@users.noreply.github.com>
2021-07-09 12:11:16 -04:00

174 lines
4.7 KiB
Rust

#[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<Vec<&str>> {
self.artist().map(|a| a.split(delimiter).collect())
}
i32_accessor!(year);
/// Returns the date
fn date(&self) -> Option<String> {
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);
str_accessor!(lyricist);
str_accessor!(composer);
/// 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<Vec<&str>> {
self.album_artist().map(|a| a.split(delimiter).collect())
}
/// Returns the front and back album covers
fn album_covers(&self) -> (Option<Picture>, Option<Picture>) {
(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<Picture> {
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<Picture> {
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<Cow<'static, [Picture]>> {
None
}
/// Returns the track number and total tracks
fn track(&self) -> (Option<u32>, Option<u32>) {
(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<u32>, Option<u32>) {
(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<dyn AudioTag> {
// 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;
}