From c0607597f196eb71f8069f23c91359968ef639ad Mon Sep 17 00:00:00 2001 From: phoenix Date: Wed, 22 Oct 2025 02:26:18 +0000 Subject: [PATCH] tsk-45: Identify type of Song (#48) Closes #45 Reviewed-on: https://git.kundeng.us/phoenix/icarus_meta/pulls/48 Co-authored-by: phoenix Co-committed-by: phoenix --- .gitea/workflows/tag_release.yml | 3 + Cargo.lock | 120 ++++++++++++++++++++++++++++++- Cargo.toml | 3 +- src/detection/coverart.rs | 43 ++++++++--- src/detection/mod.rs | 7 ++ src/detection/song.rs | 90 +++++++++++++++++++++++ 6 files changed, 256 insertions(+), 10 deletions(-) create mode 100644 src/detection/song.rs diff --git a/.gitea/workflows/tag_release.yml b/.gitea/workflows/tag_release.yml index ef2df31..3e39896 100644 --- a/.gitea/workflows/tag_release.yml +++ b/.gitea/workflows/tag_release.yml @@ -4,6 +4,9 @@ on: push: branches: - main + pull_request: + branches: + - main jobs: release: diff --git a/Cargo.lock b/Cargo.lock index 896c873..a3669f4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14,12 +14,29 @@ version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" +[[package]] +name = "bumpalo" +version = "3.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" + [[package]] name = "byteorder" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +[[package]] +name = "cfb" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d38f2da7a0a2c4ccf0065be06397cc26a81f4e528be095826eee9d4adbb8c60f" +dependencies = [ + "byteorder", + "fnv", + "uuid", +] + [[package]] name = "cfg-if" version = "1.0.4" @@ -67,6 +84,12 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + [[package]] name = "getrandom" version = "0.3.4" @@ -81,9 +104,10 @@ dependencies = [ [[package]] name = "icarus_meta" -version = "0.4.2" +version = "0.4.3" dependencies = [ "imghdr", + "infer", "lofty", "rand", "tempfile", @@ -95,6 +119,25 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8b35f3ad95576ac81603375dfe47a0450b70a368aa34d2b6e5bb0a0d7f02428" +[[package]] +name = "infer" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a588916bfdfd92e71cacef98a63d9b1f0d74d6599980d11894290e7ddefffcf7" +dependencies = [ + "cfb", +] + +[[package]] +name = "js-sys" +version = "0.3.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec48937a97411dcb524a265206ccd4c90bb711fca92b2792c407f268825b9305" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + [[package]] name = "libc" version = "0.2.177" @@ -245,6 +288,12 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + [[package]] name = "simd-adler32" version = "0.3.7" @@ -281,6 +330,16 @@ version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" +[[package]] +name = "uuid" +version = "1.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "wasip2" version = "1.0.1+wasi-0.2.4" @@ -290,6 +349,65 @@ dependencies = [ "wit-bindgen", ] +[[package]] +name = "wasm-bindgen" +version = "0.2.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1da10c01ae9f1ae40cbfac0bac3b1e724b320abfcf52229f80b547c0d250e2d" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "671c9a5a66f49d8a47345ab942e2cb93c7d1d0339065d4f8139c486121b43b19" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ca60477e4c59f5f2986c50191cd972e3a50d8a95603bc9434501cf156a9a119" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f07d2f20d4da7b26400c9f4a0511e6e0345b040694e8a75bd41d578fa4421d7" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bad67dc8b2a1a6e5448428adec4c3e84c43e561d8c9ee8a9e5aabeb193ec41d1" +dependencies = [ + "unicode-ident", +] + [[package]] name = "windows-link" version = "0.2.1" diff --git a/Cargo.toml b/Cargo.toml index 398a1e5..8090b6c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,12 +1,13 @@ [package] name = "icarus_meta" -version = "0.4.2" +version = "0.4.3" edition = "2024" rust-version = "1.90" [dependencies] lofty = { version = "0.22.4" } imghdr = { version = "0.7.0" } +infer = { version = "0.19.0" } rand = { version = "0.9.2" } [dev-dependencies] diff --git a/src/detection/coverart.rs b/src/detection/coverart.rs index e5d86de..da51d71 100644 --- a/src/detection/coverart.rs +++ b/src/detection/coverart.rs @@ -1,8 +1,16 @@ /// Gets the file type of a CoverArt given it's path -pub fn file_type_from_filepath(filepath: &str) -> Result { +pub fn file_type_from_filepath( + filepath: &str, +) -> Result { match imghdr::from_file(filepath) { - Ok(Some(imghdr::Type::Jpeg)) => Ok(String::from("jpeg")), - Ok(Some(imghdr::Type::Png)) => Ok(String::from("png")), + Ok(Some(imghdr::Type::Jpeg)) => Ok(crate::detection::FileType { + mime: String::from(constants::mime::JPEG), + file_type: String::from(constants::JPEG_TYPE), + }), + Ok(Some(imghdr::Type::Png)) => Ok(crate::detection::FileType { + mime: String::from(constants::mime::PNG), + file_type: String::from(constants::PNG_TYPE), + }), Ok(None) => Err(std::io::Error::other("Image file not supported")), Err(err) => Err(err), _ => Err(std::io::Error::other("Image file not supported")), @@ -10,15 +18,32 @@ pub fn file_type_from_filepath(filepath: &str) -> Result } /// Gets the file type of a CoverArt given it's data -pub fn file_type_from_data(data: &Vec) -> Result { +pub fn file_type_from_data(data: &Vec) -> Result { match imghdr::from_bytes(data) { - Some(imghdr::Type::Jpeg) => Ok(String::from("jpeg")), - Some(imghdr::Type::Png) => Ok(String::from("png")), + Some(imghdr::Type::Jpeg) => Ok(crate::detection::FileType { + mime: String::from(constants::mime::JPEG), + file_type: String::from(constants::JPEG_TYPE), + }), + Some(imghdr::Type::Png) => Ok(crate::detection::FileType { + mime: String::from(constants::mime::PNG), + file_type: String::from(constants::PNG_TYPE), + }), None => Err(std::io::Error::other("Image file not supported")), _ => Err(std::io::Error::other("Image file not supported")), } } +pub mod constants { + pub const PNG_TYPE: &str = "png"; + pub const JPEG_TYPE: &str = "jpeg"; + pub const JPG_TYPE: &str = "jpg"; + + pub mod mime { + pub const JPEG: &str = "image/jpeg"; + pub const PNG: &str = "image/png"; + } +} + #[cfg(test)] mod tests { #[test] @@ -30,7 +55,8 @@ mod tests { match super::file_type_from_filepath(&filepath) { Ok(filetype) => { assert_eq!( - filetype, "png", + filetype.file_type, + super::constants::PNG_TYPE, "The file type of the CoverArt should have been png" ); } @@ -50,7 +76,8 @@ mod tests { match super::file_type_from_data(&data) { Ok(filetype) => { assert_eq!( - filetype, "png", + filetype.file_type, + super::constants::PNG_TYPE, "The file type of the CoverArt should have been png" ); } diff --git a/src/detection/mod.rs b/src/detection/mod.rs index 165fc86..ad6601e 100644 --- a/src/detection/mod.rs +++ b/src/detection/mod.rs @@ -1 +1,8 @@ pub mod coverart; +pub mod song; + +#[derive(Debug, Default)] +pub struct FileType { + pub mime: String, + pub file_type: String, +} diff --git a/src/detection/song.rs b/src/detection/song.rs new file mode 100644 index 0000000..1094055 --- /dev/null +++ b/src/detection/song.rs @@ -0,0 +1,90 @@ +/// Gets the file type of a Song from it's path +pub fn file_type_from_filepath( + filepath: &str, +) -> Result { + match infer::get_from_path(filepath) { + Ok(Some(kind)) => { + let mime = kind.mime_type(); + if mime == constants::mime::FLAC { + Ok(crate::detection::FileType { + mime: String::from(mime), + file_type: String::from(constants::FLAC_TYPE), + }) + } else { + Err(std::io::Error::other("Unsupported file type")) + } + } + Ok(None) => Err(std::io::Error::other("File type not determined")), + Err(err) => Err(err), + } +} + +/// Gets the file type of a Song given it's data +pub fn file_type_from_data(data: &[u8]) -> Result { + match infer::get(data) { + Some(kind) => { + let mime = kind.mime_type(); + if mime == constants::mime::FLAC { + Ok(crate::detection::FileType { + mime: String::from(mime), + file_type: String::from(constants::FLAC_TYPE), + }) + } else { + Err(std::io::Error::other("Unsupported file type")) + } + } + None => Err(std::io::Error::other("File type not determined")), + } +} + +pub mod constants { + pub const FLAC_TYPE: &str = "flac"; + + pub mod mime { + pub const FLAC: &str = "audio/x-flac"; + } +} + +#[cfg(test)] +mod tests { + #[test] + fn test_song_file_type() { + let directory = String::from(crate::test_util::util::TESTFILEDIRECTORY); + let filename = String::from("track01.flac"); + let filepath = format!("{directory}/{filename}"); + + match super::file_type_from_filepath(&filepath) { + Ok(filetype) => { + assert_eq!( + filetype.file_type, + crate::detection::song::constants::FLAC_TYPE, + "Types do not match" + ) + } + Err(err) => { + assert!(false, "Error: {err:?}") + } + } + } + + #[test] + fn test_song_file_type_from_data() { + let directory = String::from(crate::test_util::util::TESTFILEDIRECTORY); + let filename = String::from("track01.flac"); + let filepath = format!("{directory}/{filename}"); + let data = crate::test_util::util::get_data_from_file(&filepath).unwrap(); + + match super::file_type_from_data(&data) { + Ok(filetype) => { + assert_eq!( + filetype.file_type, + crate::detection::song::constants::FLAC_TYPE, + "Types do not match" + ) + } + Err(err) => { + assert!(false, "Error: {err:?}") + } + } + } +}