mirror of
https://github.com/Serial-ATA/lofty-rs
synced 2024-12-12 13:42:34 +00:00
cow
This commit is contained in:
parent
ec3f59e1f3
commit
3b1280fcee
7 changed files with 948 additions and 655 deletions
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "audiotags"
|
||||
version = "0.1.3"
|
||||
version = "0.1.5"
|
||||
authors = ["Tianyi <ShiTianyi2001@outlook.com>"]
|
||||
edition = "2018"
|
||||
description = "Unified IO for different types of audio metadata"
|
||||
|
@ -14,3 +14,4 @@ id3 = "0.5.1"
|
|||
mp4ameta = "0.5.1"
|
||||
strum = {version = "0.19.5", features = ["derive"]}
|
||||
metaflac = "0.2"
|
||||
beef = "0.4.4"
|
||||
|
|
344
src/flac_tag.rs
344
src/flac_tag.rs
|
@ -1,185 +1,185 @@
|
|||
use super::*;
|
||||
use metaflac;
|
||||
// use super::*;
|
||||
// use metaflac;
|
||||
|
||||
pub(crate) struct FlacTag {
|
||||
inner: metaflac::Tag,
|
||||
}
|
||||
// pub(crate) struct FlacTag {
|
||||
// inner: metaflac::Tag,
|
||||
// }
|
||||
|
||||
impl FlacTag {
|
||||
pub fn read_from_path(path: impl AsRef<Path>) -> Result<Self, BoxedError> {
|
||||
Ok(Self {
|
||||
inner: metaflac::Tag::read_from_path(path)?,
|
||||
})
|
||||
}
|
||||
pub fn get_first(&self, key: &str) -> Option<&str> {
|
||||
if let Some(Some(v)) = self.inner.vorbis_comments().map(|c| c.get(key)) {
|
||||
if !v.is_empty() {
|
||||
Some(v[0].as_str())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
pub fn set_first(&mut self, key: &str, val: &str) {
|
||||
self.inner.vorbis_comments_mut().set(key, vec![val]);
|
||||
}
|
||||
pub fn remove(&mut self, k: &str) {
|
||||
self.inner.vorbis_comments_mut().comments.remove(k);
|
||||
}
|
||||
}
|
||||
// impl FlacTag {
|
||||
// pub fn read_from_path(path: impl AsRef<Path>) -> Result<Self, BoxedError> {
|
||||
// Ok(Self {
|
||||
// inner: metaflac::Tag::read_from_path(path)?,
|
||||
// })
|
||||
// }
|
||||
// pub fn get_first(&self, key: &str) -> Option<&str> {
|
||||
// if let Some(Some(v)) = self.inner.vorbis_comments().map(|c| c.get(key)) {
|
||||
// if !v.is_empty() {
|
||||
// Some(v[0].as_str())
|
||||
// } else {
|
||||
// None
|
||||
// }
|
||||
// } else {
|
||||
// None
|
||||
// }
|
||||
// }
|
||||
// pub fn set_first(&mut self, key: &str, val: &str) {
|
||||
// self.inner.vorbis_comments_mut().set(key, vec![val]);
|
||||
// }
|
||||
// pub fn remove(&mut self, k: &str) {
|
||||
// self.inner.vorbis_comments_mut().comments.remove(k);
|
||||
// }
|
||||
// }
|
||||
|
||||
impl AudioTagsIo for FlacTag {
|
||||
fn title(&self) -> Option<&str> {
|
||||
self.get_first("TITLE")
|
||||
}
|
||||
fn set_title(&mut self, title: &str) {
|
||||
self.set_first("TITLE", title);
|
||||
}
|
||||
// impl AudioTagsIo for FlacTag {
|
||||
// fn title(&self) -> Option<&str> {
|
||||
// self.get_first("TITLE")
|
||||
// }
|
||||
// fn set_title(&mut self, title: &str) {
|
||||
// self.set_first("TITLE", title);
|
||||
// }
|
||||
|
||||
fn artist(&self) -> Option<&str> {
|
||||
self.get_first("ARTIST")
|
||||
}
|
||||
fn set_artist(&mut self, artist: &str) {
|
||||
self.set_first("ARTIST", artist)
|
||||
}
|
||||
// fn artist(&self) -> Option<&str> {
|
||||
// self.get_first("ARTIST")
|
||||
// }
|
||||
// fn set_artist(&mut self, artist: &str) {
|
||||
// self.set_first("ARTIST", artist)
|
||||
// }
|
||||
|
||||
fn year(&self) -> Option<i32> {
|
||||
if let Some(Ok(y)) = self
|
||||
.get_first("DATE")
|
||||
.map(|s| s.chars().take(4).collect::<String>().parse::<i32>())
|
||||
{
|
||||
Some(y)
|
||||
} else if let Some(Ok(y)) = self.get_first("YEAR").map(|s| s.parse::<i32>()) {
|
||||
Some(y)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
fn set_year(&mut self, year: i32) {
|
||||
self.set_first("DATE", &year.to_string());
|
||||
self.set_first("YEAR", &year.to_string());
|
||||
}
|
||||
// fn year(&self) -> Option<i32> {
|
||||
// if let Some(Ok(y)) = self
|
||||
// .get_first("DATE")
|
||||
// .map(|s| s.chars().take(4).collect::<String>().parse::<i32>())
|
||||
// {
|
||||
// Some(y)
|
||||
// } else if let Some(Ok(y)) = self.get_first("YEAR").map(|s| s.parse::<i32>()) {
|
||||
// Some(y)
|
||||
// } else {
|
||||
// None
|
||||
// }
|
||||
// }
|
||||
// fn set_year(&mut self, year: i32) {
|
||||
// self.set_first("DATE", &year.to_string());
|
||||
// self.set_first("YEAR", &year.to_string());
|
||||
// }
|
||||
|
||||
fn album_title(&self) -> Option<&str> {
|
||||
self.get_first("ALBUM")
|
||||
}
|
||||
fn set_album_title(&mut self, title: &str) {
|
||||
self.set_first("ALBUM", title)
|
||||
}
|
||||
// fn album_title(&self) -> Option<&str> {
|
||||
// self.get_first("ALBUM")
|
||||
// }
|
||||
// fn set_album_title(&mut self, title: &str) {
|
||||
// self.set_first("ALBUM", title)
|
||||
// }
|
||||
|
||||
fn album_artist(&self) -> Option<&str> {
|
||||
self.get_first("ALBUMARTIST")
|
||||
}
|
||||
fn set_album_artist(&mut self, v: &str) {
|
||||
self.set_first("ALBUMARTIST", v)
|
||||
}
|
||||
// fn album_artist(&self) -> Option<&str> {
|
||||
// self.get_first("ALBUMARTIST")
|
||||
// }
|
||||
// fn set_album_artist(&mut self, v: &str) {
|
||||
// self.set_first("ALBUMARTIST", v)
|
||||
// }
|
||||
|
||||
fn album_cover(&self) -> Option<Picture> {
|
||||
if let Some(Ok(pic)) = self
|
||||
.inner
|
||||
.pictures()
|
||||
.filter(|&pic| matches!(pic.picture_type, metaflac::block::PictureType::CoverFront))
|
||||
.next()
|
||||
.map(|pic| Picture::try_with_mime(pic.data.clone(), &pic.mime_type))
|
||||
{
|
||||
Some(pic)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
fn set_album_cover(&mut self, cover: Picture) {
|
||||
self.remove_album_cover();
|
||||
let mime = String::from(cover.mime_type);
|
||||
let picture_type = metaflac::block::PictureType::CoverFront;
|
||||
self.inner.add_picture(mime, picture_type, cover.data);
|
||||
}
|
||||
// fn album_cover(&self) -> Option<Picture> {
|
||||
// if let Some(Ok(pic)) = self
|
||||
// .inner
|
||||
// .pictures()
|
||||
// .filter(|&pic| matches!(pic.picture_type, metaflac::block::PictureType::CoverFront))
|
||||
// .next()
|
||||
// .map(|pic| Picture::try_with_mime(pic.data.clone(), &pic.mime_type))
|
||||
// {
|
||||
// Some(pic)
|
||||
// } else {
|
||||
// None
|
||||
// }
|
||||
// }
|
||||
// fn set_album_cover(&mut self, cover: Picture) {
|
||||
// self.remove_album_cover();
|
||||
// let mime = String::from(cover.mime_type);
|
||||
// let picture_type = metaflac::block::PictureType::CoverFront;
|
||||
// self.inner.add_picture(mime, picture_type, cover.data);
|
||||
// }
|
||||
|
||||
fn track_number(&self) -> Option<u16> {
|
||||
if let Some(Ok(n)) = self.get_first("TRACKNUMBER").map(|x| x.parse::<u16>()) {
|
||||
Some(n)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
fn set_track_number(&mut self, v: u16) {
|
||||
self.set_first("TRACKNUMBER", &v.to_string())
|
||||
}
|
||||
// fn track_number(&self) -> Option<u16> {
|
||||
// if let Some(Ok(n)) = self.get_first("TRACKNUMBER").map(|x| x.parse::<u16>()) {
|
||||
// Some(n)
|
||||
// } else {
|
||||
// None
|
||||
// }
|
||||
// }
|
||||
// fn set_track_number(&mut self, v: u16) {
|
||||
// self.set_first("TRACKNUMBER", &v.to_string())
|
||||
// }
|
||||
|
||||
// ! not standard
|
||||
fn total_tracks(&self) -> Option<u16> {
|
||||
if let Some(Ok(n)) = self.get_first("TOTALTRACKS").map(|x| x.parse::<u16>()) {
|
||||
Some(n)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
fn set_total_tracks(&mut self, v: u16) {
|
||||
self.set_first("TOTALTRACKS", &v.to_string())
|
||||
}
|
||||
// // ! not standard
|
||||
// fn total_tracks(&self) -> Option<u16> {
|
||||
// if let Some(Ok(n)) = self.get_first("TOTALTRACKS").map(|x| x.parse::<u16>()) {
|
||||
// Some(n)
|
||||
// } else {
|
||||
// None
|
||||
// }
|
||||
// }
|
||||
// fn set_total_tracks(&mut self, v: u16) {
|
||||
// self.set_first("TOTALTRACKS", &v.to_string())
|
||||
// }
|
||||
|
||||
fn disc_number(&self) -> Option<u16> {
|
||||
if let Some(Ok(n)) = self.get_first("DISCNUMBER").map(|x| x.parse::<u16>()) {
|
||||
Some(n)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
fn set_disc_number(&mut self, v: u16) {
|
||||
self.set_first("DISCNUMBER", &v.to_string())
|
||||
}
|
||||
// fn disc_number(&self) -> Option<u16> {
|
||||
// if let Some(Ok(n)) = self.get_first("DISCNUMBER").map(|x| x.parse::<u16>()) {
|
||||
// Some(n)
|
||||
// } else {
|
||||
// None
|
||||
// }
|
||||
// }
|
||||
// fn set_disc_number(&mut self, v: u16) {
|
||||
// self.set_first("DISCNUMBER", &v.to_string())
|
||||
// }
|
||||
|
||||
// ! not standard
|
||||
fn total_discs(&self) -> Option<u16> {
|
||||
if let Some(Ok(n)) = self.get_first("TOTALDISCS").map(|x| x.parse::<u16>()) {
|
||||
Some(n)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
fn set_total_discs(&mut self, v: u16) {
|
||||
self.set_first("TOTALDISCS", &v.to_string())
|
||||
}
|
||||
// // ! not standard
|
||||
// fn total_discs(&self) -> Option<u16> {
|
||||
// if let Some(Ok(n)) = self.get_first("TOTALDISCS").map(|x| x.parse::<u16>()) {
|
||||
// Some(n)
|
||||
// } else {
|
||||
// None
|
||||
// }
|
||||
// }
|
||||
// fn set_total_discs(&mut self, v: u16) {
|
||||
// self.set_first("TOTALDISCS", &v.to_string())
|
||||
// }
|
||||
|
||||
fn remove_title(&mut self) {
|
||||
self.remove("TITLE");
|
||||
}
|
||||
fn remove_artist(&mut self) {
|
||||
self.remove("ARTIST");
|
||||
}
|
||||
fn remove_year(&mut self) {
|
||||
self.remove("YEAR");
|
||||
self.remove("DATE");
|
||||
}
|
||||
fn remove_album_title(&mut self) {
|
||||
self.remove("ALBUM");
|
||||
}
|
||||
fn remove_album_artist(&mut self) {
|
||||
self.remove("ALBUMARTIST");
|
||||
}
|
||||
fn remove_album_cover(&mut self) {
|
||||
self.inner
|
||||
.remove_picture_type(metaflac::block::PictureType::CoverFront)
|
||||
}
|
||||
fn remove_track_number(&mut self) {
|
||||
self.remove("TRACKNUMBER");
|
||||
}
|
||||
fn remove_total_tracks(&mut self) {
|
||||
self.remove("TOTALTRACKS");
|
||||
}
|
||||
fn remove_disc_number(&mut self) {
|
||||
self.remove("DISCNUMBER");
|
||||
}
|
||||
fn remove_total_discs(&mut self) {
|
||||
self.remove("TOTALDISCS");
|
||||
}
|
||||
fn write_to(&mut self, file: &mut File) -> Result<(), BoxedError> {
|
||||
self.inner.write_to(file)?;
|
||||
Ok(())
|
||||
}
|
||||
fn write_to_path(&mut self, path: &str) -> Result<(), BoxedError> {
|
||||
self.inner.write_to_path(path)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
// fn remove_title(&mut self) {
|
||||
// self.remove("TITLE");
|
||||
// }
|
||||
// fn remove_artist(&mut self) {
|
||||
// self.remove("ARTIST");
|
||||
// }
|
||||
// fn remove_year(&mut self) {
|
||||
// self.remove("YEAR");
|
||||
// self.remove("DATE");
|
||||
// }
|
||||
// fn remove_album_title(&mut self) {
|
||||
// self.remove("ALBUM");
|
||||
// }
|
||||
// fn remove_album_artist(&mut self) {
|
||||
// self.remove("ALBUMARTIST");
|
||||
// }
|
||||
// fn remove_album_cover(&mut self) {
|
||||
// self.inner
|
||||
// .remove_picture_type(metaflac::block::PictureType::CoverFront)
|
||||
// }
|
||||
// fn remove_track_number(&mut self) {
|
||||
// self.remove("TRACKNUMBER");
|
||||
// }
|
||||
// fn remove_total_tracks(&mut self) {
|
||||
// self.remove("TOTALTRACKS");
|
||||
// }
|
||||
// fn remove_disc_number(&mut self) {
|
||||
// self.remove("DISCNUMBER");
|
||||
// }
|
||||
// fn remove_total_discs(&mut self) {
|
||||
// self.remove("TOTALDISCS");
|
||||
// }
|
||||
// fn write_to(&mut self, file: &mut File) -> Result<(), BoxedError> {
|
||||
// self.inner.write_to(file)?;
|
||||
// Ok(())
|
||||
// }
|
||||
// fn write_to_path(&mut self, path: &str) -> Result<(), BoxedError> {
|
||||
// self.inner.write_to_path(path)?;
|
||||
// Ok(())
|
||||
// }
|
||||
// }
|
||||
|
|
363
src/id3_tag.rs
363
src/id3_tag.rs
|
@ -1,143 +1,246 @@
|
|||
use super::*;
|
||||
use id3;
|
||||
|
||||
pub(crate) struct Id3Tag {
|
||||
inner: id3::Tag,
|
||||
}
|
||||
pub type Id3Tag = id3::Tag;
|
||||
|
||||
impl Id3Tag {
|
||||
pub fn read_from_path(path: impl AsRef<Path>) -> Result<Self, BoxedError> {
|
||||
Ok(Self {
|
||||
inner: id3::Tag::read_from_path(path)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl AudioTagsIo for Id3Tag {
|
||||
fn title(&self) -> Option<&str> {
|
||||
self.inner.title()
|
||||
}
|
||||
fn set_title(&mut self, title: &str) {
|
||||
self.inner.set_title(title)
|
||||
}
|
||||
fn remove_title(&mut self) {
|
||||
self.inner.remove_title();
|
||||
}
|
||||
|
||||
fn artist(&self) -> Option<&str> {
|
||||
self.inner.artist()
|
||||
}
|
||||
fn set_artist(&mut self, artist: &str) {
|
||||
self.inner.set_artist(artist)
|
||||
}
|
||||
fn remove_artist(&mut self) {
|
||||
self.inner.remove_artist();
|
||||
}
|
||||
|
||||
fn year(&self) -> Option<i32> {
|
||||
self.inner.year()
|
||||
}
|
||||
fn set_year(&mut self, year: i32) {
|
||||
self.inner.set_year(year)
|
||||
}
|
||||
fn remove_year(&mut self) {
|
||||
self.inner.remove("TYER")
|
||||
// self.inner.remove_year(); // TODO
|
||||
}
|
||||
|
||||
fn album_title(&self) -> Option<&str> {
|
||||
self.inner.album()
|
||||
}
|
||||
fn set_album_title(&mut self, v: &str) {
|
||||
self.inner.set_album(v)
|
||||
}
|
||||
fn remove_album_title(&mut self) {
|
||||
self.inner.remove_album();
|
||||
}
|
||||
|
||||
fn album_artist(&self) -> Option<&str> {
|
||||
self.inner.album_artist()
|
||||
}
|
||||
fn set_album_artist(&mut self, v: &str) {
|
||||
self.inner.set_album_artist(v)
|
||||
}
|
||||
fn remove_album_artist(&mut self) {
|
||||
self.inner.remove_album_artist();
|
||||
}
|
||||
|
||||
fn album_cover(&self) -> Option<Picture> {
|
||||
if let Some(Ok(pic)) = self
|
||||
.inner
|
||||
impl<'a> From<&'a id3::Tag> for AnyTag<'a> {
|
||||
fn from(inp: &'a id3::Tag) -> Self {
|
||||
let u32tou16 = |x: u32| x as u16;
|
||||
let mut t = Self::default();
|
||||
t.title = inp.title().map(Cow::borrowed);
|
||||
// artist
|
||||
t.year = inp.year();
|
||||
t.album_title = inp.album().map(Cow::borrowed);
|
||||
// album_artist
|
||||
t.album_cover = if let Some(Ok(pic)) = inp
|
||||
.pictures()
|
||||
.filter(|&pic| matches!(pic.picture_type, id3::frame::PictureType::CoverFront))
|
||||
.next()
|
||||
.map(|pic| Picture::try_with_mime(pic.data.clone(), &pic.mime_type))
|
||||
.map(|pic| Picture::try_from(pic))
|
||||
{
|
||||
Some(pic)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
fn set_album_cover(&mut self, cover: Picture) {
|
||||
self.remove_album_cover();
|
||||
self.inner.add_picture(id3::frame::Picture {
|
||||
mime_type: String::from(cover.mime_type),
|
||||
picture_type: id3::frame::PictureType::CoverFront,
|
||||
description: "".to_owned(),
|
||||
data: cover.data,
|
||||
});
|
||||
}
|
||||
fn remove_album_cover(&mut self) {
|
||||
self.inner
|
||||
.remove_picture_by_type(id3::frame::PictureType::CoverFront);
|
||||
}
|
||||
|
||||
fn track_number(&self) -> Option<u16> {
|
||||
self.inner.track().map(|x| x as u16)
|
||||
}
|
||||
fn set_track_number(&mut self, track: u16) {
|
||||
self.inner.set_track(track as u32);
|
||||
}
|
||||
fn remove_track_number(&mut self) {
|
||||
self.inner.remove_track();
|
||||
}
|
||||
|
||||
fn total_tracks(&self) -> Option<u16> {
|
||||
self.inner.total_tracks().map(|x| x as u16)
|
||||
}
|
||||
fn set_total_tracks(&mut self, total_track: u16) {
|
||||
self.inner.set_total_tracks(total_track as u32);
|
||||
}
|
||||
fn remove_total_tracks(&mut self) {
|
||||
self.inner.remove_total_tracks();
|
||||
}
|
||||
|
||||
fn disc_number(&self) -> Option<u16> {
|
||||
self.inner.disc().map(|x| x as u16)
|
||||
}
|
||||
fn set_disc_number(&mut self, disc_number: u16) {
|
||||
self.inner.set_disc(disc_number as u32)
|
||||
}
|
||||
fn remove_disc_number(&mut self) {
|
||||
self.inner.remove_disc();
|
||||
}
|
||||
|
||||
fn total_discs(&self) -> Option<u16> {
|
||||
self.inner.total_discs().map(|x| x as u16)
|
||||
}
|
||||
fn set_total_discs(&mut self, total_discs: u16) {
|
||||
self.inner.set_total_discs(total_discs as u32)
|
||||
}
|
||||
fn remove_total_discs(&mut self) {
|
||||
self.inner.remove_total_discs();
|
||||
}
|
||||
|
||||
fn write_to(&mut self, file: &mut File) -> Result<(), BoxedError> {
|
||||
self.inner.write_to(file, id3::Version::Id3v24)?;
|
||||
Ok(())
|
||||
}
|
||||
fn write_to_path(&mut self, path: &str) -> Result<(), BoxedError> {
|
||||
self.inner.write_to_path(path, id3::Version::Id3v24)?;
|
||||
Ok(())
|
||||
};
|
||||
t.track_number = inp.track().map(u32tou16);
|
||||
t.total_tracks = inp.total_tracks().map(u32tou16);
|
||||
t.disc_number = inp.disc().map(u32tou16);
|
||||
t.total_discs = inp.total_discs().map(u32tou16);
|
||||
t
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<AnyTag<'a>> for id3::Tag {
|
||||
fn from(inp: AnyTag<'a>) -> Self {
|
||||
let mut t = id3::Tag::new();
|
||||
inp.title().map(|v| t.set_title(v));
|
||||
inp.year.map(|v| t.set_year(v));
|
||||
inp.album_title().map(|v| t.set_album(v));
|
||||
inp.track_number().map(|v| t.set_track(v as u32));
|
||||
inp.total_tracks().map(|v| t.set_total_tracks(v as u32));
|
||||
inp.disc_number().map(|v| t.set_disc(v as u32));
|
||||
inp.total_discs().map(|v| t.set_total_discs(v as u32));
|
||||
t
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> std::convert::TryFrom<&'a id3::frame::Picture> for Picture<'a> {
|
||||
type Error = crate::Error;
|
||||
fn try_from(inp: &'a id3::frame::Picture) -> Result<Self> {
|
||||
let &id3::frame::Picture {
|
||||
ref mime_type,
|
||||
ref data,
|
||||
..
|
||||
} = inp;
|
||||
let mime_type: MimeType = mime_type.as_str().try_into()?;
|
||||
Ok(Self {
|
||||
data: Cow::borrowed(&data),
|
||||
mime_type,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// pub(crate) struct Id3Tag {
|
||||
// inner: id3::Tag,
|
||||
// }
|
||||
|
||||
// impl Default for Id3Tag {
|
||||
// fn default() -> Self {
|
||||
// Self {
|
||||
// inner: id3::Tag::default(),
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// impl Id3Tag {
|
||||
// pub fn new() -> Self {
|
||||
// Self {
|
||||
// inner: id3::Tag::default(),
|
||||
// }
|
||||
// }
|
||||
// pub fn read_from_path(path: impl AsRef<Path>) -> StdResult<Self, BoxedError> {
|
||||
// Ok(Self {
|
||||
// inner: id3::Tag::read_from_path(path)?,
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
|
||||
// impl AudioTagsIo for Id3Tag {
|
||||
// fn title(&self) -> Option<&str> {
|
||||
// self.inner.title()
|
||||
// }
|
||||
// fn set_title(&mut self, title: &str) {
|
||||
// self.inner.set_title(title)
|
||||
// }
|
||||
// fn remove_title(&mut self) {
|
||||
// self.inner.remove_title();
|
||||
// }
|
||||
|
||||
// fn artist(&self) -> Option<&str> {
|
||||
// self.inner.artist()
|
||||
// }
|
||||
// fn set_artist(&mut self, artist: &str) {
|
||||
// self.inner.set_artist(artist)
|
||||
// }
|
||||
// fn remove_artist(&mut self) {
|
||||
// self.inner.remove_artist();
|
||||
// }
|
||||
|
||||
// fn year(&self) -> Option<i32> {
|
||||
// self.inner.year()
|
||||
// }
|
||||
// fn set_year(&mut self, year: i32) {
|
||||
// self.inner.set_year(year)
|
||||
// }
|
||||
// fn remove_year(&mut self) {
|
||||
// self.inner.remove("TYER")
|
||||
// // self.inner.remove_year(); // TODO
|
||||
// }
|
||||
|
||||
// fn album_title(&self) -> Option<&str> {
|
||||
// self.inner.album()
|
||||
// }
|
||||
// fn set_album_title(&mut self, v: &str) {
|
||||
// self.inner.set_album(v)
|
||||
// }
|
||||
// fn remove_album_title(&mut self) {
|
||||
// self.inner.remove_album();
|
||||
// }
|
||||
|
||||
// fn album_artist(&self) -> Option<&str> {
|
||||
// self.inner.album_artist()
|
||||
// }
|
||||
// fn set_album_artist(&mut self, v: &str) {
|
||||
// self.inner.set_album_artist(v)
|
||||
// }
|
||||
// fn remove_album_artist(&mut self) {
|
||||
// self.inner.remove_album_artist();
|
||||
// }
|
||||
|
||||
// fn album_cover(&self) -> Option<Picture> {
|
||||
// if let Some(Ok(pic)) = self
|
||||
// .inner
|
||||
// .pictures()
|
||||
// .filter(|&pic| matches!(pic.picture_type, id3::frame::PictureType::CoverFront))
|
||||
// .next()
|
||||
// .map(|pic| Picture::try_with_mime(pic.data.clone(), &pic.mime_type))
|
||||
// {
|
||||
// Some(pic)
|
||||
// } else {
|
||||
// None
|
||||
// }
|
||||
// }
|
||||
// fn set_album_cover(&mut self, cover: Picture) {
|
||||
// self.remove_album_cover();
|
||||
// self.inner.add_picture(id3::frame::Picture {
|
||||
// mime_type: String::from(cover.mime_type),
|
||||
// picture_type: id3::frame::PictureType::CoverFront,
|
||||
// description: "".to_owned(),
|
||||
// data: cover.data,
|
||||
// });
|
||||
// }
|
||||
// fn remove_album_cover(&mut self) {
|
||||
// self.inner
|
||||
// .remove_picture_by_type(id3::frame::PictureType::CoverFront);
|
||||
// }
|
||||
|
||||
// fn track_number(&self) -> Option<u16> {
|
||||
// self.inner.track().map(|x| x as u16)
|
||||
// }
|
||||
// fn set_track_number(&mut self, track: u16) {
|
||||
// self.inner.set_track(track as u32);
|
||||
// }
|
||||
// fn remove_track_number(&mut self) {
|
||||
// self.inner.remove_track();
|
||||
// }
|
||||
|
||||
// fn total_tracks(&self) -> Option<u16> {
|
||||
// self.inner.total_tracks().map(|x| x as u16)
|
||||
// }
|
||||
// fn set_total_tracks(&mut self, total_track: u16) {
|
||||
// self.inner.set_total_tracks(total_track as u32);
|
||||
// }
|
||||
// fn remove_total_tracks(&mut self) {
|
||||
// self.inner.remove_total_tracks();
|
||||
// }
|
||||
|
||||
// fn disc_number(&self) -> Option<u16> {
|
||||
// self.inner.disc().map(|x| x as u16)
|
||||
// }
|
||||
// fn set_disc_number(&mut self, disc_number: u16) {
|
||||
// self.inner.set_disc(disc_number as u32)
|
||||
// }
|
||||
// fn remove_disc_number(&mut self) {
|
||||
// self.inner.remove_disc();
|
||||
// }
|
||||
|
||||
// fn total_discs(&self) -> Option<u16> {
|
||||
// self.inner.total_discs().map(|x| x as u16)
|
||||
// }
|
||||
// fn set_total_discs(&mut self, total_discs: u16) {
|
||||
// self.inner.set_total_discs(total_discs as u32)
|
||||
// }
|
||||
// fn remove_total_discs(&mut self) {
|
||||
// self.inner.remove_total_discs();
|
||||
// }
|
||||
|
||||
// fn write_to(&mut self, file: &mut File) -> Result<(), BoxedError> {
|
||||
// self.inner.write_to(file, id3::Version::Id3v24)?;
|
||||
// Ok(())
|
||||
// }
|
||||
// fn write_to_path(&mut self, path: &str) -> Result<(), BoxedError> {
|
||||
// self.inner.write_to_path(path, id3::Version::Id3v24)?;
|
||||
// Ok(())
|
||||
// }
|
||||
// }
|
||||
|
||||
// impl<'a> From<AnyTag<'a>> for Id3Tag {
|
||||
// fn from(anytag: AnyTag) -> Self {
|
||||
// Self {
|
||||
// inner: anytag.into(),
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// impl From<AnyTag> for id3::Tag {
|
||||
// fn from(anytag: AnyTag) -> Self {
|
||||
// let mut id3tag = Self::default();
|
||||
// anytag
|
||||
// .artists_as_string(SEP_ARTIST)
|
||||
// .map(|v| id3tag.set_artist(v));
|
||||
// anytag.year().map(|v| id3tag.set_year(v));
|
||||
// anytag.album_title().map(|v| id3tag.set_album(v));
|
||||
// anytag
|
||||
// .album_artists_as_string(SEP_ARTIST)
|
||||
// .map(|v| id3tag.set_album_artist(v));
|
||||
// anytag.track_number().map(|v| id3tag.set_track(v as u32));
|
||||
// anytag
|
||||
// .total_tracks()
|
||||
// .map(|v| id3tag.set_total_tracks(v as u32));
|
||||
// anytag.disc_number().map(|v| id3tag.set_disc(v as u32));
|
||||
// anytag
|
||||
// .total_discs()
|
||||
// .map(|v| id3tag.set_total_discs(v as u32));
|
||||
// id3tag
|
||||
// }
|
||||
// }
|
||||
|
|
467
src/lib.rs
467
src/lib.rs
|
@ -41,10 +41,10 @@
|
|||
//! ```
|
||||
|
||||
mod id3_tag;
|
||||
use id3_tag::Id3Tag;
|
||||
pub use id3_tag::Id3Tag;
|
||||
mod flac_tag;
|
||||
mod mp4_tag;
|
||||
use flac_tag::FlacTag;
|
||||
// use flac_tag::FlacTag;
|
||||
use mp4_tag::Mp4Tag;
|
||||
|
||||
use std::convert::From;
|
||||
|
@ -52,13 +52,22 @@ use std::fs::File;
|
|||
use std::path::Path;
|
||||
use strum::Display;
|
||||
|
||||
use beef::lean::Cow;
|
||||
|
||||
use std::convert::{TryFrom, TryInto};
|
||||
|
||||
type BoxedError = Box<dyn std::error::Error>;
|
||||
|
||||
#[derive(Debug, Display)]
|
||||
pub enum Error {
|
||||
UnsupportedFormat(String),
|
||||
UnsupportedMimeType(String),
|
||||
NotAPicture,
|
||||
}
|
||||
|
||||
pub type StdResult<T, E> = std::result::Result<T, E>;
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
impl std::error::Error for Error {}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
|
@ -82,12 +91,13 @@ pub enum TagType {
|
|||
Mp4,
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
impl TagType {
|
||||
fn try_from_ext(ext: &str) -> Result<Self, BoxedError> {
|
||||
fn try_from_ext(ext: &str) -> StdResult<Self, BoxedError> {
|
||||
match ext {
|
||||
"mp3" => Ok(Self::Id3v2),
|
||||
"mp3" => Ok(Self::Id3v2),
|
||||
"m4a" | "m4b" | "m4p" | "m4v" | "isom" | "mp4" => Ok(Self::Mp4),
|
||||
"flac" => Ok(Self::Flac),
|
||||
"flac" => Ok(Self::Flac),
|
||||
p @ _ => Err(Box::new(Error::UnsupportedFormat(p.to_owned()))),
|
||||
}
|
||||
}
|
||||
|
@ -98,52 +108,38 @@ pub struct Tag {
|
|||
tag_type: Option<TagType>,
|
||||
}
|
||||
|
||||
impl Tag {
|
||||
pub fn with_tag_type(tag_type: TagType) -> Self {
|
||||
Self {
|
||||
tag_type: Some(tag_type),
|
||||
}
|
||||
}
|
||||
// impl Tag {
|
||||
// pub fn with_tag_type(tag_type: TagType) -> Self {
|
||||
// Self {
|
||||
// tag_type: Some(tag_type),
|
||||
// }
|
||||
// }
|
||||
|
||||
pub fn read_from_path(
|
||||
&self,
|
||||
path: impl AsRef<Path>,
|
||||
) -> Result<Box<dyn AudioTagsIo>, BoxedError> {
|
||||
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(Id3Tag::read_from_path(path)?)),
|
||||
TagType::Mp4 => Ok(Box::new(Mp4Tag::read_from_path(path)?)),
|
||||
TagType::Flac => Ok(Box::new(FlacTag::read_from_path(path)?)),
|
||||
}
|
||||
}
|
||||
}
|
||||
// pub fn read_from_path(
|
||||
// &self,
|
||||
// path: impl AsRef<Path>,
|
||||
// ) -> Result<Box<dyn AudioTagsIo>, BoxedError> {
|
||||
// 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(Id3Tag::read_from_path(path)?)),
|
||||
// TagType::Mp4 => Ok(Box::new(Mp4Tag::read_from_path(path)?)),
|
||||
// TagType::Flac => Ok(Box::new(FlacTag::read_from_path(path)?)),
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
/// Guesses the audio metadata handler from the file extension, and returns the `Box`ed IO handler.
|
||||
pub fn read_from_path(path: impl AsRef<Path>) -> Result<Box<dyn AudioTagsIo>, BoxedError> {
|
||||
match path
|
||||
.as_ref()
|
||||
.extension()
|
||||
.unwrap()
|
||||
.to_string_lossy()
|
||||
.to_string()
|
||||
.to_lowercase()
|
||||
.as_str()
|
||||
{
|
||||
"mp3" => Ok(Box::new(Id3Tag::read_from_path(path)?)),
|
||||
"m4a" | "m4b" | "m4p" | "m4v" | "isom" | "mp4" => {
|
||||
Ok(Box::new(Mp4Tag::read_from_path(path)?))
|
||||
}
|
||||
"flac" => Ok(Box::new(FlacTag::read_from_path(path)?)),
|
||||
p @ _ => Err(Box::new(Error::UnsupportedFormat(p.to_owned()))),
|
||||
}
|
||||
}
|
||||
// // ? deprecate?
|
||||
// /// Guesses the audio metadata handler from the file extension, and returns the `Box`ed IO handler.
|
||||
// pub fn read_from_path(path: impl AsRef<Path>) -> Result<Box<dyn AudioTagsIo>, BoxedError> {
|
||||
// Tag::default().read_from_path(path)
|
||||
// }
|
||||
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
|
||||
pub enum MimeType {
|
||||
|
@ -154,6 +150,20 @@ pub enum MimeType {
|
|||
Gif,
|
||||
}
|
||||
|
||||
impl TryFrom<&str> for MimeType {
|
||||
type Error = Error;
|
||||
fn try_from(inp: &str) -> Result<Self> {
|
||||
Ok(match inp {
|
||||
"image/jpeg" => MimeType::Jpeg,
|
||||
"image/png" => MimeType::Png,
|
||||
"image/tiff" => MimeType::Tiff,
|
||||
"image/bmp" => MimeType::Bmp,
|
||||
"image/gif" => MimeType::Gif,
|
||||
_ => return Err(Error::UnsupportedMimeType(inp.to_owned())),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<MimeType> for String {
|
||||
fn from(mt: MimeType) -> Self {
|
||||
match mt {
|
||||
|
@ -167,127 +177,250 @@ impl From<MimeType> for String {
|
|||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub struct Picture {
|
||||
pub data: Vec<u8>,
|
||||
pub struct Picture<'a> {
|
||||
pub data: Cow<'a, [u8]>,
|
||||
pub mime_type: MimeType,
|
||||
}
|
||||
|
||||
impl Picture {
|
||||
pub fn try_with_mime(data: Vec<u8>, mime: &str) -> Result<Self, ()> {
|
||||
let mime_type = match mime {
|
||||
"image/jpeg" => MimeType::Jpeg,
|
||||
"image/png" => MimeType::Png,
|
||||
"image/tiff" => MimeType::Tiff,
|
||||
"image/bmp" => MimeType::Bmp,
|
||||
"image/gif" => MimeType::Gif,
|
||||
_ => return Err(()),
|
||||
};
|
||||
Ok(Self { data, mime_type })
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Album {
|
||||
pub title: String,
|
||||
pub artist: Option<String>,
|
||||
pub cover: Option<Picture>,
|
||||
}
|
||||
|
||||
/// Implementors of this trait are able to read and write audio metadata.
|
||||
///
|
||||
/// Constructor methods e.g. `from_file` should be implemented separately.
|
||||
pub trait AudioTagsIo {
|
||||
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: title.to_owned(),
|
||||
artist: self.album_artist().map(|x| x.to_owned()),
|
||||
cover: self.album_cover(),
|
||||
impl<'a> Picture<'a> {
|
||||
pub fn try_with_mime(data: Vec<u8>, mime: &str) -> Result<Self> {
|
||||
let mime_type: MimeType = mime.try_into()?;
|
||||
Ok(Self {
|
||||
data: Cow::owned(data),
|
||||
mime_type,
|
||||
})
|
||||
}
|
||||
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();
|
||||
}
|
||||
|
||||
fn album_title(&self) -> Option<&str>;
|
||||
fn set_album_title(&mut self, v: &str);
|
||||
fn remove_album_title(&mut self);
|
||||
|
||||
fn album_artist(&self) -> Option<&str>;
|
||||
fn set_album_artist(&mut self, v: &str);
|
||||
fn remove_album_artist(&mut self);
|
||||
|
||||
fn album_cover(&self) -> Option<Picture>;
|
||||
fn set_album_cover(&mut self, cover: Picture);
|
||||
fn remove_album_cover(&mut self);
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
fn track_number(&self) -> Option<u16>;
|
||||
fn set_track_number(&mut self, track_number: u16);
|
||||
fn remove_track_number(&mut self);
|
||||
|
||||
fn total_tracks(&self) -> Option<u16>;
|
||||
fn set_total_tracks(&mut self, total_track: u16);
|
||||
fn remove_total_tracks(&mut self);
|
||||
|
||||
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);
|
||||
|
||||
fn write_to(&mut self, file: &mut File) -> Result<(), BoxedError>;
|
||||
// cannot use impl AsRef<Path>
|
||||
fn write_to_path(&mut self, path: &str) -> Result<(), BoxedError>;
|
||||
}
|
||||
|
||||
/// A struct for representing an album for convinience.
|
||||
#[derive(Debug)]
|
||||
pub struct Album<'a> {
|
||||
pub title: Cow<'a, str>,
|
||||
pub artist: Option<Cow<'a, str>>,
|
||||
pub cover: Option<Picture<'a>>,
|
||||
}
|
||||
|
||||
impl<'a> Album<'a> {
|
||||
pub fn with_title(title: impl Into<String>) -> Self {
|
||||
Self {
|
||||
title: Cow::owned(title.into()),
|
||||
artist: None,
|
||||
cover: None,
|
||||
}
|
||||
}
|
||||
pub fn and_artist(mut self, artist: impl Into<String>) -> Self {
|
||||
self.artist = Some(Cow::owned(artist.into()));
|
||||
self
|
||||
}
|
||||
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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const SEP_ARTIST: &'static str = ";";
|
||||
|
||||
#[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>,
|
||||
}
|
||||
|
||||
impl<'a> AnyTag<'a> {
|
||||
pub fn title(&self) -> Option<&str> {
|
||||
self.title.as_deref()
|
||||
}
|
||||
// pub fn artists(&self) -> Option<&[String]> {
|
||||
// self.artists.as_deref()
|
||||
// }
|
||||
pub fn year(&self) -> Option<i32> {
|
||||
self.year
|
||||
}
|
||||
pub fn album_title(&self) -> Option<&str> {
|
||||
self.album_title.as_deref()
|
||||
}
|
||||
// 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
|
||||
}
|
||||
pub fn disc_number(&self) -> Option<u16> {
|
||||
self.track_number
|
||||
}
|
||||
pub fn total_discs(&self) -> Option<u16> {
|
||||
self.total_tracks
|
||||
}
|
||||
}
|
||||
|
||||
pub trait TagIo {
|
||||
fn read_from_path(path: &str) -> StdResult<AnyTag, BoxedError>;
|
||||
fn write_to_path(path: &str) -> StdResult<(), BoxedError>;
|
||||
}
|
||||
|
||||
// impl<'a> AnyTag<'a> {
|
||||
// fn read_from_path<>
|
||||
// }
|
||||
|
||||
fn read_from_path<T>(path: &str) -> StdResult<AnyTag, BoxedError>
|
||||
where
|
||||
T: TagIo,
|
||||
{
|
||||
T::read_from_path(path)
|
||||
}
|
||||
|
||||
// Implementors of this trait are able to read and write audio metadata.
|
||||
//
|
||||
// Constructor methods e.g. `from_file` should be implemented separately.
|
||||
// pub trait AudioTagsIo {
|
||||
// fn title(&self) -> Option<Cow<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: title.to_owned(),
|
||||
// artist: self.album_artist().map(|x| x.to_owned()),
|
||||
// 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();
|
||||
// }
|
||||
|
||||
// fn album_title(&self) -> Option<&str>;
|
||||
// fn set_album_title(&mut self, v: &str);
|
||||
// fn remove_album_title(&mut self);
|
||||
|
||||
// fn album_artist(&self) -> Option<&str>;
|
||||
// fn set_album_artist(&mut self, v: &str);
|
||||
// fn remove_album_artist(&mut self);
|
||||
|
||||
// fn album_cover(&self) -> Option<Picture>;
|
||||
// fn set_album_cover(&mut self, cover: Picture);
|
||||
// fn remove_album_cover(&mut self);
|
||||
|
||||
// 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();
|
||||
// }
|
||||
|
||||
// fn track_number(&self) -> Option<u16>;
|
||||
// fn set_track_number(&mut self, track_number: u16);
|
||||
// fn remove_track_number(&mut self);
|
||||
|
||||
// fn total_tracks(&self) -> Option<u16>;
|
||||
// fn set_total_tracks(&mut self, total_track: u16);
|
||||
// fn remove_total_tracks(&mut self);
|
||||
|
||||
// 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);
|
||||
|
||||
// fn write_to(&mut self, file: &mut File) -> Result<(), BoxedError>;
|
||||
// // cannot use impl AsRef<Path>
|
||||
// fn write_to_path(&mut self, path: &str) -> Result<(), BoxedError>;
|
||||
// }
|
||||
|
||||
// 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),
|
||||
// }
|
||||
|
|
334
src/mp4_tag.rs
334
src/mp4_tag.rs
|
@ -1,157 +1,213 @@
|
|||
use super::*;
|
||||
use mp4ameta;
|
||||
|
||||
pub(crate) struct Mp4Tag {
|
||||
inner: mp4ameta::Tag,
|
||||
pub type Mp4Tag = mp4ameta::Tag;
|
||||
|
||||
impl<'a> From<&'a mp4ameta::Tag> for AnyTag<'a> {
|
||||
fn from(inp: &'a mp4ameta::Tag) -> Self {
|
||||
let mut t = Self::default();
|
||||
t.title = inp.title().map(Cow::borrowed);
|
||||
// artist
|
||||
if let Some(Ok(y)) = inp.year().map(|y| y.parse()) {
|
||||
t.year = Some(y);
|
||||
}
|
||||
t.album_title = inp.album().map(Cow::borrowed);
|
||||
// album_artist
|
||||
if let Some(Ok(img)) = inp.artwork().map(|a| a.try_into()) {
|
||||
t.album_cover = Some(img);
|
||||
}
|
||||
let (a, b) = inp.track();
|
||||
t.track_number = a;
|
||||
t.total_tracks = b;
|
||||
let (a, b) = inp.disc();
|
||||
t.disc_number = a;
|
||||
t.total_discs = b;
|
||||
t
|
||||
}
|
||||
}
|
||||
|
||||
impl Mp4Tag {
|
||||
pub fn read_from_path(path: impl AsRef<Path>) -> Result<Self, BoxedError> {
|
||||
Ok(Self {
|
||||
inner: mp4ameta::Tag::read_from_path(path)?,
|
||||
impl<'a> From<AnyTag<'a>> for mp4ameta::Tag {
|
||||
fn from(inp: AnyTag<'a>) -> Self {
|
||||
let mut t = mp4ameta::Tag::default();
|
||||
inp.title().map(|v| t.set_title(v));
|
||||
inp.year.map(|v| t.set_year(v.to_string()));
|
||||
inp.album_title().map(|v| t.set_album(v));
|
||||
inp.track_number().map(|v| t.set_track_number(v));
|
||||
inp.total_tracks().map(|v| t.set_total_tracks(v));
|
||||
inp.disc_number().map(|v| t.set_disc_number(v));
|
||||
inp.total_discs().map(|v| t.set_total_discs(v));
|
||||
t
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> std::convert::TryFrom<&'a mp4ameta::Data> for Picture<'a> {
|
||||
type Error = crate::Error;
|
||||
fn try_from(inp: &'a mp4ameta::Data) -> Result<Self> {
|
||||
Ok(match *inp {
|
||||
mp4ameta::Data::Png(ref data) => Self {
|
||||
data: Cow::borrowed(data),
|
||||
mime_type: MimeType::Png,
|
||||
},
|
||||
mp4ameta::Data::Jpeg(ref data) => Self {
|
||||
data: Cow::borrowed(data),
|
||||
mime_type: MimeType::Jpeg,
|
||||
},
|
||||
_ => return Err(Error::NotAPicture),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl AudioTagsIo for Mp4Tag {
|
||||
fn title(&self) -> Option<&str> {
|
||||
self.inner.title()
|
||||
}
|
||||
fn set_title(&mut self, title: &str) {
|
||||
self.inner.set_title(title)
|
||||
}
|
||||
// pub(crate) struct Mp4Tag {
|
||||
// inner: mp4ameta::Tag,
|
||||
// }
|
||||
|
||||
fn artist(&self) -> Option<&str> {
|
||||
self.inner.artist()
|
||||
}
|
||||
fn set_artist(&mut self, artist: &str) {
|
||||
self.inner.set_artist(artist)
|
||||
}
|
||||
// impl Mp4Tag {
|
||||
// pub fn read_from_path(path: impl AsRef<Path>) -> Result<Self, BoxedError> {
|
||||
// Ok(Self {
|
||||
// inner: mp4ameta::Tag::read_from_path(path)?,
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
|
||||
fn year(&self) -> Option<i32> {
|
||||
match self.inner.year() {
|
||||
Some(year) => str::parse(year).ok(),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
fn set_year(&mut self, year: i32) {
|
||||
self.inner.set_year(year.to_string())
|
||||
}
|
||||
// impl AudioTagsIo for Mp4Tag {
|
||||
// fn title(&self) -> Option<&str> {
|
||||
// self.inner.title()
|
||||
// }
|
||||
// fn set_title(&mut self, title: &str) {
|
||||
// self.inner.set_title(title)
|
||||
// }
|
||||
|
||||
fn album_title(&self) -> Option<&str> {
|
||||
self.inner.album()
|
||||
}
|
||||
fn set_album_title(&mut self, v: &str) {
|
||||
self.inner.set_album(v)
|
||||
}
|
||||
// fn artist(&self) -> Option<&str> {
|
||||
// self.inner.artist()
|
||||
// }
|
||||
// fn set_artist(&mut self, artist: &str) {
|
||||
// self.inner.set_artist(artist)
|
||||
// }
|
||||
|
||||
fn album_artist(&self) -> Option<&str> {
|
||||
self.inner.album_artist()
|
||||
}
|
||||
fn set_album_artist(&mut self, v: &str) {
|
||||
self.inner.set_album_artist(v)
|
||||
}
|
||||
// fn year(&self) -> Option<i32> {
|
||||
// match self.inner.year() {
|
||||
// Some(year) => str::parse(year).ok(),
|
||||
// None => None,
|
||||
// }
|
||||
// }
|
||||
// fn set_year(&mut self, year: i32) {
|
||||
// self.inner.set_year(year.to_string())
|
||||
// }
|
||||
|
||||
fn album_cover(&self) -> Option<Picture> {
|
||||
use mp4ameta::Data::*;
|
||||
if let Some(Some(pic)) = self.inner.artwork().map(|data| match data {
|
||||
Jpeg(d) => Some(Picture {
|
||||
data: d.clone(),
|
||||
mime_type: MimeType::Jpeg,
|
||||
}),
|
||||
Png(d) => Some(Picture {
|
||||
data: d.clone(),
|
||||
mime_type: MimeType::Png,
|
||||
}),
|
||||
_ => None,
|
||||
}) {
|
||||
Some(pic)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
fn set_album_cover(&mut self, cover: Picture) {
|
||||
self.remove_album_cover();
|
||||
self.inner.add_artwork(match cover.mime_type {
|
||||
MimeType::Png => mp4ameta::Data::Png(cover.data),
|
||||
MimeType::Jpeg => mp4ameta::Data::Jpeg(cover.data),
|
||||
_ => panic!("Only png and jpeg are supported in m4a"),
|
||||
});
|
||||
}
|
||||
// fn album_title(&self) -> Option<&str> {
|
||||
// self.inner.album()
|
||||
// }
|
||||
// fn set_album_title(&mut self, v: &str) {
|
||||
// self.inner.set_album(v)
|
||||
// }
|
||||
|
||||
fn track_number(&self) -> Option<u16> {
|
||||
self.inner.track_number()
|
||||
}
|
||||
fn total_tracks(&self) -> Option<u16> {
|
||||
self.inner.total_tracks()
|
||||
}
|
||||
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 album_artist(&self) -> Option<&str> {
|
||||
// self.inner.album_artist()
|
||||
// }
|
||||
// fn set_album_artist(&mut self, v: &str) {
|
||||
// self.inner.set_album_artist(v)
|
||||
// }
|
||||
|
||||
fn disc_number(&self) -> Option<u16> {
|
||||
self.inner.disc_number()
|
||||
}
|
||||
fn total_discs(&self) -> Option<u16> {
|
||||
self.inner.total_discs()
|
||||
}
|
||||
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 album_cover(&self) -> Option<Picture> {
|
||||
// use mp4ameta::Data::*;
|
||||
// if let Some(Some(pic)) = self.inner.artwork().map(|data| match data {
|
||||
// Jpeg(d) => Some(Picture {
|
||||
// data: d.clone(),
|
||||
// mime_type: MimeType::Jpeg,
|
||||
// }),
|
||||
// Png(d) => Some(Picture {
|
||||
// data: d.clone(),
|
||||
// mime_type: MimeType::Png,
|
||||
// }),
|
||||
// _ => None,
|
||||
// }) {
|
||||
// Some(pic)
|
||||
// } else {
|
||||
// None
|
||||
// }
|
||||
// }
|
||||
// fn set_album_cover(&mut self, cover: Picture) {
|
||||
// self.remove_album_cover();
|
||||
// self.inner.add_artwork(match cover.mime_type {
|
||||
// MimeType::Png => mp4ameta::Data::Png(cover.data),
|
||||
// MimeType::Jpeg => mp4ameta::Data::Jpeg(cover.data),
|
||||
// _ => panic!("Only png and jpeg are supported in m4a"),
|
||||
// });
|
||||
// }
|
||||
|
||||
fn remove_title(&mut self) {
|
||||
self.inner.remove_title();
|
||||
}
|
||||
fn remove_artist(&mut self) {
|
||||
self.inner.remove_data(mp4ameta::atom::ARTIST);
|
||||
self.inner.remove_artists();
|
||||
}
|
||||
fn remove_year(&mut self) {
|
||||
self.inner.remove_year();
|
||||
}
|
||||
fn remove_album_title(&mut self) {
|
||||
self.inner.remove_album();
|
||||
}
|
||||
fn remove_album_artist(&mut self) {
|
||||
self.inner.remove_data(mp4ameta::atom::ALBUM_ARTIST);
|
||||
self.inner.remove_album_artists();
|
||||
}
|
||||
fn remove_album_cover(&mut self) {
|
||||
self.inner.remove_artwork();
|
||||
}
|
||||
fn remove_track(&mut self) {
|
||||
self.inner.remove_track(); // faster than removing separately
|
||||
}
|
||||
fn remove_track_number(&mut self) {
|
||||
// self.inner.remove_data(mp4ameta::atom::TRACK_NUMBER); not correct
|
||||
// TODO: self.inner.remove_track_number();
|
||||
}
|
||||
fn remove_total_tracks(&mut self) {
|
||||
// TODO: self.inner.remove_total_tracks();
|
||||
}
|
||||
fn remove_disc(&mut self) {
|
||||
self.inner.remove_disc();
|
||||
}
|
||||
fn remove_disc_number(&mut self) {
|
||||
// self.inner.remove_data(mp4ameta::atom::DISC_NUMBER); not correct
|
||||
// TODO: self.inner.remove_disc_number();
|
||||
}
|
||||
fn remove_total_discs(&mut self) {
|
||||
// TODO: self.inner.remove_total_discs();
|
||||
}
|
||||
// fn track_number(&self) -> Option<u16> {
|
||||
// self.inner.track_number()
|
||||
// }
|
||||
// fn total_tracks(&self) -> Option<u16> {
|
||||
// self.inner.total_tracks()
|
||||
// }
|
||||
// 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 write_to(&mut self, file: &mut File) -> Result<(), BoxedError> {
|
||||
self.inner.write_to(file)?;
|
||||
Ok(())
|
||||
}
|
||||
fn write_to_path(&mut self, path: &str) -> Result<(), BoxedError> {
|
||||
self.inner.write_to_path(path)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
// fn disc_number(&self) -> Option<u16> {
|
||||
// self.inner.disc_number()
|
||||
// }
|
||||
// fn total_discs(&self) -> Option<u16> {
|
||||
// self.inner.total_discs()
|
||||
// }
|
||||
// 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 remove_title(&mut self) {
|
||||
// self.inner.remove_title();
|
||||
// }
|
||||
// fn remove_artist(&mut self) {
|
||||
// self.inner.remove_data(mp4ameta::atom::ARTIST);
|
||||
// self.inner.remove_artists();
|
||||
// }
|
||||
// fn remove_year(&mut self) {
|
||||
// self.inner.remove_year();
|
||||
// }
|
||||
// fn remove_album_title(&mut self) {
|
||||
// self.inner.remove_album();
|
||||
// }
|
||||
// fn remove_album_artist(&mut self) {
|
||||
// self.inner.remove_data(mp4ameta::atom::ALBUM_ARTIST);
|
||||
// self.inner.remove_album_artists();
|
||||
// }
|
||||
// fn remove_album_cover(&mut self) {
|
||||
// self.inner.remove_artwork();
|
||||
// }
|
||||
// fn remove_track(&mut self) {
|
||||
// self.inner.remove_track(); // faster than removing separately
|
||||
// }
|
||||
// fn remove_track_number(&mut self) {
|
||||
// // self.inner.remove_data(mp4ameta::atom::TRACK_NUMBER); not correct
|
||||
// // TODO: self.inner.remove_track_number();
|
||||
// }
|
||||
// fn remove_total_tracks(&mut self) {
|
||||
// // TODO: self.inner.remove_total_tracks();
|
||||
// }
|
||||
// fn remove_disc(&mut self) {
|
||||
// self.inner.remove_disc();
|
||||
// }
|
||||
// fn remove_disc_number(&mut self) {
|
||||
// // self.inner.remove_data(mp4ameta::atom::DISC_NUMBER); not correct
|
||||
// // TODO: self.inner.remove_disc_number();
|
||||
// }
|
||||
// fn remove_total_discs(&mut self) {
|
||||
// // TODO: self.inner.remove_total_discs();
|
||||
// }
|
||||
|
||||
// fn write_to(&mut self, file: &mut File) -> Result<(), BoxedError> {
|
||||
// self.inner.write_to(file)?;
|
||||
// Ok(())
|
||||
// }
|
||||
// fn write_to_path(&mut self, path: &str) -> Result<(), BoxedError> {
|
||||
// self.inner.write_to_path(path)?;
|
||||
// Ok(())
|
||||
// }
|
||||
// }
|
||||
|
|
0
tests/conv.rs
Normal file
0
tests/conv.rs
Normal file
92
tests/io.rs
92
tests/io.rs
|
@ -1,54 +1,54 @@
|
|||
use audiotags::{MimeType, Picture, Tag};
|
||||
// use audiotags::{MimeType, Picture, Tag};
|
||||
|
||||
macro_rules! test_file {
|
||||
( $function:ident, $file:expr ) => {
|
||||
#[test]
|
||||
fn $function() {
|
||||
let mut tags = Tag::default().read_from_path($file).unwrap();
|
||||
tags.set_title("foo title");
|
||||
assert_eq!(tags.title(), Some("foo title"));
|
||||
tags.remove_title();
|
||||
assert!(tags.title().is_none());
|
||||
tags.remove_title(); // should not panic
|
||||
// macro_rules! test_file {
|
||||
// ( $function:ident, $file:expr ) => {
|
||||
// #[test]
|
||||
// fn $function() {
|
||||
// let mut tags = Tag::default().read_from_path($file).unwrap();
|
||||
// tags.set_title("foo title");
|
||||
// assert_eq!(tags.title(), Some("foo title"));
|
||||
// tags.remove_title();
|
||||
// assert!(tags.title().is_none());
|
||||
// tags.remove_title(); // should not panic
|
||||
|
||||
tags.set_artist("foo artist");
|
||||
assert_eq!(tags.artist(), Some("foo artist"));
|
||||
tags.remove_artist();
|
||||
assert!(tags.artist().is_none());
|
||||
tags.remove_artist();
|
||||
// tags.set_artist("foo artist");
|
||||
// assert_eq!(tags.artist(), Some("foo artist"));
|
||||
// tags.remove_artist();
|
||||
// assert!(tags.artist().is_none());
|
||||
// tags.remove_artist();
|
||||
|
||||
tags.set_year(2020);
|
||||
assert_eq!(tags.year(), Some(2020));
|
||||
tags.remove_year();
|
||||
assert!(tags.year().is_none());
|
||||
tags.remove_year();
|
||||
// tags.set_year(2020);
|
||||
// assert_eq!(tags.year(), Some(2020));
|
||||
// tags.remove_year();
|
||||
// assert!(tags.year().is_none());
|
||||
// tags.remove_year();
|
||||
|
||||
tags.set_album_title("foo album title");
|
||||
assert_eq!(tags.album_title(), Some("foo album title"));
|
||||
tags.remove_album_title();
|
||||
assert!(tags.album_title().is_none());
|
||||
tags.remove_album_title();
|
||||
// tags.set_album_title("foo album title");
|
||||
// assert_eq!(tags.album_title(), Some("foo album title"));
|
||||
// tags.remove_album_title();
|
||||
// assert!(tags.album_title().is_none());
|
||||
// tags.remove_album_title();
|
||||
|
||||
tags.set_album_artist("foo album artist");
|
||||
assert_eq!(tags.album_artist(), Some("foo album artist"));
|
||||
tags.remove_album_artist();
|
||||
assert!(tags.album_artist().is_none());
|
||||
tags.remove_album_artist();
|
||||
// tags.set_album_artist("foo album artist");
|
||||
// assert_eq!(tags.album_artist(), Some("foo album artist"));
|
||||
// tags.remove_album_artist();
|
||||
// assert!(tags.album_artist().is_none());
|
||||
// tags.remove_album_artist();
|
||||
|
||||
let cover = Picture {
|
||||
mime_type: MimeType::Jpeg,
|
||||
data: vec![0u8; 10],
|
||||
};
|
||||
// let cover = Picture {
|
||||
// mime_type: MimeType::Jpeg,
|
||||
// data: vec![0u8; 10],
|
||||
// };
|
||||
|
||||
tags.set_album_cover(cover.clone());
|
||||
assert_eq!(tags.album_cover(), Some(cover));
|
||||
tags.remove_album_cover();
|
||||
assert!(tags.album_cover().is_none());
|
||||
tags.remove_album_cover();
|
||||
}
|
||||
};
|
||||
}
|
||||
// tags.set_album_cover(cover.clone());
|
||||
// assert_eq!(tags.album_cover(), Some(cover));
|
||||
// tags.remove_album_cover();
|
||||
// assert!(tags.album_cover().is_none());
|
||||
// tags.remove_album_cover();
|
||||
// }
|
||||
// };
|
||||
// }
|
||||
|
||||
test_file!(test_mp3, "assets/a.mp3");
|
||||
test_file!(test_m4a, "assets/a.m4a");
|
||||
test_file!(test_flac, "assets/a.flac");
|
||||
// test_file!(test_mp3, "assets/a.mp3");
|
||||
// test_file!(test_m4a, "assets/a.m4a");
|
||||
// test_file!(test_flac, "assets/a.flac");
|
||||
|
|
Loading…
Reference in a new issue