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 { 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?) }