2021-09-14 01:22:42 +00:00
|
|
|
use crate::{
|
|
|
|
error::{Error, UploadError},
|
|
|
|
ffmpeg::ThumbnailFormat,
|
|
|
|
};
|
2021-09-12 15:42:44 +00:00
|
|
|
use std::path::{Path, PathBuf};
|
2021-08-28 22:15:14 +00:00
|
|
|
use tracing::{debug, error, instrument};
|
|
|
|
|
2021-09-14 01:22:42 +00:00
|
|
|
fn ptos(path: &Path) -> Result<String, Error> {
|
2021-08-28 22:15:14 +00:00
|
|
|
Ok(path.to_str().ok_or(UploadError::Path)?.to_owned())
|
|
|
|
}
|
2020-06-07 17:51:45 +00:00
|
|
|
|
|
|
|
pub(crate) trait Processor {
|
2021-10-21 01:13:39 +00:00
|
|
|
const NAME: &'static str;
|
2020-06-07 19:12:19 +00:00
|
|
|
|
2021-10-21 01:13:39 +00:00
|
|
|
fn parse(k: &str, v: &str) -> Option<Self>
|
2020-06-07 19:12:19 +00:00
|
|
|
where
|
|
|
|
Self: Sized;
|
|
|
|
|
2020-06-07 17:51:45 +00:00
|
|
|
fn path(&self, path: PathBuf) -> PathBuf;
|
2021-08-28 22:15:14 +00:00
|
|
|
fn command(&self, args: Vec<String>) -> Vec<String>;
|
2020-06-07 17:51:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pub(crate) struct Identity;
|
|
|
|
|
|
|
|
impl Processor for Identity {
|
2021-10-21 01:13:39 +00:00
|
|
|
const NAME: &'static str = "identity";
|
2020-06-07 19:12:19 +00:00
|
|
|
|
2021-10-21 01:13:39 +00:00
|
|
|
fn parse(_: &str, _: &str) -> Option<Self>
|
2020-06-07 19:12:19 +00:00
|
|
|
where
|
|
|
|
Self: Sized,
|
|
|
|
{
|
2021-10-21 01:13:39 +00:00
|
|
|
Some(Identity)
|
2020-06-07 19:12:19 +00:00
|
|
|
}
|
|
|
|
|
2020-06-07 18:03:36 +00:00
|
|
|
fn path(&self, path: PathBuf) -> PathBuf {
|
2020-06-07 17:51:45 +00:00
|
|
|
path
|
|
|
|
}
|
|
|
|
|
2021-08-28 22:15:14 +00:00
|
|
|
fn command(&self, args: Vec<String>) -> Vec<String> {
|
|
|
|
args
|
2020-06-07 17:51:45 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-16 23:13:22 +00:00
|
|
|
pub(crate) struct Thumbnail(usize);
|
2020-06-07 17:51:45 +00:00
|
|
|
|
|
|
|
impl Processor for Thumbnail {
|
2021-10-21 01:13:39 +00:00
|
|
|
const NAME: &'static str = "thumbnail";
|
2020-06-07 19:12:19 +00:00
|
|
|
|
2021-10-21 01:13:39 +00:00
|
|
|
fn parse(_: &str, v: &str) -> Option<Self>
|
2020-06-07 19:12:19 +00:00
|
|
|
where
|
|
|
|
Self: Sized,
|
|
|
|
{
|
2020-06-24 16:58:46 +00:00
|
|
|
let size = v.parse().ok()?;
|
2021-10-21 01:13:39 +00:00
|
|
|
Some(Thumbnail(size))
|
2020-06-07 19:12:19 +00:00
|
|
|
}
|
|
|
|
|
2020-06-07 17:51:45 +00:00
|
|
|
fn path(&self, mut path: PathBuf) -> PathBuf {
|
2021-10-21 01:13:39 +00:00
|
|
|
path.push(Self::NAME);
|
2020-06-07 17:51:45 +00:00
|
|
|
path.push(self.0.to_string());
|
|
|
|
path
|
|
|
|
}
|
|
|
|
|
2021-08-28 22:15:14 +00:00
|
|
|
fn command(&self, mut args: Vec<String>) -> Vec<String> {
|
|
|
|
args.extend(["-sample".to_string(), format!("{}x{}>", self.0, self.0)]);
|
2020-06-17 02:05:06 +00:00
|
|
|
|
2021-08-28 22:15:14 +00:00
|
|
|
args
|
2020-06-07 17:51:45 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-10 05:05:04 +00:00
|
|
|
pub(crate) struct Resize(usize);
|
|
|
|
|
|
|
|
impl Processor for Resize {
|
2021-10-21 01:13:39 +00:00
|
|
|
const NAME: &'static str = "resize";
|
2020-12-10 05:05:04 +00:00
|
|
|
|
2021-10-21 01:13:39 +00:00
|
|
|
fn parse(_: &str, v: &str) -> Option<Self>
|
2020-12-10 05:05:04 +00:00
|
|
|
where
|
|
|
|
Self: Sized,
|
|
|
|
{
|
|
|
|
let size = v.parse().ok()?;
|
2021-10-21 01:13:39 +00:00
|
|
|
Some(Resize(size))
|
2020-12-10 05:05:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn path(&self, mut path: PathBuf) -> PathBuf {
|
2021-10-21 01:13:39 +00:00
|
|
|
path.push(Self::NAME);
|
2020-12-10 05:05:04 +00:00
|
|
|
path.push(self.0.to_string());
|
|
|
|
path
|
|
|
|
}
|
|
|
|
|
2021-08-28 22:15:14 +00:00
|
|
|
fn command(&self, mut args: Vec<String>) -> Vec<String> {
|
|
|
|
args.extend([
|
|
|
|
"-filter".to_string(),
|
2021-08-29 04:15:29 +00:00
|
|
|
"Lanczos2".to_string(),
|
2021-08-28 22:15:14 +00:00
|
|
|
"-resize".to_string(),
|
|
|
|
format!("{}x{}>", self.0, self.0),
|
|
|
|
]);
|
2020-12-10 05:05:04 +00:00
|
|
|
|
2021-08-28 22:15:14 +00:00
|
|
|
args
|
2020-12-10 05:05:04 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-17 00:20:06 +00:00
|
|
|
pub(crate) struct Crop(usize, usize);
|
|
|
|
|
|
|
|
impl Processor for Crop {
|
2021-10-21 01:13:39 +00:00
|
|
|
const NAME: &'static str = "crop";
|
2020-12-17 00:20:06 +00:00
|
|
|
|
2021-10-21 01:13:39 +00:00
|
|
|
fn parse(_: &str, v: &str) -> Option<Self> {
|
2020-12-17 00:20:06 +00:00
|
|
|
let mut iter = v.split('x');
|
|
|
|
let first = iter.next()?;
|
|
|
|
let second = iter.next()?;
|
|
|
|
|
|
|
|
let width = first.parse::<usize>().ok()?;
|
|
|
|
let height = second.parse::<usize>().ok()?;
|
|
|
|
|
|
|
|
if width == 0 || height == 0 {
|
|
|
|
return None;
|
|
|
|
}
|
|
|
|
|
|
|
|
if width > 20 || height > 20 {
|
|
|
|
return None;
|
|
|
|
}
|
|
|
|
|
2021-10-21 01:13:39 +00:00
|
|
|
Some(Crop(width, height))
|
2020-12-17 00:20:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn path(&self, mut path: PathBuf) -> PathBuf {
|
2021-10-21 01:13:39 +00:00
|
|
|
path.push(Self::NAME);
|
2020-12-17 00:20:06 +00:00
|
|
|
path.push(format!("{}x{}", self.0, self.1));
|
|
|
|
path
|
|
|
|
}
|
|
|
|
|
2021-08-28 22:15:14 +00:00
|
|
|
fn command(&self, mut args: Vec<String>) -> Vec<String> {
|
|
|
|
args.extend([
|
|
|
|
"-gravity".to_string(),
|
|
|
|
"center".to_string(),
|
|
|
|
"-crop".to_string(),
|
|
|
|
format!("{}:{}+0+0", self.0, self.1),
|
|
|
|
]);
|
2020-12-17 00:20:06 +00:00
|
|
|
|
2021-08-28 22:15:14 +00:00
|
|
|
args
|
2020-12-17 00:20:06 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-16 23:13:22 +00:00
|
|
|
pub(crate) struct Blur(f64);
|
2020-06-07 17:51:45 +00:00
|
|
|
|
|
|
|
impl Processor for Blur {
|
2021-10-21 01:13:39 +00:00
|
|
|
const NAME: &'static str = "blur";
|
2020-06-07 19:12:19 +00:00
|
|
|
|
2021-10-21 01:13:39 +00:00
|
|
|
fn parse(_: &str, v: &str) -> Option<Self> {
|
2020-06-24 16:58:46 +00:00
|
|
|
let sigma = v.parse().ok()?;
|
2021-10-21 01:13:39 +00:00
|
|
|
Some(Blur(sigma))
|
2020-06-07 19:12:19 +00:00
|
|
|
}
|
|
|
|
|
2020-06-07 17:51:45 +00:00
|
|
|
fn path(&self, mut path: PathBuf) -> PathBuf {
|
2021-10-21 01:13:39 +00:00
|
|
|
path.push(Self::NAME);
|
2020-06-07 17:51:45 +00:00
|
|
|
path.push(self.0.to_string());
|
|
|
|
path
|
|
|
|
}
|
|
|
|
|
2021-08-28 22:15:14 +00:00
|
|
|
fn command(&self, mut args: Vec<String>) -> Vec<String> {
|
|
|
|
args.extend(["-gaussian-blur".to_string(), self.0.to_string()]);
|
2020-06-24 16:58:46 +00:00
|
|
|
|
2021-08-28 22:15:14 +00:00
|
|
|
args
|
2020-06-07 17:51:45 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-14 02:28:06 +00:00
|
|
|
#[instrument]
|
2021-10-21 01:13:39 +00:00
|
|
|
pub(crate) fn build_chain(args: &[(String, String)], filename: String) -> (PathBuf, Vec<String>) {
|
|
|
|
fn parse<P: Processor>(key: &str, value: &str) -> Option<P> {
|
|
|
|
if key == P::NAME {
|
|
|
|
return P::parse(key, value);
|
|
|
|
}
|
2020-06-07 17:51:45 +00:00
|
|
|
|
2021-10-21 01:13:39 +00:00
|
|
|
None
|
|
|
|
}
|
2020-06-07 17:51:45 +00:00
|
|
|
|
2021-10-21 01:13:39 +00:00
|
|
|
macro_rules! parse {
|
2021-10-21 01:36:10 +00:00
|
|
|
($inner:expr, $x:ident, $k:expr, $v:expr) => {{
|
2021-10-21 01:13:39 +00:00
|
|
|
if let Some(processor) = parse::<$x>($k, $v) {
|
2021-10-21 01:36:10 +00:00
|
|
|
return (processor.path($inner.0), processor.command($inner.1));
|
|
|
|
};
|
2021-10-21 01:13:39 +00:00
|
|
|
}};
|
|
|
|
}
|
|
|
|
|
2021-10-21 01:36:10 +00:00
|
|
|
let (path, args) =
|
|
|
|
args.into_iter()
|
|
|
|
.fold((PathBuf::default(), vec![]), |inner, (name, value)| {
|
|
|
|
parse!(inner, Identity, name, value);
|
|
|
|
parse!(inner, Thumbnail, name, value);
|
|
|
|
parse!(inner, Resize, name, value);
|
|
|
|
parse!(inner, Crop, name, value);
|
|
|
|
parse!(inner, Blur, name, value);
|
2021-10-21 01:13:39 +00:00
|
|
|
|
2021-10-21 01:36:10 +00:00
|
|
|
debug!("Skipping {}: {}, invalid", name, value);
|
2021-10-21 01:13:39 +00:00
|
|
|
|
2021-10-21 01:36:10 +00:00
|
|
|
inner
|
|
|
|
});
|
2020-06-07 17:51:45 +00:00
|
|
|
|
2021-10-21 01:36:10 +00:00
|
|
|
(path.join(filename), args)
|
2021-08-28 22:15:14 +00:00
|
|
|
}
|
|
|
|
|
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 {
|
2020-12-26 02:39:19 +00:00
|
|
|
matches!(self, Exists::New)
|
2020-06-24 16:58:46 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub(crate) async fn prepare_image(
|
|
|
|
original_file: PathBuf,
|
2021-09-14 01:22:42 +00:00
|
|
|
) -> Result<Option<(PathBuf, Exists)>, Error> {
|
2020-06-24 16:58:46 +00:00
|
|
|
let original_path_str = ptos(&original_file)?;
|
|
|
|
let jpg_path = format!("{}.jpg", original_path_str);
|
|
|
|
let jpg_path = PathBuf::from(jpg_path);
|
|
|
|
|
2021-09-04 19:20:31 +00:00
|
|
|
if tokio::fs::metadata(&jpg_path).await.is_ok() {
|
2020-06-24 16:58:46 +00:00
|
|
|
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();
|
2021-08-28 22:15:14 +00:00
|
|
|
crate::safe_create_parent(&tmpfile).await?;
|
2020-06-24 16:58:46 +00:00
|
|
|
|
2021-08-28 22:15:14 +00:00
|
|
|
let res = crate::ffmpeg::thumbnail(orig_path, &tmpfile, ThumbnailFormat::Jpeg).await;
|
2020-06-24 16:58:46 +00:00
|
|
|
|
|
|
|
if let Err(e) = res {
|
2020-10-16 22:48:42 +00:00
|
|
|
error!("transcode error: {:?}", e);
|
2021-09-04 19:20:31 +00:00
|
|
|
tokio::fs::remove_file(&tmpfile).await?;
|
2021-09-14 01:22:42 +00:00
|
|
|
return Err(e);
|
2020-06-24 16:58:46 +00:00
|
|
|
}
|
|
|
|
|
2021-08-28 22:15:14 +00:00
|
|
|
return match crate::safe_move_file(tmpfile, jpg_path.clone()).await {
|
2021-09-14 01:22:42 +00:00
|
|
|
Err(e) if matches!(e.kind(), UploadError::FileExists) => {
|
|
|
|
Ok(Some((jpg_path, Exists::Exists)))
|
|
|
|
}
|
2020-06-24 16:58:46 +00:00
|
|
|
Err(e) => Err(e),
|
|
|
|
_ => Ok(Some((jpg_path, Exists::New))),
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(None)
|
|
|
|
}
|