mirror of
https://github.com/Serial-ATA/lofty-rs
synced 2024-12-12 13:42:34 +00:00
0.0.2
This commit is contained in:
parent
d9fd42f491
commit
5dffc0ee55
3 changed files with 126 additions and 52 deletions
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "audiotags"
|
name = "audiotags"
|
||||||
version = "0.0.1"
|
version = "0.0.2"
|
||||||
authors = ["Tianyi <ShiTianyi2001@outlook.com>"]
|
authors = ["Tianyi <ShiTianyi2001@outlook.com>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
description = "Unified IO for different types of audio metadata"
|
description = "Unified IO for different types of audio metadata"
|
||||||
|
|
41
README.md
41
README.md
|
@ -1,16 +1,23 @@
|
||||||
# audiotags
|
# 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 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.
|
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
|
## Example
|
||||||
|
|
||||||
|
(Due to copyright restrictions I cannot upload the actual audio files here)
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
use audiotags;
|
use audiotags;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
const MP3: &'static str = "a.mp3";
|
const MP3: &'static str = "お願い!シンデレラ.mp3";
|
||||||
let mut tags = audiotags::from_path(MP3).unwrap();
|
let mut tags = audiotags::from_path(MP3).unwrap();
|
||||||
// without this crate you would call id3::Tag::from_path()
|
// without this crate you would call id3::Tag::from_path()
|
||||||
println!("Title: {:?}", tags.title());
|
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"))
|
// Album title and artist: ("THE IDOLM@STER CINDERELLA GIRLS ANIMATION PROJECT 01 Star!!", Some("CINDERELLA PROJECT"))
|
||||||
// Track: (Some(2), Some(4))
|
// Track: (Some(2), Some(4))
|
||||||
|
|
||||||
const M4A: &'static str = "b.m4a";
|
const M4A: &'static str = "ふわふわ時間.m4a";
|
||||||
let mut tags = audiotags::from_path(M4A).unwrap();
|
let mut tags = audiotags::from_path(M4A).unwrap();
|
||||||
// without this crate you would call mp4ameta::Tag::from_path()
|
// without this crate you would call mp4ameta::Tag::from_path()
|
||||||
println!("Title: {:?}", tags.title());
|
println!("Title: {:?}", tags.title());
|
||||||
|
@ -40,4 +47,34 @@ fn main() {
|
||||||
// Album title and artist: ("ふわふわ時間", Some("桜高軽音部 [平沢唯・秋山澪・田井中律・琴吹紬(CV:豊崎愛生、日笠陽子、佐藤聡美、寿美菜子)]"))
|
// Album title and artist: ("ふわふわ時間", Some("桜高軽音部 [平沢唯・秋山澪・田井中律・琴吹紬(CV:豊崎愛生、日笠陽子、佐藤聡美、寿美菜子)]"))
|
||||||
// Track: (Some(1), Some(4))
|
// 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<i32>;
|
||||||
|
fn set_year(&mut self, year: i32);
|
||||||
|
fn album(&self) -> Option<Album>;
|
||||||
|
fn album_title(&self) -> Option<&str>;
|
||||||
|
fn album_artist(&self) -> Option<&str>;
|
||||||
|
fn album_cover(&self) -> Option<Picture>;
|
||||||
|
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<u16>, Option<u16>);
|
||||||
|
fn set_track_number(&mut self, track_number: u16);
|
||||||
|
fn set_total_tracks(&mut self, total_track: u16);
|
||||||
|
fn disc(&self) -> (Option<u16>, Option<u16>);
|
||||||
|
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<Path>
|
||||||
|
fn write_to_path(&self, path: &str) -> Result<(), BoxedError>;
|
||||||
|
}
|
||||||
```
|
```
|
135
src/lib.rs
135
src/lib.rs
|
@ -72,26 +72,32 @@ pub fn from_path(path: impl AsRef<Path>) -> Result<Box<dyn AudioTagsIo>, BoxedEr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub enum PictureType {
|
||||||
|
Png,
|
||||||
|
Jpeg,
|
||||||
|
Tiff,
|
||||||
|
Bmp,
|
||||||
|
Gif,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum Picture {
|
pub struct Picture {
|
||||||
Png(Vec<u8>),
|
pub data: Vec<u8>,
|
||||||
Jpeg(Vec<u8>),
|
pub picture_type: PictureType,
|
||||||
Tiff(Vec<u8>),
|
|
||||||
Bmp(Vec<u8>),
|
|
||||||
Gif(Vec<u8>),
|
|
||||||
Unknown,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Picture {
|
impl Picture {
|
||||||
pub fn with_mime(data: Vec<u8>, mime: &str) -> Self {
|
pub fn try_with_mime(data: Vec<u8>, mime: &str) -> Result<Self, ()> {
|
||||||
match mime {
|
let picture_type = match mime {
|
||||||
"image/jpeg" => Self::Jpeg(data),
|
"image/jpeg" => PictureType::Jpeg,
|
||||||
"image/png" => Self::Png(data),
|
"image/png" => PictureType::Png,
|
||||||
"image/tiff" => Self::Tiff(data),
|
"image/tiff" => PictureType::Tiff,
|
||||||
"image/bmp" => Self::Bmp(data),
|
"image/bmp" => PictureType::Bmp,
|
||||||
"image/gif" => Self::Gif(data),
|
"image/gif" => PictureType::Gif,
|
||||||
_ => Self::Unknown,
|
_ => 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_artist(&mut self, v: &str);
|
||||||
fn set_album_cover(&mut self, cover: Picture);
|
fn set_album_cover(&mut self, cover: Picture);
|
||||||
fn track(&self) -> (Option<u16>, Option<u16>);
|
fn track(&self) -> (Option<u16>, Option<u16>);
|
||||||
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 set_total_tracks(&mut self, total_track: u16);
|
||||||
|
fn disc(&self) -> (Option<u16>, Option<u16>);
|
||||||
|
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>;
|
fn write_to(&self, file: &File) -> Result<(), BoxedError>;
|
||||||
// cannot use impl AsRef<Path>
|
// cannot use impl AsRef<Path>
|
||||||
fn write_to_path(&self, path: &str) -> Result<(), BoxedError>;
|
fn write_to_path(&self, path: &str) -> Result<(), BoxedError>;
|
||||||
|
@ -173,11 +182,17 @@ impl AudioTagsIo for Id3Tags {
|
||||||
self.inner.album_artist()
|
self.inner.album_artist()
|
||||||
}
|
}
|
||||||
fn album_cover(&self) -> Option<Picture> {
|
fn album_cover(&self) -> Option<Picture> {
|
||||||
self.inner
|
if let Some(Ok(pic)) = self
|
||||||
|
.inner
|
||||||
.pictures()
|
.pictures()
|
||||||
.filter(|&pic| matches!(pic.picture_type, id3::frame::PictureType::CoverFront))
|
.filter(|&pic| matches!(pic.picture_type, id3::frame::PictureType::CoverFront))
|
||||||
.next()
|
.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) {
|
fn set_album(&mut self, album: Album) {
|
||||||
self.inner.set_album(album.title);
|
self.inner.set_album(album.title);
|
||||||
|
@ -202,38 +217,37 @@ impl AudioTagsIo for Id3Tags {
|
||||||
fn set_album_cover(&mut self, cover: Picture) {
|
fn set_album_cover(&mut self, cover: Picture) {
|
||||||
self.inner
|
self.inner
|
||||||
.remove_picture_by_type(id3::frame::PictureType::CoverFront);
|
.remove_picture_by_type(id3::frame::PictureType::CoverFront);
|
||||||
self.inner.add_picture(match cover {
|
self.inner.add_picture(match cover.picture_type {
|
||||||
Picture::Jpeg(data) => id3::frame::Picture {
|
PictureType::Jpeg => id3::frame::Picture {
|
||||||
mime_type: "jpeg".to_owned(),
|
mime_type: "jpeg".to_owned(),
|
||||||
picture_type: id3::frame::PictureType::CoverFront,
|
picture_type: id3::frame::PictureType::CoverFront,
|
||||||
description: "".to_owned(),
|
description: "".to_owned(),
|
||||||
data: data,
|
data: cover.data,
|
||||||
},
|
},
|
||||||
Picture::Png(data) => id3::frame::Picture {
|
PictureType::Png => id3::frame::Picture {
|
||||||
mime_type: "png".to_owned(),
|
mime_type: "png".to_owned(),
|
||||||
picture_type: id3::frame::PictureType::CoverFront,
|
picture_type: id3::frame::PictureType::CoverFront,
|
||||||
description: "".to_owned(),
|
description: "".to_owned(),
|
||||||
data: data,
|
data: cover.data,
|
||||||
},
|
},
|
||||||
Picture::Tiff(data) => id3::frame::Picture {
|
PictureType::Tiff => id3::frame::Picture {
|
||||||
mime_type: "tiff".to_owned(),
|
mime_type: "tiff".to_owned(),
|
||||||
picture_type: id3::frame::PictureType::CoverFront,
|
picture_type: id3::frame::PictureType::CoverFront,
|
||||||
description: "".to_owned(),
|
description: "".to_owned(),
|
||||||
data: data,
|
data: cover.data,
|
||||||
},
|
},
|
||||||
Picture::Bmp(data) => id3::frame::Picture {
|
PictureType::Bmp => id3::frame::Picture {
|
||||||
mime_type: "bmp".to_owned(),
|
mime_type: "bmp".to_owned(),
|
||||||
picture_type: id3::frame::PictureType::CoverFront,
|
picture_type: id3::frame::PictureType::CoverFront,
|
||||||
description: "".to_owned(),
|
description: "".to_owned(),
|
||||||
data: data,
|
data: cover.data,
|
||||||
},
|
},
|
||||||
Picture::Gif(data) => id3::frame::Picture {
|
PictureType::Gif => id3::frame::Picture {
|
||||||
mime_type: "gif".to_owned(),
|
mime_type: "gif".to_owned(),
|
||||||
picture_type: id3::frame::PictureType::CoverFront,
|
picture_type: id3::frame::PictureType::CoverFront,
|
||||||
description: "".to_owned(),
|
description: "".to_owned(),
|
||||||
data: data,
|
data: cover.data,
|
||||||
},
|
},
|
||||||
_ => panic!("Picture format not supported!"),
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
fn track(&self) -> (Option<u16>, Option<u16>) {
|
fn track(&self) -> (Option<u16>, Option<u16>) {
|
||||||
|
@ -242,12 +256,24 @@ impl AudioTagsIo for Id3Tags {
|
||||||
self.inner.total_tracks().map(|x| x as u16),
|
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);
|
self.inner.set_track(track as u32);
|
||||||
}
|
}
|
||||||
fn set_total_tracks(&mut self, total_track: u16) {
|
fn set_total_tracks(&mut self, total_track: u16) {
|
||||||
self.inner.set_total_tracks(total_track as u32);
|
self.inner.set_total_tracks(total_track as u32);
|
||||||
}
|
}
|
||||||
|
fn disc(&self) -> (Option<u16>, Option<u16>) {
|
||||||
|
(
|
||||||
|
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> {
|
fn write_to(&self, file: &File) -> Result<(), BoxedError> {
|
||||||
self.inner.write_to(file, id3::Version::Id3v24)?;
|
self.inner.write_to(file, id3::Version::Id3v24)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -301,11 +327,21 @@ impl AudioTagsIo for M4aTags {
|
||||||
}
|
}
|
||||||
fn album_cover(&self) -> Option<Picture> {
|
fn album_cover(&self) -> Option<Picture> {
|
||||||
use mp4ameta::Data::*;
|
use mp4ameta::Data::*;
|
||||||
self.inner.artwork().map(|data| match data {
|
if let Some(Some(pic)) = self.inner.artwork().map(|data| match data {
|
||||||
Jpeg(d) => Picture::Jpeg(d.clone()),
|
Jpeg(d) => Some(Picture {
|
||||||
Png(d) => Picture::Png(d.clone()),
|
data: d.clone(),
|
||||||
_ => Picture::Unknown,
|
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> {
|
fn album_title(&self) -> Option<&str> {
|
||||||
self.inner.album()
|
self.inner.album()
|
||||||
|
@ -328,9 +364,9 @@ impl AudioTagsIo for M4aTags {
|
||||||
}
|
}
|
||||||
fn set_album_cover(&mut self, cover: Picture) {
|
fn set_album_cover(&mut self, cover: Picture) {
|
||||||
self.inner.remove_artwork();
|
self.inner.remove_artwork();
|
||||||
self.inner.add_artwork(match cover {
|
self.inner.add_artwork(match cover.picture_type {
|
||||||
Picture::Png(data) => mp4ameta::Data::Png(data),
|
PictureType::Png => mp4ameta::Data::Png(cover.data),
|
||||||
Picture::Jpeg(data) => mp4ameta::Data::Jpeg(data),
|
PictureType::Jpeg => mp4ameta::Data::Jpeg(cover.data),
|
||||||
_ => panic!("Only png and jpeg are supported in m4a"),
|
_ => panic!("Only png and jpeg are supported in m4a"),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -343,12 +379,21 @@ impl AudioTagsIo for M4aTags {
|
||||||
fn track(&self) -> (Option<u16>, Option<u16>) {
|
fn track(&self) -> (Option<u16>, Option<u16>) {
|
||||||
self.inner.track()
|
self.inner.track()
|
||||||
}
|
}
|
||||||
fn set_track(&mut self, track: u16) {
|
fn set_track_number(&mut self, track: u16) {
|
||||||
self.inner.set_track_number(track);
|
self.inner.set_track_number(track);
|
||||||
}
|
}
|
||||||
fn set_total_tracks(&mut self, total_track: u16) {
|
fn set_total_tracks(&mut self, total_track: u16) {
|
||||||
self.inner.set_total_tracks(total_track);
|
self.inner.set_total_tracks(total_track);
|
||||||
}
|
}
|
||||||
|
fn disc(&self) -> (Option<u16>, Option<u16>) {
|
||||||
|
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> {
|
fn write_to(&self, file: &File) -> Result<(), BoxedError> {
|
||||||
self.inner.write_to(file)?;
|
self.inner.write_to(file)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -358,11 +403,3 @@ impl AudioTagsIo for M4aTags {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
#[test]
|
|
||||||
fn it_works() {
|
|
||||||
assert_eq!(2 + 2, 4);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in a new issue