pict-rs/src/processor.rs

349 lines
8.1 KiB
Rust
Raw Normal View History

2020-06-16 23:13:22 +00:00
use crate::{
config::Format,
2020-06-16 23:13:22 +00:00
error::UploadError,
validate::{ptos, Op},
};
use actix_web::web;
2020-06-16 23:13:22 +00:00
use bytes::Bytes;
use magick_rust::MagickWand;
2020-06-24 16:58:46 +00:00
use std::path::PathBuf;
use tracing::{debug, error, instrument, Span};
pub(crate) trait Processor {
2020-06-07 19:12:19 +00:00
fn name() -> &'static str
where
Self: Sized;
fn is_processor(s: &str) -> bool
where
Self: Sized;
2020-06-24 16:58:46 +00:00
fn parse(k: &str, v: &str) -> Option<Box<dyn Processor + Send>>
2020-06-07 19:12:19 +00:00
where
Self: Sized;
fn path(&self, path: PathBuf) -> PathBuf;
2020-06-24 16:58:46 +00:00
fn process(&self, wand: &mut MagickWand) -> Result<(), UploadError>;
}
pub(crate) struct Identity;
impl Processor for Identity {
2020-06-07 19:12:19 +00:00
fn name() -> &'static str
where
Self: Sized,
{
"identity"
}
fn is_processor(s: &str) -> bool
where
Self: Sized,
{
s == Self::name()
}
2020-06-24 16:58:46 +00:00
fn parse(_: &str, _: &str) -> Option<Box<dyn Processor + Send>>
2020-06-07 19:12:19 +00:00
where
Self: Sized,
{
2020-06-14 15:07:31 +00:00
debug!("Identity");
2020-06-07 19:12:19 +00:00
Some(Box::new(Identity))
}
2020-06-07 18:03:36 +00:00
fn path(&self, path: PathBuf) -> PathBuf {
path
}
2020-06-24 16:58:46 +00:00
fn process(&self, _: &mut MagickWand) -> Result<(), UploadError> {
Ok(())
}
}
2020-06-16 23:13:22 +00:00
pub(crate) struct Thumbnail(usize);
impl Processor for Thumbnail {
2020-06-07 19:12:19 +00:00
fn name() -> &'static str
where
Self: Sized,
{
"thumbnail"
}
fn is_processor(s: &str) -> bool
where
Self: Sized,
{
2020-06-24 16:58:46 +00:00
s == Self::name()
2020-06-07 19:12:19 +00:00
}
2020-06-24 16:58:46 +00:00
fn parse(_: &str, v: &str) -> Option<Box<dyn Processor + Send>>
2020-06-07 19:12:19 +00:00
where
Self: Sized,
{
2020-06-24 16:58:46 +00:00
let size = v.parse().ok()?;
2020-06-07 19:12:19 +00:00
Some(Box::new(Thumbnail(size)))
}
fn path(&self, mut path: PathBuf) -> PathBuf {
2020-06-07 19:12:19 +00:00
path.push(Self::name());
path.push(self.0.to_string());
path
}
2020-06-24 16:58:46 +00:00
fn process(&self, wand: &mut MagickWand) -> Result<(), UploadError> {
2020-06-14 15:07:31 +00:00
debug!("Thumbnail");
2020-06-16 23:13:22 +00:00
let width = wand.get_image_width();
let height = wand.get_image_height();
if width > self.0 || height > self.0 {
2020-06-17 02:05:06 +00:00
let width_ratio = width as f64 / self.0 as f64;
let height_ratio = height as f64 / self.0 as f64;
let (new_width, new_height) = if width_ratio < height_ratio {
(width as f64 / height_ratio, self.0 as f64)
} else {
(self.0 as f64, height as f64 / width_ratio)
};
wand.op(|w| w.sample_image(new_width as usize, new_height as usize))?;
}
2020-06-24 16:58:46 +00:00
Ok(())
}
}
pub(crate) struct Resize(usize);
impl Processor for Resize {
fn name() -> &'static str
where
Self: Sized,
{
"resize"
}
fn is_processor(s: &str) -> bool
where
Self: Sized,
{
s == Self::name()
}
fn parse(_: &str, v: &str) -> Option<Box<dyn Processor + Send>>
where
Self: Sized,
{
let size = v.parse().ok()?;
Some(Box::new(Resize(size)))
}
fn path(&self, mut path: PathBuf) -> PathBuf {
path.push(Self::name());
path.push(self.0.to_string());
path
}
fn process(&self, wand: &mut MagickWand) -> Result<(), UploadError> {
debug!("Resize");
let width = wand.get_image_width();
let height = wand.get_image_height();
if width > self.0 || height > self.0 {
let width_ratio = width as f64 / self.0 as f64;
let height_ratio = height as f64 / self.0 as f64;
let (new_width, new_height) = if width_ratio < height_ratio {
(width as f64 / height_ratio, self.0 as f64)
} else {
(self.0 as f64, height as f64 / width_ratio)
};
wand.resize_image(
new_width as usize,
new_height as usize,
magick_rust::bindings::FilterType_Lanczos2Filter,
);
}
Ok(())
}
}
2020-06-16 23:13:22 +00:00
pub(crate) struct Blur(f64);
impl Processor for Blur {
2020-06-07 19:12:19 +00:00
fn name() -> &'static str
where
Self: Sized,
{
"blur"
}
fn is_processor(s: &str) -> bool {
2020-06-24 16:58:46 +00:00
s == Self::name()
2020-06-07 19:12:19 +00:00
}
2020-06-24 16:58:46 +00:00
fn parse(_: &str, v: &str) -> Option<Box<dyn Processor + Send>> {
let sigma = v.parse().ok()?;
2020-06-07 19:12:19 +00:00
Some(Box::new(Blur(sigma)))
}
fn path(&self, mut path: PathBuf) -> PathBuf {
2020-06-07 19:12:19 +00:00
path.push(Self::name());
path.push(self.0.to_string());
path
}
2020-06-24 16:58:46 +00:00
fn process(&self, wand: &mut MagickWand) -> Result<(), UploadError> {
2020-06-14 15:07:31 +00:00
debug!("Blur");
if self.0 > 0.0 {
2020-06-16 23:13:22 +00:00
wand.op(|w| w.gaussian_blur_image(0.0, self.0))?;
}
2020-06-24 16:58:46 +00:00
Ok(())
}
}
2020-06-07 19:12:19 +00:00
macro_rules! parse {
2020-06-24 16:58:46 +00:00
($x:ident, $k:expr, $v:expr) => {{
if $x::is_processor($k) {
return $x::parse($k, $v);
2020-06-07 19:12:19 +00:00
}
}};
}
pub(crate) struct ProcessChain {
inner: Vec<Box<dyn Processor + Send>>,
}
impl std::fmt::Debug for ProcessChain {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
f.debug_struct("ProcessChain")
2020-06-14 15:07:31 +00:00
.field("steps", &self.inner.len())
.finish()
}
}
#[instrument]
2020-06-24 16:58:46 +00:00
pub(crate) fn build_chain(args: &[(String, String)]) -> ProcessChain {
let inner = args
.into_iter()
2020-06-24 16:58:46 +00:00
.filter_map(|(k, v)| {
let k = k.as_str();
let v = v.as_str();
parse!(Identity, k, v);
parse!(Thumbnail, k, v);
parse!(Resize, k, v);
2020-06-24 16:58:46 +00:00
parse!(Blur, k, v);
2020-06-07 19:12:19 +00:00
2020-06-24 16:58:46 +00:00
debug!("Skipping {}: {}, invalid", k, v);
2020-06-07 19:12:19 +00:00
None
})
.collect();
ProcessChain { inner }
}
pub(crate) fn build_path(base: PathBuf, chain: &ProcessChain, filename: String) -> PathBuf {
let mut path = chain
.inner
.iter()
.fold(base, |acc, processor| processor.path(acc));
path.push(filename);
path
}
2020-06-24 16:58:46 +00:00
fn is_motion(s: &str) -> bool {
s.ends_with(".gif") || s.ends_with(".mp4")
}
pub(crate) enum Exists {
Exists,
New,
}
impl Exists {
pub(crate) fn is_new(&self) -> bool {
match self {
Exists::New => true,
_ => false,
}
}
}
pub(crate) async fn prepare_image(
original_file: PathBuf,
) -> Result<Option<(PathBuf, Exists)>, UploadError> {
let original_path_str = ptos(&original_file)?;
let jpg_path = format!("{}.jpg", original_path_str);
let jpg_path = PathBuf::from(jpg_path);
if actix_fs::metadata(jpg_path.clone()).await.is_ok() {
return Ok(Some((jpg_path, Exists::Exists)));
}
if is_motion(&original_path_str) {
let orig_path = original_path_str.clone();
let tmpfile = crate::tmp_file();
crate::safe_create_parent(tmpfile.clone()).await?;
2020-06-24 16:58:46 +00:00
let tmpfile2 = tmpfile.clone();
let res = web::block(move || {
use crate::validate::transcode::{transcode, Target};
transcode(orig_path, tmpfile, Target::Jpeg).map_err(UploadError::Transcode)
})
.await;
if let Err(e) = res {
error!("transcode error: {:?}", e);
2020-06-24 16:58:46 +00:00
actix_fs::remove_file(tmpfile2).await?;
return Err(e.into());
}
return match crate::safe_move_file(tmpfile2, jpg_path.clone()).await {
Err(UploadError::FileExists) => Ok(Some((jpg_path, Exists::Exists))),
Err(e) => Err(e),
_ => Ok(Some((jpg_path, Exists::New))),
};
}
Ok(None)
}
2020-06-16 23:13:22 +00:00
#[instrument]
pub(crate) async fn process_image(
2020-06-16 23:13:22 +00:00
original_file: PathBuf,
chain: ProcessChain,
format: Format,
2020-06-24 16:58:46 +00:00
) -> Result<Bytes, UploadError> {
2020-06-16 23:13:22 +00:00
let original_path_str = ptos(&original_file)?;
2020-06-24 16:58:46 +00:00
let span = Span::current();
let bytes = web::block(move || {
2020-06-16 23:13:22 +00:00
let entered = span.enter();
let mut wand = MagickWand::new();
debug!("Reading image");
wand.op(|w| w.read_image(&original_path_str))?;
debug!("Processing image");
for processor in chain.inner.into_iter() {
debug!("Step");
2020-06-24 16:58:46 +00:00
processor.process(&mut wand)?;
2020-06-16 23:13:22 +00:00
}
let vec = wand.op(|w| w.write_image_blob(format.to_magick_format()))?;
2020-06-16 23:13:22 +00:00
drop(entered);
2020-06-24 16:58:46 +00:00
return Ok(Bytes::from(vec)) as Result<Bytes, UploadError>;
2020-06-16 23:13:22 +00:00
})
.await?;
2020-06-24 16:58:46 +00:00
Ok(bytes)
}