1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
|
/*
This file is part of jellything (https://codeberg.org/metamuffin/jellything)
which is licensed under the GNU Affero General Public License (version 3); see /COPYING.
Copyright (C) 2026 metamuffin <metamuffin.org>
*/
use anyhow::{anyhow, Context, Result};
use image::imageops::FilterType;
use jellycache::{cache, cache_read, HashKey};
use log::{debug, info};
use rgb::FromSlice;
use std::io::Cursor;
pub fn transcode(key: &str, quality: u32, speed: u8, width: usize) -> Result<Vec<u8>> {
cache(
&format!(
"transcode/image/{}-W{width}-Q{quality}-S{speed}",
HashKey(key)
),
move || {
let input = cache_read(key)?.ok_or(anyhow!("transcode cache key missing"))?;
info!("encoding image (speed={speed}, quality={quality}, width={width})");
// TODO: use better image library that supports AVIF
// TODO: magic experimentally found and probably not working in all cases but fine as long as our avif enc uses that
let is_avif = matches!(
input[0..input.len().min(12)],
[
0x00,
0x00,
0x00,
_,
b'f',
b't',
b'y',
b'p',
b'a',
b'v',
b'i',
b'f',
]
);
let original = if is_avif {
libavif_image::read(&input).unwrap().to_rgba8()
} else {
let reader = image::ImageReader::new(Cursor::new(input));
let reader = reader.with_guessed_format().context("guessing format")?;
debug!("guessed format (or fallback): {:?}", reader.format());
reader.decode().context("decoding image")?.to_rgba8()
};
let image = image::imageops::resize(
&original,
width as u32,
width as u32 * original.height() / original.width(),
FilterType::Lanczos3,
);
let pixels = image.to_vec();
let encoded = ravif::Encoder::new()
.with_speed(speed.clamp(1, 10))
.with_quality(quality.clamp(1, 100) as f32)
.encode_rgba(imgref::Img::new(
pixels.as_rgba(),
image.width() as usize,
image.height() as usize,
))
.context("avif encoding")?;
debug!("transcode finished");
Ok(encoded.avif_file)
},
)
}
|