mirror of
https://github.com/Serial-ATA/lofty-rs
synced 2024-11-10 06:34:18 +00:00
VERY rough implementation of a single VorbisTag struct for all formats using vorbis comments, seems to work so far.
This commit is contained in:
parent
c835687098
commit
702f6617c5
17 changed files with 397 additions and 55 deletions
12
Cargo.toml
12
Cargo.toml
|
@ -14,13 +14,19 @@ categories = ["accessiblity", "multimedia::audio"]
|
|||
[dependencies]
|
||||
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}
|
||||
hound = {version = "3.4.0", optional = true}
|
||||
claxon = {version = "0.4.3", optional = true}
|
||||
thiserror = "1.0.24"
|
||||
|
||||
[features]
|
||||
default = ["tags", "duration"]
|
||||
tags = ["opus_headers", "lewton", "metaflac", "mp4ameta", "id3"]
|
||||
default = ["full"]
|
||||
full = ["all_tags", "duration"]
|
||||
mp4 = ["mp4ameta"]
|
||||
mp3 = ["id3"]
|
||||
wav = ["hound"]
|
||||
vorbis = ["lewton", "claxon", "opus_headers"]
|
||||
all_tags = ["vorbis", "mp4", "mp3", "wav"]
|
||||
duration = ["mp3-duration"]
|
|
@ -1,6 +1,8 @@
|
|||
#![cfg(feature = "flac")]
|
||||
|
||||
use crate::{
|
||||
impl_tag, Album, AnyTag, AudioTag, AudioTagEdit, AudioTagWrite, Picture,
|
||||
Result, TagType, ToAny, ToAnyTag,
|
||||
impl_tag, Album, AnyTag, AudioTag, AudioTagEdit, AudioTagWrite, Picture, Result, TagType,
|
||||
ToAny, ToAnyTag,
|
||||
};
|
||||
use std::{convert::TryInto, fs::File, path::Path};
|
||||
|
||||
|
|
|
@ -1,10 +1,19 @@
|
|||
#![cfg(feature = "mp3")]
|
||||
|
||||
use crate::{
|
||||
impl_tag, Album, AnyTag, AudioTag, AudioTagEdit, AudioTagWrite, Error, MimeType, Picture,
|
||||
Result, TagType, ToAny, ToAnyTag,
|
||||
Result, ToAny, ToAnyTag, TagType
|
||||
};
|
||||
use std::{convert::TryInto, fs::File, path::Path};
|
||||
|
||||
pub use id3::Tag as Id3v2InnerTag;
|
||||
use crate::traits::ReadPath;
|
||||
|
||||
impl ReadPath for Id3v2InnerTag {
|
||||
fn from_path<P>(path: P, _tag_type: Option<TagType>) -> Result<Self> where P: AsRef<std::path::Path>, Self: Sized {
|
||||
Ok(Self::read_from_path(path)?)
|
||||
}
|
||||
}
|
||||
|
||||
impl_tag!(Id3v2Tag, Id3v2InnerTag, TagType::Id3v2);
|
||||
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
pub(crate) mod flac_tag;
|
||||
pub(crate) mod id3_tag;
|
||||
pub(crate) mod mp4_tag;
|
||||
pub(crate) mod opus_tag;
|
||||
pub(crate) mod ogg_tag;
|
||||
pub(crate) mod vorbis_tag;
|
||||
pub(crate) mod wav_tag;
|
||||
|
||||
pub use flac_tag::FlacTag;
|
||||
#[cfg(feature = "vorbis")]
|
||||
pub use vorbis_tag::VorbisTag;
|
||||
#[cfg(feature = "mp3")]
|
||||
pub use id3_tag::Id3v2Tag;
|
||||
pub use mp4_tag::Mp4Tag;
|
||||
pub use opus_tag::OpusTag;
|
||||
pub use ogg_tag::OggTag;
|
||||
#[cfg(feature = "mp4")]
|
||||
pub use mp4_tag::Mp4Tag;
|
|
@ -1,10 +1,19 @@
|
|||
#![cfg(feature = "mp4")]
|
||||
|
||||
use crate::{
|
||||
impl_tag, Album, AnyTag, AudioTag, AudioTagEdit, AudioTagWrite, Error, MimeType, Picture,
|
||||
Result, TagType, ToAny, ToAnyTag,
|
||||
Result, ToAny, ToAnyTag, TagType
|
||||
};
|
||||
use std::{fs::File, path::Path};
|
||||
|
||||
pub use mp4ameta::{FourCC, Tag as Mp4InnerTag};
|
||||
use crate::traits::ReadPath;
|
||||
|
||||
impl ReadPath for Mp4InnerTag {
|
||||
fn from_path<P>(path: P, _tag_type: Option<TagType>) -> Result<Self> where P: AsRef<std::path::Path>, Self: Sized {
|
||||
Ok(Self::read_from_path(path)?)
|
||||
}
|
||||
}
|
||||
|
||||
impl_tag!(Mp4Tag, Mp4InnerTag, TagType::Mp4);
|
||||
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
#![cfg(feature = "ogg")]
|
||||
|
||||
use crate::{
|
||||
impl_tag, traits::MissingImplementations, Album, AnyTag, AudioTag, AudioTagEdit, AudioTagWrite, Picture, Result, TagType, ToAny, ToAnyTag,
|
||||
impl_tag, traits::MissingImplementations, Album, AnyTag, AudioTag, AudioTagEdit, AudioTagWrite,
|
||||
Picture, Result, TagType, ToAny, ToAnyTag,
|
||||
};
|
||||
use std::{
|
||||
collections::{hash_map::RandomState, HashMap},
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
#![cfg(feature = "opus")]
|
||||
|
||||
use crate::{
|
||||
impl_tag, traits::MissingImplementations, Album, AnyTag, AudioTag, AudioTagEdit, AudioTagWrite,
|
||||
Picture, Result, TagType, ToAny, ToAnyTag,
|
||||
};
|
||||
use std::{
|
||||
fs::File,
|
||||
path::Path,
|
||||
};
|
||||
use std::{fs::File, path::Path};
|
||||
|
||||
use opus_headers::{CommentHeader, IdentificationHeader, OpusHeaders as OpusInnerTag};
|
||||
|
||||
|
@ -28,7 +27,7 @@ impl MissingImplementations for OpusInnerTag {
|
|||
}
|
||||
}
|
||||
|
||||
fn read_from_path<P>(path: P) -> Result<Self>
|
||||
fn from_path<P>(path: P) -> Result<Self>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
|
|
294
src/components/vorbis_tag.rs
Normal file
294
src/components/vorbis_tag.rs
Normal file
|
@ -0,0 +1,294 @@
|
|||
#![cfg(feature = "vorbis")]
|
||||
|
||||
use crate::{impl_tag, Album, AnyTag, AudioTag, AudioTagEdit, AudioTagWrite, Picture, Result, ToAny, ToAnyTag, TagType};
|
||||
use std::{fs::File, path::Path, collections::HashMap};
|
||||
|
||||
struct VorbisInnerTag {
|
||||
tag_type: Option<TagType>,
|
||||
comments: HashMap<String, String>,
|
||||
}
|
||||
|
||||
impl Default for VorbisInnerTag {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
tag_type: None,
|
||||
comments: Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl VorbisInnerTag {
|
||||
fn get_value(&self, key: &str) -> Option<&str> {
|
||||
if let Some(pair) = self.comments.get_key_value(key) {
|
||||
if !pair.1.is_empty() {
|
||||
Some(pair.1.as_str())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn set_value<V>(&mut self, key: &str, val: V) where
|
||||
V: Into<String> {
|
||||
let mut comments = self.comments.clone();
|
||||
let _ = comments.insert(key.to_string(), val.into());
|
||||
self.comments = comments;
|
||||
}
|
||||
|
||||
fn remove_key(&mut self, key: &str) {
|
||||
let mut comments = self.comments.clone();
|
||||
comments.retain(|k, _| k != key);
|
||||
self.comments = comments;
|
||||
}
|
||||
|
||||
fn from_path<P>(path: P, tag_type: Option<TagType>) -> Result<Self>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
if let Some(tag_type) = tag_type {
|
||||
match tag_type {
|
||||
TagType::Ogg => {
|
||||
let headers = lewton::inside_ogg::OggStreamReader::new(File::open(path)?).unwrap();
|
||||
let comments: HashMap<String, String> =
|
||||
headers.comment_hdr.comment_list.into_iter().collect();
|
||||
|
||||
Ok(Self {
|
||||
tag_type: Some(tag_type),
|
||||
comments,
|
||||
})
|
||||
}
|
||||
TagType::Opus => {
|
||||
let headers = opus_headers::parse_from_path(path)?;
|
||||
|
||||
Ok(Self {
|
||||
tag_type: Some(tag_type),
|
||||
comments: headers.comments.user_comments,
|
||||
})
|
||||
}
|
||||
TagType::Flac => {
|
||||
let headers = claxon::FlacReader::new(File::open(path)?).unwrap();
|
||||
let comments = headers.tags().map(|(k, v)| (k.to_string(), v.to_string())).collect();
|
||||
Ok(Self {
|
||||
tag_type: Some(tag_type),
|
||||
comments,
|
||||
})
|
||||
}
|
||||
_ => unreachable!()
|
||||
}
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl_tag!(VorbisTag, VorbisInnerTag, TagType::Ogg);
|
||||
|
||||
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));
|
||||
inp.year.map(|v| t.set_year(v as u16));
|
||||
inp.album().title.map(|v| t.set_album_title(v));
|
||||
inp.album()
|
||||
.artists
|
||||
.map(|v| t.set_album_artists(v.join(", ")));
|
||||
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 = Album::new(inp.album_title(), inp.album_artists(), 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 AudioTagEdit for VorbisTag {
|
||||
fn title(&self) -> Option<&str> {
|
||||
self.0.get_value("TITLE")
|
||||
}
|
||||
fn set_title(&mut self, title: &str) {
|
||||
self.0.set_value("TITLE", title);
|
||||
}
|
||||
|
||||
fn remove_title(&mut self) {
|
||||
self.0.remove_key("TITLE");
|
||||
}
|
||||
fn artist(&self) -> Option<&str> {
|
||||
self.0.get_value("ARTIST")
|
||||
}
|
||||
|
||||
fn set_artist(&mut self, artist: &str) {
|
||||
self.0.set_value("ARTIST", artist)
|
||||
}
|
||||
|
||||
fn add_artist(&mut self, artist: &str) {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn artists(&self) -> Option<Vec<&str>> {
|
||||
self.artist().map(|a| a.split(", ").collect())
|
||||
}
|
||||
|
||||
fn remove_artist(&mut self) {
|
||||
self.0.remove_key("ARTIST");
|
||||
}
|
||||
|
||||
fn year(&self) -> Option<u16> {
|
||||
if let Some(Ok(y)) = self.0
|
||||
.get_value("DATE")
|
||||
.map(|s| s.chars().take(4).collect::<String>().parse::<i32>())
|
||||
{
|
||||
Some(y as u16)
|
||||
} else if let Some(Ok(y)) = self.0.get_value("YEAR").map(|s| s.parse::<i32>()) {
|
||||
Some(y as u16)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
fn set_year(&mut self, year: u16) {
|
||||
self.0.set_value("DATE", &year.to_string());
|
||||
self.0.set_value("YEAR", &year.to_string());
|
||||
}
|
||||
fn remove_year(&mut self) {
|
||||
self.0.remove_key("YEAR");
|
||||
self.0.remove_key("DATE");
|
||||
}
|
||||
|
||||
fn album_title(&self) -> Option<&str> {
|
||||
self.0.get_value("ALBUM")
|
||||
}
|
||||
fn set_album_title(&mut self, title: &str) {
|
||||
self.0.set_value("ALBUM", title)
|
||||
}
|
||||
fn remove_album_title(&mut self) {
|
||||
self.0.remove_key("ALBUM");
|
||||
}
|
||||
|
||||
fn album_artists(&self) -> Option<Vec<&str>> {
|
||||
self.0.get_value("ALBUMARTIST").map(|a| vec![a])
|
||||
}
|
||||
fn set_album_artists(&mut self, artists: String) {
|
||||
self.0.set_value("ALBUMARTIST", artists)
|
||||
}
|
||||
|
||||
fn add_album_artist(&mut self, artist: &str) {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn remove_album_artists(&mut self) {
|
||||
self.0.remove_key("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.0.get_value("TRACKNUMBER").map(|x| x.parse::<u16>()) {
|
||||
Some(n)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
fn set_track_number(&mut self, v: u16) {
|
||||
self.0.set_value("TRACKNUMBER", &v.to_string())
|
||||
}
|
||||
fn remove_track_number(&mut self) {
|
||||
self.0.remove_key("TRACKNUMBER");
|
||||
}
|
||||
|
||||
// ! not standard
|
||||
fn total_tracks(&self) -> Option<u16> {
|
||||
if let Some(Ok(n)) = self.0.get_value("TOTALTRACKS").map(|x| x.parse::<u16>()) {
|
||||
Some(n)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
fn set_total_tracks(&mut self, v: u16) {
|
||||
self.0.set_value("TOTALTRACKS", &v.to_string())
|
||||
}
|
||||
fn remove_total_tracks(&mut self) {
|
||||
self.0.remove_key("TOTALTRACKS");
|
||||
}
|
||||
|
||||
fn disc_number(&self) -> Option<u16> {
|
||||
if let Some(Ok(n)) = self.0.get_value("DISCNUMBER").map(|x| x.parse::<u16>()) {
|
||||
Some(n)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
fn set_disc_number(&mut self, v: u16) {
|
||||
self.0.set_value("DISCNUMBER", &v.to_string())
|
||||
}
|
||||
fn remove_disc_number(&mut self) {
|
||||
self.0.remove_key("DISCNUMBER");
|
||||
}
|
||||
|
||||
// ! not standard
|
||||
fn total_discs(&self) -> Option<u16> {
|
||||
if let Some(Ok(n)) = self.0.get_value("TOTALDISCS").map(|x| x.parse::<u16>()) {
|
||||
Some(n)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
fn set_total_discs(&mut self, v: u16) {
|
||||
self.0.set_value("TOTALDISCS", &v.to_string())
|
||||
}
|
||||
fn remove_total_discs(&mut self) {
|
||||
self.0.remove_key("TOTALDISCS");
|
||||
}
|
||||
}
|
||||
|
||||
impl AudioTagWrite for VorbisTag {
|
||||
fn write_to(&mut self, file: &mut File) -> Result<()> {
|
||||
// self.0.write_to(file)?; TODO
|
||||
Ok(())
|
||||
}
|
||||
fn write_to_path(&mut self, path: &str) -> Result<()> {
|
||||
// self.0.write_to_path(path)?; TODO
|
||||
Ok(())
|
||||
}
|
||||
}
|
1
src/components/wav_tag.rs
Normal file
1
src/components/wav_tag.rs
Normal file
|
@ -0,0 +1 @@
|
|||
|
|
@ -12,7 +12,7 @@ pub enum Error {
|
|||
UnsupportedMimeType(String),
|
||||
|
||||
#[error(transparent)]
|
||||
FlacTagError(#[from] metaflac::Error),
|
||||
FlacTagError(#[from] claxon::Error),
|
||||
#[error(transparent)]
|
||||
Id3TagError(#[from] id3::Error),
|
||||
#[error(transparent)]
|
||||
|
|
|
@ -43,14 +43,17 @@
|
|||
//!
|
||||
//! # Features
|
||||
//!
|
||||
//! By default, `tags` and `duration` are enabled.
|
||||
//! By default, `full` (`all_tags` and `duration`) are enabled.
|
||||
//!
|
||||
//! `tags` provides all the track metadata (`artists`, `album`, etc.) in [AnyTag].
|
||||
//! `all_tags` provides all the track metadata (`artists`, `album`, etc.) in [AnyTag].
|
||||
//!
|
||||
//! `duration` provides the `duration` field in [AnyTag].
|
||||
//!
|
||||
//! Either one can be disabled if it doesn't fit your use case.
|
||||
//!
|
||||
//! In addition to this, each format can be individually enabled.
|
||||
//! All features are: `opus, ogg, flac, mp4, mp3, wav`.
|
||||
//!
|
||||
//! ## Performance
|
||||
//!
|
||||
//! Using lofty incurs a little overhead due to vtables if you want to guess the metadata format (from file extension).
|
||||
|
@ -91,7 +94,7 @@ mod error;
|
|||
pub use crate::error::{Error, Result};
|
||||
|
||||
mod components;
|
||||
pub use crate::components::{FlacTag, Id3v2Tag, Mp4Tag, OpusTag, OggTag};
|
||||
pub use crate::components::*;
|
||||
|
||||
mod traits;
|
||||
pub use crate::traits::{AudioTag, AudioTagEdit, AudioTagWrite, ToAny, ToAnyTag};
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
#[doc(hidden)]
|
||||
#[macro_export]
|
||||
macro_rules! impl_tag {
|
||||
($tag:ident , $inner:ident, $tag_type:expr) => {
|
||||
#[doc(hidden)]
|
||||
($tag:ident, $inner:ident, $tag_type:expr) => {
|
||||
#[doc(hidden)]
|
||||
pub struct $tag($inner);
|
||||
|
||||
impl Default for $tag {
|
||||
|
@ -15,11 +15,11 @@ macro_rules! impl_tag {
|
|||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
pub fn read_from_path<P>(path: P) -> Result<Self>
|
||||
pub fn read_from_path<P>(path: P, tag_type: Option<TagType>) -> Result<Self>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
Ok(Self($inner::read_from_path(path)?))
|
||||
Ok(Self($inner::from_path(path, tag_type)?))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -71,14 +71,15 @@ macro_rules! impl_tag {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// From dyn AudioTag to inner (any type)
|
||||
impl std::convert::From<Box<dyn AudioTag>> for $inner {
|
||||
impl From<Box<dyn AudioTag>> for $inner {
|
||||
fn from(inp: Box<dyn AudioTag>) -> Self {
|
||||
let t: $tag = inp.into();
|
||||
t.into()
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert a concrete tag type into another
|
||||
|
|
28
src/tag.rs
28
src/tag.rs
|
@ -1,5 +1,6 @@
|
|||
use super::{AudioTag, Error, FlacTag, Id3v2Tag, Mp4Tag, OpusTag, Result, OggTag};
|
||||
use super::{components::*, AudioTag, Error, Result};
|
||||
use std::path::Path;
|
||||
use crate::vorbis_tag::VorbisTag;
|
||||
|
||||
/// A builder for `Box<dyn AudioTag>`. If you do not want a trait object, you can use individual types.
|
||||
#[derive(Default)]
|
||||
|
@ -18,27 +19,33 @@ impl Tag {
|
|||
pub fn read_from_path(&self, path: impl AsRef<Path>) -> 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::Ogg => Ok(Box::new(OggTag::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)?)),
|
||||
match self.0.as_ref().unwrap_or(&TagType::try_from_ext(extension)?) {
|
||||
#[cfg(feature = "mp3")]
|
||||
TagType::Id3v2 => Ok(Box::new(Id3v2Tag::read_from_path(path, None)?)),
|
||||
#[cfg(feature = "mp4")]
|
||||
TagType::Mp4 => Ok(Box::new(Mp4Tag::read_from_path(path, None)?)),
|
||||
#[cfg(feature = "vorbis")]
|
||||
id @ _ => Ok(Box::new(VorbisTag::read_from_path(path, Some(id.to_owned()))?)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The tag type, based on the file extension.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum TagType {
|
||||
#[cfg(feature = "mp3")]
|
||||
/// Common file extensions: `.mp3`
|
||||
Id3v2,
|
||||
#[cfg(feature = "vorbis")]
|
||||
/// Common file extensions: `.ogg, .oga`
|
||||
Ogg,
|
||||
#[cfg(feature = "vorbis")]
|
||||
/// Common file extensions: `.opus`
|
||||
Opus,
|
||||
#[cfg(feature = "vorbis")]
|
||||
/// Common file extensions: `.flac`
|
||||
Flac,
|
||||
#[cfg(feature = "mp4")]
|
||||
/// Common file extensions: `.mp4, .m4a, .m4p, .m4b, .m4r, .m4v`
|
||||
Mp4,
|
||||
}
|
||||
|
@ -46,10 +53,15 @@ pub enum TagType {
|
|||
impl TagType {
|
||||
fn try_from_ext(ext: &str) -> Result<Self> {
|
||||
match ext {
|
||||
#[cfg(feature = "mp3")]
|
||||
"mp3" => Ok(Self::Id3v2),
|
||||
#[cfg(feature = "vorbis")]
|
||||
"opus" => Ok(Self::Opus),
|
||||
#[cfg(feature = "vorbis")]
|
||||
"flac" => Ok(Self::Flac),
|
||||
#[cfg(feature = "vorbis")]
|
||||
"ogg" | "oga" => Ok(Self::Ogg),
|
||||
#[cfg(feature = "mp4")]
|
||||
"m4a" | "m4b" | "m4p" | "m4v" | "isom" | "mp4" => Ok(Self::Mp4),
|
||||
_ => Err(Error::UnsupportedFormat(ext.to_owned())),
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
use crate::{
|
||||
Album, AnyTag, FlacTag, Id3v2Tag, Mp4Tag, OpusTag, Picture, Result, TagType, OggTag,
|
||||
};
|
||||
use std::{fs::File, path::Path};
|
||||
use crate::{components::*, Album, AnyTag, Picture, Result, TagType};
|
||||
use std::fs::File;
|
||||
use crate::vorbis_tag::VorbisTag;
|
||||
|
||||
pub trait AudioTag: AudioTagEdit + AudioTagWrite + ToAnyTag {}
|
||||
|
||||
|
@ -103,10 +102,15 @@ pub trait ToAnyTag: ToAny {
|
|||
// 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 {
|
||||
#[cfg(feature = "mp3")]
|
||||
TagType::Id3v2 => Box::new(Id3v2Tag::from(self.to_anytag())),
|
||||
TagType::Ogg => Box::new(OggTag::from(self.to_anytag())),
|
||||
TagType::Opus => Box::new(OpusTag::from(self.to_anytag())),
|
||||
TagType::Flac => Box::new(FlacTag::from(self.to_anytag())),
|
||||
#[cfg(feature = "vorbis")]
|
||||
TagType::Ogg => Box::new(VorbisTag::from(self.to_anytag())),
|
||||
#[cfg(feature = "vorbis")]
|
||||
TagType::Opus => Box::new(VorbisTag::from(self.to_anytag())),
|
||||
#[cfg(feature = "vorbis")]
|
||||
TagType::Flac => Box::new(VorbisTag::from(self.to_anytag())),
|
||||
#[cfg(feature = "mp4")]
|
||||
TagType::Mp4 => Box::new(Mp4Tag::from(self.to_anytag())),
|
||||
}
|
||||
}
|
||||
|
@ -117,10 +121,6 @@ pub trait ToAny {
|
|||
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;
|
||||
}
|
||||
pub trait ReadPath {
|
||||
fn from_path<P>(path: P, _tag_type: Option<TagType>) -> Result<Self> where P: AsRef<std::path::Path>, Self: Sized;
|
||||
}
|
|
@ -1,6 +1,5 @@
|
|||
use crate::Album;
|
||||
|
||||
|
||||
/// The tag returned from `read_from_path`
|
||||
#[derive(Default)]
|
||||
pub struct AnyTag<'a> {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use lofty::*;
|
||||
|
||||
#[test]
|
||||
#[cfg(all(feature = "mp3", feature = "flac"))]
|
||||
fn test_inner() {
|
||||
let mut innertag = metaflac::Tag::default();
|
||||
innertag
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
#![cfg(feature = "default")]
|
||||
use lofty::{MimeType, Picture, Tag};
|
||||
|
||||
macro_rules! test_file {
|
||||
|
@ -50,9 +51,11 @@ macro_rules! test_file {
|
|||
}
|
||||
|
||||
test_file!(test_ape, "tests/assets/a.ape");
|
||||
test_file!(test_flac, "tests/assets/a.flac");
|
||||
test_file!(test_m4a, "tests/assets/a.m4a");
|
||||
test_file!(test_mp3, "tests/assets/a.mp3");
|
||||
test_file!(test_wav, "tests/assets/a.wav");
|
||||
|
||||
// Vorbis comments
|
||||
test_file!(test_flac, "tests/assets/a.flac");
|
||||
test_file!(test_ogg, "tests/assets/a.ogg");
|
||||
test_file!(test_opus, "tests/assets/a.opus");
|
||||
test_file!(test_wav, "tests/assets/a.wav");
|
||||
|
|
Loading…
Reference in a new issue