diff --git a/docs/audiotags.epub b/docs/audiotags.epub index 7af99507..bdc78fc8 100644 Binary files a/docs/audiotags.epub and b/docs/audiotags.epub differ diff --git a/docs/audiotags.pdf b/docs/audiotags.pdf index a1503079..5e2dfa71 100644 Binary files a/docs/audiotags.pdf and b/docs/audiotags.pdf differ diff --git a/docs/audiotags.tex b/docs/audiotags.tex index 4a0e0641..04929ecd 100644 --- a/docs/audiotags.tex +++ b/docs/audiotags.tex @@ -205,7 +205,8 @@ The following example shows how you can read the tag in an \texttt{mp3} file, co \CommentTok{// we have an mp3 and an m4a file} \KeywordTok{const}\NormalTok{ MP3\_FILE}\OperatorTok{:} \OperatorTok{\&}\OtherTok{\textquotesingle{}static} \DataTypeTok{str} \OperatorTok{=} \StringTok{"assets/a.mp3"}\OperatorTok{;} \KeywordTok{const}\NormalTok{ M4A\_FILE}\OperatorTok{:} \OperatorTok{\&}\OtherTok{\textquotesingle{}static} \DataTypeTok{str} \OperatorTok{=} \StringTok{"assets/a.m4a"}\OperatorTok{;} - \CommentTok{// read tag from the mp3 file. Using \textasciigrave{}default()\textasciigrave{} so that the type of tag is guessed from the file extension} + \CommentTok{// read tag from the mp3 file. Using \textasciigrave{}default()\textasciigrave{} so that the} + \CommentTok{// type of tag is guessed from the file extension} \KeywordTok{let} \KeywordTok{mut}\NormalTok{ mp3tag }\OperatorTok{=} \PreprocessorTok{Tag::}\KeywordTok{default}\NormalTok{()}\OperatorTok{.}\NormalTok{read\_from\_path(MP3\_FILE)}\OperatorTok{.}\NormalTok{unwrap()}\OperatorTok{;} \CommentTok{// set the title} \NormalTok{ mp3tag}\OperatorTok{.}\NormalTok{set\_title(}\StringTok{"title from mp3 file"}\NormalTok{)}\OperatorTok{;} @@ -213,11 +214,13 @@ The following example shows how you can read the tag in an \texttt{mp3} file, co \KeywordTok{let} \KeywordTok{mut}\NormalTok{ mp4tag }\OperatorTok{=}\NormalTok{ mp3tag}\OperatorTok{.}\NormalTok{into\_tag(}\PreprocessorTok{TagType::}\NormalTok{Mp4)}\OperatorTok{;} \NormalTok{ mp4tag}\OperatorTok{.}\NormalTok{write\_to\_path(M4A\_FILE)}\OperatorTok{.}\NormalTok{unwrap()}\OperatorTok{;} - \CommentTok{// reload the tag from the m4a file; this time specifying the tag type (you can also use \textasciigrave{}default()\textasciigrave{})} + \CommentTok{// reload the tag from the m4a file; this time specifying the} + \CommentTok{// tag type (you can also use \textasciigrave{}default()\textasciigrave{})} \KeywordTok{let} \KeywordTok{mut}\NormalTok{ mp4tag }\OperatorTok{=} \PreprocessorTok{Tag::}\NormalTok{with\_tag\_type(}\PreprocessorTok{TagType::}\NormalTok{Mp4)} \OperatorTok{.}\NormalTok{read\_from\_path(M4A\_FILE)} \OperatorTok{.}\NormalTok{unwrap()}\OperatorTok{;} - \CommentTok{// the tag originated from an mp3 file is successfully written to an m4a file!} + \CommentTok{// the tag originated from an mp3 file is successfully written} + \CommentTok{// to an m4a file!} \PreprocessorTok{assert\_eq!}\NormalTok{(mp4tag}\OperatorTok{.}\NormalTok{title()}\OperatorTok{,} \ConstantTok{Some}\NormalTok{(}\StringTok{"title from mp3 file"}\NormalTok{))}\OperatorTok{;} \CommentTok{// multiple artists} \NormalTok{ mp4tag}\OperatorTok{.}\NormalTok{add\_artist(}\StringTok{"artist1 of mp4"}\NormalTok{)}\OperatorTok{;} @@ -280,7 +283,9 @@ The following example shows how you can downcast a \texttt{Box\textless{}dyn\ Au \OperatorTok{.}\NormalTok{expect(}\StringTok{"Fail to read!"}\NormalTok{)}\OperatorTok{;} \PreprocessorTok{assert\_eq!}\NormalTok{(id3tag\_reload}\OperatorTok{.}\NormalTok{title()}\OperatorTok{,} \ConstantTok{Some}\NormalTok{(}\StringTok{"title from metaflac::Tag"}\NormalTok{))}\OperatorTok{;} - \KeywordTok{let} \KeywordTok{mut}\NormalTok{ id3tag\_inner}\OperatorTok{:} \PreprocessorTok{id3::}\NormalTok{Tag }\OperatorTok{=} \PreprocessorTok{downcast!}\NormalTok{(id3tag\_reload}\OperatorTok{,}\NormalTok{ Id3v2Tag)}\OperatorTok{;} + \KeywordTok{let} \KeywordTok{mut}\NormalTok{ id3tag\_inner}\OperatorTok{:} \PreprocessorTok{id3::}\NormalTok{Tag }\OperatorTok{=}\NormalTok{ id3tag\_reload}\OperatorTok{.}\NormalTok{try\_into()}\OperatorTok{.}\NormalTok{unwrap()}\OperatorTok{;} + \CommentTok{// this would fail if \textasciigrave{}id3tag\_reload\textasciigrave{} isn\textquotesingle{}t really a id3 tag.} + \KeywordTok{let}\NormalTok{ timestamp }\OperatorTok{=} \PreprocessorTok{id3::}\NormalTok{Timestamp }\OperatorTok{\{} \NormalTok{ year}\OperatorTok{:} \DecValTok{2013}\OperatorTok{,} \NormalTok{ month}\OperatorTok{:} \ConstantTok{Some}\NormalTok{(}\DecValTok{2u8}\NormalTok{)}\OperatorTok{,} @@ -294,7 +299,8 @@ The following example shows how you can downcast a \texttt{Box\textless{}dyn\ Au \OperatorTok{.}\NormalTok{write\_to\_path(}\StringTok{"assets/a.mp3"}\OperatorTok{,} \PreprocessorTok{id3::Version::}\NormalTok{Id3v24)} \OperatorTok{.}\NormalTok{expect(}\StringTok{"Fail to write!"}\NormalTok{)}\OperatorTok{;} - \KeywordTok{let}\NormalTok{ id3tag\_reload }\OperatorTok{=} \PreprocessorTok{id3::Tag::}\NormalTok{read\_from\_path(}\StringTok{"assets/a.mp3"}\NormalTok{)}\OperatorTok{.}\NormalTok{expect(}\StringTok{"Fail to read!"}\NormalTok{)}\OperatorTok{;} + \KeywordTok{let}\NormalTok{ id3tag\_reload }\OperatorTok{=} \PreprocessorTok{id3::Tag::}\NormalTok{read\_from\_path(}\StringTok{"assets/a.mp3"}\NormalTok{)} + \OperatorTok{.}\NormalTok{expect(}\StringTok{"Fail to read!"}\NormalTok{)}\OperatorTok{;} \PreprocessorTok{assert\_eq!}\NormalTok{(id3tag\_reload}\OperatorTok{.}\NormalTok{date\_recorded()}\OperatorTok{,} \ConstantTok{Some}\NormalTok{(timestamp))}\OperatorTok{;} \OperatorTok{\}} \end{Highlighting} diff --git a/docs/conversion.html b/docs/conversion.html index b4b556b4..79a137b8 100644 --- a/docs/conversion.html +++ b/docs/conversion.html @@ -171,33 +171,36 @@ code span.wa { color: #60a0b0; font-weight: bold; font-style: italic; } /* Warni // we have an mp3 and an m4a file const MP3_FILE: &'static str = "assets/a.mp3"; const M4A_FILE: &'static str = "assets/a.m4a"; - // read tag from the mp3 file. Using `default()` so that the type of tag is guessed from the file extension - let mut mp3tag = Tag::default().read_from_path(MP3_FILE).unwrap(); - // set the title - mp3tag.set_title("title from mp3 file"); - // we can convert it to an mp4 tag and save it to an m4a file. - let mut mp4tag = mp3tag.into_tag(TagType::Mp4); - mp4tag.write_to_path(M4A_FILE).unwrap(); - - // reload the tag from the m4a file; this time specifying the tag type (you can also use `default()`) - let mut mp4tag = Tag::with_tag_type(TagType::Mp4) - .read_from_path(M4A_FILE) - .unwrap(); - // the tag originated from an mp3 file is successfully written to an m4a file! - assert_eq!(mp4tag.title(), Some("title from mp3 file")); - // multiple artists - mp4tag.add_artist("artist1 of mp4"); - mp4tag.add_artist("artist2 of mp4"); - assert_eq!( - mp4tag.artists(), - Some(vec!["artist1 of mp4", "artist2 of mp4"]) - ); - // convert to id3 tag, which does not support multiple artists - mp4tag.set_config(Config::default().sep_artist("/")); - // separator is by default `;` but we can customise it - let mp3tag = mp4tag.into_tag(TagType::Id3v2); - assert_eq!(mp3tag.artist(), Some("artist1 of mp4/artist2 of mp4")); -} + // read tag from the mp3 file. Using `default()` so that the + // type of tag is guessed from the file extension + let mut mp3tag = Tag::default().read_from_path(MP3_FILE).unwrap(); + // set the title + mp3tag.set_title("title from mp3 file"); + // we can convert it to an mp4 tag and save it to an m4a file. + let mut mp4tag = mp3tag.into_tag(TagType::Mp4); + mp4tag.write_to_path(M4A_FILE).unwrap(); + + // reload the tag from the m4a file; this time specifying the + // tag type (you can also use `default()`) + let mut mp4tag = Tag::with_tag_type(TagType::Mp4) + .read_from_path(M4A_FILE) + .unwrap(); + // the tag originated from an mp3 file is successfully written + // to an m4a file! + assert_eq!(mp4tag.title(), Some("title from mp3 file")); + // multiple artists + mp4tag.add_artist("artist1 of mp4"); + mp4tag.add_artist("artist2 of mp4"); + assert_eq!( + mp4tag.artists(), + Some(vec!["artist1 of mp4", "artist2 of mp4"]) + ); + // convert to id3 tag, which does not support multiple artists + mp4tag.set_config(Config::default().sep_artist("/")); + // separator is by default `;` but we can customise it + let mp3tag = mp4tag.into_tag(TagType::Id3v2); + assert_eq!(mp3tag.artist(), Some("artist1 of mp4/artist2 of mp4")); +} diff --git a/docs/downcast.html b/docs/downcast.html index 24d7d49b..8bf90876 100644 --- a/docs/downcast.html +++ b/docs/downcast.html @@ -183,23 +183,26 @@ code span.wa { color: #60a0b0; font-weight: bold; font-style: italic; } /* Warni .expect("Fail to read!"); assert_eq!(id3tag_reload.title(), Some("title from metaflac::Tag")); - let mut id3tag_inner: id3::Tag = downcast!(id3tag_reload, Id3v2Tag); - 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 mut id3tag_inner: id3::Tag = id3tag_reload.try_into().unwrap(); + // this would fail if `id3tag_reload` isn't really a id3 tag. + + 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)); +} diff --git a/docs/search_index.json b/docs/search_index.json index cfcfe6ef..82a9ae91 100644 --- a/docs/search_index.json +++ b/docs/search_index.json @@ -1 +1 @@ -[["index.html", "audiotags Manual Preface", " audiotags Manual Tianyi Shi 2020-10-27 Preface Thank you for considering audiotags! Before you start, please let me introduce to you some great features of bookdown: use left and right arrow keys to navigate to the previous/next page click the “font” (big “A”) button on the top-left and change to a serif font if you happen to hate sans-serif fonts as I do. If you believe that serious stuff must be rendered by LaTeX as I do, there is a LaTex-rendered PDF for you to download (click the download button on the top) If you love to read on a Kindle, there is also an epub output (click the download button on the top). Examples in this manual If you want to run the examples in this book: clone the repo and navigate into it create src/main.rs all examples, unless otherwise specified, can be copied verbatim from this book to src/main.rs and run with cargo run (if you’re reading it online, the copy button wil show if you hover over a code block) "],["start-simple.html", "Chapter 1 Start Simple", " Chapter 1 Start Simple The following example shows how you can read an audio file, parse, set, and save its metadata: use audiotags::{MimeType, Picture, Tag, TagType}; const MP3_FILE: &'static str = "assets/a.mp3"; fn main() { // using `default()` so that the metadata format is guessed // (from the file extension) (in this case, Id3v2 tag is read) let mut tag = Tag::default().read_from_path(MP3_FILE).unwrap(); // You can also specify the metadata format (tag type): let _tag = Tag::with_tag_type(TagType::Id3v2) .read_from_path(MP3_FILE) .expect("Fail to read!"); tag.set_title("foo title"); assert_eq!(tag.title(), Some("foo title")); tag.remove_title(); assert!(tag.title().is_none()); tag.remove_title(); // trying to remove a field that's already empty won't hurt let cover = Picture { mime_type: MimeType::Jpeg, data: &vec![0u8; 10], }; tag.set_album_cover(cover.clone()); assert_eq!(tag.album_cover(), Some(cover)); tag.remove_album_cover(); assert!(tag.album_cover().is_none()); tag.remove_album_cover(); tag.save_to_path(MP3_FILE).expect("Fail to save"); // TASK: reload the file and prove the data have been saved } "],["conversion.html", "Chapter 2 Conversion", " Chapter 2 Conversion The following example shows how you can read the tag in an mp3 file, convert it into an mp4 tag, and write it to an m4a file. use audiotags::{Config, Tag, TagType}; fn main() { // we have an mp3 and an m4a file const MP3_FILE: &'static str = "assets/a.mp3"; const M4A_FILE: &'static str = "assets/a.m4a"; // read tag from the mp3 file. Using `default()` so that the type of tag is guessed from the file extension let mut mp3tag = Tag::default().read_from_path(MP3_FILE).unwrap(); // set the title mp3tag.set_title("title from mp3 file"); // we can convert it to an mp4 tag and save it to an m4a file. let mut mp4tag = mp3tag.into_tag(TagType::Mp4); mp4tag.write_to_path(M4A_FILE).unwrap(); // reload the tag from the m4a file; this time specifying the tag type (you can also use `default()`) let mut mp4tag = Tag::with_tag_type(TagType::Mp4) .read_from_path(M4A_FILE) .unwrap(); // the tag originated from an mp3 file is successfully written to an m4a file! assert_eq!(mp4tag.title(), Some("title from mp3 file")); // multiple artists mp4tag.add_artist("artist1 of mp4"); mp4tag.add_artist("artist2 of mp4"); assert_eq!( mp4tag.artists(), Some(vec!["artist1 of mp4", "artist2 of mp4"]) ); // convert to id3 tag, which does not support multiple artists mp4tag.set_config(Config::default().sep_artist("/")); // separator is by default `;` but we can customise it let mp3tag = mp4tag.into_tag(TagType::Id3v2); assert_eq!(mp3tag.artist(), Some("artist1 of mp4/artist2 of mp4")); } "],["anytag.html", "Chapter 3 AnyTag", " Chapter 3 AnyTag The following example shows how you can create a “generic” AnyTag and convert it into a specific tag type. use audiotags::{AnyTag, AudioTagEdit, Id3v2Tag}; fn main() { let mut tag = AnyTag::default(); tag.set_title("foo"); tag.set_year(2001); let tag: Id3v2Tag = tag.into(); assert_eq!(tag.year(), Some(2001)); tag.write_to_path("assets/a.mp3").unwrap(); } "],["downcast.html", "Chapter 4 Downcast", " Chapter 4 Downcast The following example shows how you can downcast a Box<dyn AudioTag> into its “backend” tag type. This allows you to set the uncommon metadata supported by the corresponding backend but not by audiotags. use audiotags::*; fn main() { 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.into_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 mut id3tag_inner: id3::Tag = downcast!(id3tag_reload, Id3v2Tag); 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)); } "],["references.html", "References", " References "]] +[["index.html", "audiotags Manual Preface", " audiotags Manual Tianyi Shi 2020-10-27 Preface Thank you for considering audiotags! Before you start, please let me introduce to you some great features of bookdown: use left and right arrow keys to navigate to the previous/next page click the “font” (big “A”) button on the top-left and change to a serif font if you happen to hate sans-serif fonts as I do. If you believe that serious stuff must be rendered by LaTeX as I do, there is a LaTex-rendered PDF for you to download (click the download button on the top) If you love to read on a Kindle, there is also an epub output (click the download button on the top). Examples in this manual If you want to run the examples in this book: clone the repo and navigate into it create src/main.rs all examples, unless otherwise specified, can be copied verbatim from this book to src/main.rs and run with cargo run (if you’re reading it online, the copy button wil show if you hover over a code block) "],["start-simple.html", "Chapter 1 Start Simple", " Chapter 1 Start Simple The following example shows how you can read an audio file, parse, set, and save its metadata: use audiotags::{MimeType, Picture, Tag, TagType}; const MP3_FILE: &'static str = "assets/a.mp3"; fn main() { // using `default()` so that the metadata format is guessed // (from the file extension) (in this case, Id3v2 tag is read) let mut tag = Tag::default().read_from_path(MP3_FILE).unwrap(); // You can also specify the metadata format (tag type): let _tag = Tag::with_tag_type(TagType::Id3v2) .read_from_path(MP3_FILE) .expect("Fail to read!"); tag.set_title("foo title"); assert_eq!(tag.title(), Some("foo title")); tag.remove_title(); assert!(tag.title().is_none()); tag.remove_title(); // trying to remove a field that's already empty won't hurt let cover = Picture { mime_type: MimeType::Jpeg, data: &vec![0u8; 10], }; tag.set_album_cover(cover.clone()); assert_eq!(tag.album_cover(), Some(cover)); tag.remove_album_cover(); assert!(tag.album_cover().is_none()); tag.remove_album_cover(); tag.save_to_path(MP3_FILE).expect("Fail to save"); // TASK: reload the file and prove the data have been saved } "],["conversion.html", "Chapter 2 Conversion", " Chapter 2 Conversion The following example shows how you can read the tag in an mp3 file, convert it into an mp4 tag, and write it to an m4a file. use audiotags::{Config, Tag, TagType}; fn main() { // we have an mp3 and an m4a file const MP3_FILE: &'static str = "assets/a.mp3"; const M4A_FILE: &'static str = "assets/a.m4a"; // read tag from the mp3 file. Using `default()` so that the // type of tag is guessed from the file extension let mut mp3tag = Tag::default().read_from_path(MP3_FILE).unwrap(); // set the title mp3tag.set_title("title from mp3 file"); // we can convert it to an mp4 tag and save it to an m4a file. let mut mp4tag = mp3tag.into_tag(TagType::Mp4); mp4tag.write_to_path(M4A_FILE).unwrap(); // reload the tag from the m4a file; this time specifying the // tag type (you can also use `default()`) let mut mp4tag = Tag::with_tag_type(TagType::Mp4) .read_from_path(M4A_FILE) .unwrap(); // the tag originated from an mp3 file is successfully written // to an m4a file! assert_eq!(mp4tag.title(), Some("title from mp3 file")); // multiple artists mp4tag.add_artist("artist1 of mp4"); mp4tag.add_artist("artist2 of mp4"); assert_eq!( mp4tag.artists(), Some(vec!["artist1 of mp4", "artist2 of mp4"]) ); // convert to id3 tag, which does not support multiple artists mp4tag.set_config(Config::default().sep_artist("/")); // separator is by default `;` but we can customise it let mp3tag = mp4tag.into_tag(TagType::Id3v2); assert_eq!(mp3tag.artist(), Some("artist1 of mp4/artist2 of mp4")); } "],["anytag.html", "Chapter 3 AnyTag", " Chapter 3 AnyTag The following example shows how you can create a “generic” AnyTag and convert it into a specific tag type. use audiotags::{AnyTag, AudioTagEdit, Id3v2Tag}; fn main() { let mut tag = AnyTag::default(); tag.set_title("foo"); tag.set_year(2001); let tag: Id3v2Tag = tag.into(); assert_eq!(tag.year(), Some(2001)); tag.write_to_path("assets/a.mp3").unwrap(); } "],["downcast.html", "Chapter 4 Downcast", " Chapter 4 Downcast The following example shows how you can downcast a Box<dyn AudioTag> into its “backend” tag type. This allows you to set the uncommon metadata supported by the corresponding backend but not by audiotags. use audiotags::*; fn main() { 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.into_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 mut id3tag_inner: id3::Tag = id3tag_reload.try_into().unwrap(); // this would fail if `id3tag_reload` isn't really a id3 tag. 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)); } "],["references.html", "References", " References "]] diff --git a/src/traits.rs b/src/traits.rs index da7fc769..588bea6e 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -142,6 +142,8 @@ pub trait IntoAnyTag { /// Convert the tag type, which can be lossy. fn into_tag(&self, tag_type: TagType) -> Box { + // 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.into_anytag())), TagType::Mp4 => Box::new(Mp4Tag::from(self.into_anytag())),