use anyhow::Context; use image::{imageops::FilterType, ImageFormat}; use jellybase::{cache_file, AssetLocationExt}; use jellycommon::AssetLocation; use log::{debug, 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 { let original_path = asset.path(); let path = cache_file(&[ original_path.as_os_str().to_str().unwrap(), &format!("{width} {quality} {speed}"), ]) .path(); if !path.exists() { info!("encoding {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"); File::create(&path)?.write_all(&encoded.avif_file)?; } Ok(path) }