Skip to content

Commit

Permalink
feat: change images processing from ril to image-rs (#115)
Browse files Browse the repository at this point in the history
  • Loading branch information
ramiroaisen authored Jul 8, 2023
2 parents bd8e3db + 5ab0a35 commit 9fea330
Show file tree
Hide file tree
Showing 9 changed files with 467 additions and 27 deletions.
270 changes: 263 additions & 7 deletions Cargo.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion defs/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
4 changes: 4 additions & 0 deletions front/app/src/lib/components/Player/player.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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" },
]

Expand Down
2 changes: 1 addition & 1 deletion rs/config/constants/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion rs/packages/api/src/error/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -498,7 +498,7 @@ impl From<CreateStationPictureError> 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}")),
Expand Down
59 changes: 44 additions & 15 deletions rs/packages/api/src/routes/station_pictures/mod.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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
}
Expand Down
1 change: 1 addition & 0 deletions rs/packages/db/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"] }
Expand Down
152 changes: 151 additions & 1 deletion rs/packages/db/src/models/station_picture/mod.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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<StationPictureVariant>), CreateStationPictureError> {
tokio::task::spawn_blocking(
move || -> Result<(StationPicture, Vec<StationPictureVariant>), CreateStationPictureError> {
// use image::io::Reader;
// use image::GenericImageView;
// use std::io::Cursor;

// use ril::Image;

// let img = Image::<Rgba>::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,
Expand Down Expand Up @@ -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<ril::Error> for CreateStationPictureError {
Expand Down
2 changes: 1 addition & 1 deletion rs/patches/bson/rustfmt.toml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
combine_control_expr = false
rucombine_control_expr = false
comment_width = 100
condense_wildcard_suffixes = true
format_strings = true
Expand Down

0 comments on commit 9fea330

Please sign in to comment.