use crate::LOCAL_IMAGE_TRANSCODING_TASKS; use anyhow::Context; use image::imageops::FilterType; use jellybase::{cache::async_cache_file, AssetLocationExt}; use jellycommon::AssetLocation; use log::{debug, info}; use rgb::FromSlice; use std::{ fs::File, io::{BufReader, Read, Seek, SeekFrom}, }; 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( &[ "image-tc", original_path.as_os_str().to_str().unwrap(), &format!("{width} {quality} {speed}"), ], move |mut output| async move { let _permit = LOCAL_IMAGE_TRANSCODING_TASKS.acquire().await?; info!("encoding {asset:?} (speed={speed}, quality={quality}, width={width})"); let encoded = tokio::task::spawn_blocking(move || { let original_path = asset.path(); let mut file = BufReader::new(File::open(&original_path).context("opening source")?); // TODO: use better image library that supports AVIF let is_avif = { let mut magic = [0u8; 12]; file.read_exact(&mut magic).context("reading magic")?; file.seek(SeekFrom::Start(0)) .context("seeking back to start")?; // TODO: magic experimentally found and probably not working in all cases but fine as long as our avif enc uses that magic == [ 0x00, 0x00, 0x00, 0x18, 0x66, 0x74, 0x79, 0x70, 0x61, 0x76, 0x69, 0x66, ] }; let original = if is_avif { let mut buf = Vec::new(); file.read_to_end(&mut buf).context("reading image")?; libavif_image::read(&buf).unwrap().to_rgba8() } else { let reader = image::io::Reader::new(file); 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.)) .encode_rgba(imgref::Img::new( pixels.as_rgba(), image.width() as usize, image.height() as usize, )) .context("avif encoding")?; info!("transcode finished"); Ok::<_, anyhow::Error>(encoded) }) .await??; output .write_all(&encoded.avif_file) .await .context("writing encoded image")?; Ok(()) }, ) .await?) }