From dc5fdf4b9033b9e6b210c508810c88b26e2dd8cc Mon Sep 17 00:00:00 2001 From: Lyssieth Date: Sat, 14 Oct 2023 16:43:37 +0300 Subject: [PATCH] feat: support cover art + resizing --- Cargo.lock | 254 ++++++++++++++++++++++ Justfile | 2 +- entities/src/cover_art.rs | 3 +- migration/src/m000003_create_cover_art.rs | 4 +- rave/Cargo.toml | 2 + rave/src/rest/get_cover_art.rs | 162 ++++++++++++++ rave/src/rest/mod.rs | 3 + rave/src/scan.rs | 68 +++++- rave/src/scan/flac.rs | 40 +++- rave/src/scan/mp3.rs | 40 +++- rave/src/subsonic/types/artist.rs | 3 +- 11 files changed, 566 insertions(+), 15 deletions(-) create mode 100644 rave/src/rest/get_cover_art.rs diff --git a/Cargo.lock b/Cargo.lock index e20fdb9..12ee043 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -163,6 +163,18 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "arrayref" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545" + +[[package]] +name = "arrayvec" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" + [[package]] name = "async-attributes" version = "1.1.2" @@ -391,6 +403,12 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" +[[package]] +name = "bit_field" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61" + [[package]] name = "bitflags" version = "1.3.2" @@ -406,6 +424,19 @@ dependencies = [ "serde", ] +[[package]] +name = "blake3" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0231f06152bf547e9c2b5194f247cd97aacf6dcd8b15d8e5ec0663f64580da87" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", +] + [[package]] name = "block-buffer" version = "0.10.4" @@ -458,6 +489,12 @@ version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" +[[package]] +name = "bytemuck" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "374d28ec25809ee0e23827c2ab573d729e293f281dfe393500e7ad618baa61c6" + [[package]] name = "byteorder" version = "1.5.0" @@ -574,6 +611,12 @@ dependencies = [ "tracing-error", ] +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + [[package]] name = "colorchoice" version = "1.0.0" @@ -595,6 +638,12 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28c122c3980598d243d63d9a704629a2d748d101f278052ff068be5a4423ab6f" +[[package]] +name = "constant_time_eq" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2" + [[package]] name = "convert_case" version = "0.4.0" @@ -678,6 +727,30 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "crossbeam-deque" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" +dependencies = [ + "cfg-if", + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7" +dependencies = [ + "autocfg", + "cfg-if", + "crossbeam-utils", + "memoffset", + "scopeguard", +] + [[package]] name = "crossbeam-queue" version = "0.3.8" @@ -697,6 +770,12 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + [[package]] name = "crypto-common" version = "0.1.6" @@ -884,6 +963,22 @@ version = "2.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" +[[package]] +name = "exr" +version = "1.71.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "832a761f35ab3e6664babfbdc6cef35a4860e816ec3916dcfd0882954e98a8a8" +dependencies = [ + "bit_field", + "flume", + "half", + "lebe", + "miniz_oxide", + "rayon-core", + "smallvec", + "zune-inflate", +] + [[package]] name = "eyre" version = "0.6.8" @@ -909,6 +1004,15 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" +[[package]] +name = "fdeflate" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d329bdeac514ee06249dabc27877490f17f5d371ec693360768b838e19f3ae10" +dependencies = [ + "simd-adler32", +] + [[package]] name = "findshlibs" version = "0.10.2" @@ -1124,6 +1228,16 @@ dependencies = [ "polyval", ] +[[package]] +name = "gif" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80792593675e051cf94a4b111980da2ba60d4a83e43e0048c5693baab3977045" +dependencies = [ + "color_quant", + "weezl", +] + [[package]] name = "gimli" version = "0.28.0" @@ -1167,6 +1281,15 @@ dependencies = [ "tracing", ] +[[package]] +name = "half" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b4af3693f1b705df946e9fe5631932443781d0aabb423b62fcd4d73f6d2fd0" +dependencies = [ + "crunchy", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -1410,6 +1533,25 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "image" +version = "0.24.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f3dfdbdd72063086ff443e297b61695500514b1e41095b6fb9a5ab48a70a711" +dependencies = [ + "bytemuck", + "byteorder", + "color_quant", + "exr", + "gif", + "jpeg-decoder", + "num-rational", + "num-traits", + "png", + "qoi", + "tiff", +] + [[package]] name = "indenter" version = "0.3.3" @@ -1497,6 +1639,15 @@ version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" +[[package]] +name = "jpeg-decoder" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc0000e42512c92e31c2252315bda326620a4e034105e900c98ec492fa077b3e" +dependencies = [ + "rayon", +] + [[package]] name = "js-sys" version = "0.3.64" @@ -1524,6 +1675,12 @@ dependencies = [ "spin 0.5.2", ] +[[package]] +name = "lebe" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" + [[package]] name = "libc" version = "0.2.149" @@ -1615,6 +1772,15 @@ version = "2.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" +[[package]] +name = "memoffset" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" +dependencies = [ + "autocfg", +] + [[package]] name = "metaflac" version = "0.2.5" @@ -1663,6 +1829,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" dependencies = [ "adler", + "simd-adler32", ] [[package]] @@ -1787,6 +1954,17 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-rational" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.17" @@ -2060,6 +2238,19 @@ version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" +[[package]] +name = "png" +version = "0.17.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd75bf2d8dd3702b9707cdbc56a5b9ef42cec752eb8b3bafc01234558442aa64" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + [[package]] name = "poem" version = "1.3.58" @@ -2262,6 +2453,15 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "qoi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001" +dependencies = [ + "bytemuck", +] + [[package]] name = "quick-xml" version = "0.30.0" @@ -2316,10 +2516,12 @@ name = "rave" version = "0.1.0" dependencies = [ "audiotags", + "blake3", "cfg-if", "color-eyre", "entities", "futures", + "image", "md5", "migration", "once_cell", @@ -2340,6 +2542,26 @@ dependencies = [ "url-escape", ] +[[package]] +name = "rayon" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c27db03db7734835b3f53954b534c91069375ce6ccaa2e065441e07d9b6cdb1" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ce3fb6ad83f861aac485e76e1985cd109d9a3713802152be56c3b1f0e0658ed" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + [[package]] name = "readme-rustdocifier" version = "0.1.1" @@ -2993,6 +3215,12 @@ dependencies = [ "rand_core", ] +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + [[package]] name = "slab" version = "0.4.9" @@ -3387,6 +3615,17 @@ dependencies = [ "once_cell", ] +[[package]] +name = "tiff" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d172b0f4d3fba17ba89811858b9d3d97f928aece846475bbda076ca46736211" +dependencies = [ + "flate2", + "jpeg-decoder", + "weezl", +] + [[package]] name = "time" version = "0.3.29" @@ -3944,6 +4183,12 @@ version = "0.25.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14247bb57be4f377dfb94c72830b8ce8fc6beac03cf4bf7b9732eadd414123fc" +[[package]] +name = "weezl" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9193164d4de03a926d909d3bc7c30543cecb35400c02114792c2cae20d5e2dbb" + [[package]] name = "whoami" version = "1.4.1" @@ -4071,3 +4316,12 @@ name = "zeroize" version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" + +[[package]] +name = "zune-inflate" +version = "0.2.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" +dependencies = [ + "simd-adler32", +] diff --git a/Justfile b/Justfile index 178c17d..6853221 100644 --- a/Justfile +++ b/Justfile @@ -10,7 +10,7 @@ unmount: bash ./mount-tool.sh unmount run: mount - RAVE_STORAGE_DIR=/tmp/media-for-rave cargo r + RAVE_STORAGE_DIR=/tmp/media-for-rave RAVE_CACHE_DIR=/tmp/cache-for-rave cargo r refresh: sea migrate fresh diff --git a/entities/src/cover_art.rs b/entities/src/cover_art.rs index 48ecdfe..9d69c40 100644 --- a/entities/src/cover_art.rs +++ b/entities/src/cover_art.rs @@ -7,7 +7,8 @@ use serde::{Deserialize, Serialize}; #[sea_orm(table_name = "cover_art")] pub struct Model { #[sea_orm(primary_key)] - pub id: i32, + pub id: i64, + pub hash: String, pub path: String, } diff --git a/migration/src/m000003_create_cover_art.rs b/migration/src/m000003_create_cover_art.rs index 100e8ed..af862de 100644 --- a/migration/src/m000003_create_cover_art.rs +++ b/migration/src/m000003_create_cover_art.rs @@ -13,12 +13,13 @@ impl MigrationTrait for Migration { .if_not_exists() .col( ColumnDef::new(CoverArt::Id) - .integer() + .big_integer() .not_null() .auto_increment() .primary_key() .unique_key(), ) + .col(ColumnDef::new(CoverArt::Hash).string().not_null()) .col(ColumnDef::new(CoverArt::Path).string().not_null()) .to_owned(), ) @@ -36,5 +37,6 @@ impl MigrationTrait for Migration { pub enum CoverArt { Table, Id, + Hash, Path, } diff --git a/rave/Cargo.toml b/rave/Cargo.toml index 8ce0c3a..19c8c4f 100644 --- a/rave/Cargo.toml +++ b/rave/Cargo.toml @@ -54,3 +54,5 @@ sentry = { version = "0.31.7", default-features = false, features = [ "tracing", ] } sentry-tracing = { version = "0.31.7", features = ["backtrace"] } +blake3 = "1.5.0" +image = "0.24.7" diff --git a/rave/src/rest/get_cover_art.rs b/rave/src/rest/get_cover_art.rs new file mode 100644 index 0000000..70e974c --- /dev/null +++ b/rave/src/rest/get_cover_art.rs @@ -0,0 +1,162 @@ +use std::{io::Cursor, path::PathBuf}; + +use entities::prelude::CoverArt; +use poem::{ + http::StatusCode, + web::{Data, Query}, + IntoResponse, Response, +}; +use poem_ext::db::DbTxn; +use sea_orm::EntityTrait; +use serde::Deserialize; +use tracing::{error, instrument}; + +use crate::{ + authentication::Authentication, + subsonic::{Error, SubsonicResponse}, + utils::{self}, +}; + +#[poem::handler] +#[instrument(skip(txn, auth))] +pub async fn get_cover_art( + Data(txn): Data<&DbTxn>, + auth: Authentication, + Query(params): Query, +) -> Response { + let u = utils::verify_user(txn.clone(), auth).await; + + match u { + Ok(_) => {} + Err(e) => return e.into_response(), + } + + let cover_art_id = match params.id.strip_prefix("ca-").ok_or_else(|| { + Error::RequiredParameterMissing(Some("Album IDs must be formatted as `ca-{}`".to_string())) + }) { + Ok(id) => id, + Err(e) => return SubsonicResponse::new_error(e).into_response(), + }; + let cover_art_id = match cover_art_id.parse::() { + Ok(id) => id, + Err(e) => { + error!( + error = &e as &dyn std::error::Error, + "Error parsing cover art ID: {e}" + ); + return SubsonicResponse::new_error(Error::Generic(None)).into_response(); + } + }; + + let cover_art = CoverArt::find_by_id(cover_art_id).one(&**txn).await; + + let Ok(Some(cover_art)) = cover_art else { + match cover_art { + Ok(Some(_)) => unreachable!("Ok(Some(_)) covered by `let .. else`"), + Ok(None) => { + return SubsonicResponse::new_error(Error::RequestedDataWasNotFound(None)) + .into_response() + } + Err(e) => { + error!( + error = &e as &dyn std::error::Error, + "Error getting album: {e}" + ); + return SubsonicResponse::new_error(Error::Generic(None)).into_response(); + } + } + }; + + let path = cover_art.path; + let path: PathBuf = path.into(); + + if !path.exists() { + return SubsonicResponse::new_error(Error::RequestedDataWasNotFound(None)).into_response(); + } + + let data = match tokio::fs::read(&path).await { + Ok(v) => v, + Err(e) => { + error!( + error = &e as &dyn std::error::Error, + "Error reading cover art file: {e}" + ); + return SubsonicResponse::new_error(Error::Generic(None)).into_response(); + } + }; + + let ext = path + .extension() + .expect("File had no extension") + .to_str() + .expect("PathBuf extension is not valid UTF-8"); + + if params.size.is_none() { + return Response::builder() + .status(StatusCode::OK) + .content_type(format!("image/{ext}")) + .body(data); + } + let size = params.size.expect("params.size.is_none() was false"); + + match size { + 0.. => {} + ..=-1 => { + return SubsonicResponse::new_error(Error::Generic(None)).into_response(); + } + } + + let image = match image::load_from_memory(&data) { + Ok(v) => v, + Err(e) => { + error!( + error = &e as &dyn std::error::Error, + "Error loading image from memory: {e}" + ); + return SubsonicResponse::new_error(Error::Generic(None)).into_response(); + } + }; + + #[allow(clippy::cast_sign_loss)] + let resize = image.resize( + size as u32, + size as u32, + image::imageops::FilterType::Lanczos3, + ); + + let mut buf = Cursor::new(Vec::new()); + + let res = resize.write_to( + &mut buf, + match ext { + "jpg" => image::ImageOutputFormat::Jpeg(100), + "png" => image::ImageOutputFormat::Png, + "gif" => image::ImageOutputFormat::Gif, + _ => { + return SubsonicResponse::new_error(Error::Generic(None)).into_response(); + } + }, + ); + + let () = match res { + Ok(v) => v, + Err(e) => { + error!( + error = &e as &dyn std::error::Error, + "Error writing resized image to buffer: {e}" + ); + return SubsonicResponse::new_error(Error::Generic(None)).into_response(); + } + }; + + Response::builder() + .status(StatusCode::OK) + .content_type(format!("image/{ext}")) + .body(buf.into_inner()) +} + +#[derive(Debug, Clone, Deserialize)] +pub struct GetCoverArtParams { + id: String, + size: Option, +} diff --git a/rave/src/rest/mod.rs b/rave/src/rest/mod.rs index 53cb163..ec11de9 100644 --- a/rave/src/rest/mod.rs +++ b/rave/src/rest/mod.rs @@ -18,6 +18,8 @@ mod start_scan; mod get_scan_status; // rest/search3 mod search3; +// rest/getCoverArt +mod get_cover_art; pub fn build() -> Box> { Route::new() @@ -31,5 +33,6 @@ pub fn build() -> Box> { .at("/startScan", start_scan::start_scan) .at("/getScanStatus", get_scan_status::get_scan_status) .at("/search3", search3::search3) + .at("/getCoverArt", get_cover_art::get_cover_art) .boxed() } diff --git a/rave/src/scan.rs b/rave/src/scan.rs index 6240c53..5e11875 100644 --- a/rave/src/scan.rs +++ b/rave/src/scan.rs @@ -1,7 +1,8 @@ +use audiotags::MimeType; use color_eyre::{Report, Result}; use entities::{ - genre, music_folder, - prelude::{Genre, MusicFolder}, + cover_art, genre, music_folder, + prelude::{CoverArt, Genre, MusicFolder}, }; use futures::StreamExt; use once_cell::sync::Lazy; @@ -14,7 +15,7 @@ use std::{ path::{Path, PathBuf}, sync::Arc, }; -use tokio::sync::RwLock; +use tokio::{io::AsyncWriteExt, sync::RwLock}; use tracing::{debug, error, info, instrument}; mod walk; @@ -390,7 +391,60 @@ fn get_root_dir() -> PathBuf { PathBuf::from(root_dir) } -// fn get_cache_dir() -> PathBuf { -// let cache_dir = std::env::var("RAVE_CACHE_DIR").expect("RAVE_CACHE_DIR not set"); -// PathBuf::from(cache_dir) -// } +fn get_cache_dir() -> PathBuf { + let cache_dir = std::env::var("RAVE_CACHE_DIR").expect("RAVE_CACHE_DIR not set"); + PathBuf::from(cache_dir) +} + +async fn find_or_create_cover_art( + tx: &DatabaseTransaction, + data: &[u8], + mime_type: MimeType, +) -> Result { + let hash = blake3::hash(data).to_string(); + + debug!("Finding cover art with hash {hash}"); + + let res = CoverArt::find() + .filter(cover_art::Column::Hash.eq(&hash)) + .one(tx) + .await?; + + if let Some(cover_art) = res { + return Ok(cover_art); + } + + debug!("Trying to create cover art"); + + let path = get_cache_dir(); + let path = path + .join(hash.clone()) + .with_extension(mime_type_to_ext(mime_type)); + + { + let mut file = tokio::fs::File::create(&path).await?; + file.write_all(data).await?; + } + + let path = path.to_string_lossy().to_string(); + + let am = cover_art::ActiveModel { + hash: Set(hash.clone()), + path: Set(path), + ..Default::default() + }; + + let model = CoverArt::insert(am).exec_with_returning(tx).await; + + Ok(model?) +} + +const fn mime_type_to_ext(mime: MimeType) -> &'static str { + match mime { + MimeType::Png => "png", + MimeType::Jpeg => "jpg", + MimeType::Tiff => "tif", + MimeType::Bmp => "bmp", + MimeType::Gif => "gif", + } +} diff --git a/rave/src/scan/flac.rs b/rave/src/scan/flac.rs index d2c3b0b..a0f508d 100644 --- a/rave/src/scan/flac.rs +++ b/rave/src/scan/flac.rs @@ -2,7 +2,7 @@ use std::borrow::Cow; use std::path::PathBuf; use std::sync::Arc; -use audiotags::{AudioTagEdit, FlacTag, Tag}; +use audiotags::{AudioTagEdit, FlacTag, MimeType, Tag}; use color_eyre::Report; use entities::{ album, artist, @@ -14,7 +14,7 @@ use sea_orm::{ }; use time::OffsetDateTime; use tokio::{fs::File, sync::RwLock}; -use tracing::{debug, error, instrument}; +use tracing::{debug, error, instrument, warn}; use super::ScanState; @@ -67,6 +67,24 @@ pub async fn handle( Set(duration) }; + let cover_art = tag.album_cover(); + if let Some(cover_art) = cover_art { + let data = match cover_art.mime_type { + MimeType::Png | MimeType::Jpeg | MimeType::Gif => Some(cover_art.data), + _ => { + warn!( + "Unknown cover art mime type: {mime_type:?}", + mime_type = cover_art.mime_type + ); + None + } + }; + + if let Some(data) = data { + let cover_art = super::find_or_create_cover_art(tx, data, cover_art.mime_type).await?; + am.cover_art_id = Set(Some(cover_art.id)); + } + } am.created = Set(OffsetDateTime::now_utc()); am.size = Set(meta .len() @@ -122,6 +140,24 @@ async fn find_album( .map(|c| c.round().rem_euclid(2f64.powi(32)) as i64) // TODO: figure out how to do this properly .unwrap_or_default()); am.created = Set(OffsetDateTime::now_utc()); + let cover_art = tag.album_cover(); + if let Some(cover_art) = cover_art { + let data = match cover_art.mime_type { + MimeType::Png | MimeType::Jpeg | MimeType::Gif => Some(cover_art.data), + _ => { + warn!( + "Unknown cover art mime type: {mime_type:?}", + mime_type = cover_art.mime_type + ); + None + } + }; + + if let Some(data) = data { + let cover_art = super::find_or_create_cover_art(tx, data, cover_art.mime_type).await?; + am.cover_art_id = Set(Some(cover_art.id)); + } + } let model = Album::insert(am).exec_with_returning(tx).await; diff --git a/rave/src/scan/mp3.rs b/rave/src/scan/mp3.rs index 353ed57..c47d6f2 100644 --- a/rave/src/scan/mp3.rs +++ b/rave/src/scan/mp3.rs @@ -2,7 +2,7 @@ use std::borrow::Cow; use std::path::PathBuf; use std::sync::Arc; -use audiotags::{AudioTagEdit, Id3v2Tag, Tag}; +use audiotags::{AudioTagEdit, Id3v2Tag, MimeType, Tag}; use color_eyre::Report; use entities::{ album, artist, @@ -14,7 +14,7 @@ use sea_orm::{ }; use time::OffsetDateTime; use tokio::{fs::File, sync::RwLock}; -use tracing::{debug, error, instrument}; +use tracing::{debug, error, instrument, warn}; use super::ScanState; @@ -67,6 +67,24 @@ pub async fn handle( Set(duration) }; + let cover_art = tag.album_cover(); + if let Some(cover_art) = cover_art { + let data = match cover_art.mime_type { + MimeType::Png | MimeType::Jpeg | MimeType::Gif => Some(cover_art.data), + _ => { + warn!( + "Unknown cover art mime type: {mime_type:?}", + mime_type = cover_art.mime_type + ); + None + } + }; + + if let Some(data) = data { + let cover_art = super::find_or_create_cover_art(tx, data, cover_art.mime_type).await?; + am.cover_art_id = Set(Some(cover_art.id)); + } + } am.created = Set(OffsetDateTime::now_utc()); am.size = Set(meta .len() @@ -122,6 +140,24 @@ async fn find_album( .map(|c| c.round().rem_euclid(2f64.powi(32)) as i64) // TODO: figure out how to do this properly .unwrap_or_default()); am.created = Set(OffsetDateTime::now_utc()); + let cover_art = tag.album_cover(); + if let Some(cover_art) = cover_art { + let data = match cover_art.mime_type { + MimeType::Png | MimeType::Jpeg | MimeType::Gif => Some(cover_art.data), + _ => { + warn!( + "Unknown cover art mime type: {mime_type:?}", + mime_type = cover_art.mime_type + ); + None + } + }; + + if let Some(data) = data { + let cover_art = super::find_or_create_cover_art(tx, data, cover_art.mime_type).await?; + am.cover_art_id = Set(Some(cover_art.id)); + } + } let model = Album::insert(am).exec_with_returning(tx).await; diff --git a/rave/src/subsonic/types/artist.rs b/rave/src/subsonic/types/artist.rs index deee55e..e7d8cec 100644 --- a/rave/src/subsonic/types/artist.rs +++ b/rave/src/subsonic/types/artist.rs @@ -18,12 +18,13 @@ pub struct Artist { pub starred: Option, } +#[allow(clippy::cast_sign_loss)] impl From for Artist { fn from(artist: artist::Model) -> Self { Self { id: format!("ar-{}", artist.id), name: artist.name, - cover_art: artist.cover_art_id.map(|v| format!("ca-{}", v)), + cover_art: artist.cover_art_id.map(|v| format!("ca-{v}")), artist_image_url: artist.artist_image_url, album_count: artist.album_count as u64, starred: artist