From 5ab0a35ba12a64dc3fb75204fa2df72be9c2b3df Mon Sep 17 00:00:00 2001 From: ramiroaisen <52116153+ramiroaisen@users.noreply.github.com> Date: Sat, 8 Jul 2023 15:34:00 -0300 Subject: [PATCH] feat: change images processing from ril to image-rs --- Cargo.lock | 270 +++++++++++++++++- defs/constants.ts | 2 +- front/app/src/lib/components/Player/player.ts | 4 + rs/config/constants/src/lib.rs | 2 +- rs/packages/api/src/error/mod.rs | 2 +- .../api/src/routes/station_pictures/mod.rs | 59 +++- rs/packages/db/Cargo.toml | 1 + .../db/src/models/station_picture/mod.rs | 152 +++++++++- rs/patches/bson/rustfmt.toml | 2 +- 9 files changed, 467 insertions(+), 27 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9a0d4181..84c90149 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -73,6 +73,17 @@ dependencies = [ "version_check", ] +[[package]] +name = "ahash" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", +] + [[package]] name = "aho-corasick" version = "0.7.20" @@ -400,7 +411,7 @@ dependencies = [ "cc", "cfg-if", "libc", - "miniz_oxide", + "miniz_oxide 0.6.2", "object", "rustc-demangle", ] @@ -483,12 +494,27 @@ dependencies = [ "serde", ] +[[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" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitreader" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f10043e4864d975e7f197f993ec4018636ad93946724b2571c4474d51845869b" +dependencies = [ + "cfg-if", +] + [[package]] name = "block-buffer" version = "0.10.3" @@ -533,7 +559,7 @@ dependencies = [ name = "bson" version = "2.5.0" dependencies = [ - "ahash", + "ahash 0.7.6", "base64 0.13.1", "hex", "indexmap", @@ -1009,6 +1035,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + [[package]] name = "crypt" version = "0.1.0" @@ -1210,6 +1242,7 @@ dependencies = [ "geoip", "human_bytes", "hyper", + "image", "indexmap", "lazy-regex", "lazy_static", @@ -1242,6 +1275,16 @@ dependencies = [ "woothee", ] +[[package]] +name = "dcv-color-primitives" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1457f4dd8395fef9f61996b5783b82ed7b234b4b55e1843d04e07fded0538005" +dependencies = [ + "paste", + "wasm-bindgen", +] + [[package]] name = "debug_unreachable" version = "0.1.1" @@ -1529,6 +1572,19 @@ dependencies = [ "syn 1.0.107", ] +[[package]] +name = "env_logger" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + [[package]] name = "env_logger" version = "0.10.0" @@ -1561,6 +1617,31 @@ dependencies = [ "libc", ] +[[package]] +name = "exr" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1e481eb11a482815d3e9d618db8c42a93207134662873809335a92327440c18" +dependencies = [ + "bit_field", + "flume", + "half", + "lebe", + "miniz_oxide 0.7.1", + "rayon-core", + "smallvec 1.10.0", + "zune-inflate", +] + +[[package]] +name = "fallible_collections" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a88c69768c0a15262df21899142bc6df9b9b823546d4b4b9a7bc2d6c448ec6fd" +dependencies = [ + "hashbrown 0.13.2", +] + [[package]] name = "fast_image_resize" version = "2.7.0" @@ -1610,7 +1691,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841" dependencies = [ "crc32fast", - "miniz_oxide", + "miniz_oxide 0.6.2", +] + +[[package]] +name = "flume" +version = "0.10.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1657b4441c3403d9f7b3409e47575237dac27b1b5726df654a6ecbf92f0f7577" +dependencies = [ + "futures-core", + "futures-sink", + "nanorand", + "pin-project", + "spin 0.9.4", ] [[package]] @@ -1832,8 +1926,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi 0.11.0+wasi-snapshot-preview1", + "wasm-bindgen", ] [[package]] @@ -1862,6 +1958,12 @@ version = "0.27.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e" +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + [[package]] name = "globset" version = "0.4.10" @@ -1894,6 +1996,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 = "hash32" version = "0.2.1" @@ -1909,7 +2020,7 @@ version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" dependencies = [ - "ahash", + "ahash 0.7.6", ] [[package]] @@ -1918,6 +2029,15 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +[[package]] +name = "hashbrown" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" +dependencies = [ + "ahash 0.8.3", +] + [[package]] name = "hdrhistogram" version = "7.5.2" @@ -2276,6 +2396,29 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "image" +version = "0.24.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "527909aa81e20ac3a44803521443a765550f09b5130c2c2fa1ea59c2f8f50a3a" +dependencies = [ + "bytemuck", + "byteorder", + "color_quant", + "dcv-color-primitives", + "exr", + "gif", + "jpeg-decoder", + "mp4parse", + "num-rational", + "num-traits", + "png", + "qoi", + "rgb", + "tiff", + "webp", +] + [[package]] name = "indexmap" version = "1.9.2" @@ -2539,6 +2682,12 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +[[package]] +name = "lebe" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" + [[package]] name = "lettre" version = "0.10.4" @@ -2653,6 +2802,16 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "348108ab3fba42ec82ff6e9564fc4ca0247bdccdc68dd8af9764bbc79c3c8ffb" +[[package]] +name = "libwebp-sys" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c2a79bd4556d1b538c76e59147768b2d63f97e2dfb185c896548136af408e58" +dependencies = [ + "cc", + "glob", +] + [[package]] name = "libwebp-sys2" version = "0.1.7" @@ -2721,7 +2880,7 @@ version = "0.1.0" dependencies = [ "chrono", "crossterm", - "env_logger", + "env_logger 0.10.0", "log", "owo-colors 3.5.0", "static_init", @@ -2976,6 +3135,15 @@ dependencies = [ "adler", ] +[[package]] +name = "miniz_oxide" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +dependencies = [ + "adler", +] + [[package]] name = "mio" version = "0.8.8" @@ -3067,6 +3235,21 @@ dependencies = [ "tokio-util", ] +[[package]] +name = "mp4parse" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10d189404bad70963b8848d9619032b02a115093f1fe9fb3d8ddf858e9b69ee9" +dependencies = [ + "bitreader", + "byteorder", + "env_logger 0.8.4", + "fallible_collections", + "log", + "num-traits", + "static_assertions", +] + [[package]] name = "multiqueue" version = "0.3.2" @@ -3086,6 +3269,15 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "999681fe3c0524336e98ece1c25ee4278607f25cc1e361ad0f9201c8bf56dc2c" +[[package]] +name = "nanorand" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3" +dependencies = [ + "getrandom 0.2.8", +] + [[package]] name = "native-tls" version = "0.2.11" @@ -3168,6 +3360,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.15" @@ -3632,7 +3835,7 @@ dependencies = [ "bitflags", "crc32fast", "flate2", - "miniz_oxide", + "miniz_oxide 0.6.2", ] [[package]] @@ -3786,6 +3989,15 @@ dependencies = [ "lazy-regex", ] +[[package]] +name = "qoi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001" +dependencies = [ + "bytemuck", +] + [[package]] name = "quick-error" version = "1.2.3" @@ -4126,6 +4338,15 @@ dependencies = [ "quick-error", ] +[[package]] +name = "rgb" +version = "0.8.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20ec2d3e3fc7a92ced357df9cebd5a10b6fb2aa1ee797bf7e9ce2f17dffc8f59" +dependencies = [ + "bytemuck", +] + [[package]] name = "ril" version = "0.9.0" @@ -4700,6 +4921,12 @@ dependencies = [ "libc", ] +[[package]] +name = "simd-adler32" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "238abfbb77c1915110ad968465608b68e869e0772622c9656714e73e5a1a522f" + [[package]] name = "siphasher" version = "0.3.10" @@ -5038,7 +5265,7 @@ version = "0.29.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebd844dfbd9969a9ef8430e954661de43edde353d65e987f935a328619698883" dependencies = [ - "ahash", + "ahash 0.7.6", "ast_node", "better_scoped_tls", "cfg-if", @@ -5346,6 +5573,17 @@ dependencies = [ "once_cell", ] +[[package]] +name = "tiff" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7449334f9ff2baf290d55d73983a7d6fa15e01198faef72af07e2a8db851e471" +dependencies = [ + "flate2", + "jpeg-decoder", + "weezl", +] + [[package]] name = "time" version = "0.1.45" @@ -6115,6 +6353,15 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "webp" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69ebe8a8a48890b0d4f75a6db37fe2042da4b112e2fef19b8a138027787921bc" +dependencies = [ + "libwebp-sys", +] + [[package]] name = "webpki" version = "0.22.0" @@ -6430,3 +6677,12 @@ dependencies = [ "libc", "pkg-config", ] + +[[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/defs/constants.ts b/defs/constants.ts index 4b9e27fe..1dc8ea93 100644 --- a/defs/constants.ts +++ b/defs/constants.ts @@ -35,7 +35,7 @@ export const REAL_IP_HEADER = "x-real-ip"; export const RELAY_NO_LISTENERS_SHUTDOWN_DELAY_SECS = 10; -export const STATION_PICTURES_VERSION = 2.0; +export const STATION_PICTURES_VERSION = 3.0; export const STREAM_BURST_LENGTH = 12; diff --git a/front/app/src/lib/components/Player/player.ts b/front/app/src/lib/components/Player/player.ts index beb78f96..aba4064f 100644 --- a/front/app/src/lib/components/Player/player.ts +++ b/front/app/src/lib/components/Player/player.ts @@ -293,7 +293,11 @@ if (hasMediaSession) { const storage_url = get(page).data.config.storage_public_url; const artwork = [ + { src: `${storage_url}/station-pictures/png/32/${picture_id}.png`, sizes: "32x32", type: "image/png" }, + { src: `${storage_url}/station-pictures/png/64/${picture_id}.png`, sizes: "64x64", type: "image/png" }, + { src: `${storage_url}/station-pictures/png/128/${picture_id}.png`, sizes: "128x128", type: "image/png" }, { src: `${storage_url}/station-pictures/png/192/${picture_id}.png`, sizes: "192x192", type: "image/png" }, + { src: `${storage_url}/station-pictures/png/256/${picture_id}.png`, sizes: "256x256", type: "image/png" }, { src: `${storage_url}/station-pictures/png/512/${picture_id}.png`, sizes: "512x512", type: "image/png" }, ] diff --git a/rs/config/constants/src/lib.rs b/rs/config/constants/src/lib.rs index 1b579768..13b595fe 100644 --- a/rs/config/constants/src/lib.rs +++ b/rs/config/constants/src/lib.rs @@ -108,7 +108,7 @@ pub const PAYMENTS_ACCESS_TOKEN_HEADER: &str = "x-access-token"; /// changing to this value will make startup check and recreation of outdated images /// and invalidation of service workers station pictures caches #[const_register] -pub const STATION_PICTURES_VERSION: f64 = 2.0; +pub const STATION_PICTURES_VERSION: f64 = 3.0; #[cfg(test)] pub mod test { diff --git a/rs/packages/api/src/error/mod.rs b/rs/packages/api/src/error/mod.rs index 1eec7081..47f1344d 100644 --- a/rs/packages/api/src/error/mod.rs +++ b/rs/packages/api/src/error/mod.rs @@ -498,7 +498,7 @@ impl From for ApiError { use CreateStationPictureError::*; match e { Db(e) => e.into(), - ImageTooLargeBytes | ImageNotSquare | ImageTooSmallSize | Ril(_) => { + ImageTooLargeBytes | ImageNotSquare | ImageTooSmallSize | Ril(_) | Image(_) | Io(_) => { ApiError::PayloadInvalid(format!("{e}")) } AccountNotFound(_) => ApiError::QueryStringCustom(format!("{e}")), diff --git a/rs/packages/api/src/routes/station_pictures/mod.rs b/rs/packages/api/src/routes/station_pictures/mod.rs index 146ed4a1..eaa73ac8 100644 --- a/rs/packages/api/src/routes/station_pictures/mod.rs +++ b/rs/packages/api/src/routes/station_pictures/mod.rs @@ -1,7 +1,8 @@ use async_trait::async_trait; +use db::station_picture::StationPicture; use db::station_picture_variant::{StationPictureVariant, StationPictureVariantFormat}; use db::Model; -use hyper::header::{CACHE_CONTROL, CONTENT_TYPE}; +use hyper::header::{/*CACHE_CONTROL,*/ CONTENT_LENGTH, CONTENT_TYPE, ETAG, IF_NONE_MATCH}; use hyper::http::HeaderValue; use hyper::{Body, StatusCode}; use mongodb::bson::doc; @@ -45,24 +46,52 @@ impl Handler for StationPicHandler { Ok(r) => match r { None => ApiError::ResourceNotFound {}.into_json_response(), Some(doc) => { - let mut res = Response::new(StatusCode::OK); - - match HeaderValue::from_str(&doc.content_type) { - Err(_) => res.headers_mut().append( - CONTENT_TYPE, - HeaderValue::from_static("application/octet-stream"), - ), - Ok(value) => res.headers_mut().append(CONTENT_TYPE, value), + let response_etag = format!("W/\"{}-{}-{}\"", doc.id, doc.size, StationPicture::VERSION); + + let request_etag = match req.headers().get(IF_NONE_MATCH) { + None => None, + Some(v) => v.to_str().ok(), + }; + + let is_match = if let Some(v) = request_etag { + v == response_etag + } else { + false }; - res.headers_mut().append( - CACHE_CONTROL, - HeaderValue::from_static("public, max-age=31536000, immutable"), // 365 days - ); + let res = if is_match { + Response::new(StatusCode::NOT_MODIFIED) + } else { + let mut res = Response::new(StatusCode::OK); + + match HeaderValue::from_str(&doc.content_type) { + Err(_) => res.headers_mut().append( + CONTENT_TYPE, + HeaderValue::from_static("application/octet-stream"), + ), + Ok(value) => res.headers_mut().append(CONTENT_TYPE, value), + }; + + // res.headers_mut().append( + // CACHE_CONTROL, + // HeaderValue::from_static("public, max-age=600, immutable"), // 10 mins + // ); - let body = Body::from(doc.data); + res + .headers_mut() + .append(ETAG, HeaderValue::from_str(&response_etag).unwrap()); - *res.body_mut() = body; + res.headers_mut().append( + CONTENT_LENGTH, + HeaderValue::from_str(doc.data.len().to_string().as_str()).unwrap(), + ); + + let body = Body::from(doc.data); + + *res.body_mut() = body; + + res + }; res } diff --git a/rs/packages/db/Cargo.toml b/rs/packages/db/Cargo.toml index 2a707303..d8de5bfb 100644 --- a/rs/packages/db/Cargo.toml +++ b/rs/packages/db/Cargo.toml @@ -53,6 +53,7 @@ random-string = "1.0.0" url = "2.4.0" constants = { version = "0.1.0", path = "../../config/constants" } paste = "1.0.12" +image = { version = "0.24.6", features = ["webp-encoder", "webp", "rgb", "dcv-color-primitives", "mp4parse"] } [dev-dependencies] serde_json = { version = "1.0", features = ["preserve_order"] } diff --git a/rs/packages/db/src/models/station_picture/mod.rs b/rs/packages/db/src/models/station_picture/mod.rs index 5e2dec07..c7da15f5 100644 --- a/rs/packages/db/src/models/station_picture/mod.rs +++ b/rs/packages/db/src/models/station_picture/mod.rs @@ -1,7 +1,10 @@ +use std::io::Cursor; + use crate::account::Account; use crate::station_picture_variant::{StationPictureVariant, StationPictureVariantFormat}; use crate::{run_transaction, Model}; use bytes::Bytes; +use image::{ImageBuffer, EncodableLayout}; use mongodb::bson::doc; use mongodb::{ClientSession, IndexModel}; use ril::{Encoder, Paste, Rgba}; @@ -241,7 +244,7 @@ impl StationPicture { Ok(r) } - pub async fn create_variants( + pub async fn create_variants_ril( id: String, account_id: String, filename: String, @@ -408,6 +411,149 @@ impl StationPicture { .unwrap() } + pub async fn create_variants( + id: String, + account_id: String, + filename: String, + content_type: String, + data: Bytes, + ) -> Result<(StationPicture, Vec), CreateStationPictureError> { + tokio::task::spawn_blocking( + move || -> Result<(StationPicture, Vec), CreateStationPictureError> { + // use image::io::Reader; + // use image::GenericImageView; + // use std::io::Cursor; + + // use ril::Image; + + // let img = Image::::from_bytes_inferred(&data)?; + + use image::ImageEncoder; + + let img = image::io::Reader::new(Cursor::new(&data)).with_guessed_format()?.decode()?.to_rgba8(); + + // let img = match Reader::new(Cursor::new(data.as_ref())).with_guessed_format() { + // Err(e) => return Err(e.into()), + // Ok(reader) => match reader.decode() { + // Err(e) => return Err(e.into()), + // Ok(img) => img, + // }, + // }; + + let (w, h) = img.dimensions(); + + if w != h { + return Err(CreateStationPictureError::ImageNotSquare); + } + + if w < 512 { + return Err(CreateStationPictureError::ImageTooSmallSize); + } + + let now = DateTime::now(); + + let doc = StationPicture { + id, + account_id: account_id.clone(), + version: StationPicture::VERSION, + src_filename: filename.clone(), + src_content_type: content_type.clone(), + src_size: w as f64, + src_size_bytes: data.len() as f64, + src_sha256: crypt::sha256(&data), + png_sizes: StationPicture::PNG_SIZES.to_vec(), + webp_sizes: StationPicture::WEBP_SIZES.to_vec(), + created_at: now, + updated_at: now, + }; + + let mut variants = vec![]; + + let source = StationPictureVariant { + id: StationPictureVariant::uid(), + picture_id: doc.id.clone(), + content_type: content_type.clone(), + format: StationPictureVariantFormat::Source, + size: w as f64, + size_bytes: data.len() as f64, + data, + created_at: now, + updated_at: now, + }; + + variants.push(source); + + for size in StationPicture::PNG_SIZES { + + // PNG images are used for Android apps icons so they get a white background + let mut bg = ImageBuffer::from_fn(size as u32, size as u32, |_, _| { + image::Rgba([ 255, 255, 255, 255 ]) + }); + + let img = image::imageops::resize(&img, size as u32, size as u32, image::imageops::FilterType::Lanczos3); + image::imageops::overlay(&mut bg, &img, 0, 0); + + let img = bg; + + let mut buf = vec![]; + + let encoder = image::codecs::png::PngEncoder::new_with_quality( + Cursor::new(&mut buf), + image::codecs::png::CompressionType::Best, + Default::default(), + ); + + encoder.write_image(img.as_bytes(), img.width(), img.height(), image::ColorType::Rgba8)?; + + let variant = StationPictureVariant { + id: StationPictureVariant::uid(), + picture_id: doc.id.clone(), + content_type: String::from("image/png"), + format: StationPictureVariantFormat::Png, + size, + size_bytes: buf.len() as f64, + data: Bytes::from(buf), + created_at: now, + updated_at: now, + }; + + variants.push(variant); + } + + for size in StationPicture::WEBP_SIZES { + let img = image::imageops::resize(&img, size as u32, size as u32, image::imageops::FilterType::Lanczos3); + + let mut buf = vec![]; + + let encoder = image::codecs::webp::WebPEncoder::new_with_quality( + Cursor::new(&mut buf), + image::codecs::webp::WebPQuality::lossy(100), + ); + + encoder.write_image(img.as_bytes(), img.width(), img.height(), image::ColorType::Rgba8)?; + + let variant = StationPictureVariant { + id: StationPictureVariant::uid(), + picture_id: doc.id.clone(), + content_type: String::from("image/webp"), + format: StationPictureVariantFormat::Webp, + size, + size_bytes: buf.len() as f64, + data: Bytes::from(buf), + created_at: now, + updated_at: now, + }; + + variants.push(variant); + } + + Ok((doc, variants)) + }, + ) + .await + .unwrap() + } + pub async fn create( account_id: String, filename: String, @@ -446,6 +592,10 @@ pub enum CreateStationPictureError { AccountNotFound(String), #[error("Image is not supported or invalid ({0})")] Ril(ril::Error), + #[error("I/O error reading the image: {0}")] + Io(#[from] std::io::Error), + #[error("Image is not supported or invalid ({0})")] + Image(#[from] image::error::ImageError) } impl From for CreateStationPictureError { diff --git a/rs/patches/bson/rustfmt.toml b/rs/patches/bson/rustfmt.toml index ff7209cf..ed78e203 100644 --- a/rs/patches/bson/rustfmt.toml +++ b/rs/patches/bson/rustfmt.toml @@ -1,4 +1,4 @@ -combine_control_expr = false +rucombine_control_expr = false comment_width = 100 condense_wildcard_suffixes = true format_strings = true