audiotags/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//!
39//! // using `default()` or `new()` alone so that the metadata format is
40//! // guessed (from the file extension) (in this case, Id3v2 tag is read)
41//! let mut tag = Tag::new().read_from_path("test.mp3").unwrap();
42//!
43//! tag.set_title("foo title");
44//! assert_eq!(tag.title(), Some("foo title"));
45//! tag.remove_title();
46//! assert!(tag.title().is_none());
47//! tag.remove_title();
48//! // trying to remove a field that's already empty won't hurt
49//!
50//! let cover = Picture {
51//! mime_type: MimeType::Jpeg,
52//! data: &vec![0u8; 10],
53//! };
54//!
55//! tag.set_album_cover(cover.clone());
56//! assert_eq!(tag.album_cover(), Some(cover));
57//! tag.remove_album_cover();
58//! assert!(tag.album_cover().is_none());
59//! tag.remove_album_cover();
60//!
61//! tag.write_to_path("test.mp3").expect("Fail to save");
62//! ```
63
64pub(crate) use audiotags_macro::*;
65
66pub mod anytag;
67pub use anytag::*;
68
69pub mod components;
70pub use components::*;
71
72pub mod error;
73pub use error::{Error, Result};
74
75pub mod traits;
76pub use traits::*;
77
78pub mod types;
79pub use types::*;
80
81pub mod config;
82pub use config::Config;
83
84use std::convert::From;
85use std::fs::File;
86use std::path::Path;
87
88pub use std::convert::{TryFrom, TryInto};
89
90/// A builder for `Box<dyn AudioTag>`. If you do not want a trait object, you can use individual types.
91///
92/// # Examples
93///
94/// ```no_run
95/// use audiotags::{Tag, TagType};
96///
97/// # fn main() -> audiotags::Result<()> {
98/// // Guess the format by default
99/// let mut tag = Tag::new().read_from_path("assets/a.mp3").unwrap();
100/// tag.set_title("Foo");
101///
102/// // you can convert the tag type and save the metadata to another file.
103/// tag.to_dyn_tag(TagType::Mp4).write_to_path("assets/a.m4a")?;
104///
105/// // you can specify the tag type (but when you want to do this, also consider directly using the concrete type)
106/// let tag = Tag::new().with_tag_type(TagType::Mp4).read_from_path("assets/a.m4a").unwrap();
107/// assert_eq!(tag.title(), Some("Foo"));
108/// # Ok(()) }
109/// ```
110#[derive(Default)]
111pub struct Tag {
112 /// The tag type which can be specified with `.with_tag_type()` before parsing.
113 tag_type: Option<TagType>,
114 /// The config which can be specified with `.with_config()` before parsing.
115 config: Config,
116}
117
118impl Tag {
119 /// Initiate a new Tag (a builder for `Box<dyn AudioTag>`) with default configurations.
120 /// You can then optionally chain `with_tag_type` and/or `with_config`.
121 /// Finally, you `read_from_path`
122 pub fn new() -> Self {
123 Self::default()
124 }
125 /// Specify the tag type
126 pub fn with_tag_type(self, tag_type: TagType) -> Self {
127 Self {
128 tag_type: Some(tag_type),
129 config: self.config,
130 }
131 }
132 /// Specify configuration, if you do not want to use the default
133 pub fn with_config(self, config: Config) -> Self {
134 Self {
135 tag_type: self.tag_type,
136 config,
137 }
138 }
139 pub fn read_from_path(
140 &self,
141 path: impl AsRef<Path>,
142 ) -> crate::Result<Box<dyn AudioTag + Send + Sync>> {
143 match self.tag_type.unwrap_or(TagType::try_from_ext(
144 path.as_ref()
145 .extension()
146 .ok_or(Error::UnknownFileExtension(String::new()))?
147 .to_string_lossy()
148 .to_string()
149 .to_lowercase()
150 .as_str(),
151 )?) {
152 TagType::Id3v2 => Ok(Box::new({
153 let mut t = Id3v2Tag::read_from_path(path)?;
154 t.set_config(self.config);
155 t
156 })),
157 TagType::Mp4 => Ok(Box::new({
158 let mut t = Mp4Tag::read_from_path(path)?;
159 t.set_config(self.config);
160 t
161 })),
162 TagType::Flac => Ok(Box::new({
163 let mut t = FlacTag::read_from_path(path)?;
164 t.set_config(self.config);
165 t
166 })),
167 }
168 }
169}
170
171#[derive(Clone, Copy, Debug)]
172pub enum TagType {
173 /// ## Common file extensions
174 ///
175 /// `.mp3`
176 ///
177 /// ## References
178 ///
179 /// - <https://www.wikiwand.com/en/ID3>
180 Id3v2,
181 Flac,
182 /// ## Common file extensions
183 ///
184 /// `.mp4, .m4a, .m4p, .m4b, .m4r and .m4v`
185 ///
186 /// ## References
187 ///
188 /// - <https://www.wikiwand.com/en/MPEG-4_Part_14>
189 Mp4,
190}
191
192#[rustfmt::skip]
193impl TagType {
194 fn try_from_ext(ext: &str) -> crate::Result<Self> {
195 match ext {
196 "mp3" => Ok(Self::Id3v2),
197 "m4a" | "m4b" | "m4p" | "m4v" | "isom" | "mp4" => Ok(Self::Mp4),
198 "flac" => Ok(Self::Flac),
199 p => Err(crate::Error::UnsupportedFormat(p.to_owned())),
200 }
201 }
202}
203
204/// Convert a concrete tag type into another
205#[macro_export]
206macro_rules! convert {
207 ($inp:expr, $target_type:ty) => {
208 $target_type::from(inp.to_anytag())
209 };
210}