audiotags_qobuz/lib.rs
1//! [](https://crates.io/crates/audiotags)
2//! [](https://crates.io/crates/audiotags)
3//! [](https://crates.io/crates/audiotags)
4//! [](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}