101 lines
2.4 KiB
Rust
101 lines
2.4 KiB
Rust
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<T: ImageTransformer + Send + 'static>(
|
|
&self,
|
|
args: &str,
|
|
) -> Result<Option<PileValue>, 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<Option<PileValue>, 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::<MaxDimTransformer>(args).await,
|
|
"crop" => self.apply::<CropTransformer>(args).await,
|
|
_ => Ok(None),
|
|
}
|
|
}
|
|
|
|
#[expect(clippy::unwrap_used)]
|
|
async fn fields(&self) -> Result<Vec<Label>, std::io::Error> {
|
|
Ok(vec![
|
|
Label::new("maxdim").unwrap(),
|
|
Label::new("crop").unwrap(),
|
|
])
|
|
}
|
|
}
|