aboutsummaryrefslogtreecommitdiff
path: root/transcoder/src/image.rs
blob: 38653484a0b8f6abb7f57812e77722cd51f24f39 (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
use anyhow::Context;
use image::{imageops::FilterType, ImageFormat};
use jellybase::{async_cache_file, AssetLocationExt};
use jellycommon::AssetLocation;
use log::{debug, info};
use rgb::FromSlice;
use std::{fs::File, io::BufReader};
use tokio::io::AsyncWriteExt;

pub async fn transcode(
    asset: AssetLocation,
    quality: f32,
    speed: u8,
    width: usize,
) -> anyhow::Result<AssetLocation> {
    let original_path = asset.path();
    let asset = asset.clone();
    Ok(async_cache_file(
        &[
            original_path.as_os_str().to_str().unwrap(),
            &format!("{width} {quality} {speed}"),
        ],
        move |mut output| async move {
            let encoded = tokio::task::spawn_blocking(move || {
                let original_path = asset.path();
                info!(
                    "encoding {original_path:?} (speed={speed}, quality={quality}, width={width})"
                );
                // TODO shouldn't be neccessary with guessed format.
                let file = BufReader::new(File::open(&original_path).context("opening source")?);
                let mut reader = image::io::Reader::new(file);
                reader.set_format(ImageFormat::Avif);
                let reader = reader.with_guessed_format().context("guessing format")?;
                debug!("guessed format (or fallback): {:?}", reader.format());
                let original = 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.))
                    .encode_rgba(imgref::Img::new(
                        pixels.as_rgba(),
                        image.width() as usize,
                        image.height() as usize,
                    ))?;
                info!("transcode finished");
                Ok::<_, anyhow::Error>(encoded)
            })
            .await??;
            output.write_all(&encoded.avif_file).await?;
            Ok(())
        },
    )
    .await?)
}