diff options
author | metamuffin <metamuffin@disroot.org> | 2023-08-06 13:52:09 +0200 |
---|---|---|
committer | metamuffin <metamuffin@disroot.org> | 2023-08-06 13:52:09 +0200 |
commit | c3c4734beb7b9650936b3c74df21d72a597cd94c (patch) | |
tree | 3728d62a70cfc65231beac41ae62f0da4d971308 | |
parent | 8551bf2e34d9543fa41a83fae785ed81d6a6c10f (diff) | |
download | jellything-c3c4734beb7b9650936b3c74df21d72a597cd94c.tar jellything-c3c4734beb7b9650936b3c74df21d72a597cd94c.tar.bz2 jellything-c3c4734beb7b9650936b3c74df21d72a597cd94c.tar.zst |
transcode images
-rw-r--r-- | Cargo.lock | 193 | ||||
-rw-r--r-- | base/src/lib.rs | 28 | ||||
-rw-r--r-- | server/src/import.rs | 8 | ||||
-rw-r--r-- | server/src/main.rs | 2 | ||||
-rw-r--r-- | server/src/routes/ui/assets.rs | 24 | ||||
-rw-r--r-- | transcoder/Cargo.toml | 5 | ||||
-rw-r--r-- | transcoder/src/image.rs | 46 | ||||
-rw-r--r-- | transcoder/src/lib.rs | 1 |
8 files changed, 272 insertions, 35 deletions
@@ -449,6 +449,12 @@ dependencies = [ ] [[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" @@ -648,6 +654,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b" [[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" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -800,6 +812,12 @@ dependencies = [ ] [[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" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -933,6 +951,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" [[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", + "rayon-core", + "smallvec", + "zune-inflate", +] + +[[package]] name = "fastrand" version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -942,6 +976,15 @@ dependencies = [ ] [[package]] +name = "fdeflate" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d329bdeac514ee06249dabc27877490f17f5d371ec693360768b838e19f3ae10" +dependencies = [ + "simd-adler32", +] + +[[package]] name = "fern" version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -965,6 +1008,29 @@ dependencies = [ ] [[package]] +name = "flate2" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b9429470923de8e8cbd4d2dc513535400b4b3fef0319fb5c4e1f520a7bef743" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[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", +] + +[[package]] name = "fnv" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1147,8 +1213,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi 0.11.0+wasi-snapshot-preview1", + "wasm-bindgen", ] [[package]] @@ -1162,6 +1230,16 @@ dependencies = [ ] [[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.27.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1218,6 +1296,15 @@ dependencies = [ ] [[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" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1364,6 +1451,25 @@ dependencies = [ ] [[package]] +name = "image" +version = "0.24.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "527909aa81e20ac3a44803521443a765550f09b5130c2c2fa1ea59c2f8f50a3a" +dependencies = [ + "bytemuck", + "byteorder", + "color_quant", + "exr", + "gif", + "jpeg-decoder", + "num-rational", + "num-traits", + "png", + "qoi", + "tiff", +] + +[[package]] name = "imgref" version = "1.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1576,8 +1682,13 @@ dependencies = [ name = "jellytranscoder" version = "0.1.0" dependencies = [ + "anyhow", + "image", + "jellybase", + "jellycommon", "log", "ravif", + "rgb", ] [[package]] @@ -1590,6 +1701,15 @@ dependencies = [ ] [[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.63" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1620,6 +1740,12 @@ 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 = "libc" version = "0.2.147" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1782,6 +1908,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" dependencies = [ "adler", + "simd-adler32", ] [[package]] @@ -1816,6 +1943,15 @@ dependencies = [ ] [[package]] +name = "nanorand" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3" +dependencies = [ + "getrandom", +] + +[[package]] name = "nasm-rs" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2158,6 +2294,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" [[package]] +name = "png" +version = "0.17.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59871cc5b6cce7eaccca5a802b4173377a1c2ba90654246789a8fa2334426d11" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + +[[package]] name = "polling" version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2214,6 +2363,15 @@ dependencies = [ ] [[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 = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2756,6 +2914,12 @@ dependencies = [ ] [[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + +[[package]] name = "simd_helpers" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2810,6 +2974,9 @@ name = "spin" version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] [[package]] name = "stable-pattern" @@ -2945,6 +3112,17 @@ dependencies = [ ] [[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" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -3452,6 +3630,12 @@ dependencies = [ ] [[package]] +name = "weezl" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9193164d4de03a926d909d3bc7c30543cecb35400c02114792c2cae20d5e2dbb" + +[[package]] name = "winapi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -3673,3 +3857,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/base/src/lib.rs b/base/src/lib.rs index 62f93e7..df80036 100644 --- a/base/src/lib.rs +++ b/base/src/lib.rs @@ -5,8 +5,8 @@ */ #![feature(lazy_cell)] use base64::Engine; -use jellycommon::config::GlobalConfig; -use std::{fs::File, path::PathBuf, str::FromStr, sync::LazyLock}; +use jellycommon::{config::GlobalConfig, AssetLocation}; +use std::{fs::File, path::PathBuf, sync::LazyLock}; pub static CONF: LazyLock<GlobalConfig> = LazyLock::new(|| { serde_json::from_reader( @@ -20,7 +20,7 @@ pub static CONF: LazyLock<GlobalConfig> = LazyLock::new(|| { .unwrap() }); -pub fn cache_file(seed: &[&str]) -> (PathBuf, Option<File>) { +pub fn cache_file(seed: &[&str]) -> PathBuf { use sha2::Digest; let mut d = sha2::Sha512::new(); for s in seed { @@ -30,13 +30,19 @@ pub fn cache_file(seed: &[&str]) -> (PathBuf, Option<File>) { let d = d.finalize(); let fname = base64::engine::general_purpose::URL_SAFE.encode(d); let fname = &fname[..22]; // about 128 bits - let fullpath = CONF.cache_path.join(fname); - let cachepath = PathBuf::from_str(fname).unwrap(); + let path = CONF.cache_path.join(fname); + path +} - let f = if !fullpath.exists() { - Some(File::create(&fullpath).unwrap()) - } else { - None - }; - (cachepath, f) +pub trait AssetLocationExt { + fn path(&self) -> PathBuf; +} +impl AssetLocationExt for AssetLocation { + fn path(&self) -> PathBuf { + match self { + AssetLocation::Assets(p) => CONF.asset_path.join(p), + AssetLocation::Cache(p) => CONF.cache_path.join(p), + AssetLocation::Library(p) => CONF.library_path.join(p), + } + } } diff --git a/server/src/import.rs b/server/src/import.rs index 48bdcdc..ef58e09 100644 --- a/server/src/import.rs +++ b/server/src/import.rs @@ -186,9 +186,11 @@ async fn cache_federation_asset( identifier: &String, role: &str, ) -> anyhow::Result<PathBuf> { - let (poster, download) = cache_file(&["federation-asset", role, identifier]); - if let Some(d) = download { - session.node_asset(&identifier, role, d).await?; + let poster = cache_file(&["federation-asset", role, identifier]); + if !poster.exists() { + session + .node_asset(&identifier, role, File::create(&poster)?) + .await?; } Ok(poster) } diff --git a/server/src/main.rs b/server/src/main.rs index 73d90a4..2b8b91a 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -11,6 +11,7 @@ use federation::Federation; use jellybase::CONF; use jellyremuxer::RemuxerContext; use routes::build_rocket; +use tokio::fs::create_dir_all; pub mod database; pub mod federation; @@ -29,6 +30,7 @@ fn main() { .block_on(async_main()) } async fn async_main() { + create_dir_all(&CONF.cache_path).await.unwrap(); let remuxer = RemuxerContext::new(); let database = Database::open(&CONF.database_path).unwrap(); let federation = Federation::initialize(); diff --git a/server/src/routes/ui/assets.rs b/server/src/routes/ui/assets.rs index 8a14133..2d175f9 100644 --- a/server/src/routes/ui/assets.rs +++ b/server/src/routes/ui/assets.rs @@ -8,7 +8,6 @@ use crate::{ routes::ui::{account::session::Session, error::MyError, CacheControlFile}, }; use anyhow::anyhow; -use jellybase::CONF; use jellycommon::AssetLocation; use log::info; use rocket::{get, http::ContentType, FromFormField, State, UriDisplayQuery}; @@ -29,7 +28,7 @@ pub async fn r_item_assets( db: &State<Database>, id: String, role: AssetRole, - width: Option<u32>, + width: Option<usize>, ) -> Result<(ContentType, CacheControlFile), MyError> { let node = db.node.get(&id)?.ok_or(anyhow!("node does not exist"))?; let mut asset = match role { @@ -48,27 +47,10 @@ pub async fn r_item_assets( let asset = asset.unwrap_or(AssetLocation::Assets( PathBuf::from_str("fallback.jpeg").unwrap(), )); - let path = asset.path(); + let path = jellytranscoder::image::transcode(asset, 50., 5, width.unwrap_or(2048))?; info!("loading asset from {path:?}"); - let ext = path - .extension() - .map(|e| e.to_str().unwrap()) - .unwrap_or("jpeg"); Ok(( - ContentType::from_extension(ext).unwrap(), + ContentType::AVIF, CacheControlFile::new(File::open(path).await?).await, )) } - -pub trait AssetLocationExt { - fn path(&self) -> PathBuf; -} -impl AssetLocationExt for AssetLocation { - fn path(&self) -> PathBuf { - match self { - AssetLocation::Assets(p) => CONF.asset_path.join(p), - AssetLocation::Cache(p) => CONF.cache_path.join(p), - AssetLocation::Library(p) => CONF.library_path.join(p), - } - } -} diff --git a/transcoder/Cargo.toml b/transcoder/Cargo.toml index bfdbedf..6fe4f05 100644 --- a/transcoder/Cargo.toml +++ b/transcoder/Cargo.toml @@ -4,5 +4,10 @@ version = "0.1.0" edition = "2021" [dependencies] +jellycommon = { path = "../common" } +jellybase = { path = "../base" } log = "0.4.19" ravif = "0.11.2" +image = "0.24.6" +anyhow = "1.0.72" +rgb = "0.8.36" diff --git a/transcoder/src/image.rs b/transcoder/src/image.rs new file mode 100644 index 0000000..d721e7b --- /dev/null +++ b/transcoder/src/image.rs @@ -0,0 +1,46 @@ +use jellybase::{cache_file, AssetLocationExt}; +use jellycommon::AssetLocation; +use log::info; +use rgb::FromSlice; +use std::{ + fs::File, + io::{BufReader, Write}, + path::PathBuf, +}; + +pub fn transcode( + asset: AssetLocation, + quality: f32, + speed: u8, + width: usize, +) -> anyhow::Result<PathBuf> { + let original_path = asset.path(); + let path = cache_file(&[ + original_path.as_os_str().to_str().unwrap(), + &format!("{width} {quality} {speed}"), + ]); + if !path.exists() { + info!("encoding {path:?} (speed={speed}, quality={quality}, width={width})"); + let reader = image::io::Reader::new(BufReader::new(File::open(original_path)?)) + .with_guessed_format()?; + let original = reader.decode()?.to_rgba8(); + let image = image::imageops::resize( + &original, + width as u32, + width as u32 * original.height() / original.width(), + image::imageops::FilterType::Lanczos3, + ); + let pixels = image.to_vec(); + let encoded = ravif::Encoder::new() + .with_speed(speed.clamp(1, 10)) + .with_quality(quality.clamp(1., 100.)) + .encode_rgba(ravif::Img::new( + pixels.as_rgba(), + image.width() as usize, + image.height() as usize, + ))?; + info!("writing to cache: {path:?}"); + File::create(&path)?.write_all(&encoded.avif_file)?; + } + Ok(path) +} diff --git a/transcoder/src/lib.rs b/transcoder/src/lib.rs index 6ddc2a4..e74a7f5 100644 --- a/transcoder/src/lib.rs +++ b/transcoder/src/lib.rs @@ -3,3 +3,4 @@ which is licensed under the GNU Affero General Public License (version 3); see /COPYING. Copyright (C) 2023 metamuffin <metamuffin.org> */ +pub mod image; |