diff --git a/Cargo.lock b/Cargo.lock index 56722bd..2e69719 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1457,6 +1457,11 @@ dependencies = [ "vcpkg 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "opus_headers" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "parking_lot" version = "0.9.0" @@ -1589,6 +1594,7 @@ dependencies = [ "metaflac 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", "mp3-duration 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", "mp4ameta 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "opus_headers 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "pbkdf2 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", @@ -3079,6 +3085,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum openssl 0.10.30 (registry+https://github.com/rust-lang/crates.io-index)" = "8d575eff3665419f9b83678ff2815858ad9d11567e082f5ac1814baba4e2bcb4" "checksum openssl-probe 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de" "checksum openssl-sys 0.9.58 (registry+https://github.com/rust-lang/crates.io-index)" = "a842db4709b604f0fe5d1170ae3565899be2ad3d9cbc72dedc789ac0511f78de" +"checksum opus_headers 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "afbb993947f111397c2bc536944f8dac7f54a4e73383d478efe1990b56404b60" "checksum parking_lot 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a4893845fa2ca272e647da5d0e46660a314ead9c2fdd9a883aabc32e481a8733" "checksum parking_lot 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f842b1982eb6c2fe34036a4fbfb06dd185a3f5c8edfaacdf7d1ea10b07de6252" "checksum parking_lot_core 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b876b1b9e7ac6e1a74a6da34d25c42e17e8862aa409cbbbdcfc8d86c6f3bc62b" diff --git a/Cargo.toml b/Cargo.toml index 9517ef3..faa7dc6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,6 +30,7 @@ log = "0.4.5" metaflac = "0.2.3" mp3-duration = "0.1.9" mp4ameta = "0.2.2" +opus_headers = "0.1.2" pbkdf2 = "0.4" rand = "0.7" rayon = "1.3" diff --git a/src/index/metadata.rs b/src/index/metadata.rs index da08cb1..5040c3e 100644 --- a/src/index/metadata.rs +++ b/src/index/metadata.rs @@ -9,6 +9,7 @@ use mp4ameta; use regex::Regex; use std::fs; use std::path::Path; +use opus_headers; use crate::utils; use crate::utils::AudioFormat; @@ -33,6 +34,7 @@ pub fn read(path: &Path) -> Option { Some(AudioFormat::MP4) => Some(read_mp4(path)), Some(AudioFormat::MPC) => Some(read_ape(path)), Some(AudioFormat::OGG) => Some(read_vorbis(path)), + Some(AudioFormat::OPUS) => Some(read_opus(path)), _ => None, }; match data { @@ -160,15 +162,50 @@ fn read_vorbis(path: &Path) -> Result { }; for (key, value) in source.comment_hdr.comment_list { - match key.as_str() { - "TITLE" => tags.title = Some(value), - "ALBUM" => tags.album = Some(value), - "ARTIST" => tags.artist = Some(value), - "ALBUMARTIST" => tags.album_artist = Some(value), - "TRACKNUMBER" => tags.track_number = value.parse::().ok(), - "DISCNUMBER" => tags.disc_number = value.parse::().ok(), - "DATE" => tags.year = value.parse::().ok(), - _ => (), + utils::match_ignore_case! { + match key { + "TITLE" => tags.title = Some(value), + "ALBUM" => tags.album = Some(value), + "ARTIST" => tags.artist = Some(value), + "ALBUMARTIST" => tags.album_artist = Some(value), + "TRACKNUMBER" => tags.track_number = value.parse::().ok(), + "DISCNUMBER" => tags.disc_number = value.parse::().ok(), + "DATE" => tags.year = value.parse::().ok(), + _ => (), + } + } + } + + Ok(tags) +} + +#[cfg_attr(feature = "profile-index", flame)] +fn read_opus(path: &Path) -> Result { + let headers = opus_headers::parse_from_path(path)?; + + let mut tags = SongTags { + artist: None, + album_artist: None, + album: None, + title: None, + duration: None, + disc_number: None, + track_number: None, + year: None, + }; + + for (key, value) in headers.comments.user_comments { + utils::match_ignore_case! { + match key { + "TITLE" => tags.title = Some(value), + "ALBUM" => tags.album = Some(value), + "ARTIST" => tags.artist = Some(value), + "ALBUMARTIST" => tags.album_artist = Some(value), + "TRACKNUMBER" => tags.track_number = value.parse::().ok(), + "DISCNUMBER" => tags.disc_number = value.parse::().ok(), + "DATE" => tags.year = value.parse::().ok(), + _ => (), + } } } @@ -258,4 +295,5 @@ fn test_read_metadata() { assert_eq!(read(Path::new("test/sample.ogg")).unwrap(), sample_tags); assert_eq!(read(Path::new("test/sample.flac")).unwrap(), flac_sample_tag); assert_eq!(read(Path::new("test/sample.m4a")).unwrap(), m4a_sample_tag); + assert_eq!(read(Path::new("test/sample.opus")).unwrap(), sample_tags); } diff --git a/src/utils.rs b/src/utils.rs index 45e5e44..edd847a 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -3,6 +3,18 @@ use app_dirs::{app_root, AppDataType, AppInfo}; use std::fs; use std::path::{Path, PathBuf}; +#[macro_export] +macro_rules! match_ignore_case { + (match $v:ident { + $( $lit:literal => $res:expr, )* + _ => $catch_all:expr $(,)? + }) => {{ + $( if $lit.eq_ignore_ascii_case(&$v) { $res } else )* + { $catch_all } + }}; +} +pub use crate::match_ignore_case; + #[cfg(target_family = "windows")] const APP_INFO: AppInfo = AppInfo { name: "Polaris", @@ -30,6 +42,7 @@ pub enum AudioFormat { MP4, MPC, OGG, + OPUS, } #[cfg_attr(feature = "profile-index", flame)] @@ -48,6 +61,7 @@ pub fn get_audio_format(path: &Path) -> Option { "m4a" => Some(AudioFormat::MP4), "mpc" => Some(AudioFormat::MPC), "ogg" => Some(AudioFormat::OGG), + "opus" => Some(AudioFormat::OPUS), _ => None, } } diff --git a/test/sample.opus b/test/sample.opus new file mode 100644 index 0000000..b5291d5 Binary files /dev/null and b/test/sample.opus differ