aboutsummaryrefslogtreecommitdiff
path: root/transcoder/src/image.rs
blob: 14b3e530d1a5dc2761f620ba93c5eb54b2496695 (plain)
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)
        },
    )
}