diff --git a/Cargo.toml b/Cargo.toml index d13cf7e3..6a642984 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "audiotags" -version = "0.0.1" +version = "0.0.2" authors = ["Tianyi "] edition = "2018" description = "Unified IO for different types of audio metadata" diff --git a/README.md b/README.md index 196a40c7..98b93bc2 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,23 @@ # audiotags +[![Crate](https://img.shields.io/crates/v/audiotags.svg)](https://crates.io/crates/audiotags) +[![Crate](https://img.shields.io/crates/d/audiotags.svg)](https://crates.io/crates/audiotags) +[![Crate](https://img.shields.io/crates/l/audiotags.svg)](https://crates.io/crates/audiotags) +[![Documentation](https://docs.rs/audiotags/badge.svg)](https://docs.rs/audiotags/) + This crate makes it easier to parse tags/metadata in audio files of different file types. 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: `audiotags::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. ## Example +(Due to copyright restrictions I cannot upload the actual audio files here) + ```rust use audiotags; fn main() { - const MP3: &'static str = "a.mp3"; + const MP3: &'static str = "お願い!シンデレラ.mp3"; let mut tags = audiotags::from_path(MP3).unwrap(); // without this crate you would call id3::Tag::from_path() println!("Title: {:?}", tags.title()); @@ -25,7 +32,7 @@ fn main() { // 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"; + const M4A: &'static str = "ふわふわ時間.m4a"; let mut tags = audiotags::from_path(M4A).unwrap(); // without this crate you would call mp4ameta::Tag::from_path() println!("Title: {:?}", tags.title()); @@ -40,4 +47,34 @@ fn main() { // Album title and artist: ("ふわふわ時間", Some("桜高軽音部 [平沢唯・秋山澪・田井中律・琴吹紬(CV:豊崎愛生、日笠陽子、佐藤聡美、寿美菜子)]")) // Track: (Some(1), Some(4)) } +``` + +## Supported Methods + +```rust +pub trait AudioTagsIo { + fn title(&self) -> Option<&str>; + fn set_title(&mut self, title: &str); + fn artist(&self) -> Option<&str>; + fn set_artist(&mut self, artist: &str); + fn year(&self) -> Option; + fn set_year(&mut self, year: i32); + fn album(&self) -> Option; + fn album_title(&self) -> Option<&str>; + fn album_artist(&self) -> Option<&str>; + fn album_cover(&self) -> Option; + fn set_album(&mut self, album: Album); + fn set_album_title(&mut self, v: &str); + fn set_album_artist(&mut self, v: &str); + fn set_album_cover(&mut self, cover: Picture); + fn track(&self) -> (Option, Option); + fn set_track_number(&mut self, track_number: u16); + fn set_total_tracks(&mut self, total_track: u16); + fn disc(&self) -> (Option, Option); + fn set_disc_number(&mut self, disc_number: u16); + fn set_total_discs(&mut self, total_discs: u16); + fn write_to(&self, file: &File) -> Result<(), BoxedError>; + // cannot use impl AsRef + fn write_to_path(&self, path: &str) -> Result<(), BoxedError>; +} ``` \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 22055f1e..50c15da9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -72,26 +72,32 @@ pub fn from_path(path: impl AsRef) -> Result, BoxedEr } } +#[derive(Debug, Clone, Copy)] +pub enum PictureType { + Png, + Jpeg, + Tiff, + Bmp, + Gif, +} + #[derive(Debug, Clone)] -pub enum Picture { - Png(Vec), - Jpeg(Vec), - Tiff(Vec), - Bmp(Vec), - Gif(Vec), - Unknown, +pub struct Picture { + pub data: Vec, + pub picture_type: PictureType, } impl Picture { - pub fn with_mime(data: Vec, mime: &str) -> Self { - match mime { - "image/jpeg" => Self::Jpeg(data), - "image/png" => Self::Png(data), - "image/tiff" => Self::Tiff(data), - "image/bmp" => Self::Bmp(data), - "image/gif" => Self::Gif(data), - _ => Self::Unknown, - } + pub fn try_with_mime(data: Vec, mime: &str) -> Result { + let picture_type = match mime { + "image/jpeg" => PictureType::Jpeg, + "image/png" => PictureType::Png, + "image/tiff" => PictureType::Tiff, + "image/bmp" => PictureType::Bmp, + "image/gif" => PictureType::Gif, + _ => return Err(()), + }; + Ok(Self { data, picture_type }) } } @@ -121,8 +127,11 @@ pub trait AudioTagsIo { fn set_album_artist(&mut self, v: &str); fn set_album_cover(&mut self, cover: Picture); fn track(&self) -> (Option, Option); - fn set_track(&mut self, track: u16); + fn set_track_number(&mut self, track_number: u16); fn set_total_tracks(&mut self, total_track: u16); + fn disc(&self) -> (Option, Option); + fn set_disc_number(&mut self, disc_number: u16); + fn set_total_discs(&mut self, total_discs: u16); fn write_to(&self, file: &File) -> Result<(), BoxedError>; // cannot use impl AsRef fn write_to_path(&self, path: &str) -> Result<(), BoxedError>; @@ -173,11 +182,17 @@ impl AudioTagsIo for Id3Tags { self.inner.album_artist() } fn album_cover(&self) -> Option { - self.inner + if let Some(Ok(pic)) = self + .inner .pictures() .filter(|&pic| matches!(pic.picture_type, id3::frame::PictureType::CoverFront)) .next() - .map(|pic| Picture::with_mime(pic.data.clone(), &pic.mime_type)) + .map(|pic| Picture::try_with_mime(pic.data.clone(), &pic.mime_type)) + { + Some(pic) + } else { + None + } } fn set_album(&mut self, album: Album) { self.inner.set_album(album.title); @@ -202,38 +217,37 @@ impl AudioTagsIo for Id3Tags { fn set_album_cover(&mut self, cover: Picture) { self.inner .remove_picture_by_type(id3::frame::PictureType::CoverFront); - self.inner.add_picture(match cover { - Picture::Jpeg(data) => id3::frame::Picture { + self.inner.add_picture(match cover.picture_type { + PictureType::Jpeg => id3::frame::Picture { mime_type: "jpeg".to_owned(), picture_type: id3::frame::PictureType::CoverFront, description: "".to_owned(), - data: data, + data: cover.data, }, - Picture::Png(data) => id3::frame::Picture { + PictureType::Png => id3::frame::Picture { mime_type: "png".to_owned(), picture_type: id3::frame::PictureType::CoverFront, description: "".to_owned(), - data: data, + data: cover.data, }, - Picture::Tiff(data) => id3::frame::Picture { + PictureType::Tiff => id3::frame::Picture { mime_type: "tiff".to_owned(), picture_type: id3::frame::PictureType::CoverFront, description: "".to_owned(), - data: data, + data: cover.data, }, - Picture::Bmp(data) => id3::frame::Picture { + PictureType::Bmp => id3::frame::Picture { mime_type: "bmp".to_owned(), picture_type: id3::frame::PictureType::CoverFront, description: "".to_owned(), - data: data, + data: cover.data, }, - Picture::Gif(data) => id3::frame::Picture { + PictureType::Gif => id3::frame::Picture { mime_type: "gif".to_owned(), picture_type: id3::frame::PictureType::CoverFront, description: "".to_owned(), - data: data, + data: cover.data, }, - _ => panic!("Picture format not supported!"), }); } fn track(&self) -> (Option, Option) { @@ -242,12 +256,24 @@ impl AudioTagsIo for Id3Tags { self.inner.total_tracks().map(|x| x as u16), ) } - fn set_track(&mut self, track: u16) { + fn set_track_number(&mut self, track: u16) { self.inner.set_track(track as u32); } fn set_total_tracks(&mut self, total_track: u16) { self.inner.set_total_tracks(total_track as u32); } + fn disc(&self) -> (Option, Option) { + ( + self.inner.disc().map(|x| x as u16), + self.inner.total_discs().map(|x| x as u16), + ) + } + fn set_disc_number(&mut self, disc_number: u16) { + self.inner.set_disc(disc_number as u32) + } + fn set_total_discs(&mut self, total_discs: u16) { + self.inner.set_total_discs(total_discs as u32) + } fn write_to(&self, file: &File) -> Result<(), BoxedError> { self.inner.write_to(file, id3::Version::Id3v24)?; Ok(()) @@ -301,11 +327,21 @@ impl AudioTagsIo for M4aTags { } fn album_cover(&self) -> Option { use mp4ameta::Data::*; - self.inner.artwork().map(|data| match data { - Jpeg(d) => Picture::Jpeg(d.clone()), - Png(d) => Picture::Png(d.clone()), - _ => Picture::Unknown, - }) + if let Some(Some(pic)) = self.inner.artwork().map(|data| match data { + Jpeg(d) => Some(Picture { + data: d.clone(), + picture_type: PictureType::Jpeg, + }), + Png(d) => Some(Picture { + data: d.clone(), + picture_type: PictureType::Png, + }), + _ => None, + }) { + Some(pic) + } else { + None + } } fn album_title(&self) -> Option<&str> { self.inner.album() @@ -328,9 +364,9 @@ impl AudioTagsIo for M4aTags { } fn set_album_cover(&mut self, cover: Picture) { self.inner.remove_artwork(); - self.inner.add_artwork(match cover { - Picture::Png(data) => mp4ameta::Data::Png(data), - Picture::Jpeg(data) => mp4ameta::Data::Jpeg(data), + self.inner.add_artwork(match cover.picture_type { + PictureType::Png => mp4ameta::Data::Png(cover.data), + PictureType::Jpeg => mp4ameta::Data::Jpeg(cover.data), _ => panic!("Only png and jpeg are supported in m4a"), }); } @@ -343,12 +379,21 @@ impl AudioTagsIo for M4aTags { fn track(&self) -> (Option, Option) { self.inner.track() } - fn set_track(&mut self, track: u16) { + fn set_track_number(&mut self, track: u16) { self.inner.set_track_number(track); } fn set_total_tracks(&mut self, total_track: u16) { self.inner.set_total_tracks(total_track); } + fn disc(&self) -> (Option, Option) { + self.inner.disc() + } + fn set_disc_number(&mut self, disc_number: u16) { + self.inner.set_disc_number(disc_number) + } + fn set_total_discs(&mut self, total_discs: u16) { + self.inner.set_total_discs(total_discs) + } fn write_to(&self, file: &File) -> Result<(), BoxedError> { self.inner.write_to(file)?; Ok(()) @@ -358,11 +403,3 @@ impl AudioTagsIo for M4aTags { Ok(()) } } - -#[cfg(test)] -mod tests { - #[test] - fn it_works() { - assert_eq!(2 + 2, 4); - } -}