Skip to main content

audiotags_qobuz/
lib.rs

1//! [![Crate](https://img.shields.io/crates/v/audiotags.svg)](https://crates.io/crates/audiotags)
2//! [![Crate](https://img.shields.io/crates/d/audiotags.svg)](https://crates.io/crates/audiotags)
3//! [![Crate](https://img.shields.io/crates/l/audiotags.svg)](https://crates.io/crates/audiotags)
4//! [![Documentation](https://docs.rs/audiotags/badge.svg)](https://docs.rs/audiotags/)
5//!
6//! This crate makes it easier to parse, convert and write metadata (a.k.a tag) in audio files of different file types.
7//!
8//! This crate aims to provide a unified trait for parsers and writers of different audio file formats.
9//! This means that you can parse tags in mp3, flac, and m4a files with a single function: `Tag::default().
10//! read_from_path()` and get fields by directly calling `.album()`, `.artist()` on its result. Without this
11//! crate, you would otherwise need to learn different APIs in **id3**, **mp4ameta** etc. in order to parse
12//! metadata in different file formats.
13//!
14//! ## Performance
15//!
16//! Using **audiotags** incurs a little overhead due to vtables if you want to guess the metadata format
17//! (from file extension). Apart from this the performance is almost the same as directly calling function
18//! provided by those 'specialized' crates.
19//!
20//! No copies will be made if you only need to read and write metadata of one format. If you want to convert
21//! between tags, copying is unavoidable no matter if you use **audiotags** or use getters and setters provided
22//! by specialized libraries. **audiotags** is not making additional unnecessary copies.
23//!
24//! ## Supported Formats
25//!
26//! | File Format   | Metadata Format       | backend                                                     |
27//! |---------------|-----------------------|-------------------------------------------------------------|
28//! | `mp3`         | id3v2.4               | [**id3**](https://github.com/polyfloyd/rust-id3)            |
29//! | `m4a/mp4/...` | MPEG-4 audio metadata | [**mp4ameta**](https://github.com/Saecki/rust-mp4ameta)     |
30//! | `flac`        | Vorbis comment        | [**metaflac**](https://github.com/jameshurst/rust-metaflac) |
31//!
32//! ## Examples
33//!
34//! Read the [manual](https://docs.rs/audiotags) for some examples, but here's a quick-one:
35//!
36//! ```rust,no_run
37//! use audiotags::{Tag, Picture, MimeType};
38//! use std::path::Path;
39//!
40//! // using `default()` or `new()` alone so that the metadata format is
41//! // guessed (from the file extension) (in this case, Id3v2 tag is read)
42//! let mut tag = Tag::new().read_from_path("test.mp3").unwrap();
43//!
44//! tag.set_title("foo title");
45//! assert_eq!(tag.title(), Some("foo title"));
46//! tag.remove_title();
47//! assert!(tag.title().is_none());
48//! tag.remove_title();
49//! // trying to remove a field that's already empty won't hurt
50//!
51//! let cover = Picture {
52//!     mime_type: MimeType::Jpeg,
53//!     data: &vec![0u8; 10],
54//! };
55//!
56//! tag.set_album_cover(cover.clone());
57//! assert_eq!(tag.album_cover(), Some(cover));
58//! tag.remove_album_cover();
59//! assert!(tag.album_cover().is_none());
60//! tag.remove_album_cover();
61//!
62//! tag.write_to_path(Path::new("test.mp3")).expect("Fail to save");
63//! ```
64
65pub(crate) use audiotags_macro::*;
66
67pub mod anytag;
68pub use anytag::*;
69
70pub mod components;
71pub use components::*;
72
73pub mod error;
74pub use error::{Error, Result};
75
76pub mod traits;
77pub use traits::*;
78
79pub mod types;
80pub use types::*;
81
82pub mod config;
83pub use config::Config;
84
85use std::convert::From;
86use std::fs::File;
87use std::path::Path;
88
89pub use std::convert::{TryFrom, TryInto};
90
91/// A builder for `Box<dyn AudioTag>`. If you do not want a trait object, you can use individual types.
92///
93/// # Examples
94///
95/// ```no_run
96/// use audiotags::{Tag, TagType};
97/// use std::path::Path;
98///
99/// # fn main() -> audiotags::Result<()> {
100/// // Guess the format by default
101/// let mut tag = Tag::new().read_from_path("assets/a.mp3").unwrap();
102/// tag.set_title("Foo");
103///
104/// // you can convert the tag type and save the metadata to another file.
105/// tag.to_dyn_tag(TagType::Mp4).write_to_path(Path::new("assets/a.m4a"))?;
106///
107/// // you can specify the tag type (but when you want to do this, also consider directly using the concrete type)
108/// let tag = Tag::new().with_tag_type(TagType::Mp4).read_from_path("assets/a.m4a").unwrap();
109/// assert_eq!(tag.title(), Some("Foo"));
110/// # Ok(()) }
111/// ```
112#[derive(Default)]
113pub struct Tag {
114    /// The tag type which can be specified with `.with_tag_type()` before parsing.
115    tag_type: Option<TagType>,
116    /// The config which can be specified with `.with_config()` before parsing.
117    config: Config,
118}
119
120impl Tag {
121    /// Initiate a new Tag (a builder for `Box<dyn AudioTag>`) with default configurations.
122    /// You can then optionally chain `with_tag_type` and/or `with_config`.
123    /// Finally, you `read_from_path`
124    pub fn new() -> Self {
125        Self::default()
126    }
127    /// Specify the tag type
128    pub fn with_tag_type(self, tag_type: TagType) -> Self {
129        Self {
130            tag_type: Some(tag_type),
131            config: self.config,
132        }
133    }
134    /// Specify configuration, if you do not want to use the default
135    pub fn with_config(self, config: Config) -> Self {
136        Self {
137            tag_type: self.tag_type,
138            config,
139        }
140    }
141    pub fn read_from_path(
142        &self,
143        path: impl AsRef<Path>,
144    ) -> crate::Result<Box<dyn AudioTag + Send + Sync>> {
145        match self.tag_type.unwrap_or(TagType::try_from_ext(
146            path.as_ref()
147                .extension()
148                .ok_or(Error::UnknownFileExtension(String::new()))?
149                .to_string_lossy()
150                .to_string()
151                .to_lowercase()
152                .as_str(),
153        )?) {
154            TagType::Id3v2 => Ok(Box::new({
155                let mut t = Id3v2Tag::read_from_path(path)?;
156                t.set_config(self.config);
157                t
158            })),
159            TagType::Mp4 => Ok(Box::new({
160                let mut t = Mp4Tag::read_from_path(path)?;
161                t.set_config(self.config);
162                t
163            })),
164            TagType::Flac => Ok(Box::new({
165                let mut t = FlacTag::read_from_path(path)?;
166                t.set_config(self.config);
167                t
168            })),
169        }
170    }
171}
172
173#[derive(Clone, Copy, Debug)]
174pub enum TagType {
175    /// ## Common file extensions
176    ///
177    /// `.mp3`
178    ///
179    /// ## References
180    ///
181    /// - <https://www.wikiwand.com/en/ID3>
182    Id3v2,
183    Flac,
184    /// ## Common file extensions
185    ///
186    /// `.mp4, .m4a, .m4p, .m4b, .m4r and .m4v`
187    ///
188    /// ## References
189    ///
190    /// - <https://www.wikiwand.com/en/MPEG-4_Part_14>
191    Mp4,
192}
193
194#[rustfmt::skip]
195impl TagType {
196    fn try_from_ext(ext: &str) -> crate::Result<Self> {
197        match ext {
198                                                     "mp3" => Ok(Self::Id3v2),
199            "m4a" | "m4b" | "m4p" | "m4v" | "isom" | "mp4" => Ok(Self::Mp4),
200                                                    "flac" => Ok(Self::Flac),
201            p => Err(crate::Error::UnsupportedFormat(p.to_owned())),
202        }
203    }
204}
205
206/// Convert a concrete tag type into another
207#[macro_export]
208macro_rules! convert {
209    ($inp:expr, $target_type:ty) => {
210        $target_type::from(inp.to_anytag())
211    };
212}