2020-10-25 14:58:50 +00:00
//! This crate makes it easier to parse tags/metadata in audio files of different file types.
//!
2020-10-26 02:14:28 +00:00
//! This crate aims to provide a unified trait for parsers and writers of different audio file formats. This means that you can parse tags in mp3 and m4a files with a single function: `Tag::default().read_from_path()` and get fields by directly calling `.album()`, `.artist()` on its result. Without this crate, you would otherwise need to learn different APIs in **id3**, **mp4ameta** crates in order to parse metadata in different file foramts.
2020-10-25 14:58:50 +00:00
//!
//! ## Example
//!
//! ```ignore
2020-10-26 02:14:28 +00:00
//! use audiotags::Tag;
2020-10-25 14:58:50 +00:00
//!
//! fn main() {
//! const MP3: &'static str = "a.mp3";
2020-10-26 02:14:28 +00:00
//! let mut tags = Tag::default().read_from_path(MP3).unwrap();
2020-10-25 21:39:38 +00:00
//! // without this crate you would call id3::Tag::read_from_path()
2020-10-25 14:58:50 +00:00
//! println!("Title: {:?}", tags.title());
//! println!("Artist: {:?}", tags.artist());
//! tags.set_album_artist("CINDERELLA PROJECT");
//! let album = tags.album().unwrap();
//! println!("Album title and artist: {:?}", (album.title, album.artist));
//! println!("Track: {:?}", tags.track());
//! tags.write_to_path(MP3).unwrap();
//! // Title: Some("お願い!シンデレラ")
//! // Artist: Some("高垣楓、城ヶ崎美嘉、小日向美穂、十時愛梨、川島瑞樹、日野茜、輿水幸子、佐久間まゆ、白坂小梅")
//! // Album title and artist: ("THE IDOLM@STER CINDERELLA GIRLS ANIMATION PROJECT 01 Star!!", Some("CINDERELLA PROJECT"))
//! // Track: (Some(2), Some(4))
//!
//! const M4A: &'static str = "b.m4a";
2020-10-26 02:14:28 +00:00
//! let mut tags = Tag::default().read_from_path(M4A).unwrap();
2020-10-25 21:39:38 +00:00
//! // without this crate you would call mp4ameta::Tag::read_from_path()
2020-10-25 14:58:50 +00:00
//! println!("Title: {:?}", tags.title());
//! println!("Artist: {:?}", tags.artist());
//! let album = tags.album().unwrap();
//! println!("Album title and artist: {:?}", (album.title, album.artist));
//! tags.set_total_tracks(4);
//! println!("Track: {:?}", tags.track());
//! tags.write_to_path(M4A).unwrap();
//! // Title: Some("ふわふわ時間")
//! // Artist: Some("桜高軽音部 [平沢唯・秋山澪・田井中律・琴吹紬(CV:豊崎愛生、日笠陽子、佐藤聡美、寿美菜子)]")
//! // Album title and artist: ("ふわふわ時間", Some("桜高軽音部 [平沢唯・秋山澪・田井中律・琴吹紬(CV:豊崎愛生、日笠陽子、佐藤聡美、寿美菜子)]"))
//! // Track: (Some(1), Some(4))
//! }
//! ```
2020-10-26 02:14:28 +00:00
mod id3_tag ;
2020-10-26 20:43:11 +00:00
pub use id3_tag ::Id3v2Tag ;
2020-10-26 02:14:28 +00:00
mod flac_tag ;
mod mp4_tag ;
2020-10-26 20:43:11 +00:00
pub use flac_tag ::FlacTag ;
pub use mp4_tag ::Mp4Tag ;
2020-10-26 02:14:28 +00:00
2020-10-26 23:16:04 +00:00
pub mod utils ;
2020-10-26 23:19:27 +00:00
pub use utils ::{ Error , Result } ;
2020-10-26 23:16:04 +00:00
2020-10-25 21:39:38 +00:00
use std ::convert ::From ;
2020-10-25 14:58:50 +00:00
use std ::fs ::File ;
use std ::path ::Path ;
2020-10-26 15:37:10 +00:00
use beef ::lean ::Cow ;
use std ::convert ::{ TryFrom , TryInto } ;
2020-10-26 20:43:11 +00:00
#[ macro_export ]
macro_rules ! convert_tag {
( $tag :expr , $target :ty ) = > { {
let target_tag : $target = $tag . into_anytag ( ) . into ( ) ;
target_tag
} } ;
}
2020-10-25 14:58:50 +00:00
type BoxedError = Box < dyn std ::error ::Error > ;
2020-10-26 02:14:28 +00:00
#[ derive(Clone, Copy, Debug) ]
pub enum TagType {
2020-10-26 20:43:11 +00:00
// /// Guess the tag type based on the file extension
// Guess,
2020-10-26 02:14:28 +00:00
/// ## Common file extensions
///
/// `.mp3`
///
/// ## References
///
/// - https://www.wikiwand.com/en/ID3
Id3v2 ,
Flac ,
/// ## Common file extensions
///
/// `.mp4, .m4a, .m4p, .m4b, .m4r and .m4v`
///
/// ## References
///
/// - https://www.wikiwand.com/en/MPEG-4_Part_14
Mp4 ,
}
2020-10-26 15:37:10 +00:00
#[ rustfmt::skip ]
2020-10-26 02:14:28 +00:00
impl TagType {
2020-10-26 23:19:27 +00:00
fn try_from_ext ( ext : & str ) -> crate ::Result < Self > {
2020-10-26 02:14:28 +00:00
match ext {
2020-10-26 15:37:10 +00:00
" mp3 " = > Ok ( Self ::Id3v2 ) ,
2020-10-26 02:14:28 +00:00
" m4a " | " m4b " | " m4p " | " m4v " | " isom " | " mp4 " = > Ok ( Self ::Mp4 ) ,
2020-10-26 15:37:10 +00:00
" flac " = > Ok ( Self ::Flac ) ,
2020-10-26 23:19:27 +00:00
p @ _ = > Err ( crate ::Error ::UnsupportedFormat ( p . to_owned ( ) ) ) ,
2020-10-26 02:14:28 +00:00
}
}
}
#[ derive(Default) ]
pub struct Tag {
tag_type : Option < TagType > ,
}
2020-10-26 20:43:11 +00:00
impl Tag {
pub fn with_tag_type ( tag_type : TagType ) -> Self {
Self {
tag_type : Some ( tag_type ) ,
}
}
2020-10-26 02:14:28 +00:00
2020-10-26 23:19:27 +00:00
pub fn read_from_path ( & self , path : impl AsRef < Path > ) -> crate ::Result < Box < dyn AudioTagIo > > {
2020-10-26 20:43:11 +00:00
match self . tag_type . unwrap_or ( TagType ::try_from_ext (
path . as_ref ( )
. extension ( )
. unwrap ( )
. to_string_lossy ( )
. to_string ( )
. to_lowercase ( )
. as_str ( ) ,
) ? ) {
TagType ::Id3v2 = > Ok ( Box ::new ( Id3v2Tag ::read_from_path ( path ) ? ) ) ,
TagType ::Mp4 = > Ok ( Box ::new ( Mp4Tag ::read_from_path ( path ) ? ) ) ,
TagType ::Flac = > Ok ( Box ::new ( FlacTag ::read_from_path ( path ) ? ) ) ,
}
}
}
2020-10-26 02:14:28 +00:00
2020-10-26 15:37:10 +00:00
// // ? deprecate?
// /// Guesses the audio metadata handler from the file extension, and returns the `Box`ed IO handler.
2020-10-26 20:43:11 +00:00
// pub fn read_from_path(path: impl AsRef<Path>) -> Result<Box<dyn AudioTagIo>, BoxedError> {
2020-10-26 15:37:10 +00:00
// Tag::default().read_from_path(path)
// }
2020-10-25 14:58:50 +00:00
2020-10-26 01:42:12 +00:00
#[ derive(Debug, Clone, Copy, Eq, PartialEq) ]
2020-10-25 21:39:38 +00:00
pub enum MimeType {
2020-10-25 16:29:14 +00:00
Png ,
Jpeg ,
Tiff ,
Bmp ,
Gif ,
}
2020-10-26 15:37:10 +00:00
impl TryFrom < & str > for MimeType {
2020-10-26 23:19:27 +00:00
type Error = crate ::Error ;
fn try_from ( inp : & str ) -> crate ::Result < Self > {
2020-10-26 15:37:10 +00:00
Ok ( match inp {
" image/jpeg " = > MimeType ::Jpeg ,
" image/png " = > MimeType ::Png ,
" image/tiff " = > MimeType ::Tiff ,
" image/bmp " = > MimeType ::Bmp ,
" image/gif " = > MimeType ::Gif ,
2020-10-26 23:19:27 +00:00
_ = > return Err ( crate ::Error ::UnsupportedMimeType ( inp . to_owned ( ) ) ) ,
2020-10-26 15:37:10 +00:00
} )
}
}
2020-10-26 20:43:11 +00:00
impl From < MimeType > for & 'static str {
2020-10-25 21:39:38 +00:00
fn from ( mt : MimeType ) -> Self {
match mt {
2020-10-26 20:43:11 +00:00
MimeType ::Jpeg = > " image/jpeg " ,
MimeType ::Png = > " image/png " ,
MimeType ::Tiff = > " image/tiff " ,
MimeType ::Bmp = > " image/bmp " ,
MimeType ::Gif = > " image/gif " ,
2020-10-25 21:39:38 +00:00
}
}
}
2020-10-26 20:43:11 +00:00
impl From < MimeType > for String {
fn from ( mt : MimeType ) -> Self {
< MimeType as Into < & 'static str > > ::into ( mt ) . to_owned ( )
}
}
2020-10-26 01:42:12 +00:00
#[ derive(Debug, Clone, Eq, PartialEq) ]
2020-10-26 15:37:10 +00:00
pub struct Picture < ' a > {
pub data : Cow < ' a , [ u8 ] > ,
2020-10-25 21:39:38 +00:00
pub mime_type : MimeType ,
2020-10-25 14:58:50 +00:00
}
2020-10-26 15:37:10 +00:00
impl < ' a > Picture < ' a > {
2020-10-26 20:43:11 +00:00
pub fn new ( data : & ' a [ u8 ] , mime_type : MimeType ) -> Self {
Self {
data : Cow ::borrowed ( data ) ,
2020-10-26 15:37:10 +00:00
mime_type ,
2020-10-26 20:43:11 +00:00
}
2020-10-25 14:58:50 +00:00
}
}
2020-10-26 15:37:10 +00:00
/// A struct for representing an album for convinience.
2020-10-25 14:58:50 +00:00
#[ derive(Debug) ]
2020-10-26 15:37:10 +00:00
pub struct Album < ' a > {
pub title : Cow < ' a , str > ,
pub artist : Option < Cow < ' a , str > > ,
pub cover : Option < Picture < ' a > > ,
2020-10-25 14:58:50 +00:00
}
2020-10-26 15:37:10 +00:00
impl < ' a > Album < ' a > {
pub fn with_title ( title : impl Into < String > ) -> Self {
Self {
title : Cow ::owned ( title . into ( ) ) ,
artist : None ,
cover : None ,
2020-10-25 21:39:38 +00:00
}
}
2020-10-26 15:37:10 +00:00
pub fn and_artist ( mut self , artist : impl Into < String > ) -> Self {
self . artist = Some ( Cow ::owned ( artist . into ( ) ) ) ;
self
2020-10-26 01:18:49 +00:00
}
2020-10-26 15:37:10 +00:00
pub fn and_cover ( mut self , cover : Picture < ' a > ) -> Self {
self . cover = Some ( cover ) ;
self
}
pub fn with_all (
title : impl Into < String > ,
artist : impl Into < String > ,
cover : Picture < ' a > ,
) -> Self {
Self {
title : Cow ::owned ( title . into ( ) ) ,
artist : Some ( Cow ::owned ( artist . into ( ) ) ) ,
cover : Some ( cover ) ,
}
}
}
2020-10-26 01:18:49 +00:00
2020-10-26 15:37:10 +00:00
const SEP_ARTIST : & 'static str = " ; " ;
2020-10-26 01:18:49 +00:00
2020-10-26 15:37:10 +00:00
#[ derive(Default) ]
pub struct AnyTag < ' a > {
pub title : Option < Cow < ' a , str > > ,
// pub artists: Option<Vec<Cow<'a, str>>>, // ? iterator
pub year : Option < i32 > ,
pub album_title : Option < Cow < ' a , str > > ,
// pub album_artists: Option<Vec<Cow<'a, str>>>, // ? iterator
pub album_cover : Option < Picture < ' a > > ,
pub track_number : Option < u16 > ,
pub total_tracks : Option < u16 > ,
pub disc_number : Option < u16 > ,
pub total_discs : Option < u16 > ,
}
2020-10-26 01:18:49 +00:00
2020-10-26 15:37:10 +00:00
impl < ' a > AnyTag < ' a > {
pub fn title ( & self ) -> Option < & str > {
self . title . as_deref ( )
2020-10-25 21:39:38 +00:00
}
2020-10-26 15:37:10 +00:00
// pub fn artists(&self) -> Option<&[String]> {
// self.artists.as_deref()
// }
pub fn year ( & self ) -> Option < i32 > {
self . year
2020-10-25 21:39:38 +00:00
}
2020-10-26 15:37:10 +00:00
pub fn album_title ( & self ) -> Option < & str > {
self . album_title . as_deref ( )
2020-10-25 21:39:38 +00:00
}
2020-10-26 15:37:10 +00:00
// pub fn album_artists(&self) -> Option<&[String]> {
// self.album_artists.as_deref()
// }
pub fn track_number ( & self ) -> Option < u16 > {
self . track_number
}
pub fn total_tracks ( & self ) -> Option < u16 > {
self . total_tracks
2020-10-25 21:39:38 +00:00
}
2020-10-26 15:37:10 +00:00
pub fn disc_number ( & self ) -> Option < u16 > {
self . track_number
2020-10-25 21:39:38 +00:00
}
2020-10-26 15:37:10 +00:00
pub fn total_discs ( & self ) -> Option < u16 > {
self . total_tracks
2020-10-25 21:39:38 +00:00
}
2020-10-26 15:37:10 +00:00
}
2020-10-26 01:18:49 +00:00
2020-10-26 15:37:10 +00:00
pub trait TagIo {
2020-10-26 23:19:27 +00:00
fn read_from_path ( path : & str ) -> crate ::Result < AnyTag > ;
fn write_to_path ( path : & str ) -> crate ::Result < ( ) > ;
2020-10-26 15:37:10 +00:00
}
2020-10-26 01:18:49 +00:00
2020-10-26 15:37:10 +00:00
// impl<'a> AnyTag<'a> {
2020-10-26 20:43:11 +00:00
// fn read_from_path(path: &str, tag_type: TagType) -> StdResult<Self, BoxedError> {
// match tag_type {
// TagType::Id3v2 => Ok(Id3v2Tag::read_from_path(path)?.into()),
// _ => Err(Box::new(Error::UnsupportedFormat(".".to_owned()))),
// }
// }
2020-10-26 15:37:10 +00:00
// }
2020-10-26 01:18:49 +00:00
2020-10-26 23:16:04 +00:00
pub struct SuperTag < T >
where
T : AudioTagIo ,
{
inner : T ,
}
2020-10-26 15:37:10 +00:00
2020-10-26 20:43:11 +00:00
/// Implementors of this trait are able to read and write audio metadata.
///
/// Constructor methods e.g. `from_file` should be implemented separately.
pub trait AudioTagIo {
fn title ( & self ) -> Option < & str > ;
fn set_title ( & mut self , title : & str ) ;
fn remove_title ( & mut self ) ;
fn artist ( & self ) -> Option < & str > ;
fn set_artist ( & mut self , artist : & str ) ;
fn remove_artist ( & mut self ) ;
fn year ( & self ) -> Option < i32 > ;
fn set_year ( & mut self , year : i32 ) ;
fn remove_year ( & mut self ) ;
fn album ( & self ) -> Option < Album < '_ > > {
self . album_title ( ) . map ( | title | Album {
title : Cow ::borrowed ( title ) ,
artist : self . album_artist ( ) . map ( Cow ::borrowed ) ,
cover : self . album_cover ( ) ,
} )
}
fn set_album ( & mut self , album : Album ) {
self . set_album_title ( & album . title ) ;
if let Some ( artist ) = album . artist {
self . set_album_artist ( & artist )
} else {
self . remove_album_artist ( )
}
if let Some ( pic ) = album . cover {
self . set_album_cover ( pic )
} else {
self . remove_album_cover ( )
}
}
fn remove_album ( & mut self ) {
self . remove_album_title ( ) ;
self . remove_album_artist ( ) ;
self . remove_album_cover ( ) ;
}
2020-10-26 15:37:10 +00:00
2020-10-26 20:43:11 +00:00
fn album_title ( & self ) -> Option < & str > ;
fn set_album_title ( & mut self , v : & str ) ;
fn remove_album_title ( & mut self ) ;
2020-10-26 15:37:10 +00:00
2020-10-26 20:43:11 +00:00
fn album_artist ( & self ) -> Option < & str > ;
fn set_album_artist ( & mut self , v : & str ) ;
fn remove_album_artist ( & mut self ) ;
2020-10-26 15:37:10 +00:00
2020-10-26 20:43:11 +00:00
fn album_cover ( & self ) -> Option < Picture > ;
fn set_album_cover ( & mut self , cover : Picture ) ;
fn remove_album_cover ( & mut self ) ;
2020-10-26 15:37:10 +00:00
2020-10-26 20:43:11 +00:00
fn track ( & self ) -> ( Option < u16 > , Option < u16 > ) {
( self . track_number ( ) , self . total_tracks ( ) )
}
fn set_track ( & mut self , track : ( u16 , u16 ) ) {
self . set_track_number ( track . 0 ) ;
self . set_total_tracks ( track . 1 ) ;
}
fn remove_track ( & mut self ) {
self . remove_track_number ( ) ;
self . remove_total_tracks ( ) ;
}
2020-10-26 15:37:10 +00:00
2020-10-26 20:43:11 +00:00
fn track_number ( & self ) -> Option < u16 > ;
fn set_track_number ( & mut self , track_number : u16 ) ;
fn remove_track_number ( & mut self ) ;
2020-10-26 15:37:10 +00:00
2020-10-26 20:43:11 +00:00
fn total_tracks ( & self ) -> Option < u16 > ;
fn set_total_tracks ( & mut self , total_track : u16 ) ;
fn remove_total_tracks ( & mut self ) ;
2020-10-26 15:37:10 +00:00
2020-10-26 20:43:11 +00:00
fn disc ( & self ) -> ( Option < u16 > , Option < u16 > ) {
( self . disc_number ( ) , self . total_discs ( ) )
}
fn set_disc ( & mut self , disc : ( u16 , u16 ) ) {
self . set_disc_number ( disc . 0 ) ;
self . set_total_discs ( disc . 1 ) ;
}
fn remove_disc ( & mut self ) {
self . remove_disc_number ( ) ;
self . remove_total_discs ( ) ;
}
fn disc_number ( & self ) -> Option < u16 > ;
fn set_disc_number ( & mut self , disc_number : u16 ) ;
fn remove_disc_number ( & mut self ) ;
fn total_discs ( & self ) -> Option < u16 > ;
fn set_total_discs ( & mut self , total_discs : u16 ) ;
fn remove_total_discs ( & mut self ) ;
2020-10-26 15:37:10 +00:00
2020-10-26 23:19:27 +00:00
fn write_to ( & mut self , file : & mut File ) -> crate ::Result < ( ) > ;
2020-10-26 20:43:11 +00:00
// cannot use impl AsRef<Path>
2020-10-26 23:19:27 +00:00
fn write_to_path ( & mut self , path : & str ) -> crate ::Result < ( ) > ;
2020-10-26 15:37:10 +00:00
2020-10-26 20:43:11 +00:00
fn into_anytag ( & self ) -> AnyTag < '_ > ;
}
2020-10-26 23:16:04 +00:00
pub trait IntoTag < ' a > : AudioTagIo {
fn into_tag < T : From < AnyTag < ' a > > > ( & self ) -> T ;
}
2020-10-26 20:43:11 +00:00
// pub trait IntoTag: AudioTagIo {
2020-10-26 15:37:10 +00:00
2020-10-26 20:43:11 +00:00
// fn into_tag<'a, T>(&'a self) -> T
// where T: From<AnyTag<'a> {
// self.into_anytag().into()
// }
2020-10-26 15:37:10 +00:00
// }
// impl AnyTag {
// pub fn artists_as_string(&self, sep: &str) -> Option<String> {
// self.artists().map(|artists| artists.join(sep))
// }
// pub fn album_artists_as_string(&self, sep: &str) -> Option<String> {
// self.album_artists().map(|artists| artists.join(sep))
// }
// }
// #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
// pub enum PictureType {
// Other,
// Icon,
// OtherIcon,
// CoverFront,
// CoverBack,
// Leaflet,
// Media,
// LeadArtist,
// Artist,
// Conductor,
// Band,
// Composer,
// Lyricist,
// RecordingLocation,
// DuringRecording,
// DuringPerformance,
// ScreenCapture,
// BrightFish,
// Illustration,
// BandLogo,
// PublisherLogo,
// Undefined(u8),
// }