mirror of
https://github.com/Serial-ATA/lofty-rs
synced 2024-11-10 06:34:18 +00:00
config
This commit is contained in:
parent
d2d1bedafa
commit
6abb1e9d4d
12 changed files with 272 additions and 226 deletions
|
@ -1,3 +1,7 @@
|
|||
## [0.2.5] 2020-10-27
|
||||
|
||||
- Naive implementation of config
|
||||
|
||||
## [0.2.3] 2020-10-27
|
||||
|
||||
- multiple artists
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "audiotags"
|
||||
version = "0.2.3"
|
||||
version = "0.2.5"
|
||||
authors = ["Tianyi <ShiTianyi2001@outlook.com>"]
|
||||
edition = "2018"
|
||||
description = "Unified IO for different types of audio metadata"
|
||||
|
@ -15,3 +15,4 @@ mp4ameta = "0.6"
|
|||
metaflac = "0.2"
|
||||
beef = "0.4.4"
|
||||
thiserror = "1.0.21"
|
||||
audiotags-dev-macro = {path = "./audiotags-dev-macro"}
|
|
@ -100,8 +100,10 @@ fn main() {
|
|||
Some(vec!["artist1 of mp4", "artist2 of mp4"])
|
||||
);
|
||||
// convert to id3 tag, which does not support multiple artists
|
||||
let mp3tag = mp4tag.into_tag(TagType::Id3v2);
|
||||
assert_eq!(mp3tag.artist(), Some("artist1 of mp4;artist2 of mp4"));
|
||||
let mp3tag = mp4tag
|
||||
.with_config(Config::default().sep_artist("/")) // separator is by default `;`
|
||||
.into_tag(TagType::Id3v2);
|
||||
assert_eq!(mp3tag.artist(), Some("artist1 of mp4/artist2 of mp4"));
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -116,7 +118,7 @@ fn main() {
|
|||
## Getters and Setters
|
||||
|
||||
```rust
|
||||
pub trait AudioTagIo {
|
||||
pub trait AudioTag {
|
||||
fn title(&self) -> Option<&str>;
|
||||
fn set_title(&mut self, title: &str);
|
||||
fn remove_title(&mut self);
|
||||
|
|
11
audiotags-dev-macro/.gitignore
vendored
Normal file
11
audiotags-dev-macro/.gitignore
vendored
Normal file
|
@ -0,0 +1,11 @@
|
|||
# Generated by Cargo
|
||||
# will have compiled files and executables
|
||||
/target/
|
||||
|
||||
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
|
||||
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
|
||||
Cargo.lock
|
||||
|
||||
# These are backup files generated by rustfmt
|
||||
**/*.rs.bk
|
||||
|
9
audiotags-dev-macro/Cargo.toml
Normal file
9
audiotags-dev-macro/Cargo.toml
Normal file
|
@ -0,0 +1,9 @@
|
|||
[package]
|
||||
name = "audiotags-dev-macro"
|
||||
version = "0.1.0"
|
||||
authors = ["Tianyi <ShiTianyi2001@outlook.com>"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
35
audiotags-dev-macro/src/lib.rs
Normal file
35
audiotags-dev-macro/src/lib.rs
Normal file
|
@ -0,0 +1,35 @@
|
|||
#[macro_export]
|
||||
macro_rules! impl_tag {
|
||||
($tag:ident , $inner:ident) => {
|
||||
#[derive(Default)]
|
||||
pub struct $tag {
|
||||
inner: $inner,
|
||||
config: Config,
|
||||
}
|
||||
impl $tag {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
pub fn read_from_path(path: impl AsRef<Path>) -> crate::Result<Self> {
|
||||
Ok(Self {
|
||||
inner: $inner::read_from_path(path)?,
|
||||
config: Config::default(),
|
||||
})
|
||||
}
|
||||
}
|
||||
impl AudioTagCommon for $tag {
|
||||
fn config(&self) -> &Config {
|
||||
&self.config
|
||||
}
|
||||
fn with_config(&self, config: Config) -> Box<dyn AudioTag> {
|
||||
Box::new(Self {
|
||||
inner: self.inner.clone(),
|
||||
config,
|
||||
})
|
||||
}
|
||||
fn into_anytag(&self) -> AnyTag<'_> {
|
||||
self.into()
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
27
src/config.rs
Normal file
27
src/config.rs
Normal file
|
@ -0,0 +1,27 @@
|
|||
#[derive(Clone, Copy)]
|
||||
pub struct Config {
|
||||
/// The separator used when parsing and formatting multiple artists in metadata formats that does not explicitly support
|
||||
/// multiple artists (i.e. artist is a single string separated by the separator)
|
||||
pub sep_artist: &'static str,
|
||||
/// Parse multiple artists from a single string using the separator specified above
|
||||
pub parse_multiple_artists: bool,
|
||||
}
|
||||
|
||||
impl<'a> Default for Config {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
sep_artist: ";",
|
||||
parse_multiple_artists: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Config {
|
||||
pub fn sep_artist(mut self, sep: &'static str) -> Self {
|
||||
self.sep_artist = sep;
|
||||
self
|
||||
}
|
||||
pub fn parse_multiple_artists(mut self, parse_multiple_artists: bool) -> Self {
|
||||
self.parse_multiple_artists = parse_multiple_artists;
|
||||
self
|
||||
}
|
||||
}
|
|
@ -1,9 +1,8 @@
|
|||
use super::*;
|
||||
use metaflac;
|
||||
use metaflac::Tag as InnerTag;
|
||||
|
||||
pub struct FlacTag {
|
||||
inner: metaflac::Tag,
|
||||
}
|
||||
impl_tag!(FlacTag, InnerTag);
|
||||
|
||||
impl<'a> From<AnyTag<'a>> for FlacTag {
|
||||
fn from(inp: AnyTag<'a>) -> Self {
|
||||
|
@ -13,7 +12,7 @@ impl<'a> From<AnyTag<'a>> for FlacTag {
|
|||
.map(|i| {
|
||||
i.iter().fold(String::new(), |mut v, a| {
|
||||
v.push_str(&a);
|
||||
v.push_str(SEP_ARTIST);
|
||||
v.push_str(inp.config.sep_artist);
|
||||
v
|
||||
})
|
||||
})
|
||||
|
@ -24,7 +23,7 @@ impl<'a> From<AnyTag<'a>> for FlacTag {
|
|||
.map(|i| {
|
||||
i.iter().fold(String::new(), |mut v, a| {
|
||||
v.push_str(&a);
|
||||
v.push_str(SEP_ARTIST);
|
||||
v.push_str(inp.config.sep_artist);
|
||||
v
|
||||
})
|
||||
})
|
||||
|
@ -41,10 +40,14 @@ impl<'a> From<&'a FlacTag> for AnyTag<'a> {
|
|||
fn from(inp: &'a FlacTag) -> Self {
|
||||
let mut t = Self::default();
|
||||
t.title = inp.title().map(Cow::borrowed);
|
||||
t.artists = inp.artist().map(|v| vec![Cow::borrowed(v)]);
|
||||
t.artists = inp
|
||||
.artists()
|
||||
.map(|i| i.into_iter().map(Cow::borrowed).collect::<Vec<_>>());
|
||||
t.year = inp.year();
|
||||
t.album_title = inp.album_title().map(Cow::borrowed);
|
||||
t.album_artists = inp.album_artist().map(|v| vec![Cow::borrowed(v)]);
|
||||
t.album_artists = inp
|
||||
.album_artists()
|
||||
.map(|i| i.into_iter().map(Cow::borrowed).collect::<Vec<_>>());
|
||||
t.album_cover = inp.album_cover();
|
||||
t.track_number = inp.track_number();
|
||||
t.total_tracks = inp.total_tracks();
|
||||
|
@ -54,20 +57,7 @@ impl<'a> From<&'a FlacTag> for AnyTag<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
impl Default for FlacTag {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
inner: metaflac::Tag::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FlacTag {
|
||||
pub fn read_from_path(path: impl AsRef<Path>) -> crate::Result<Self> {
|
||||
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() {
|
||||
|
@ -87,10 +77,7 @@ impl FlacTag {
|
|||
}
|
||||
}
|
||||
|
||||
impl AudioTagIo for FlacTag {
|
||||
fn into_anytag(&self) -> AnyTag<'_> {
|
||||
self.into()
|
||||
}
|
||||
impl AudioTag for FlacTag {
|
||||
fn title(&self) -> Option<&str> {
|
||||
self.get_first("TITLE")
|
||||
}
|
||||
|
|
137
src/id3_tag.rs
137
src/id3_tag.rs
|
@ -1,94 +1,67 @@
|
|||
use super::*;
|
||||
use id3;
|
||||
|
||||
pub struct Id3v2Tag {
|
||||
inner: id3::Tag,
|
||||
}
|
||||
use id3::Tag as InnerTag;
|
||||
|
||||
impl Default for Id3v2Tag {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
inner: id3::Tag::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Id3v2Tag {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
inner: id3::Tag::default(),
|
||||
}
|
||||
}
|
||||
pub fn read_from_path(path: impl AsRef<Path>) -> crate::Result<Self> {
|
||||
Ok(Self {
|
||||
inner: id3::Tag::read_from_path(path)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
impl_tag!(Id3v2Tag, InnerTag);
|
||||
|
||||
impl<'a> From<&'a Id3v2Tag> for AnyTag<'a> {
|
||||
fn from(inp: &'a Id3v2Tag) -> Self {
|
||||
(&inp.inner).into()
|
||||
Self {
|
||||
config: inp.config.clone(),
|
||||
|
||||
title: inp.title().map(Cow::borrowed),
|
||||
artists: inp
|
||||
.artists()
|
||||
.map(|i| i.into_iter().map(Cow::borrowed).collect::<Vec<_>>()),
|
||||
year: inp.year(),
|
||||
album_title: inp.album_title().map(Cow::borrowed),
|
||||
album_artists: inp
|
||||
.album_artists()
|
||||
.map(|i| i.into_iter().map(Cow::borrowed).collect::<Vec<_>>()),
|
||||
album_cover: inp.album_cover(),
|
||||
track_number: inp.track_number(),
|
||||
total_tracks: inp.total_tracks(),
|
||||
disc_number: inp.disc_number(),
|
||||
total_discs: inp.total_discs(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<AnyTag<'a>> for Id3v2Tag {
|
||||
fn from(inp: AnyTag<'a>) -> Self {
|
||||
Self { inner: inp.into() }
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
t.artists = inp.artist().map(|v| vec![Cow::borrowed(v)]);
|
||||
t.year = inp.year();
|
||||
t.album_title = inp.album().map(Cow::borrowed);
|
||||
t.album_artists = inp.album_artist().map(|v| vec![Cow::borrowed(v)]);
|
||||
t.album_cover = inp
|
||||
.pictures()
|
||||
.filter(|&pic| matches!(pic.picture_type, id3::frame::PictureType::CoverFront))
|
||||
.next()
|
||||
.and_then(|pic| Picture::try_from(pic).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.artists()
|
||||
.map(|i| {
|
||||
i.iter().fold(String::new(), |mut v, a| {
|
||||
v.push_str(&a);
|
||||
v.push_str(SEP_ARTIST);
|
||||
v
|
||||
})
|
||||
})
|
||||
.map(|v| t.set_artist(&v[..v.len() - 1]));
|
||||
inp.year.map(|v| t.set_year(v));
|
||||
inp.album_title().map(|v| t.set_album(v));
|
||||
inp.album_artists()
|
||||
.map(|i| {
|
||||
i.iter().fold(String::new(), |mut v, a| {
|
||||
v.push_str(&a);
|
||||
v.push_str(SEP_ARTIST);
|
||||
v
|
||||
})
|
||||
})
|
||||
.map(|v| t.set_album_artist(&v[..v.len() - 1]));
|
||||
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
|
||||
Self {
|
||||
config: inp.config.clone(),
|
||||
inner: {
|
||||
let mut t = id3::Tag::new();
|
||||
inp.title().map(|v| t.set_title(v));
|
||||
inp.artists()
|
||||
.map(|i| {
|
||||
i.iter().fold(String::new(), |mut v, a| {
|
||||
v.push_str(&a);
|
||||
v.push_str(inp.config.sep_artist);
|
||||
v
|
||||
})
|
||||
})
|
||||
.map(|v| t.set_artist(&v[..v.len() - 1]));
|
||||
inp.year.map(|v| t.set_year(v));
|
||||
inp.album_title().map(|v| t.set_album(v));
|
||||
inp.album_artists()
|
||||
.map(|i| {
|
||||
i.iter().fold(String::new(), |mut v, a| {
|
||||
v.push_str(&a);
|
||||
v.push_str(inp.config.sep_artist);
|
||||
v
|
||||
})
|
||||
})
|
||||
.map(|v| t.set_album_artist(&v[..v.len() - 1]));
|
||||
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
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -122,7 +95,7 @@ impl<'a> std::convert::TryFrom<id3::frame::Picture> for Picture<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
impl AudioTagIo for Id3v2Tag {
|
||||
impl AudioTag for Id3v2Tag {
|
||||
fn title(&self) -> Option<&str> {
|
||||
self.inner.title()
|
||||
}
|
||||
|
@ -248,10 +221,6 @@ impl AudioTagIo for Id3v2Tag {
|
|||
self.inner.write_to_path(path, id3::Version::Id3v24)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn into_anytag(&self) -> AnyTag<'_> {
|
||||
self.into()
|
||||
}
|
||||
}
|
||||
|
||||
// impl<'a> From<AnyTag<'a>> for Id3Tag {
|
||||
|
|
99
src/lib.rs
99
src/lib.rs
|
@ -107,7 +107,7 @@
|
|||
//! ## Getters and Setters
|
||||
//!
|
||||
//! ```ignore
|
||||
//! pub trait AudioTagIo {
|
||||
//! pub trait AudioTag {
|
||||
//! fn title(&self) -> Option<&str>;
|
||||
//! fn set_title(&mut self, title: &str);
|
||||
//! fn remove_title(&mut self);
|
||||
|
@ -153,6 +153,8 @@
|
|||
//! }
|
||||
//! ```
|
||||
|
||||
pub(crate) use audiotags_dev_macro::*;
|
||||
|
||||
mod id3_tag;
|
||||
pub use id3_tag::Id3v2Tag;
|
||||
mod flac_tag;
|
||||
|
@ -163,6 +165,9 @@ pub use mp4_tag::Mp4Tag;
|
|||
pub mod error;
|
||||
pub use error::{Error, Result};
|
||||
|
||||
pub mod config;
|
||||
pub use config::Config;
|
||||
|
||||
use std::convert::From;
|
||||
use std::fs::File;
|
||||
use std::path::Path;
|
||||
|
@ -209,16 +214,30 @@ impl TagType {
|
|||
#[derive(Default)]
|
||||
pub struct Tag {
|
||||
tag_type: Option<TagType>,
|
||||
config: Config,
|
||||
}
|
||||
|
||||
impl Tag {
|
||||
pub fn with_tag_type(tag_type: TagType) -> Self {
|
||||
Self {
|
||||
tag_type: Some(tag_type),
|
||||
config: Config::default(),
|
||||
}
|
||||
}
|
||||
// pub fn with_config(config: Config) -> Self {
|
||||
// Self {
|
||||
// tag_type: None,
|
||||
// config: config.clone(),
|
||||
// }
|
||||
// }
|
||||
// pub fn with_tag_type_and_config(tag_type: TagType, config: Config) -> Self {
|
||||
// Self {
|
||||
// tag_type: Some(tag_type),
|
||||
// config: config.clone(),
|
||||
// }
|
||||
// }
|
||||
|
||||
pub fn read_from_path(&self, path: impl AsRef<Path>) -> crate::Result<Box<dyn AudioTagIo>> {
|
||||
pub fn read_from_path(&self, path: impl AsRef<Path>) -> crate::Result<Box<dyn AudioTag>> {
|
||||
match self.tag_type.unwrap_or(TagType::try_from_ext(
|
||||
path.as_ref()
|
||||
.extension()
|
||||
|
@ -228,9 +247,15 @@ impl Tag {
|
|||
.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)?)),
|
||||
TagType::Id3v2 => Ok(Box::new(
|
||||
Id3v2Tag::read_from_path(path)?, //).with_config(self.config.clone()),
|
||||
)),
|
||||
TagType::Mp4 => Ok(Box::new(
|
||||
Mp4Tag::read_from_path(path)?, //).with_config(self.config.clone()),
|
||||
)),
|
||||
TagType::Flac => Ok(Box::new(
|
||||
FlacTag::read_from_path(path)?, //.with_config(self.config.clone()),
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -328,10 +353,9 @@ impl<'a> Album<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
const SEP_ARTIST: &'static str = ";";
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct AnyTag<'a> {
|
||||
pub config: Config,
|
||||
pub title: Option<Cow<'a, str>>,
|
||||
pub artists: Option<Vec<Cow<'a, str>>>, // ? iterator
|
||||
pub year: Option<i32>,
|
||||
|
@ -344,7 +368,7 @@ pub struct AnyTag<'a> {
|
|||
pub total_discs: Option<u16>,
|
||||
}
|
||||
|
||||
impl<'a> AnyTag<'a> {
|
||||
impl AnyTag<'_> {
|
||||
pub fn title(&self) -> Option<&str> {
|
||||
self.title.as_deref()
|
||||
}
|
||||
|
@ -374,24 +398,26 @@ impl<'a> AnyTag<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
impl AnyTag<'_> {
|
||||
pub fn artists_as_string(&self) -> Option<String> {
|
||||
self.artists()
|
||||
.map(|artists| artists.join(self.config.sep_artist))
|
||||
}
|
||||
pub fn album_artists_as_string(&self) -> Option<String> {
|
||||
self.album_artists()
|
||||
.map(|artists| artists.join(self.config.sep_artist))
|
||||
}
|
||||
}
|
||||
|
||||
pub trait TagIo {
|
||||
fn read_from_path(path: &str) -> crate::Result<AnyTag>;
|
||||
fn write_to_path(path: &str) -> crate::Result<()>;
|
||||
}
|
||||
|
||||
// impl<'a> AnyTag<'a> {
|
||||
// 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()))),
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
/// 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 {
|
||||
pub trait AudioTag: AudioTagCommon {
|
||||
fn title(&self) -> Option<&str>;
|
||||
fn set_title(&mut self, title: &str);
|
||||
fn remove_title(&mut self);
|
||||
|
@ -401,7 +427,12 @@ pub trait AudioTagIo {
|
|||
fn remove_artist(&mut self);
|
||||
|
||||
fn artists(&self) -> Option<Vec<&str>> {
|
||||
self.artist().map(|v| vec![v])
|
||||
if self.config().parse_multiple_artists {
|
||||
self.artist()
|
||||
.map(|a| a.split(self.config().sep_artist).collect::<Vec<&str>>())
|
||||
} else {
|
||||
self.artist().map(|v| vec![v])
|
||||
}
|
||||
}
|
||||
fn add_artist(&mut self, artist: &str) {
|
||||
self.set_artist(artist);
|
||||
|
@ -446,7 +477,12 @@ pub trait AudioTagIo {
|
|||
fn remove_album_artist(&mut self);
|
||||
|
||||
fn album_artists(&self) -> Option<Vec<&str>> {
|
||||
self.artist().map(|v| vec![v])
|
||||
if self.config().parse_multiple_artists {
|
||||
self.album_artist()
|
||||
.map(|a| a.split(self.config().sep_artist).collect::<Vec<&str>>())
|
||||
} else {
|
||||
self.album_artist().map(|v| vec![v])
|
||||
}
|
||||
}
|
||||
fn add_album_artist(&mut self, artist: &str) {
|
||||
self.set_album_artist(artist);
|
||||
|
@ -499,11 +535,15 @@ pub trait AudioTagIo {
|
|||
fn write_to(&mut self, file: &mut File) -> crate::Result<()>;
|
||||
// cannot use impl AsRef<Path>
|
||||
fn write_to_path(&mut self, path: &str) -> crate::Result<()>;
|
||||
}
|
||||
|
||||
pub trait AudioTagCommon {
|
||||
fn config(&self) -> &Config;
|
||||
fn with_config(&self, config: Config) -> Box<dyn AudioTag>;
|
||||
fn into_anytag(&self) -> AnyTag<'_>;
|
||||
|
||||
/// Convert the tag type, which can be lossy.
|
||||
fn into_tag(&self, tag_type: TagType) -> Box<dyn AudioTagIo> {
|
||||
fn into_tag(&self, tag_type: TagType) -> Box<dyn AudioTag> {
|
||||
match tag_type {
|
||||
TagType::Id3v2 => Box::new(Id3v2Tag::from(self.into_anytag())),
|
||||
TagType::Mp4 => Box::new(Mp4Tag::from(self.into_anytag())),
|
||||
|
@ -519,23 +559,6 @@ pub trait AudioTagIo {
|
|||
// }
|
||||
// }
|
||||
|
||||
// pub trait IntoTag: AudioTagIo {
|
||||
|
||||
// fn into_tag<'a, T>(&'a self) -> T
|
||||
// where T: From<AnyTag<'a> {
|
||||
// self.into_anytag().into()
|
||||
// }
|
||||
// }
|
||||
|
||||
// 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,
|
||||
|
|
122
src/mp4_tag.rs
122
src/mp4_tag.rs
|
@ -1,84 +1,64 @@
|
|||
use super::*;
|
||||
use mp4ameta;
|
||||
|
||||
pub struct Mp4Tag {
|
||||
inner: mp4ameta::Tag,
|
||||
}
|
||||
use mp4ameta::Tag as InnerTag;
|
||||
|
||||
impl Mp4Tag {
|
||||
pub fn read_from_path(path: impl AsRef<Path>) -> crate::Result<Self> {
|
||||
Ok(Self {
|
||||
inner: mp4ameta::Tag::read_from_path(path)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
impl_tag!(Mp4Tag, InnerTag);
|
||||
|
||||
impl<'a> From<&'a Mp4Tag> for AnyTag<'a> {
|
||||
fn from(inp: &'a Mp4Tag) -> Self {
|
||||
(&inp.inner).into()
|
||||
let title = inp.title().map(Cow::borrowed);
|
||||
let artists = inp
|
||||
.artists()
|
||||
.map(|i| i.into_iter().map(Cow::borrowed).collect::<Vec<_>>());
|
||||
let year = inp.year();
|
||||
let album_title = inp.album_title().map(Cow::borrowed);
|
||||
let album_artists = inp
|
||||
.album_artists()
|
||||
.map(|i| i.into_iter().map(Cow::borrowed).collect::<Vec<_>>());
|
||||
let album_cover = inp.album_cover();
|
||||
let (a, b) = inp.track();
|
||||
let track_number = a;
|
||||
let total_tracks = b;
|
||||
let (a, b) = inp.disc();
|
||||
let disc_number = a;
|
||||
let total_discs = b;
|
||||
Self {
|
||||
config: inp.config.clone(),
|
||||
title,
|
||||
artists,
|
||||
year,
|
||||
album_title,
|
||||
album_cover,
|
||||
album_artists,
|
||||
track_number,
|
||||
total_tracks,
|
||||
disc_number,
|
||||
total_discs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<AnyTag<'a>> for Mp4Tag {
|
||||
fn from(inp: AnyTag<'a>) -> Self {
|
||||
Self { inner: inp.into() }
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
let artists = inp.artists().fold(Vec::new(), |mut v, a| {
|
||||
v.push(Cow::borrowed(a));
|
||||
v
|
||||
});
|
||||
t.artists = if artists.len() > 0 {
|
||||
Some(artists)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
if let Some(Ok(y)) = inp.year().map(|y| y.parse()) {
|
||||
t.year = Some(y);
|
||||
Self {
|
||||
config: inp.config.clone(),
|
||||
inner: {
|
||||
let mut t = mp4ameta::Tag::default();
|
||||
inp.title().map(|v| t.set_title(v));
|
||||
inp.artists()
|
||||
.map(|i| i.iter().for_each(|a| t.add_artist(a.as_ref())));
|
||||
inp.year.map(|v| t.set_year(v.to_string()));
|
||||
inp.album_title().map(|v| t.set_album(v));
|
||||
inp.album_artists()
|
||||
.map(|i| i.iter().for_each(|a| t.add_album_artist(a.as_ref())));
|
||||
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
|
||||
},
|
||||
}
|
||||
t.album_title = inp.album().map(Cow::borrowed);
|
||||
let album_artists = inp.album_artists().fold(Vec::new(), |mut v, a| {
|
||||
v.push(Cow::borrowed(a));
|
||||
v
|
||||
});
|
||||
t.album_artists = if album_artists.len() > 0 {
|
||||
Some(album_artists)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
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<'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.artists()
|
||||
.map(|i| i.iter().for_each(|a| t.add_artist(a.as_ref())));
|
||||
inp.year.map(|v| t.set_year(v.to_string()));
|
||||
inp.album_title().map(|v| t.set_album(v));
|
||||
inp.album_artists()
|
||||
.map(|i| i.iter().for_each(|a| t.add_album_artist(a.as_ref())));
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -99,11 +79,7 @@ impl<'a> std::convert::TryFrom<&'a mp4ameta::Data> for Picture<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
impl AudioTagIo for Mp4Tag {
|
||||
fn into_anytag(&self) -> AnyTag<'_> {
|
||||
self.into()
|
||||
}
|
||||
|
||||
impl AudioTag for Mp4Tag {
|
||||
fn title(&self) -> Option<&str> {
|
||||
self.inner.title()
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use audiotags::{Tag, TagType};
|
||||
use audiotags::{Config, Tag, TagType};
|
||||
|
||||
#[test]
|
||||
fn test_convert_mp3_to_mp4() {
|
||||
|
@ -27,6 +27,8 @@ fn test_convert_mp3_to_mp4() {
|
|||
Some(vec!["artist1 of mp4", "artist2 of mp4"])
|
||||
);
|
||||
// convert to id3 tag, which does not support multiple artists
|
||||
let mp3tag = mp4tag.into_tag(TagType::Id3v2);
|
||||
assert_eq!(mp3tag.artist(), Some("artist1 of mp4;artist2 of mp4"));
|
||||
let mp3tag = mp4tag
|
||||
.with_config(Config::default().sep_artist("/")) // separator is by default `;`
|
||||
.into_tag(TagType::Id3v2);
|
||||
assert_eq!(mp3tag.artist(), Some("artist1 of mp4/artist2 of mp4"));
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue