aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormetamuffin <metamuffin@disroot.org>2023-08-06 13:52:09 +0200
committermetamuffin <metamuffin@disroot.org>2023-08-06 13:52:09 +0200
commitc3c4734beb7b9650936b3c74df21d72a597cd94c (patch)
tree3728d62a70cfc65231beac41ae62f0da4d971308
parent8551bf2e34d9543fa41a83fae785ed81d6a6c10f (diff)
downloadjellything-c3c4734beb7b9650936b3c74df21d72a597cd94c.tar
jellything-c3c4734beb7b9650936b3c74df21d72a597cd94c.tar.bz2
jellything-c3c4734beb7b9650936b3c74df21d72a597cd94c.tar.zst
transcode images
-rw-r--r--Cargo.lock193
-rw-r--r--base/src/lib.rs28
-rw-r--r--server/src/import.rs8
-rw-r--r--server/src/main.rs2
-rw-r--r--server/src/routes/ui/assets.rs24
-rw-r--r--transcoder/Cargo.toml5
-rw-r--r--transcoder/src/image.rs46
-rw-r--r--transcoder/src/lib.rs1
8 files changed, 272 insertions, 35 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 1b8b601..9653265 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -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;