use image::ImageFormat; use mime::Mime; use pile_config::Label; use pile_io::AsyncReader; use std::{io::Cursor, str::FromStr, sync::Arc}; use tracing::trace; use transform::{CropTransformer, ImageTransformer, MaxDimTransformer}; mod transform; use crate::{ extract::traits::{ExtractState, ObjectExtractor}, value::{ArcBytes, BinaryPileValue, PileValue}, }; pub struct ImageExtractor { item: BinaryPileValue, } impl ImageExtractor { pub fn new(item: &BinaryPileValue) -> Self { Self { item: item.clone() } } async fn apply( &self, args: &str, ) -> Result, std::io::Error> { let transformer = match T::parse_args(args) { Ok(t) => t, Err(_) => return Ok(None), }; let mime = self.item.mime().clone(); let bytes = self.item.read().await?.read_to_end().await?; let Some(format) = ImageFormat::from_mime_type(&mime) else { return Ok(Some(PileValue::Binary(BinaryPileValue::Blob { mime, bytes: ArcBytes(Arc::new(bytes)), }))); }; let bytes_for_closure = bytes.clone(); let result = tokio::task::spawn_blocking(move || { let mut img = image::load_from_memory_with_format(&bytes_for_closure, format)?; transformer.transform(&mut img); let mut out = Cursor::new(Vec::new()); img.write_to(&mut out, format)?; let out_mime = Mime::from_str(format.to_mime_type()).unwrap_or(mime::APPLICATION_OCTET_STREAM); Ok::<_, image::ImageError>((out_mime, out.into_inner())) }) .await?; match result { Ok((out_mime, out_bytes)) => Ok(Some(PileValue::Binary(BinaryPileValue::Blob { mime: out_mime, bytes: ArcBytes(Arc::new(out_bytes)), }))), Err(_) => Ok(Some(PileValue::Binary(BinaryPileValue::Blob { mime, bytes: ArcBytes(Arc::new(bytes)), }))), } } } #[async_trait::async_trait] impl ObjectExtractor for ImageExtractor { async fn field( &self, _state: &ExtractState, name: &Label, args: Option<&str>, ) -> Result, std::io::Error> { let Some(args) = args else { return Ok(None); }; trace!(?args, "Getting field {name:?} from ImageExtractor",); match name.as_str() { "maxdim" => self.apply::(args).await, "crop" => self.apply::(args).await, _ => Ok(None), } } #[expect(clippy::unwrap_used)] async fn fields(&self) -> Result, std::io::Error> { Ok(vec![ Label::new("maxdim").unwrap(), Label::new("crop").unwrap(), ]) } }