mirror of
https://github.com/Serial-ATA/lofty-rs
synced 2025-03-04 14:57:17 +00:00
Initial work
Signed-off-by: Serial <69764315+Serial-ATA@users.noreply.github.com>
This commit is contained in:
parent
0ac19f09b4
commit
555819e6a8
19 changed files with 1572 additions and 1163 deletions
24
Cargo.toml
24
Cargo.toml
|
@ -1,21 +1,25 @@
|
|||
[package]
|
||||
name = "audiotags"
|
||||
name = "lofty"
|
||||
version = "0.2.7182"
|
||||
authors = ["Tianyi <ShiTianyi2001@outlook.com>"]
|
||||
authors = ["Serial <69764315+Serial-ATA@users.noreply.github.com>", "Tianyi <ShiTianyi2001@outlook.com>"]
|
||||
edition = "2018"
|
||||
description = "Unified IO for different types of audio metadata"
|
||||
description = "Fork of https://github.com/TianyiShi2001/audiotags, adding support for more file types and (optionally) duration"
|
||||
license = "MIT"
|
||||
repository = "https://github.com/TianyiShi2001/audiotags"
|
||||
repository = "https://github.com/Serial-ATA/lofty"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
id3 = "0.5.1"
|
||||
mp4ameta = "0.6"
|
||||
metaflac = "0.2"
|
||||
thiserror = "1.0.21"
|
||||
opus_headers = {version = "0.1.2", optional = true}
|
||||
lewton = {version = "0.10.2", optional = true}
|
||||
metaflac = {version = "0.2.4", optional = true}
|
||||
mp4ameta = {version = "0.9.1", optional = true}
|
||||
id3 = {version = "0.6.2", optional = true}
|
||||
mp3-duration = {version = "0.1.10", optional = true}
|
||||
thiserror = "1.0.24"
|
||||
audiotags-dev-macro = {path = "./audiotags-dev-macro", version = "0.1.4"}
|
||||
|
||||
[features]
|
||||
defualt = ['from']
|
||||
from = []
|
||||
default = ["tags", "duration"]
|
||||
tags = ["opus_headers", "lewton", "metaflac", "mp4ameta", "id3"]
|
||||
duration = ["mp3-duration"]
|
BIN
assets/a.m4a
BIN
assets/a.m4a
Binary file not shown.
BIN
assets/a.mp3
BIN
assets/a.mp3
Binary file not shown.
|
@ -1,95 +1,80 @@
|
|||
#[macro_export]
|
||||
macro_rules! impl_audiotag_config {
|
||||
($tag:ident) => {
|
||||
impl AudioTagConfig for $tag {
|
||||
fn config(&self) -> &Config {
|
||||
&self.config
|
||||
}
|
||||
fn set_config(&mut self, config: Config) {
|
||||
self.config = config.clone();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! impl_tag {
|
||||
($tag:ident , $inner:ident, $tag_type:expr) => {
|
||||
#[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_audiotag_config!($tag);
|
||||
($tag:ident , $inner:ident, $tag_type:expr) => {
|
||||
pub struct $tag($inner);
|
||||
|
||||
use std::any::Any;
|
||||
impl Default for $tag {
|
||||
fn default() -> Self {
|
||||
Self($inner::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl ToAnyTag for $tag {
|
||||
fn to_anytag(&self) -> AnyTag<'_> {
|
||||
self.into()
|
||||
}
|
||||
}
|
||||
impl $tag {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
pub fn read_from_path<P>(path: P) -> crate::Result<Self>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
Ok(Self($inner::read_from_path(path)?))
|
||||
}
|
||||
}
|
||||
|
||||
impl ToAny for $tag {
|
||||
fn to_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
fn to_any_mut(&mut self) -> &mut dyn Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
use std::any::Any;
|
||||
|
||||
impl AudioTag for $tag {}
|
||||
impl ToAnyTag for $tag {
|
||||
fn to_anytag(&self) -> AnyTag<'_> {
|
||||
self.into()
|
||||
}
|
||||
}
|
||||
|
||||
// From wrapper to inner (same type)
|
||||
impl From<$tag> for $inner {
|
||||
fn from(inp: $tag) -> Self {
|
||||
inp.inner
|
||||
}
|
||||
}
|
||||
impl ToAny for $tag {
|
||||
fn to_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
fn to_any_mut(&mut self) -> &mut dyn Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
// From inner to wrapper (same type)
|
||||
impl From<$inner> for $tag {
|
||||
fn from(inp: $inner) -> Self {
|
||||
Self {
|
||||
inner: inp,
|
||||
config: Config::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl AudioTag for $tag {}
|
||||
|
||||
// From dyn AudioTag to wrapper (any type)
|
||||
impl From<Box<dyn AudioTag>> for $tag {
|
||||
fn from(inp: Box<dyn AudioTag>) -> Self {
|
||||
let mut inp = inp;
|
||||
if let Some(t_refmut) = inp.to_any_mut().downcast_mut::<$tag>() {
|
||||
let t = std::mem::replace(t_refmut, $tag::new()); // TODO: can we avoid creating the dummy tag?
|
||||
t
|
||||
} else {
|
||||
let mut t = inp.to_dyn_tag($tag_type);
|
||||
let t_refmut = t.to_any_mut().downcast_mut::<$tag>().unwrap();
|
||||
let t = std::mem::replace(t_refmut, $tag::new());
|
||||
t
|
||||
}
|
||||
}
|
||||
}
|
||||
// From dyn AudioTag to inner (any type)
|
||||
impl std::convert::From<Box<dyn AudioTag>> for $inner {
|
||||
fn from(inp: Box<dyn AudioTag>) -> Self {
|
||||
let t: $tag = inp.into();
|
||||
t.into()
|
||||
}
|
||||
}
|
||||
};
|
||||
// From wrapper to inner (same type)
|
||||
impl From<$tag> for $inner {
|
||||
fn from(inp: $tag) -> Self {
|
||||
inp.0
|
||||
}
|
||||
}
|
||||
|
||||
// From inner to wrapper (same type)
|
||||
impl From<$inner> for $tag {
|
||||
fn from(inp: $inner) -> Self {
|
||||
Self(inp)
|
||||
}
|
||||
}
|
||||
|
||||
// From dyn AudioTag to wrapper (any type)
|
||||
impl From<Box<dyn AudioTag>> for $tag {
|
||||
fn from(inp: Box<dyn AudioTag>) -> Self {
|
||||
let mut inp = inp;
|
||||
if let Some(t_refmut) = inp.to_any_mut().downcast_mut::<$tag>() {
|
||||
let t = std::mem::replace(t_refmut, $tag::new()); // TODO: can we avoid creating the dummy tag?
|
||||
t
|
||||
} else {
|
||||
let mut t = inp.to_dyn_tag($tag_type);
|
||||
let t_refmut = t.to_any_mut().downcast_mut::<$tag>().unwrap();
|
||||
let t = std::mem::replace(t_refmut, $tag::new());
|
||||
t
|
||||
}
|
||||
}
|
||||
}
|
||||
// From dyn AudioTag to inner (any type)
|
||||
impl std::convert::From<Box<dyn AudioTag>> for $inner {
|
||||
fn from(inp: Box<dyn AudioTag>) -> Self {
|
||||
let t: $tag = inp.into();
|
||||
t.into()
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
6
rustfmt.toml
Normal file
6
rustfmt.toml
Normal file
|
@ -0,0 +1,6 @@
|
|||
edition = "2018"
|
||||
use_field_init_shorthand = true
|
||||
format_strings = true
|
||||
normalize_comments = true
|
||||
match_block_trailing_comma = true
|
||||
hard_tabs = true
|
120
src/anytag.rs
120
src/anytag.rs
|
@ -2,72 +2,68 @@ use crate::*;
|
|||
|
||||
#[derive(Default)]
|
||||
pub struct AnyTag<'a> {
|
||||
pub config: Config,
|
||||
pub title: Option<&'a str>,
|
||||
pub artists: Option<Vec<&'a str>>,
|
||||
pub year: Option<i32>,
|
||||
pub album_title: Option<&'a str>,
|
||||
pub album_artists: Option<Vec<&'a str>>,
|
||||
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 AudioTagConfig for AnyTag<'_> {
|
||||
fn config(&self) -> &Config {
|
||||
&self.config
|
||||
}
|
||||
fn set_config(&mut self, config: Config) {
|
||||
self.config = config.clone();
|
||||
}
|
||||
pub title: Option<&'a str>,
|
||||
pub artists: Option<Vec<&'a str>>,
|
||||
pub album: Option<&'a str>,
|
||||
pub album_artists: Option<Vec<&'a str>>,
|
||||
pub comments: Option<Vec<&'a str>>,
|
||||
pub year: Option<i32>,
|
||||
pub date: Option<&'a str>,
|
||||
pub track_number: Option<u16>,
|
||||
pub total_tracks: Option<u16>,
|
||||
pub disc_number: Option<u16>,
|
||||
pub total_discs: Option<u16>,
|
||||
pub cover: Option<Picture<'a>>,
|
||||
#[cfg(feature = "duration")]
|
||||
pub duration_ms: Option<u32>,
|
||||
}
|
||||
|
||||
impl<'a> AnyTag<'a> {
|
||||
pub fn title(&self) -> Option<&str> {
|
||||
self.title.as_deref()
|
||||
}
|
||||
pub fn set_title(&mut self, title: &'a str) {
|
||||
self.title = Some(title);
|
||||
}
|
||||
pub fn artists(&self) -> Option<&[&str]> {
|
||||
self.artists.as_deref()
|
||||
}
|
||||
// set_artists; add_artist
|
||||
pub fn year(&self) -> Option<i32> {
|
||||
self.year
|
||||
}
|
||||
pub fn set_year(&mut self, year: i32) {
|
||||
self.year = Some(year);
|
||||
}
|
||||
pub fn album_title(&self) -> Option<&str> {
|
||||
self.album_title.as_deref()
|
||||
}
|
||||
pub fn album_artists(&self) -> Option<&[&str]> {
|
||||
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 fn title(&self) -> Option<&str> {
|
||||
self.title.as_deref()
|
||||
}
|
||||
pub fn set_title(&mut self, title: &'a str) {
|
||||
self.title = Some(title);
|
||||
}
|
||||
pub fn artists(&self) -> Option<&[&str]> {
|
||||
self.artists.as_deref()
|
||||
}
|
||||
// set_artists; add_artist TODO?
|
||||
pub fn year(&self) -> Option<i32> {
|
||||
self.year
|
||||
}
|
||||
pub fn set_year(&mut self, year: i32) {
|
||||
self.year = Some(year);
|
||||
}
|
||||
pub fn album_title(&self) -> Option<&str> {
|
||||
self.album.as_deref()
|
||||
}
|
||||
pub fn album_artists(&self) -> Option<&[&str]> {
|
||||
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
|
||||
}
|
||||
#[cfg(feature = "duration")]
|
||||
pub fn duration(&self) -> Option<u32> {
|
||||
self.duration_ms
|
||||
}
|
||||
}
|
||||
|
||||
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 fn artists_as_string(&self) -> Option<String> {
|
||||
self.artists().map(|artists| artists.join(","))
|
||||
}
|
||||
pub fn album_artists_as_string(&self) -> Option<String> {
|
||||
self.album_artists().map(|artists| artists.join(","))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
mod id3_tag;
|
||||
pub use id3_tag::Id3v2Tag;
|
||||
mod flac_tag;
|
||||
mod id3_tag;
|
||||
mod mp4_tag;
|
||||
mod opus_tag;
|
||||
mod vorbis_tag;
|
||||
|
||||
pub use flac_tag::FlacTag;
|
||||
pub use id3_tag::Id3v2Tag;
|
||||
pub use mp4_tag::Mp4Tag;
|
||||
pub use opus_tag::OpusTag;
|
||||
pub use vorbis_tag::VorbisTag;
|
||||
|
|
|
@ -6,211 +6,211 @@ pub use metaflac::Tag as FlacInnerTag;
|
|||
impl_tag!(FlacTag, FlacInnerTag, TagType::Flac);
|
||||
|
||||
impl<'a> From<AnyTag<'a>> for FlacTag {
|
||||
fn from(inp: AnyTag<'a>) -> Self {
|
||||
let mut t = FlacTag::default();
|
||||
inp.title().map(|v| t.set_title(v));
|
||||
inp.artists_as_string().map(|v| t.set_artist(&v));
|
||||
inp.year.map(|v| t.set_year(v));
|
||||
inp.album_title().map(|v| t.set_album_title(v));
|
||||
inp.album_artists_as_string().map(|v| t.set_artist(&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
|
||||
}
|
||||
fn from(inp: AnyTag<'a>) -> Self {
|
||||
let mut t = FlacTag::default();
|
||||
inp.title().map(|v| t.set_title(v));
|
||||
inp.artists_as_string().map(|v| t.set_artist(&v));
|
||||
inp.year.map(|v| t.set_year(v as u16));
|
||||
inp.album_title().map(|v| t.set_album_title(v));
|
||||
inp.album_artists_as_string().map(|v| t.set_artist(&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> From<&'a FlacTag> for AnyTag<'a> {
|
||||
fn from(inp: &'a FlacTag) -> Self {
|
||||
let mut t = Self::default();
|
||||
t.title = inp.title();
|
||||
t.artists = inp.artists();
|
||||
t.year = inp.year();
|
||||
t.album_title = inp.album_title();
|
||||
t.album_artists = inp.album_artists();
|
||||
t.album_cover = inp.album_cover();
|
||||
t.track_number = inp.track_number();
|
||||
t.total_tracks = inp.total_tracks();
|
||||
t.disc_number = inp.disc_number();
|
||||
t.total_discs = inp.total_discs();
|
||||
t
|
||||
}
|
||||
fn from(inp: &'a FlacTag) -> Self {
|
||||
let mut t = Self::default();
|
||||
t.title = inp.title();
|
||||
t.artists = inp.artists();
|
||||
t.year = inp.year().map(|y| y as i32);
|
||||
t.album = inp.album_title();
|
||||
t.album_artists = inp.album_artists();
|
||||
t.cover = inp.album_cover();
|
||||
t.track_number = inp.track_number();
|
||||
t.total_tracks = inp.total_tracks();
|
||||
t.disc_number = inp.disc_number();
|
||||
t.total_discs = inp.total_discs();
|
||||
t
|
||||
}
|
||||
}
|
||||
|
||||
impl FlacTag {
|
||||
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);
|
||||
}
|
||||
pub fn get_first(&self, key: &str) -> Option<&str> {
|
||||
if let Some(Some(v)) = self.0.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.0.vorbis_comments_mut().set(key, vec![val]);
|
||||
}
|
||||
pub fn remove(&mut self, k: &str) {
|
||||
self.0.vorbis_comments_mut().comments.remove(k);
|
||||
}
|
||||
}
|
||||
|
||||
impl AudioTagEdit for FlacTag {
|
||||
fn title(&self) -> Option<&str> {
|
||||
self.get_first("TITLE")
|
||||
}
|
||||
fn set_title(&mut self, title: &str) {
|
||||
self.set_first("TITLE", title);
|
||||
}
|
||||
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 remove_title(&mut self) {
|
||||
self.remove("TITLE");
|
||||
}
|
||||
fn artist(&self) -> Option<&str> {
|
||||
self.get_first("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 set_artist(&mut self, artist: &str) {
|
||||
self.set_first("ARTIST", artist)
|
||||
}
|
||||
fn remove_artist(&mut self) {
|
||||
self.remove("ARTIST");
|
||||
}
|
||||
|
||||
fn album_title(&self) -> Option<&str> {
|
||||
self.get_first("ALBUM")
|
||||
}
|
||||
fn set_album_title(&mut self, title: &str) {
|
||||
self.set_first("ALBUM", title)
|
||||
}
|
||||
fn year(&self) -> Option<u16> {
|
||||
if let Some(Ok(y)) = self
|
||||
.get_first("DATE")
|
||||
.map(|s| s.chars().take(4).collect::<String>().parse::<i32>())
|
||||
{
|
||||
Some(y as u16)
|
||||
} else if let Some(Ok(y)) = self.get_first("YEAR").map(|s| s.parse::<i32>()) {
|
||||
Some(y as u16)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
fn set_year(&mut self, year: u16) {
|
||||
self.set_first("DATE", &year.to_string());
|
||||
self.set_first("YEAR", &year.to_string());
|
||||
}
|
||||
|
||||
fn album_artist(&self) -> Option<&str> {
|
||||
self.get_first("ALBUMARTIST")
|
||||
}
|
||||
fn set_album_artist(&mut self, v: &str) {
|
||||
self.set_first("ALBUMARTIST", v)
|
||||
}
|
||||
fn remove_year(&mut self) {
|
||||
self.remove("YEAR");
|
||||
self.remove("DATE");
|
||||
}
|
||||
fn album_title(&self) -> Option<&str> {
|
||||
self.get_first("ALBUM")
|
||||
}
|
||||
|
||||
fn album_cover(&self) -> Option<Picture> {
|
||||
self.inner
|
||||
.pictures()
|
||||
.filter(|&pic| matches!(pic.picture_type, metaflac::block::PictureType::CoverFront))
|
||||
.next()
|
||||
.and_then(|pic| {
|
||||
Some(Picture {
|
||||
data: &pic.data,
|
||||
mime_type: (pic.mime_type.as_str()).try_into().ok()?,
|
||||
})
|
||||
})
|
||||
}
|
||||
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).to_owned());
|
||||
}
|
||||
fn set_album_title(&mut self, title: &str) {
|
||||
self.set_first("ALBUM", title)
|
||||
}
|
||||
fn remove_album_title(&mut self) {
|
||||
self.remove("ALBUM");
|
||||
}
|
||||
|
||||
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 album_artist(&self) -> Option<&str> {
|
||||
self.get_first("ALBUMARTIST")
|
||||
}
|
||||
fn set_album_artist(&mut self, v: &str) {
|
||||
self.set_first("ALBUMARTIST", v)
|
||||
}
|
||||
|
||||
// ! 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 remove_album_artist(&mut self) {
|
||||
self.remove("ALBUMARTIST");
|
||||
}
|
||||
fn album_cover(&self) -> Option<Picture> {
|
||||
self.0
|
||||
.pictures()
|
||||
.filter(|&pic| matches!(pic.picture_type, metaflac::block::PictureType::CoverFront))
|
||||
.next()
|
||||
.and_then(|pic| {
|
||||
Some(Picture {
|
||||
data: &pic.data,
|
||||
mime_type: (pic.mime_type.as_str()).try_into().ok()?,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
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 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.0
|
||||
.add_picture(mime, picture_type, (cover.data).to_owned());
|
||||
}
|
||||
fn remove_album_cover(&mut self) {
|
||||
self.0
|
||||
.remove_picture_type(metaflac::block::PictureType::CoverFront)
|
||||
}
|
||||
|
||||
// ! 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 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 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 remove_track_number(&mut self) {
|
||||
self.remove("TRACKNUMBER");
|
||||
}
|
||||
// ! 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 remove_total_tracks(&mut self) {
|
||||
self.remove("TOTALTRACKS");
|
||||
}
|
||||
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 remove_disc_number(&mut self) {
|
||||
self.remove("DISCNUMBER");
|
||||
}
|
||||
// ! 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_total_discs(&mut self) {
|
||||
self.remove("TOTALDISCS");
|
||||
}
|
||||
}
|
||||
|
||||
impl AudioTagWrite for FlacTag {
|
||||
fn write_to(&mut self, file: &mut File) -> crate::Result<()> {
|
||||
self.inner.write_to(file)?;
|
||||
Ok(())
|
||||
}
|
||||
fn write_to_path(&mut self, path: &str) -> crate::Result<()> {
|
||||
self.inner.write_to_path(path)?;
|
||||
Ok(())
|
||||
}
|
||||
fn write_to(&mut self, file: &mut File) -> crate::Result<()> {
|
||||
self.0.write_to(file)?;
|
||||
Ok(())
|
||||
}
|
||||
fn write_to_path(&mut self, path: &str) -> crate::Result<()> {
|
||||
self.0.write_to_path(path)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,218 +6,185 @@ pub use id3::Tag as Id3v2InnerTag;
|
|||
impl_tag!(Id3v2Tag, Id3v2InnerTag, TagType::Id3v2);
|
||||
|
||||
impl<'a> From<&'a Id3v2Tag> for AnyTag<'a> {
|
||||
fn from(inp: &'a Id3v2Tag) -> Self {
|
||||
Self {
|
||||
config: inp.config.clone(),
|
||||
|
||||
title: inp.title(),
|
||||
artists: inp.artists(),
|
||||
year: inp.year(),
|
||||
album_title: inp.album_title(),
|
||||
album_artists: inp.album_artists(),
|
||||
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(),
|
||||
}
|
||||
}
|
||||
fn from(inp: &'a Id3v2Tag) -> Self {
|
||||
Self {
|
||||
title: inp.title(),
|
||||
artists: inp.artists(),
|
||||
year: inp.year().map(|y| y as i32),
|
||||
album: inp.album_title(),
|
||||
album_artists: inp.album_artists(),
|
||||
cover: inp.album_cover(),
|
||||
track_number: inp.track_number(),
|
||||
total_tracks: inp.total_tracks(),
|
||||
disc_number: inp.disc_number(),
|
||||
total_discs: inp.total_discs(),
|
||||
comments: None,
|
||||
date: None, // TODO
|
||||
duration_ms: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<AnyTag<'a>> for Id3v2Tag {
|
||||
fn from(inp: AnyTag<'a>) -> Self {
|
||||
Self {
|
||||
config: inp.config.clone(),
|
||||
inner: {
|
||||
let mut t = id3::Tag::new();
|
||||
inp.title().map(|v| t.set_title(v));
|
||||
inp.artists_as_string().map(|v| t.set_artist(&v));
|
||||
inp.year.map(|v| t.set_year(v));
|
||||
inp.album_title().map(|v| t.set_album(v));
|
||||
inp.album_artists_as_string().map(|v| t.set_artist(&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
|
||||
},
|
||||
}
|
||||
}
|
||||
fn from(inp: AnyTag<'a>) -> Self {
|
||||
let mut tag = Id3v2Tag::new();
|
||||
|
||||
inp.title().map(|v| tag.set_title(v));
|
||||
inp.artists_as_string().map(|v| tag.set_artist(v.as_str()));
|
||||
inp.year.map(|v| tag.set_year(v as u16));
|
||||
inp.album_title().map(|v| tag.set_album_title(v));
|
||||
inp.album_artists_as_string()
|
||||
.map(|v| tag.set_album_artist(v.as_str()));
|
||||
inp.track_number().map(|v| tag.set_track(v as u16));
|
||||
inp.total_tracks().map(|v| tag.set_total_tracks(v as u16));
|
||||
inp.disc_number().map(|v| tag.set_disc(v as u16));
|
||||
inp.total_discs().map(|v| tag.set_total_discs(v as u16));
|
||||
tag
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> std::convert::TryFrom<&'a id3::frame::Picture> for Picture<'a> {
|
||||
type Error = crate::Error;
|
||||
fn try_from(inp: &'a id3::frame::Picture) -> crate::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: &data,
|
||||
mime_type,
|
||||
})
|
||||
}
|
||||
type Error = TaggedError;
|
||||
fn try_from(inp: &'a id3::frame::Picture) -> crate::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: &data,
|
||||
mime_type,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl AudioTagEdit for Id3v2Tag {
|
||||
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 title(&self) -> Option<&str> {
|
||||
self.0.title()
|
||||
}
|
||||
fn set_title(&mut self, title: &str) {
|
||||
self.0.set_title(title)
|
||||
}
|
||||
fn remove_title(&mut self) {
|
||||
self.0.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 artist(&self) -> Option<&str> {
|
||||
self.0.artist()
|
||||
}
|
||||
fn set_artist(&mut self, artist: &str) {
|
||||
self.0.set_artist(artist)
|
||||
}
|
||||
fn remove_artist(&mut self) {
|
||||
self.0.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 year(&self) -> Option<u16> {
|
||||
self.0.year().map(|y| y as u16)
|
||||
}
|
||||
fn set_year(&mut self, year: u16) {
|
||||
self.0.set_year(year as i32)
|
||||
}
|
||||
fn remove_year(&mut self) {
|
||||
self.0.remove("TYER")
|
||||
// self.0.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_title(&self) -> Option<&str> {
|
||||
self.0.album()
|
||||
}
|
||||
fn set_album_title(&mut self, v: &str) {
|
||||
self.0.set_album(v)
|
||||
}
|
||||
fn remove_album_title(&mut self) {
|
||||
self.0.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_artist(&self) -> Option<&str> {
|
||||
self.0.album_artist()
|
||||
}
|
||||
fn set_album_artist(&mut self, v: &str) {
|
||||
self.0.set_album_artist(v)
|
||||
}
|
||||
fn remove_album_artist(&mut self) {
|
||||
self.0.remove_album_artist();
|
||||
}
|
||||
|
||||
fn album_cover(&self) -> Option<Picture> {
|
||||
self.inner
|
||||
.pictures()
|
||||
.filter(|&pic| matches!(pic.picture_type, id3::frame::PictureType::CoverFront))
|
||||
.next()
|
||||
.and_then(|pic| {
|
||||
Some(Picture {
|
||||
data: &pic.data,
|
||||
mime_type: (pic.mime_type.as_str()).try_into().ok()?,
|
||||
})
|
||||
})
|
||||
}
|
||||
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.to_owned(),
|
||||
});
|
||||
}
|
||||
fn remove_album_cover(&mut self) {
|
||||
self.inner
|
||||
.remove_picture_by_type(id3::frame::PictureType::CoverFront);
|
||||
}
|
||||
fn album_cover(&self) -> Option<Picture> {
|
||||
self.0
|
||||
.pictures()
|
||||
.filter(|&pic| matches!(pic.picture_type, id3::frame::PictureType::CoverFront))
|
||||
.next()
|
||||
.and_then(|pic| {
|
||||
Some(Picture {
|
||||
data: &pic.data,
|
||||
mime_type: (pic.mime_type.as_str()).try_into().ok()?,
|
||||
})
|
||||
})
|
||||
}
|
||||
fn set_album_cover(&mut self, cover: Picture) {
|
||||
self.remove_album_cover();
|
||||
self.0.add_picture(id3::frame::Picture {
|
||||
mime_type: String::from(cover.mime_type),
|
||||
picture_type: id3::frame::PictureType::CoverFront,
|
||||
description: "".to_owned(),
|
||||
data: cover.data.to_owned(),
|
||||
});
|
||||
}
|
||||
fn remove_album_cover(&mut self) {
|
||||
self.0
|
||||
.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 track_number(&self) -> Option<u16> {
|
||||
self.0.track().map(|x| x as u16)
|
||||
}
|
||||
fn set_track_number(&mut self, track: u16) {
|
||||
self.0.set_track(track as u32);
|
||||
}
|
||||
fn remove_track_number(&mut self) {
|
||||
self.0.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 total_tracks(&self) -> Option<u16> {
|
||||
self.0.total_tracks().map(|x| x as u16)
|
||||
}
|
||||
fn set_total_tracks(&mut self, total_track: u16) {
|
||||
self.0.set_total_tracks(total_track as u32);
|
||||
}
|
||||
fn remove_total_tracks(&mut self) {
|
||||
self.0.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 disc_number(&self) -> Option<u16> {
|
||||
self.0.disc().map(|x| x as u16)
|
||||
}
|
||||
fn set_disc_number(&mut self, disc_number: u16) {
|
||||
self.0.set_disc(disc_number as u32)
|
||||
}
|
||||
fn remove_disc_number(&mut self) {
|
||||
self.0.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 total_discs(&self) -> Option<u16> {
|
||||
self.0.total_discs().map(|x| x as u16)
|
||||
}
|
||||
fn set_total_discs(&mut self, total_discs: u16) {
|
||||
self.0.set_total_discs(total_discs as u32)
|
||||
}
|
||||
fn remove_total_discs(&mut self) {
|
||||
self.0.remove_total_discs();
|
||||
}
|
||||
}
|
||||
|
||||
impl AudioTagWrite for Id3v2Tag {
|
||||
fn write_to(&mut self, file: &mut File) -> crate::Result<()> {
|
||||
self.inner.write_to(file, id3::Version::Id3v24)?;
|
||||
Ok(())
|
||||
}
|
||||
fn write_to_path(&mut self, path: &str) -> crate::Result<()> {
|
||||
self.inner.write_to_path(path, id3::Version::Id3v24)?;
|
||||
Ok(())
|
||||
}
|
||||
fn write_to(&mut self, file: &mut File) -> crate::Result<()> {
|
||||
self.0.write_to(file, id3::Version::Id3v24)?;
|
||||
Ok(())
|
||||
}
|
||||
fn write_to_path(&mut self, path: &str) -> crate::Result<()> {
|
||||
self.0.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
|
||||
// }
|
||||
// }
|
||||
|
|
|
@ -1,242 +1,237 @@
|
|||
use crate::*;
|
||||
use mp4ameta;
|
||||
|
||||
pub use mp4ameta::Tag as Mp4InnerTag;
|
||||
pub use mp4ameta::{FourCC, Tag as Mp4InnerTag};
|
||||
|
||||
impl_tag!(Mp4Tag, Mp4InnerTag, TagType::Mp4);
|
||||
|
||||
impl<'a> From<&'a Mp4Tag> for AnyTag<'a> {
|
||||
fn from(inp: &'a Mp4Tag) -> Self {
|
||||
let title = inp.title();
|
||||
let artists = inp.artists().map(|i| i.into_iter().collect::<Vec<_>>());
|
||||
let year = inp.year();
|
||||
let album_title = inp.album_title();
|
||||
let album_artists = inp
|
||||
.album_artists()
|
||||
.map(|i| i.into_iter().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,
|
||||
}
|
||||
}
|
||||
fn from(inp: &'a Mp4Tag) -> Self {
|
||||
let title = inp.title();
|
||||
let artists = inp.artists().map(|i| i.into_iter().collect::<Vec<_>>());
|
||||
let year = inp.year().map(|y| y as i32);
|
||||
let album = inp.album_title();
|
||||
let album_artists = inp
|
||||
.album_artists()
|
||||
.map(|i| i.into_iter().collect::<Vec<_>>());
|
||||
let cover = inp.album_cover();
|
||||
let (track_number, total_tracks) = inp.track();
|
||||
let (disc_number, total_discs) = inp.disc();
|
||||
|
||||
Self {
|
||||
title,
|
||||
artists,
|
||||
year,
|
||||
album,
|
||||
cover,
|
||||
album_artists,
|
||||
track_number,
|
||||
total_tracks,
|
||||
disc_number,
|
||||
total_discs,
|
||||
comments: None,
|
||||
date: None,
|
||||
duration_ms: None, // TODO?
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<AnyTag<'a>> for Mp4Tag {
|
||||
fn from(inp: AnyTag<'a>) -> Self {
|
||||
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)));
|
||||
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)));
|
||||
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
|
||||
},
|
||||
}
|
||||
}
|
||||
fn from(inp: AnyTag<'a>) -> Self {
|
||||
let mut tag = Mp4Tag::new();
|
||||
|
||||
inp.title().map(|v| tag.set_title(v));
|
||||
inp.artists()
|
||||
.map(|i| i.iter().for_each(|&a| tag.add_artist(a)));
|
||||
inp.year.map(|v| tag.set_year(v as u16));
|
||||
inp.album_title().map(|v| tag.set_album_title(v));
|
||||
inp.album_artists()
|
||||
.map(|i| i.iter().for_each(|&a| tag.add_album_artist(a)));
|
||||
inp.track_number().map(|v| tag.set_track_number(v));
|
||||
inp.total_tracks().map(|v| tag.set_total_tracks(v));
|
||||
inp.disc_number().map(|v| tag.set_disc_number(v));
|
||||
inp.total_discs().map(|v| tag.set_total_discs(v));
|
||||
tag
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> std::convert::TryFrom<&'a mp4ameta::Data> for Picture<'a> {
|
||||
type Error = crate::Error;
|
||||
fn try_from(inp: &'a mp4ameta::Data) -> crate::Result<Self> {
|
||||
Ok(match *inp {
|
||||
mp4ameta::Data::Png(ref data) => Self {
|
||||
data,
|
||||
mime_type: MimeType::Png,
|
||||
},
|
||||
mp4ameta::Data::Jpeg(ref data) => Self {
|
||||
data,
|
||||
mime_type: MimeType::Jpeg,
|
||||
},
|
||||
_ => return Err(crate::Error::NotAPicture),
|
||||
})
|
||||
}
|
||||
type Error = TaggedError;
|
||||
fn try_from(inp: &'a mp4ameta::Data) -> crate::Result<Self> {
|
||||
Ok(match *inp {
|
||||
mp4ameta::Data::Png(ref data) => Self {
|
||||
data,
|
||||
mime_type: MimeType::Png,
|
||||
},
|
||||
mp4ameta::Data::Jpeg(ref data) => Self {
|
||||
data,
|
||||
mime_type: MimeType::Jpeg,
|
||||
},
|
||||
_ => return Err(TaggedError::NotAPicture),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl AudioTagEdit for Mp4Tag {
|
||||
fn title(&self) -> Option<&str> {
|
||||
self.inner.title()
|
||||
}
|
||||
fn set_title(&mut self, title: &str) {
|
||||
self.inner.set_title(title)
|
||||
}
|
||||
fn title(&self) -> Option<&str> {
|
||||
self.0.title()
|
||||
}
|
||||
fn set_title(&mut self, title: &str) {
|
||||
self.0.set_title(title)
|
||||
}
|
||||
|
||||
fn artist(&self) -> Option<&str> {
|
||||
self.inner.artist()
|
||||
}
|
||||
fn set_artist(&mut self, artist: &str) {
|
||||
self.inner.set_artist(artist)
|
||||
}
|
||||
fn artists(&self) -> Option<Vec<&str>> {
|
||||
let v = self.inner.artists().fold(Vec::new(), |mut v, a| {
|
||||
v.push(a);
|
||||
v
|
||||
});
|
||||
if v.len() > 0 {
|
||||
Some(v)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
fn add_artist(&mut self, v: &str) {
|
||||
self.inner.add_artist(v);
|
||||
}
|
||||
fn remove_title(&mut self) {
|
||||
self.0.remove_title();
|
||||
}
|
||||
fn artist(&self) -> Option<&str> {
|
||||
self.0.artist()
|
||||
}
|
||||
fn set_artist(&mut self, artist: &str) {
|
||||
self.0.set_artist(artist)
|
||||
}
|
||||
fn remove_artist(&mut self) {
|
||||
self.0.remove_artists();
|
||||
}
|
||||
|
||||
fn year(&self) -> Option<i32> {
|
||||
self.inner.year().and_then(|x| str::parse(x).ok())
|
||||
}
|
||||
fn set_year(&mut self, year: i32) {
|
||||
self.inner.set_year(year.to_string())
|
||||
}
|
||||
fn artists(&self) -> Option<Vec<&str>> {
|
||||
let v = self.0.artists().fold(Vec::new(), |mut v, a| {
|
||||
v.push(a);
|
||||
v
|
||||
});
|
||||
if v.len() > 0 {
|
||||
Some(v)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
fn add_artist(&mut self, v: &str) {
|
||||
self.0.add_artist(v);
|
||||
}
|
||||
|
||||
fn album_title(&self) -> Option<&str> {
|
||||
self.inner.album()
|
||||
}
|
||||
fn set_album_title(&mut self, v: &str) {
|
||||
self.inner.set_album(v)
|
||||
}
|
||||
fn year(&self) -> Option<u16> {
|
||||
self.0.year().and_then(|x| str::parse(x).ok())
|
||||
}
|
||||
fn set_year(&mut self, year: u16) {
|
||||
self.0.set_year(year.to_string())
|
||||
}
|
||||
|
||||
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_year(&mut self) {
|
||||
self.0.remove_year();
|
||||
}
|
||||
fn album_title(&self) -> Option<&str> {
|
||||
self.0.album()
|
||||
}
|
||||
|
||||
fn album_artists(&self) -> Option<Vec<&str>> {
|
||||
let v = self.inner.album_artists().fold(Vec::new(), |mut v, a| {
|
||||
v.push(a);
|
||||
v
|
||||
});
|
||||
if v.len() > 0 {
|
||||
Some(v)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
fn add_album_artist(&mut self, v: &str) {
|
||||
self.inner.add_album_artist(v);
|
||||
}
|
||||
fn set_album_title(&mut self, v: &str) {
|
||||
self.0.set_album(v)
|
||||
}
|
||||
fn remove_album_title(&mut self) {
|
||||
self.0.remove_album();
|
||||
}
|
||||
|
||||
fn album_cover(&self) -> Option<Picture> {
|
||||
use mp4ameta::Data::*;
|
||||
self.inner.artwork().and_then(|data| match data {
|
||||
Jpeg(d) => Some(Picture {
|
||||
data: d,
|
||||
mime_type: MimeType::Jpeg,
|
||||
}),
|
||||
Png(d) => Some(Picture {
|
||||
data: d,
|
||||
mime_type: MimeType::Png,
|
||||
}),
|
||||
_ => 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.to_owned()),
|
||||
MimeType::Jpeg => mp4ameta::Data::Jpeg(cover.data.to_owned()),
|
||||
_ => panic!("Only png and jpeg are supported in m4a"),
|
||||
});
|
||||
}
|
||||
fn album_artist(&self) -> Option<&str> {
|
||||
self.0.album_artist()
|
||||
}
|
||||
fn set_album_artist(&mut self, v: &str) {
|
||||
self.0.set_album_artist(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 remove_album_artist(&mut self) {
|
||||
self.0.remove_data(&FourCC(*b"aART"));
|
||||
self.0.remove_album_artists();
|
||||
}
|
||||
fn album_artists(&self) -> Option<Vec<&str>> {
|
||||
let v = self.0.album_artists().fold(Vec::new(), |mut v, a| {
|
||||
v.push(a);
|
||||
v
|
||||
});
|
||||
if v.len() > 0 {
|
||||
Some(v)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
fn add_album_artist(&mut self, v: &str) {
|
||||
self.0.add_album_artist(v);
|
||||
}
|
||||
fn album_cover(&self) -> Option<Picture> {
|
||||
use mp4ameta::Data::*;
|
||||
self.0.artwork().and_then(|data| match data {
|
||||
Jpeg(d) => Some(Picture {
|
||||
data: d,
|
||||
mime_type: MimeType::Jpeg,
|
||||
}),
|
||||
Png(d) => Some(Picture {
|
||||
data: d,
|
||||
mime_type: MimeType::Png,
|
||||
}),
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
|
||||
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 set_album_cover(&mut self, cover: Picture) {
|
||||
self.remove_album_cover();
|
||||
self.0.add_artwork(match cover.mime_type {
|
||||
MimeType::Png => mp4ameta::Data::Png(cover.data.to_owned()),
|
||||
MimeType::Jpeg => mp4ameta::Data::Jpeg(cover.data.to_owned()),
|
||||
_ => panic!("Only png and jpeg are supported in m4a"),
|
||||
});
|
||||
}
|
||||
fn remove_album_cover(&mut self) {
|
||||
self.0.remove_artwork();
|
||||
}
|
||||
fn remove_track(&mut self) {
|
||||
self.0.remove_track(); // faster than removing separately
|
||||
}
|
||||
fn track_number(&self) -> Option<u16> {
|
||||
self.0.track_number()
|
||||
}
|
||||
|
||||
fn remove_title(&mut self) {
|
||||
self.inner.remove_title();
|
||||
}
|
||||
fn remove_artist(&mut self) {
|
||||
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_track_number();
|
||||
}
|
||||
fn remove_total_tracks(&mut self) {
|
||||
self.inner.remove_total_tracks();
|
||||
}
|
||||
fn remove_disc(&mut self) {
|
||||
self.inner.remove_disc();
|
||||
}
|
||||
fn remove_disc_number(&mut self) {
|
||||
self.inner.remove_disc_number();
|
||||
}
|
||||
fn remove_total_discs(&mut self) {
|
||||
self.inner.remove_total_discs();
|
||||
}
|
||||
fn set_track_number(&mut self, track: u16) {
|
||||
self.0.set_track_number(track);
|
||||
}
|
||||
fn remove_track_number(&mut self) {
|
||||
self.0.remove_track_number();
|
||||
}
|
||||
fn total_tracks(&self) -> Option<u16> {
|
||||
self.0.total_tracks()
|
||||
}
|
||||
fn set_total_tracks(&mut self, total_track: u16) {
|
||||
self.0.set_total_tracks(total_track);
|
||||
}
|
||||
fn remove_total_tracks(&mut self) {
|
||||
self.0.remove_total_tracks();
|
||||
}
|
||||
fn remove_disc(&mut self) {
|
||||
self.0.remove_disc();
|
||||
}
|
||||
fn disc_number(&self) -> Option<u16> {
|
||||
self.0.disc_number()
|
||||
}
|
||||
fn set_disc_number(&mut self, disc_number: u16) {
|
||||
self.0.set_disc_number(disc_number)
|
||||
}
|
||||
fn remove_disc_number(&mut self) {
|
||||
self.0.remove_disc_number();
|
||||
}
|
||||
fn total_discs(&self) -> Option<u16> {
|
||||
self.0.total_discs()
|
||||
}
|
||||
fn set_total_discs(&mut self, total_discs: u16) {
|
||||
self.0.set_total_discs(total_discs)
|
||||
}
|
||||
fn remove_total_discs(&mut self) {
|
||||
self.0.remove_total_discs();
|
||||
}
|
||||
}
|
||||
|
||||
impl AudioTagWrite for Mp4Tag {
|
||||
fn write_to(&mut self, file: &mut File) -> crate::Result<()> {
|
||||
self.inner.write_to(file)?;
|
||||
Ok(())
|
||||
}
|
||||
fn write_to_path(&mut self, path: &str) -> crate::Result<()> {
|
||||
self.inner.write_to_path(path)?;
|
||||
Ok(())
|
||||
}
|
||||
fn write_to(&mut self, file: &mut File) -> crate::Result<()> {
|
||||
self.0.write_to(file)?;
|
||||
Ok(())
|
||||
}
|
||||
fn write_to_path(&mut self, path: &str) -> crate::Result<()> {
|
||||
self.0.write_to_path(path)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
259
src/components/opus_tag.rs
Normal file
259
src/components/opus_tag.rs
Normal file
|
@ -0,0 +1,259 @@
|
|||
use crate::*;
|
||||
use opus_headers;
|
||||
|
||||
use opus_headers::{CommentHeader, IdentificationHeader, OpusHeaders as OpusInnerTag};
|
||||
use std::borrow::BorrowMut;
|
||||
use std::collections::hash_map::RandomState;
|
||||
use std::collections::HashMap;
|
||||
|
||||
impl MissingImplementations for OpusInnerTag {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
id: IdentificationHeader {
|
||||
version: 0,
|
||||
channel_count: 0,
|
||||
pre_skip: 0,
|
||||
input_sample_rate: 0,
|
||||
output_gain: 0,
|
||||
channel_mapping_family: 0,
|
||||
channel_mapping_table: None,
|
||||
},
|
||||
comments: CommentHeader {
|
||||
vendor: "".to_string(),
|
||||
user_comments: Default::default(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn read_from_path<P>(path: P) -> Result<Self>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl_tag!(OpusTag, OpusInnerTag, TagType::Opus);
|
||||
|
||||
impl<'a> From<AnyTag<'a>> for OpusTag {
|
||||
fn from(inp: AnyTag<'a>) -> Self {
|
||||
let mut t = OpusTag::default();
|
||||
inp.title().map(|v| t.set_title(v));
|
||||
inp.artists_as_string().map(|v| t.set_artist(&v));
|
||||
inp.year.map(|v| t.set_year(v as u16));
|
||||
inp.album_title().map(|v| t.set_album_title(v));
|
||||
inp.album_artists_as_string().map(|v| t.set_artist(&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> From<&'a OpusTag> for AnyTag<'a> {
|
||||
fn from(inp: &'a OpusTag) -> Self {
|
||||
let mut t = Self::default();
|
||||
t.title = inp.title();
|
||||
t.artists = inp.artists();
|
||||
t.year = inp.year().map(|y| y as i32);
|
||||
t.album = inp.album_title();
|
||||
t.album_artists = inp.album_artists();
|
||||
t.cover = inp.album_cover();
|
||||
t.track_number = inp.track_number();
|
||||
t.total_tracks = inp.total_tracks();
|
||||
t.disc_number = inp.disc_number();
|
||||
t.total_discs = inp.total_discs();
|
||||
t
|
||||
}
|
||||
}
|
||||
|
||||
impl OpusTag {
|
||||
pub fn get_first(&self, key: &str) -> Option<&str> {
|
||||
let comments = &self.0.comments.user_comments;
|
||||
|
||||
if let Some(pair) = comments.get_key_value(key) {
|
||||
if !pair.1.is_empty() {
|
||||
Some(pair.1.as_str())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
pub fn set_first(&mut self, key: &str, val: &str) {
|
||||
let comments: &mut HashMap<String, String, RandomState> =
|
||||
self.0.comments.user_comments.borrow_mut();
|
||||
match comments.get_mut(key) {
|
||||
Some(mut v) => v = &mut val.to_string(),
|
||||
None => {},
|
||||
}
|
||||
}
|
||||
pub fn remove(&mut self, key: &str) {
|
||||
let comments: &mut HashMap<String, String, RandomState> =
|
||||
self.0.comments.user_comments.borrow_mut();
|
||||
comments.retain(|k, _| k != key)
|
||||
}
|
||||
}
|
||||
|
||||
impl AudioTagEdit for OpusTag {
|
||||
fn title(&self) -> Option<&str> {
|
||||
self.get_first("TITLE")
|
||||
}
|
||||
fn set_title(&mut self, title: &str) {
|
||||
self.set_first("TITLE", title);
|
||||
}
|
||||
|
||||
fn remove_title(&mut self) {
|
||||
self.remove("TITLE");
|
||||
}
|
||||
fn artist(&self) -> Option<&str> {
|
||||
self.get_first("ARTIST")
|
||||
}
|
||||
|
||||
fn set_artist(&mut self, artist: &str) {
|
||||
self.set_first("ARTIST", artist)
|
||||
}
|
||||
fn remove_artist(&mut self) {
|
||||
self.remove("ARTIST");
|
||||
}
|
||||
|
||||
fn year(&self) -> Option<u16> {
|
||||
if let Some(Ok(y)) = self
|
||||
.get_first("DATE")
|
||||
.map(|s| s.chars().take(4).collect::<String>().parse::<i32>())
|
||||
{
|
||||
Some(y as u16)
|
||||
} else if let Some(Ok(y)) = self.get_first("YEAR").map(|s| s.parse::<i32>()) {
|
||||
Some(y as u16)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
fn set_year(&mut self, year: u16) {
|
||||
self.set_first("DATE", &year.to_string());
|
||||
self.set_first("YEAR", &year.to_string());
|
||||
}
|
||||
|
||||
fn remove_year(&mut self) {
|
||||
self.remove("YEAR");
|
||||
self.remove("DATE");
|
||||
}
|
||||
fn album_title(&self) -> Option<&str> {
|
||||
self.get_first("ALBUM")
|
||||
}
|
||||
|
||||
fn set_album_title(&mut self, title: &str) {
|
||||
self.set_first("ALBUM", title)
|
||||
}
|
||||
fn remove_album_title(&mut self) {
|
||||
self.remove("ALBUM");
|
||||
}
|
||||
|
||||
fn album_artist(&self) -> Option<&str> {
|
||||
self.get_first("ALBUMARTIST")
|
||||
}
|
||||
fn set_album_artist(&mut self, v: &str) {
|
||||
self.set_first("ALBUMARTIST", v)
|
||||
}
|
||||
|
||||
fn remove_album_artist(&mut self) {
|
||||
self.remove("ALBUMARTIST");
|
||||
}
|
||||
fn album_cover(&self) -> Option<Picture> {
|
||||
// TODO
|
||||
// self.0
|
||||
// .pictures()
|
||||
// .filter(|&pic| matches!(pic.picture_type, metaflac::block::PictureType::CoverFront))
|
||||
// .next()
|
||||
// .and_then(|pic| {
|
||||
// Some(Picture {
|
||||
// data: &pic.data,
|
||||
// mime_type: (pic.mime_type.as_str()).try_into().ok()?,
|
||||
// })
|
||||
// })
|
||||
None
|
||||
}
|
||||
|
||||
fn set_album_cover(&mut self, cover: Picture) {
|
||||
// TODO
|
||||
// self.remove_album_cover();
|
||||
// let mime = String::from(cover.mime_type);
|
||||
// let picture_type = metaflac::block::PictureType::CoverFront;
|
||||
// self.0
|
||||
// .add_picture(mime, picture_type, (cover.data).to_owned());
|
||||
}
|
||||
fn remove_album_cover(&mut self) {
|
||||
// TODO
|
||||
// self.0
|
||||
// .remove_picture_type(metaflac::block::PictureType::CoverFront)
|
||||
}
|
||||
|
||||
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 remove_track_number(&mut self) {
|
||||
self.remove("TRACKNUMBER");
|
||||
}
|
||||
// ! 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 remove_total_tracks(&mut self) {
|
||||
self.remove("TOTALTRACKS");
|
||||
}
|
||||
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 remove_disc_number(&mut self) {
|
||||
self.remove("DISCNUMBER");
|
||||
}
|
||||
// ! 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_total_discs(&mut self) {
|
||||
self.remove("TOTALDISCS");
|
||||
}
|
||||
}
|
||||
|
||||
impl AudioTagWrite for OpusTag {
|
||||
fn write_to(&mut self, file: &mut File) -> crate::Result<()> {
|
||||
// self.0.write_to(file)?; TODO
|
||||
Ok(())
|
||||
}
|
||||
fn write_to_path(&mut self, path: &str) -> crate::Result<()> {
|
||||
// self.0.write_to_path(path)?; TODO
|
||||
Ok(())
|
||||
}
|
||||
}
|
257
src/components/vorbis_tag.rs
Normal file
257
src/components/vorbis_tag.rs
Normal file
|
@ -0,0 +1,257 @@
|
|||
use crate::*;
|
||||
use lewton;
|
||||
|
||||
use lewton::header::CommentHeader as VorbisInnerTag;
|
||||
use lewton::inside_ogg::OggStreamReader;
|
||||
use std::collections::hash_map::RandomState;
|
||||
use std::collections::HashMap;
|
||||
|
||||
impl MissingImplementations for VorbisInnerTag {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
vendor: "".to_string(),
|
||||
comment_list: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
fn read_from_path<P>(path: P) -> Result<Self>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
let stream = OggStreamReader::new(File::open(path).unwrap()).unwrap();
|
||||
|
||||
Ok(Self {
|
||||
vendor: stream.comment_hdr.vendor,
|
||||
comment_list: stream.comment_hdr.comment_list,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl_tag!(VorbisTag, VorbisInnerTag, TagType::Vorbis);
|
||||
|
||||
impl<'a> From<AnyTag<'a>> for VorbisTag {
|
||||
fn from(inp: AnyTag<'a>) -> Self {
|
||||
let mut t = VorbisTag::default();
|
||||
inp.title().map(|v| t.set_title(v));
|
||||
inp.artists_as_string().map(|v| t.set_artist(v.as_str()));
|
||||
inp.year.map(|v| t.set_year(v as u16));
|
||||
inp.album_title().map(|v| t.set_album_title(v));
|
||||
inp.album_artists_as_string()
|
||||
.map(|v| t.set_artist(v.as_str()));
|
||||
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> From<&'a VorbisTag> for AnyTag<'a> {
|
||||
fn from(inp: &'a VorbisTag) -> Self {
|
||||
let mut t = Self::default();
|
||||
t.title = inp.title();
|
||||
t.artists = inp.artists();
|
||||
t.year = inp.year().map(|y| y as i32);
|
||||
t.album = inp.album_title();
|
||||
t.album_artists = inp.album_artists();
|
||||
t.cover = inp.album_cover();
|
||||
t.track_number = inp.track_number();
|
||||
t.total_tracks = inp.total_tracks();
|
||||
t.disc_number = inp.disc_number();
|
||||
t.total_discs = inp.total_discs();
|
||||
t
|
||||
}
|
||||
}
|
||||
|
||||
impl VorbisTag {
|
||||
// TODO: rename these
|
||||
pub fn get_first(&self, key: &str) -> Option<&str> {
|
||||
let comments = &self.0.comment_list;
|
||||
|
||||
for (k, v) in comments {
|
||||
if k.as_str() == key {
|
||||
return Some(v.as_str());
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
pub fn set_first(&mut self, key: &str, val: &str) {
|
||||
let mut comments: HashMap<String, String, RandomState> =
|
||||
self.0.comment_list.clone().into_iter().collect();
|
||||
match comments.get_mut(key) {
|
||||
Some(mut v) => v = &mut val.to_string(),
|
||||
None => {},
|
||||
}
|
||||
}
|
||||
pub fn remove(&mut self, key: &str) {
|
||||
let mut comments: HashMap<String, String, RandomState> =
|
||||
self.0.comment_list.clone().into_iter().collect();
|
||||
comments.retain(|k, _| k != key)
|
||||
}
|
||||
pub fn pictures(&self) {}
|
||||
}
|
||||
|
||||
impl AudioTagEdit for VorbisTag {
|
||||
fn title(&self) -> Option<&str> {
|
||||
self.get_first("TITLE")
|
||||
}
|
||||
fn set_title(&mut self, title: &str) {
|
||||
self.set_first("TITLE", title);
|
||||
}
|
||||
|
||||
fn remove_title(&mut self) {
|
||||
self.remove("TITLE");
|
||||
}
|
||||
fn artist(&self) -> Option<&str> {
|
||||
self.get_first("ARTIST")
|
||||
}
|
||||
|
||||
fn set_artist(&mut self, artist: &str) {
|
||||
self.set_first("ARTIST", artist)
|
||||
}
|
||||
fn remove_artist(&mut self) {
|
||||
self.remove("ARTIST");
|
||||
}
|
||||
|
||||
fn year(&self) -> Option<u16> {
|
||||
if let Some(Ok(y)) = self
|
||||
.get_first("DATE")
|
||||
.map(|s| s.chars().take(4).collect::<String>().parse::<i32>())
|
||||
{
|
||||
Some(y as u16)
|
||||
} else if let Some(Ok(y)) = self.get_first("YEAR").map(|s| s.parse::<i32>()) {
|
||||
Some(y as u16)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
fn set_year(&mut self, year: u16) {
|
||||
self.set_first("DATE", &year.to_string());
|
||||
self.set_first("YEAR", &year.to_string());
|
||||
}
|
||||
|
||||
fn remove_year(&mut self) {
|
||||
self.remove("YEAR");
|
||||
self.remove("DATE");
|
||||
}
|
||||
fn album_title(&self) -> Option<&str> {
|
||||
self.get_first("ALBUM")
|
||||
}
|
||||
|
||||
fn set_album_title(&mut self, title: &str) {
|
||||
self.set_first("ALBUM", title)
|
||||
}
|
||||
fn remove_album_title(&mut self) {
|
||||
self.remove("ALBUM");
|
||||
}
|
||||
|
||||
fn album_artist(&self) -> Option<&str> {
|
||||
self.get_first("ALBUMARTIST")
|
||||
}
|
||||
fn set_album_artist(&mut self, v: &str) {
|
||||
self.set_first("ALBUMARTIST", v)
|
||||
}
|
||||
|
||||
fn remove_album_artist(&mut self) {
|
||||
self.remove("ALBUMARTIST");
|
||||
}
|
||||
// TODO
|
||||
fn album_cover(&self) -> Option<Picture> {
|
||||
// self.get_first("PICTURE")
|
||||
// self.0
|
||||
// .pictures()
|
||||
// .filter(|&pic| matches!(pic.picture_type, metaflac::block::PictureType::CoverFront))
|
||||
// .next()
|
||||
// .and_then(|pic| {
|
||||
// Some(Picture {
|
||||
// data: &pic.data,
|
||||
// mime_type: (pic.mime_type.as_str()).try_into().ok()?,
|
||||
// })
|
||||
// })
|
||||
None
|
||||
}
|
||||
|
||||
fn set_album_cover(&mut self, cover: Picture) {
|
||||
// TODO
|
||||
// self.remove_album_cover();
|
||||
// let mime = String::from(cover.mime_type);
|
||||
// let picture_type = metaflac::block::PictureType::CoverFront;
|
||||
// self.0
|
||||
// .add_picture(mime, picture_type, (cover.data).to_owned());
|
||||
}
|
||||
fn remove_album_cover(&mut self) {
|
||||
// TODO
|
||||
// self.0
|
||||
// .remove_picture_type(metaflac::block::PictureType::CoverFront)
|
||||
}
|
||||
|
||||
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 remove_track_number(&mut self) {
|
||||
self.remove("TRACKNUMBER");
|
||||
}
|
||||
// ! 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 remove_total_tracks(&mut self) {
|
||||
self.remove("TOTALTRACKS");
|
||||
}
|
||||
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 remove_disc_number(&mut self) {
|
||||
self.remove("DISCNUMBER");
|
||||
}
|
||||
// ! 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_total_discs(&mut self) {
|
||||
self.remove("TOTALDISCS");
|
||||
}
|
||||
}
|
||||
// TODO
|
||||
impl AudioTagWrite for VorbisTag {
|
||||
fn write_to(&mut self, file: &mut File) -> crate::Result<()> {
|
||||
// TODO
|
||||
// self.0.write_to(file)?;
|
||||
Ok(())
|
||||
}
|
||||
fn write_to_path(&mut self, path: &str) -> crate::Result<()> {
|
||||
// TODO
|
||||
// self.0.write_to_path(path)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -1,27 +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,
|
||||
/// 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,
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
50
src/error.rs
50
src/error.rs
|
@ -1,33 +1,33 @@
|
|||
/// Error that could occur in this library.
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum Error {
|
||||
/// Fail to guess the metadata format based on the file extension.
|
||||
#[error("Fail to guess the metadata format based on the file extension.")]
|
||||
UnknownFileExtension(String),
|
||||
pub enum TaggedError {
|
||||
/// Fail to guess the metadata format based on the file extension.
|
||||
#[error("Fail to guess the metadata format based on the file extension.")]
|
||||
UnknownFileExtension(String),
|
||||
|
||||
/// Represents a failure to read from input.
|
||||
#[error("Read error")]
|
||||
ReadError { source: std::io::Error },
|
||||
/// Represents a failure to read from input.
|
||||
#[error("Read error")]
|
||||
ReadError { source: std::io::Error },
|
||||
|
||||
/// Represents all other cases of `std::io::Error`.
|
||||
#[error(transparent)]
|
||||
IOError(#[from] std::io::Error),
|
||||
/// Represents all other cases of `std::io::Error`.
|
||||
#[error(transparent)]
|
||||
IOError(#[from] std::io::Error),
|
||||
|
||||
#[error("Unsupported format: {0}")]
|
||||
UnsupportedFormat(String),
|
||||
#[error("Unsupported mime type: {0}")]
|
||||
UnsupportedMimeType(String),
|
||||
#[error("")]
|
||||
NotAPicture,
|
||||
#[error("Unsupported format: {0}")]
|
||||
UnsupportedFormat(String),
|
||||
#[error("Unsupported mime type: {0}")]
|
||||
UnsupportedMimeType(String),
|
||||
#[error("")]
|
||||
NotAPicture,
|
||||
|
||||
#[error(transparent)]
|
||||
FlacTagError(#[from] metaflac::Error),
|
||||
|
||||
#[error(transparent)]
|
||||
Mp4TagError(#[from] mp4ameta::Error),
|
||||
|
||||
#[error(transparent)]
|
||||
Id3TagError(#[from] id3::Error),
|
||||
#[error(transparent)]
|
||||
Id3TagError(#[from] id3::Error),
|
||||
#[error(transparent)]
|
||||
VorbisError(#[from] lewton::VorbisError),
|
||||
#[error(transparent)]
|
||||
FlacTagError(#[from] metaflac::Error),
|
||||
#[error(transparent)]
|
||||
Mp4TagError(#[from] mp4ameta::Error),
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
pub type Result<T> = std::result::Result<T, TaggedError>;
|
||||
|
|
170
src/lib.rs
170
src/lib.rs
|
@ -1,9 +1,9 @@
|
|||
//! # audiotags
|
||||
//! # lofty
|
||||
//!
|
||||
//! [](https://crates.io/crates/audiotags)
|
||||
//! [](https://crates.io/crates/audiotags)
|
||||
//! [](https://crates.io/crates/audiotags)
|
||||
//! [](https://docs.rs/audiotags/)
|
||||
//! [](https://crates.io/crates/lofty)
|
||||
//! [](https://crates.io/crates/lofty)
|
||||
//! [](https://crates.io/crates/lofty)
|
||||
//! [](https://docs.rs/lofty/)
|
||||
//!
|
||||
//! This crate makes it easier to parse, convert and write metadata (a.k.a tag) in audio files of different file types.
|
||||
//!
|
||||
|
@ -15,13 +15,13 @@
|
|||
//!
|
||||
//! ## Performance
|
||||
//!
|
||||
//! Using **audiotags** incurs a little overhead due to vtables if you want to guess the metadata format
|
||||
//! Using **lofty** incurs a little overhead due to vtables if you want to guess the metadata format
|
||||
//! (from file extension). Apart from this the performance is almost the same as directly calling function
|
||||
//! provided by those 'specialized' crates.
|
||||
//!
|
||||
//! No copies will be made if you only need to read and write metadata of one format. If you want to convert
|
||||
//! between tags, copying is unavoidable no matter if you use **audiotags** or use getters and setters provided
|
||||
//! by specialized libraries. **audiotags** is not making additional unnecessary copies.
|
||||
//! between tags, copying is unavoidable no matter if you use **lofty** or use getters and setters provided
|
||||
//! by specialized libraries. **lofty** is not making additional unnecessary copies.
|
||||
//!
|
||||
//! Theoretically it is possible to achieve zero-copy conversions if all parsers can parse into a unified
|
||||
//! struct. However, this is going to be a lot of work. I might be able to implement them, but it will be no
|
||||
|
@ -29,6 +29,17 @@
|
|||
//!
|
||||
//! Read the [manual](https://tianyishi2001.github.io/audiotags) for some examples.
|
||||
|
||||
//#![forbid(unused_crate_dependencies, unused_import_braces)]
|
||||
#![warn(clippy::pedantic)]
|
||||
#![allow(
|
||||
clippy::too_many_lines,
|
||||
clippy::cast_precision_loss,
|
||||
clippy::cast_sign_loss,
|
||||
clippy::cast_possible_wrap,
|
||||
clippy::clippy::cast_possible_truncation,
|
||||
clippy::module_name_repetitions
|
||||
)]
|
||||
|
||||
pub(crate) use audiotags_dev_macro::*;
|
||||
|
||||
pub mod anytag;
|
||||
|
@ -38,7 +49,7 @@ pub mod components;
|
|||
pub use components::*;
|
||||
|
||||
pub mod error;
|
||||
pub use error::{Error, Result};
|
||||
pub use error::{Result, TaggedError};
|
||||
|
||||
pub mod traits;
|
||||
pub use traits::*;
|
||||
|
@ -60,7 +71,8 @@ pub use std::convert::{TryFrom, TryInto};
|
|||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use audiotags::{Tag, TagType};
|
||||
/// use lofty::{Tag, TagType};
|
||||
///
|
||||
/// // Guess the format by default
|
||||
/// let mut tag = Tag::new().read_from_path("assets/a.mp3").unwrap();
|
||||
/// tag.set_title("Foo");
|
||||
|
@ -70,101 +82,75 @@ pub use std::convert::{TryFrom, TryInto};
|
|||
/// let tag = Tag::new().with_tag_type(TagType::Mp4).read_from_path("assets/a.m4a").unwrap();
|
||||
/// assert_eq!(tag.title(), Some("Foo"));
|
||||
/// ```
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Tag {
|
||||
/// The tag type which can be specified with `.with_tag_type()` before parsing.
|
||||
tag_type: Option<TagType>,
|
||||
/// The config which can be specified with `.with_config()` before parsing.
|
||||
config: Config,
|
||||
}
|
||||
pub struct Tag(Option<TagType>);
|
||||
|
||||
impl Tag {
|
||||
/// Initiate a new Tag (a builder for `Box<dyn AudioTag>`) with default configurations.
|
||||
/// You can then optionally chain `with_tag_type` and/or `with_config`.
|
||||
/// Finally, you `read_from_path`
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
/// Specify the tag type
|
||||
pub fn with_tag_type(self, tag_type: TagType) -> Self {
|
||||
Self {
|
||||
tag_type: Some(tag_type),
|
||||
config: self.config,
|
||||
}
|
||||
}
|
||||
/// Specify configuration, if you do not want to use the default
|
||||
pub fn with_config(self, config: Config) -> Self {
|
||||
Self {
|
||||
tag_type: self.tag_type,
|
||||
config: config.clone(),
|
||||
}
|
||||
}
|
||||
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()
|
||||
.unwrap()
|
||||
.to_string_lossy()
|
||||
.to_string()
|
||||
.to_lowercase()
|
||||
.as_str(),
|
||||
)?) {
|
||||
TagType::Id3v2 => Ok(Box::new({
|
||||
let mut t = Id3v2Tag::read_from_path(path)?;
|
||||
t.set_config(self.config.clone());
|
||||
t
|
||||
})),
|
||||
TagType::Mp4 => Ok(Box::new({
|
||||
let mut t = Mp4Tag::read_from_path(path)?;
|
||||
t.set_config(self.config.clone());
|
||||
t
|
||||
})),
|
||||
TagType::Flac => Ok(Box::new({
|
||||
let mut t = FlacTag::read_from_path(path)?;
|
||||
t.set_config(self.config.clone());
|
||||
t
|
||||
})),
|
||||
}
|
||||
}
|
||||
/// Initiate a new Tag (a builder for `Box<dyn AudioTag>`) with default configurations.
|
||||
/// You can then optionally chain `with_tag_type` and/or `with_config`.
|
||||
/// Finally, you `read_from_path`
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
/// Specify the tag type
|
||||
pub fn with_tag_type(self, tag_type: TagType) -> Self {
|
||||
Self(Some(tag_type))
|
||||
}
|
||||
|
||||
pub fn read_from_path(&self, path: impl AsRef<Path>) -> crate::Result<Box<dyn AudioTag>> {
|
||||
let extension = path.as_ref().extension().unwrap().to_str().unwrap();
|
||||
|
||||
match self.0.unwrap_or(TagType::try_from_ext(extension)?) {
|
||||
TagType::Id3v2 => Ok(Box::new({ Id3v2Tag::read_from_path(path)? })),
|
||||
TagType::Vorbis => Ok(Box::new({ VorbisTag::read_from_path(path)? })),
|
||||
TagType::Opus => Ok(Box::new({ OpusTag::read_from_path(path)? })),
|
||||
TagType::Flac => Ok(Box::new({ FlacTag::read_from_path(path)? })),
|
||||
TagType::Mp4 => Ok(Box::new({ Mp4Tag::read_from_path(path)? })),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum TagType {
|
||||
/// ## Common file extensions
|
||||
///
|
||||
/// `.mp3`
|
||||
///
|
||||
/// ## References
|
||||
///
|
||||
/// - https://www.wikiwand.com/en/ID3
|
||||
Id3v2,
|
||||
Flac,
|
||||
/// ## Common file extensions
|
||||
///
|
||||
/// `.mp4, .m4a, .m4p, .m4b, .m4r and .m4v`
|
||||
///
|
||||
/// ## References
|
||||
///
|
||||
/// - https://www.wikiwand.com/en/MPEG-4_Part_14
|
||||
Mp4,
|
||||
/// ## Common file extensions
|
||||
///
|
||||
/// `.mp3`
|
||||
///
|
||||
/// ## References
|
||||
///
|
||||
/// - https://www.wikiwand.com/en/ID3
|
||||
Id3v2,
|
||||
Vorbis,
|
||||
Opus,
|
||||
Flac,
|
||||
/// ## Common file extensions
|
||||
///
|
||||
/// `.mp4, .m4a, .m4p, .m4b, .m4r and .m4v`
|
||||
///
|
||||
/// ## References
|
||||
///
|
||||
/// - https://www.wikiwand.com/en/MPEG-4_Part_14
|
||||
Mp4,
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
impl TagType {
|
||||
fn try_from_ext(ext: &str) -> crate::Result<Self> {
|
||||
match ext {
|
||||
"mp3" => Ok(Self::Id3v2),
|
||||
"m4a" | "m4b" | "m4p" | "m4v" | "isom" | "mp4" => Ok(Self::Mp4),
|
||||
"flac" => Ok(Self::Flac),
|
||||
p @ _ => Err(crate::Error::UnsupportedFormat(p.to_owned())),
|
||||
}
|
||||
}
|
||||
fn try_from_ext(ext: &str) -> crate::Result<Self> {
|
||||
match ext {
|
||||
"mp3" => Ok(Self::Id3v2),
|
||||
"ogg" => Ok(Self::Vorbis),
|
||||
"opus" => Ok(Self::Opus),
|
||||
"flac" => Ok(Self::Flac),
|
||||
"m4a" | "m4b" | "m4p" | "m4v" | "isom" | "mp4" => Ok(Self::Mp4),
|
||||
_ => Err(TaggedError::UnsupportedFormat(ext.to_owned())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert a concrete tag type into another
|
||||
#[macro_export]
|
||||
macro_rules! convert {
|
||||
($inp:expr, $target_type:ty) => {
|
||||
$target_type::from(inp.to_anytag())
|
||||
};
|
||||
($inp:expr, $target_type:ty) => {
|
||||
$target_type::from(inp.to_anytag())
|
||||
};
|
||||
}
|
||||
|
|
217
src/traits.rs
217
src/traits.rs
|
@ -10,149 +10,124 @@ pub trait AudioTag: AudioTagEdit + AudioTagWrite + ToAnyTag {}
|
|||
/// Implementors of this trait are able to read and write audio metadata.
|
||||
///
|
||||
/// Constructor methods e.g. `from_file` should be implemented separately.
|
||||
pub trait AudioTagEdit: AudioTagConfig {
|
||||
fn title(&self) -> Option<&str>;
|
||||
fn set_title(&mut self, title: &str);
|
||||
fn remove_title(&mut self);
|
||||
pub trait AudioTagEdit {
|
||||
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 artist(&self) -> Option<&str>;
|
||||
fn set_artist(&mut self, artist: &str);
|
||||
fn remove_artist(&mut self);
|
||||
|
||||
fn artists(&self) -> Option<Vec<&str>> {
|
||||
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);
|
||||
}
|
||||
fn artists(&self) -> Option<Vec<&str>> {
|
||||
self.artist().map(|v| vec![v])
|
||||
}
|
||||
fn add_artist(&mut self, artist: &str) {
|
||||
self.set_artist(artist);
|
||||
}
|
||||
|
||||
fn year(&self) -> Option<i32>;
|
||||
fn set_year(&mut self, year: i32);
|
||||
fn remove_year(&mut self);
|
||||
fn year(&self) -> Option<u16>;
|
||||
fn set_year(&mut self, year: u16);
|
||||
fn remove_year(&mut self);
|
||||
|
||||
fn album(&self) -> Option<Album<'_>> {
|
||||
self.album_title().map(|title| Album {
|
||||
title,
|
||||
artist: self.album_artist(),
|
||||
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(&self) -> Option<Album<'_>> {
|
||||
self.album_title().map(|title| Album {
|
||||
title,
|
||||
artist: self.album_artist(),
|
||||
cover: self.album_cover(),
|
||||
})
|
||||
}
|
||||
|
||||
fn album_title(&self) -> Option<&str>;
|
||||
fn set_album_title(&mut self, v: &str);
|
||||
fn remove_album_title(&mut self);
|
||||
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_artist(&self) -> Option<&str>;
|
||||
fn set_album_artist(&mut self, v: &str);
|
||||
fn remove_album_artist(&mut self);
|
||||
|
||||
fn album_artists(&self) -> Option<Vec<&str>> {
|
||||
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);
|
||||
}
|
||||
fn album_artists(&self) -> Option<Vec<&str>> {
|
||||
self.album_artist().map(|v| vec![v])
|
||||
}
|
||||
fn add_album_artist(&mut self, artist: &str) {
|
||||
self.set_album_artist(artist);
|
||||
}
|
||||
|
||||
fn album_cover(&self) -> Option<Picture>;
|
||||
fn set_album_cover(&mut self, cover: Picture);
|
||||
fn remove_album_cover(&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(&self) -> (Option<u16>, Option<u16>) {
|
||||
(self.track_number(), self.total_tracks())
|
||||
}
|
||||
fn set_track(&mut self, track: u16) {
|
||||
self.set_track_number(track);
|
||||
}
|
||||
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 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 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(&self) -> (Option<u16>, Option<u16>) {
|
||||
(self.disc_number(), self.total_discs())
|
||||
}
|
||||
fn set_disc(&mut self, disc: u16) {
|
||||
self.set_disc_number(disc);
|
||||
}
|
||||
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 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 total_discs(&self) -> Option<u16>;
|
||||
fn set_total_discs(&mut self, total_discs: u16);
|
||||
fn remove_total_discs(&mut self);
|
||||
}
|
||||
|
||||
pub trait AudioTagWrite {
|
||||
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 AudioTagConfig {
|
||||
fn config(&self) -> &Config;
|
||||
fn set_config(&mut self, config: Config);
|
||||
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 ToAnyTag: ToAny {
|
||||
fn to_anytag(&self) -> AnyTag<'_>;
|
||||
fn to_anytag(&self) -> AnyTag<'_>;
|
||||
|
||||
/// Convert the tag type, which can be lossy.
|
||||
fn to_dyn_tag(&self, tag_type: TagType) -> Box<dyn AudioTag> {
|
||||
// TODO: write a macro or something that implement this method for every tag type so that if the
|
||||
// TODO: target type is the same, just return self
|
||||
match tag_type {
|
||||
TagType::Id3v2 => Box::new(Id3v2Tag::from(self.to_anytag())),
|
||||
TagType::Mp4 => Box::new(Mp4Tag::from(self.to_anytag())),
|
||||
TagType::Flac => Box::new(FlacTag::from(self.to_anytag())),
|
||||
}
|
||||
}
|
||||
/// Convert the tag type, which can be lossy.
|
||||
fn to_dyn_tag(&self, tag_type: TagType) -> Box<dyn AudioTag> {
|
||||
// TODO: write a macro or something that implement this method for every tag type so that if the
|
||||
// TODO: target type is the same, just return self
|
||||
match tag_type {
|
||||
TagType::Id3v2 => Box::new(Id3v2Tag::from(self.to_anytag())),
|
||||
TagType::Vorbis => Box::new(VorbisTag::from(self.to_anytag())),
|
||||
TagType::Opus => Box::new(OpusTag::from(self.to_anytag())),
|
||||
TagType::Flac => Box::new(FlacTag::from(self.to_anytag())),
|
||||
TagType::Mp4 => Box::new(Mp4Tag::from(self.to_anytag())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ToAny {
|
||||
fn to_any(&self) -> &dyn std::any::Any;
|
||||
fn to_any_mut(&mut self) -> &mut dyn std::any::Any;
|
||||
fn to_any(&self) -> &dyn std::any::Any;
|
||||
fn to_any_mut(&mut self) -> &mut dyn std::any::Any;
|
||||
}
|
||||
|
||||
pub trait MissingImplementations {
|
||||
fn default() -> Self;
|
||||
fn read_from_path<P>(path: P) -> Result<Self>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
Self: Sized;
|
||||
}
|
||||
|
|
144
src/types.rs
144
src/types.rs
|
@ -2,112 +2,86 @@ pub use super::*;
|
|||
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
|
||||
pub enum MimeType {
|
||||
Png,
|
||||
Jpeg,
|
||||
Tiff,
|
||||
Bmp,
|
||||
Gif,
|
||||
Png,
|
||||
Jpeg,
|
||||
Tiff,
|
||||
Bmp,
|
||||
Gif,
|
||||
}
|
||||
|
||||
impl TryFrom<&str> for MimeType {
|
||||
type Error = crate::Error;
|
||||
fn try_from(inp: &str) -> crate::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(crate::Error::UnsupportedMimeType(inp.to_owned())),
|
||||
})
|
||||
}
|
||||
type Error = TaggedError;
|
||||
fn try_from(inp: &str) -> crate::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(TaggedError::UnsupportedMimeType(inp.to_owned())),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<MimeType> for &'static str {
|
||||
fn from(mt: MimeType) -> Self {
|
||||
match mt {
|
||||
MimeType::Jpeg => "image/jpeg",
|
||||
MimeType::Png => "image/png",
|
||||
MimeType::Tiff => "image/tiff",
|
||||
MimeType::Bmp => "image/bmp",
|
||||
MimeType::Gif => "image/gif",
|
||||
}
|
||||
}
|
||||
fn from(mt: MimeType) -> Self {
|
||||
match mt {
|
||||
MimeType::Jpeg => "image/jpeg",
|
||||
MimeType::Png => "image/png",
|
||||
MimeType::Tiff => "image/tiff",
|
||||
MimeType::Bmp => "image/bmp",
|
||||
MimeType::Gif => "image/gif",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<MimeType> for String {
|
||||
fn from(mt: MimeType) -> Self {
|
||||
<MimeType as Into<&'static str>>::into(mt).to_owned()
|
||||
}
|
||||
fn from(mt: MimeType) -> Self {
|
||||
<MimeType as Into<&'static str>>::into(mt).to_owned()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub struct Picture<'a> {
|
||||
pub data: &'a [u8],
|
||||
pub mime_type: MimeType,
|
||||
pub data: &'a [u8],
|
||||
pub mime_type: MimeType,
|
||||
}
|
||||
|
||||
impl<'a> Picture<'a> {
|
||||
pub fn new(data: &'a [u8], mime_type: MimeType) -> Self {
|
||||
Self { data, mime_type }
|
||||
}
|
||||
pub fn new(data: &'a [u8], mime_type: MimeType) -> Self {
|
||||
Self { data, mime_type }
|
||||
}
|
||||
}
|
||||
|
||||
/// A struct for representing an album for convinience.
|
||||
/// A struct for representing an album for convenience.
|
||||
#[derive(Debug)]
|
||||
pub struct Album<'a> {
|
||||
pub title: &'a str,
|
||||
pub artist: Option<&'a str>,
|
||||
pub cover: Option<Picture<'a>>,
|
||||
pub title: &'a str,
|
||||
pub artist: Option<&'a str>,
|
||||
pub cover: Option<Picture<'a>>,
|
||||
}
|
||||
|
||||
impl<'a> Album<'a> {
|
||||
pub fn with_title(title: &'a str) -> Self {
|
||||
Self {
|
||||
title: title,
|
||||
artist: None,
|
||||
cover: None,
|
||||
}
|
||||
}
|
||||
pub fn and_artist(mut self, artist: &'a str) -> Self {
|
||||
self.artist = Some(artist);
|
||||
self
|
||||
}
|
||||
pub fn and_cover(mut self, cover: Picture<'a>) -> Self {
|
||||
self.cover = Some(cover);
|
||||
self
|
||||
}
|
||||
pub fn with_all(title: &'a str, artist: &'a str, cover: Picture<'a>) -> Self {
|
||||
Self {
|
||||
title,
|
||||
artist: Some(artist),
|
||||
cover: Some(cover),
|
||||
}
|
||||
}
|
||||
pub fn with_title(title: &'a str) -> Self {
|
||||
Self {
|
||||
title,
|
||||
artist: None,
|
||||
cover: None,
|
||||
}
|
||||
}
|
||||
pub fn and_artist(mut self, artist: &'a str) -> Self {
|
||||
self.artist = Some(artist);
|
||||
self
|
||||
}
|
||||
pub fn and_cover(mut self, cover: Picture<'a>) -> Self {
|
||||
self.cover = Some(cover);
|
||||
self
|
||||
}
|
||||
pub fn with_all(title: &'a str, artist: &'a str, cover: Picture<'a>) -> Self {
|
||||
Self {
|
||||
title,
|
||||
artist: Some(artist),
|
||||
cover: Some(cover),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// #[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),
|
||||
// }
|
||||
|
|
|
@ -1,37 +1,37 @@
|
|||
use audiotags::*;
|
||||
use lofty::*;
|
||||
|
||||
#[test]
|
||||
fn test_inner() {
|
||||
let mut innertag = metaflac::Tag::default();
|
||||
innertag
|
||||
.vorbis_comments_mut()
|
||||
.set_title(vec!["title from metaflac::Tag"]);
|
||||
let tag: FlacTag = innertag.into();
|
||||
let mut id3tag = tag.to_dyn_tag(TagType::Id3v2);
|
||||
id3tag
|
||||
.write_to_path("assets/a.mp3")
|
||||
.expect("Fail to write!");
|
||||
let mut innertag = metaflac::Tag::default();
|
||||
innertag
|
||||
.vorbis_comments_mut()
|
||||
.set_title(vec!["title from metaflac::Tag"]);
|
||||
let tag: FlacTag = innertag.into();
|
||||
let mut id3tag = tag.to_dyn_tag(TagType::Id3v2);
|
||||
id3tag
|
||||
.write_to_path("assets/a.mp3")
|
||||
.expect("Fail to write!");
|
||||
|
||||
let id3tag_reload = Tag::default()
|
||||
.read_from_path("assets/a.mp3")
|
||||
.expect("Fail to read!");
|
||||
assert_eq!(id3tag_reload.title(), Some("title from metaflac::Tag"));
|
||||
let id3tag_reload = Tag::default()
|
||||
.read_from_path("assets/a.mp3")
|
||||
.expect("Fail to read!");
|
||||
assert_eq!(id3tag_reload.title(), Some("title from metaflac::Tag"));
|
||||
|
||||
// let id3tag: Id3v2Tag = id3tag_reload.into();
|
||||
let mut id3tag_inner: id3::Tag = id3tag_reload.into();
|
||||
let timestamp = id3::Timestamp {
|
||||
year: 2013,
|
||||
month: Some(2u8),
|
||||
day: Some(5u8),
|
||||
hour: Some(6u8),
|
||||
minute: None,
|
||||
second: None,
|
||||
};
|
||||
id3tag_inner.set_date_recorded(timestamp.clone());
|
||||
id3tag_inner
|
||||
.write_to_path("assets/a.mp3", id3::Version::Id3v24)
|
||||
.expect("Fail to write!");
|
||||
// let id3tag: Id3v2Tag = id3tag_reload.into();
|
||||
let mut id3tag_inner: id3::Tag = id3tag_reload.into();
|
||||
let timestamp = id3::Timestamp {
|
||||
year: 2013,
|
||||
month: Some(2u8),
|
||||
day: Some(5u8),
|
||||
hour: Some(6u8),
|
||||
minute: None,
|
||||
second: None,
|
||||
};
|
||||
id3tag_inner.set_date_recorded(timestamp.clone());
|
||||
id3tag_inner
|
||||
.write_to_path("assets/a.mp3", id3::Version::Id3v24)
|
||||
.expect("Fail to write!");
|
||||
|
||||
let id3tag_reload = id3::Tag::read_from_path("assets/a.mp3").expect("Fail to read!");
|
||||
assert_eq!(id3tag_reload.date_recorded(), Some(timestamp));
|
||||
let id3tag_reload = id3::Tag::read_from_path("assets/a.mp3").expect("Fail to read!");
|
||||
assert_eq!(id3tag_reload.date_recorded(), Some(timestamp));
|
||||
}
|
||||
|
|
82
tests/io.rs
82
tests/io.rs
|
@ -1,52 +1,52 @@
|
|||
use audiotags::{MimeType, Picture, Tag};
|
||||
use lofty::{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
|
||||
( $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");
|
||||
|
|
Loading…
Add table
Reference in a new issue