mirror of
https://git.asonix.dog/asonix/pict-rs
synced 2024-12-22 19:31:35 +00:00
Better discovery validation, proper invoking of imagemagick
This commit is contained in:
parent
9b1dd1f8c3
commit
ec6c9aac1e
9 changed files with 215 additions and 110 deletions
|
@ -25,6 +25,18 @@ pub(crate) struct DiscoveryLite {
|
||||||
pub(crate) frames: Option<u32>,
|
pub(crate) frames: Option<u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
pub(crate) enum DiscoverError {
|
||||||
|
#[error("No frames in uploaded media")]
|
||||||
|
NoFrames,
|
||||||
|
|
||||||
|
#[error("Not all frames have same image format")]
|
||||||
|
FormatMismatch,
|
||||||
|
|
||||||
|
#[error("Input file type {0} is unsupported")]
|
||||||
|
UnsupportedFileType(String),
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) async fn discover_bytes_lite(
|
pub(crate) async fn discover_bytes_lite(
|
||||||
bytes: Bytes,
|
bytes: Bytes,
|
||||||
) -> Result<DiscoveryLite, crate::error::Error> {
|
) -> Result<DiscoveryLite, crate::error::Error> {
|
||||||
|
|
|
@ -6,6 +6,7 @@ use futures_util::Stream;
|
||||||
use tokio::io::AsyncReadExt;
|
use tokio::io::AsyncReadExt;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
discover::DiscoverError,
|
||||||
formats::{AnimationFormat, ImageFormat, ImageInput, InputFile, VideoFormat},
|
formats::{AnimationFormat, ImageFormat, ImageInput, InputFile, VideoFormat},
|
||||||
magick::MagickError,
|
magick::MagickError,
|
||||||
process::Process,
|
process::Process,
|
||||||
|
@ -77,8 +78,7 @@ pub(super) async fn confirm_bytes(
|
||||||
) -> Result<Discovery, MagickError> {
|
) -> Result<Discovery, MagickError> {
|
||||||
match discovery {
|
match discovery {
|
||||||
Some(Discovery {
|
Some(Discovery {
|
||||||
input:
|
input: InputFile::Animation(AnimationFormat::Avif),
|
||||||
InputFile::Animation( AnimationFormat::Avif,),
|
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
..
|
..
|
||||||
|
@ -92,15 +92,14 @@ pub(super) async fn confirm_bytes(
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
return Ok(Discovery {
|
return Ok(Discovery {
|
||||||
input: InputFile::Animation( AnimationFormat::Avif,),
|
input: InputFile::Animation(AnimationFormat::Avif),
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
frames: Some(frames),
|
frames: Some(frames),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
Some(Discovery {
|
Some(Discovery {
|
||||||
input:
|
input: InputFile::Animation(AnimationFormat::Webp),
|
||||||
InputFile::Animation( AnimationFormat::Webp,),
|
|
||||||
..
|
..
|
||||||
}) => {
|
}) => {
|
||||||
// continue
|
// continue
|
||||||
|
@ -151,6 +150,10 @@ where
|
||||||
.await
|
.await
|
||||||
.map_err(MagickError::RemoveFile)?;
|
.map_err(MagickError::RemoveFile)?;
|
||||||
|
|
||||||
|
if output.is_empty() {
|
||||||
|
return Err(MagickError::Empty);
|
||||||
|
}
|
||||||
|
|
||||||
let lines: u32 = output
|
let lines: u32 = output
|
||||||
.lines()
|
.lines()
|
||||||
.count()
|
.count()
|
||||||
|
@ -158,7 +161,7 @@ where
|
||||||
.expect("Reasonable frame count");
|
.expect("Reasonable frame count");
|
||||||
|
|
||||||
if lines == 0 {
|
if lines == 0 {
|
||||||
todo!("Error");
|
return Err(MagickError::Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(lines)
|
Ok(lines)
|
||||||
|
@ -202,17 +205,21 @@ where
|
||||||
.await
|
.await
|
||||||
.map_err(MagickError::RemoveFile)?;
|
.map_err(MagickError::RemoveFile)?;
|
||||||
|
|
||||||
|
if output.is_empty() {
|
||||||
|
return Err(MagickError::Empty);
|
||||||
|
}
|
||||||
|
|
||||||
let output: Vec<MagickDiscovery> =
|
let output: Vec<MagickDiscovery> =
|
||||||
serde_json::from_slice(&output).map_err(MagickError::Json)?;
|
serde_json::from_slice(&output).map_err(MagickError::Json)?;
|
||||||
|
|
||||||
parse_discovery(output)
|
parse_discovery(output).map_err(MagickError::Discover)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_discovery(output: Vec<MagickDiscovery>) -> Result<Discovery, MagickError> {
|
fn parse_discovery(output: Vec<MagickDiscovery>) -> Result<Discovery, DiscoverError> {
|
||||||
let frames = output.len();
|
let frames = output.len();
|
||||||
|
|
||||||
if frames == 0 {
|
if frames == 0 {
|
||||||
todo!("Error")
|
return Err(DiscoverError::NoFrames);
|
||||||
}
|
}
|
||||||
|
|
||||||
let width = output
|
let width = output
|
||||||
|
@ -250,7 +257,7 @@ fn parse_discovery(output: Vec<MagickDiscovery>) -> Result<Discovery, MagickErro
|
||||||
image: Image { format, .. },
|
image: Image { format, .. },
|
||||||
}| format != first_format,
|
}| format != first_format,
|
||||||
) {
|
) {
|
||||||
todo!("Error")
|
return Err(DiscoverError::FormatMismatch);
|
||||||
}
|
}
|
||||||
|
|
||||||
let frames: u32 = frames.try_into().expect("Reasonable frame count");
|
let frames: u32 = frames.try_into().expect("Reasonable frame count");
|
||||||
|
@ -259,7 +266,7 @@ fn parse_discovery(output: Vec<MagickDiscovery>) -> Result<Discovery, MagickErro
|
||||||
"AVIF" => {
|
"AVIF" => {
|
||||||
if frames > 1 {
|
if frames > 1 {
|
||||||
Ok(Discovery {
|
Ok(Discovery {
|
||||||
input: InputFile::Animation( AnimationFormat::Avif,),
|
input: InputFile::Animation(AnimationFormat::Avif),
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
frames: Some(frames),
|
frames: Some(frames),
|
||||||
|
@ -277,13 +284,13 @@ fn parse_discovery(output: Vec<MagickDiscovery>) -> Result<Discovery, MagickErro
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"APNG" => Ok(Discovery {
|
"APNG" => Ok(Discovery {
|
||||||
input: InputFile::Animation( AnimationFormat::Apng,),
|
input: InputFile::Animation(AnimationFormat::Apng),
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
frames: Some(frames),
|
frames: Some(frames),
|
||||||
}),
|
}),
|
||||||
"GIF" => Ok(Discovery {
|
"GIF" => Ok(Discovery {
|
||||||
input: InputFile::Animation( AnimationFormat::Gif,),
|
input: InputFile::Animation(AnimationFormat::Gif),
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
frames: Some(frames),
|
frames: Some(frames),
|
||||||
|
@ -324,7 +331,7 @@ fn parse_discovery(output: Vec<MagickDiscovery>) -> Result<Discovery, MagickErro
|
||||||
"WEBP" => {
|
"WEBP" => {
|
||||||
if frames > 1 {
|
if frames > 1 {
|
||||||
Ok(Discovery {
|
Ok(Discovery {
|
||||||
input: InputFile::Animation( AnimationFormat::Webp,),
|
input: InputFile::Animation(AnimationFormat::Webp),
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
frames: Some(frames),
|
frames: Some(frames),
|
||||||
|
@ -347,6 +354,6 @@ fn parse_discovery(output: Vec<MagickDiscovery>) -> Result<Discovery, MagickErro
|
||||||
height,
|
height,
|
||||||
frames: Some(frames),
|
frames: Some(frames),
|
||||||
}),
|
}),
|
||||||
otherwise => todo!("Error {otherwise}"),
|
otherwise => Err(DiscoverError::UnsupportedFileType(String::from(otherwise))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -78,6 +78,9 @@ pub(crate) enum UploadError {
|
||||||
#[error("Error in exiftool")]
|
#[error("Error in exiftool")]
|
||||||
Exiftool(#[from] crate::exiftool::ExifError),
|
Exiftool(#[from] crate::exiftool::ExifError),
|
||||||
|
|
||||||
|
#[error("Requested file extension cannot be served by source file")]
|
||||||
|
InvalidProcessExtension,
|
||||||
|
|
||||||
#[error("Provided process path is invalid")]
|
#[error("Provided process path is invalid")]
|
||||||
ParsePath,
|
ParsePath,
|
||||||
|
|
||||||
|
@ -170,7 +173,8 @@ impl ResponseError for Error {
|
||||||
))
|
))
|
||||||
| UploadError::Repo(crate::repo::RepoError::AlreadyClaimed)
|
| UploadError::Repo(crate::repo::RepoError::AlreadyClaimed)
|
||||||
| UploadError::Validation(_)
|
| UploadError::Validation(_)
|
||||||
| UploadError::UnsupportedProcessExtension,
|
| UploadError::UnsupportedProcessExtension
|
||||||
|
| UploadError::InvalidProcessExtension,
|
||||||
) => StatusCode::BAD_REQUEST,
|
) => StatusCode::BAD_REQUEST,
|
||||||
Some(UploadError::Magick(e)) if e.is_client_error() => StatusCode::BAD_REQUEST,
|
Some(UploadError::Magick(e)) if e.is_client_error() => StatusCode::BAD_REQUEST,
|
||||||
Some(UploadError::Ffmpeg(e)) if e.is_client_error() => StatusCode::BAD_REQUEST,
|
Some(UploadError::Ffmpeg(e)) if e.is_client_error() => StatusCode::BAD_REQUEST,
|
||||||
|
|
|
@ -93,16 +93,23 @@ async fn process<R: FullRepo, S: Store + 'static>(
|
||||||
details
|
details
|
||||||
};
|
};
|
||||||
|
|
||||||
let Some(format) = input_details.internal_format().and_then(|format| format.processable_format()) else {
|
let input_format = input_details
|
||||||
todo!("Error")
|
.internal_format()
|
||||||
|
.and_then(|format| format.processable_format())
|
||||||
|
.expect("Valid details should always have internal format");
|
||||||
|
|
||||||
|
let Some(format) = input_format.process_to(output_format) else {
|
||||||
|
return Err(UploadError::InvalidProcessExtension.into());
|
||||||
};
|
};
|
||||||
|
|
||||||
let Some(format) = format.process_to(output_format) else {
|
let mut processed_reader = crate::magick::process_image_store_read(
|
||||||
todo!("Error")
|
store,
|
||||||
};
|
&identifier,
|
||||||
|
thumbnail_args,
|
||||||
let mut processed_reader =
|
input_format,
|
||||||
crate::magick::process_image_store_read(store.clone(), identifier, thumbnail_args, format)?;
|
format,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
let mut vec = Vec::new();
|
let mut vec = Vec::new();
|
||||||
processed_reader
|
processed_reader
|
||||||
|
|
|
@ -40,7 +40,7 @@ where
|
||||||
Ok(buf.into_bytes())
|
Ok(buf.into_bytes())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip(repo, store, stream))]
|
#[tracing::instrument(skip(repo, store, stream, media))]
|
||||||
pub(crate) async fn ingest<R, S>(
|
pub(crate) async fn ingest<R, S>(
|
||||||
repo: &R,
|
repo: &R,
|
||||||
store: &S,
|
store: &S,
|
||||||
|
@ -56,7 +56,6 @@ where
|
||||||
|
|
||||||
let bytes = aggregate(stream).await?;
|
let bytes = aggregate(stream).await?;
|
||||||
|
|
||||||
// TODO: load from config
|
|
||||||
let prescribed = Validations {
|
let prescribed = Validations {
|
||||||
image: &media.image,
|
image: &media.image,
|
||||||
animation: &media.animation,
|
animation: &media.animation,
|
||||||
|
@ -71,8 +70,13 @@ where
|
||||||
let (_, magick_args) =
|
let (_, magick_args) =
|
||||||
crate::processor::build_chain(operations, format.file_extension())?;
|
crate::processor::build_chain(operations, format.file_extension())?;
|
||||||
|
|
||||||
let processed_reader =
|
let processed_reader = crate::magick::process_image_async_read(
|
||||||
crate::magick::process_image_async_read(validated_reader, magick_args, format)?;
|
validated_reader,
|
||||||
|
magick_args,
|
||||||
|
format,
|
||||||
|
format,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
Either::left(processed_reader)
|
Either::left(processed_reader)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -10,6 +10,9 @@ pub(crate) enum MagickError {
|
||||||
#[error("Error in imagemagick process")]
|
#[error("Error in imagemagick process")]
|
||||||
Process(#[source] ProcessError),
|
Process(#[source] ProcessError),
|
||||||
|
|
||||||
|
#[error("Error in store")]
|
||||||
|
Store(#[source] crate::store::StoreError),
|
||||||
|
|
||||||
#[error("Invalid output format")]
|
#[error("Invalid output format")]
|
||||||
Json(#[source] serde_json::Error),
|
Json(#[source] serde_json::Error),
|
||||||
|
|
||||||
|
@ -31,6 +34,12 @@ pub(crate) enum MagickError {
|
||||||
#[error("Error removing file")]
|
#[error("Error removing file")]
|
||||||
RemoveFile(#[source] std::io::Error),
|
RemoveFile(#[source] std::io::Error),
|
||||||
|
|
||||||
|
#[error("Error in metadata discovery")]
|
||||||
|
Discover(#[source] crate::discover::DiscoverError),
|
||||||
|
|
||||||
|
#[error("Command output is empty")]
|
||||||
|
Empty,
|
||||||
|
|
||||||
#[error("Invalid file path")]
|
#[error("Invalid file path")]
|
||||||
Path,
|
Path,
|
||||||
}
|
}
|
||||||
|
@ -42,13 +51,30 @@ impl MagickError {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn process_image(
|
async fn process_image<F, Fut>(
|
||||||
process_args: Vec<String>,
|
process_args: Vec<String>,
|
||||||
|
input_format: ProcessableFormat,
|
||||||
format: ProcessableFormat,
|
format: ProcessableFormat,
|
||||||
) -> Result<Process, ProcessError> {
|
write_file: F,
|
||||||
let command = "magick";
|
) -> Result<impl AsyncRead + Unpin, MagickError>
|
||||||
let convert_args = ["convert", "-"];
|
where
|
||||||
let last_arg = format!("{}:-", format.magick_format());
|
F: FnOnce(crate::file::File) -> Fut,
|
||||||
|
Fut: std::future::Future<Output = Result<crate::file::File, MagickError>>,
|
||||||
|
{
|
||||||
|
let input_file = crate::tmp_file::tmp_file(None);
|
||||||
|
let input_file_str = input_file.to_str().ok_or(MagickError::Path)?;
|
||||||
|
crate::store::file_store::safe_create_parent(&input_file)
|
||||||
|
.await
|
||||||
|
.map_err(MagickError::CreateDir)?;
|
||||||
|
|
||||||
|
let tmp_one = crate::file::File::create(&input_file)
|
||||||
|
.await
|
||||||
|
.map_err(MagickError::CreateFile)?;
|
||||||
|
let tmp_one = (write_file)(tmp_one).await?;
|
||||||
|
tmp_one.close().await.map_err(MagickError::CloseFile)?;
|
||||||
|
|
||||||
|
let input_arg = format!("{}:{input_file_str}", input_format.magick_format());
|
||||||
|
let output_arg = format!("{}:-", format.magick_format());
|
||||||
|
|
||||||
let len = if format.coalesce() {
|
let len = if format.coalesce() {
|
||||||
process_args.len() + 4
|
process_args.len() + 4
|
||||||
|
@ -56,34 +82,58 @@ fn process_image(
|
||||||
process_args.len() + 3
|
process_args.len() + 3
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut args = Vec::with_capacity(len);
|
let mut args: Vec<&str> = Vec::with_capacity(len);
|
||||||
args.extend_from_slice(&convert_args[..]);
|
args.push("convert");
|
||||||
|
args.push(&input_arg);
|
||||||
args.extend(process_args.iter().map(|s| s.as_str()));
|
args.extend(process_args.iter().map(|s| s.as_str()));
|
||||||
if format.coalesce() {
|
if format.coalesce() {
|
||||||
args.push("-coalesce");
|
args.push("-coalesce");
|
||||||
}
|
}
|
||||||
args.push(&last_arg);
|
args.push(&output_arg);
|
||||||
|
|
||||||
Process::run(command, &args)
|
let reader = Process::run("magick", &args)
|
||||||
|
.map_err(MagickError::Process)?
|
||||||
|
.read();
|
||||||
|
|
||||||
|
let clean_reader = crate::tmp_file::cleanup_tmpfile(reader, input_file);
|
||||||
|
|
||||||
|
Ok(Box::pin(clean_reader))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn process_image_store_read<S: Store + 'static>(
|
pub(crate) async fn process_image_store_read<S: Store + 'static>(
|
||||||
store: S,
|
store: &S,
|
||||||
identifier: S::Identifier,
|
identifier: &S::Identifier,
|
||||||
args: Vec<String>,
|
args: Vec<String>,
|
||||||
|
input_format: ProcessableFormat,
|
||||||
format: ProcessableFormat,
|
format: ProcessableFormat,
|
||||||
) -> Result<impl AsyncRead + Unpin, MagickError> {
|
) -> Result<impl AsyncRead + Unpin, MagickError> {
|
||||||
Ok(process_image(args, format)
|
let stream = store
|
||||||
.map_err(MagickError::Process)?
|
.to_stream(identifier, None, None)
|
||||||
.store_read(store, identifier))
|
.await
|
||||||
|
.map_err(MagickError::Store)?;
|
||||||
|
|
||||||
|
process_image(args, input_format, format, |mut tmp_file| async move {
|
||||||
|
tmp_file
|
||||||
|
.write_from_stream(stream)
|
||||||
|
.await
|
||||||
|
.map_err(MagickError::Write)?;
|
||||||
|
Ok(tmp_file)
|
||||||
|
})
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn process_image_async_read<A: AsyncRead + Unpin + 'static>(
|
pub(crate) async fn process_image_async_read<A: AsyncRead + Unpin + 'static>(
|
||||||
async_read: A,
|
async_read: A,
|
||||||
args: Vec<String>,
|
args: Vec<String>,
|
||||||
|
input_format: ProcessableFormat,
|
||||||
format: ProcessableFormat,
|
format: ProcessableFormat,
|
||||||
) -> Result<impl AsyncRead + Unpin, MagickError> {
|
) -> Result<impl AsyncRead + Unpin, MagickError> {
|
||||||
Ok(process_image(args, format)
|
process_image(args, input_format, format, |mut tmp_file| async move {
|
||||||
.map_err(MagickError::Process)?
|
tmp_file
|
||||||
.pipe_async_read(async_read))
|
.write_from_async_read(async_read)
|
||||||
|
.await
|
||||||
|
.map_err(MagickError::Write)?;
|
||||||
|
Ok(tmp_file)
|
||||||
|
})
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
use crate::store::Store;
|
|
||||||
use actix_rt::task::JoinHandle;
|
use actix_rt::task::JoinHandle;
|
||||||
use actix_web::web::Bytes;
|
use actix_web::web::Bytes;
|
||||||
use std::{
|
use std::{
|
||||||
|
@ -18,6 +17,7 @@ use tracing::{Instrument, Span};
|
||||||
struct StatusError(ExitStatus);
|
struct StatusError(ExitStatus);
|
||||||
|
|
||||||
pub(crate) struct Process {
|
pub(crate) struct Process {
|
||||||
|
command: String,
|
||||||
child: Child,
|
child: Child,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,8 +59,8 @@ pub(crate) enum ProcessError {
|
||||||
|
|
||||||
impl Process {
|
impl Process {
|
||||||
pub(crate) fn run(command: &str, args: &[&str]) -> Result<Self, ProcessError> {
|
pub(crate) fn run(command: &str, args: &[&str]) -> Result<Self, ProcessError> {
|
||||||
let res = tracing::trace_span!(parent: None, "Create command")
|
let res = tracing::trace_span!(parent: None, "Create command", %command)
|
||||||
.in_scope(|| Self::spawn(Command::new(command).args(args)));
|
.in_scope(|| Self::spawn(command, Command::new(command).args(args)));
|
||||||
|
|
||||||
match res {
|
match res {
|
||||||
Ok(this) => Ok(this),
|
Ok(this) => Ok(this),
|
||||||
|
@ -72,14 +72,17 @@ impl Process {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn spawn(cmd: &mut Command) -> std::io::Result<Self> {
|
fn spawn(command: &str, cmd: &mut Command) -> std::io::Result<Self> {
|
||||||
tracing::trace_span!(parent: None, "Spawn command").in_scope(|| {
|
tracing::trace_span!(parent: None, "Spawn command", %command).in_scope(|| {
|
||||||
let cmd = cmd
|
let cmd = cmd
|
||||||
.stdin(Stdio::piped())
|
.stdin(Stdio::piped())
|
||||||
.stdout(Stdio::piped())
|
.stdout(Stdio::piped())
|
||||||
.kill_on_drop(true);
|
.kill_on_drop(true);
|
||||||
|
|
||||||
cmd.spawn().map(|child| Process { child })
|
cmd.spawn().map(|child| Process {
|
||||||
|
child,
|
||||||
|
command: String::from(command),
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -105,30 +108,6 @@ impl Process {
|
||||||
self.spawn_fn(|_| async { Ok(()) })
|
self.spawn_fn(|_| async { Ok(()) })
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn pipe_async_read<A: AsyncRead + Unpin + 'static>(
|
|
||||||
self,
|
|
||||||
mut async_read: A,
|
|
||||||
) -> impl AsyncRead + Unpin {
|
|
||||||
self.spawn_fn(move |mut stdin| async move {
|
|
||||||
tokio::io::copy(&mut async_read, &mut stdin)
|
|
||||||
.await
|
|
||||||
.map(|_| ())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn store_read<S: Store + 'static>(
|
|
||||||
self,
|
|
||||||
store: S,
|
|
||||||
identifier: S::Identifier,
|
|
||||||
) -> impl AsyncRead + Unpin {
|
|
||||||
self.spawn_fn(move |mut stdin| {
|
|
||||||
let store = store;
|
|
||||||
let identifier = identifier;
|
|
||||||
|
|
||||||
async move { store.read_into(&identifier, &mut stdin).await }
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(unknown_lints)]
|
#[allow(unknown_lints)]
|
||||||
#[allow(clippy::let_with_type_underscore)]
|
#[allow(clippy::let_with_type_underscore)]
|
||||||
#[tracing::instrument(level = "trace", skip_all)]
|
#[tracing::instrument(level = "trace", skip_all)]
|
||||||
|
@ -140,14 +119,15 @@ impl Process {
|
||||||
let stdin = self.child.stdin.take().expect("stdin exists");
|
let stdin = self.child.stdin.take().expect("stdin exists");
|
||||||
let stdout = self.child.stdout.take().expect("stdout exists");
|
let stdout = self.child.stdout.take().expect("stdout exists");
|
||||||
|
|
||||||
let (tx, rx) = tracing::trace_span!(parent: None, "Create channel")
|
let (tx, rx) = tracing::trace_span!(parent: None, "Create channel", %self.command)
|
||||||
.in_scope(channel::<std::io::Error>);
|
.in_scope(channel::<std::io::Error>);
|
||||||
|
|
||||||
let span = tracing::info_span!(parent: None, "Background process task");
|
let span = tracing::info_span!(parent: None, "Background process task", %self.command);
|
||||||
span.follows_from(Span::current());
|
span.follows_from(Span::current());
|
||||||
|
|
||||||
let mut child = self.child;
|
let mut child = self.child;
|
||||||
let handle = tracing::trace_span!(parent: None, "Spawn task").in_scope(|| {
|
let command = self.command;
|
||||||
|
let handle = tracing::trace_span!(parent: None, "Spawn task", %command).in_scope(|| {
|
||||||
actix_rt::spawn(
|
actix_rt::spawn(
|
||||||
async move {
|
async move {
|
||||||
if let Err(e) = (f)(stdin).await {
|
if let Err(e) = (f)(stdin).await {
|
||||||
|
|
|
@ -28,6 +28,9 @@ pub(crate) enum ValidationError {
|
||||||
#[error("Too many frames")]
|
#[error("Too many frames")]
|
||||||
Frames,
|
Frames,
|
||||||
|
|
||||||
|
#[error("Uploaded file is empty")]
|
||||||
|
Empty,
|
||||||
|
|
||||||
#[error("Filesize too large")]
|
#[error("Filesize too large")]
|
||||||
Filesize,
|
Filesize,
|
||||||
|
|
||||||
|
@ -42,6 +45,10 @@ pub(crate) async fn validate_bytes(
|
||||||
bytes: Bytes,
|
bytes: Bytes,
|
||||||
validations: Validations<'_>,
|
validations: Validations<'_>,
|
||||||
) -> Result<(InternalFormat, impl AsyncRead + Unpin), Error> {
|
) -> Result<(InternalFormat, impl AsyncRead + Unpin), Error> {
|
||||||
|
if bytes.is_empty() {
|
||||||
|
return Err(ValidationError::Empty.into());
|
||||||
|
}
|
||||||
|
|
||||||
let Discovery {
|
let Discovery {
|
||||||
input,
|
input,
|
||||||
width,
|
width,
|
||||||
|
@ -112,7 +119,7 @@ async fn process_image(
|
||||||
} = input.build_output(validations.format);
|
} = input.build_output(validations.format);
|
||||||
|
|
||||||
let read = if needs_transcode {
|
let read = if needs_transcode {
|
||||||
Either::left(magick::convert_image(input.format, format, bytes)?)
|
Either::left(magick::convert_image(input.format, format, bytes).await?)
|
||||||
} else {
|
} else {
|
||||||
Either::right(exiftool::clear_metadata_bytes_read(bytes)?)
|
Either::right(exiftool::clear_metadata_bytes_read(bytes)?)
|
||||||
};
|
};
|
||||||
|
@ -163,7 +170,7 @@ async fn process_animation(
|
||||||
} = input.build_output(validations.animation.format);
|
} = input.build_output(validations.animation.format);
|
||||||
|
|
||||||
let read = if needs_transcode {
|
let read = if needs_transcode {
|
||||||
Either::left(magick::convert_animation(input, format, bytes)?)
|
Either::left(magick::convert_animation(input, format, bytes).await?)
|
||||||
} else {
|
} else {
|
||||||
Either::right(Either::left(exiftool::clear_metadata_bytes_read(bytes)?))
|
Either::right(Either::left(exiftool::clear_metadata_bytes_read(bytes)?))
|
||||||
};
|
};
|
||||||
|
@ -177,7 +184,9 @@ async fn process_animation(
|
||||||
validations.video.allow_audio,
|
validations.video.allow_audio,
|
||||||
);
|
);
|
||||||
|
|
||||||
let read = Either::right(Either::right(magick::convert_video(input, output, bytes)?));
|
let read = Either::right(Either::right(
|
||||||
|
magick::convert_video(input, output, bytes).await?,
|
||||||
|
));
|
||||||
|
|
||||||
Ok((InternalFormat::Video(output.internal_format()), read))
|
Ok((InternalFormat::Video(output.internal_format()), read))
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,47 +7,79 @@ use crate::{
|
||||||
process::Process,
|
process::Process,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub(super) fn convert_image(
|
pub(super) async fn convert_image(
|
||||||
input: ImageFormat,
|
input: ImageFormat,
|
||||||
output: ImageFormat,
|
output: ImageFormat,
|
||||||
bytes: Bytes,
|
bytes: Bytes,
|
||||||
) -> Result<impl AsyncRead + Unpin, MagickError> {
|
) -> Result<impl AsyncRead + Unpin, MagickError> {
|
||||||
let input_arg = format!("{}:-", input.magick_format());
|
convert(input.magick_format(), output.magick_format(), false, bytes).await
|
||||||
let output_arg = format!("{}:-", output.magick_format());
|
|
||||||
|
|
||||||
let process = Process::run(
|
|
||||||
"magick",
|
|
||||||
&["-strip", "-auto-orient", &input_arg, &output_arg],
|
|
||||||
)
|
|
||||||
.map_err(MagickError::Process)?;
|
|
||||||
|
|
||||||
Ok(process.bytes_read(bytes))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn convert_animation(
|
pub(super) async fn convert_animation(
|
||||||
input: AnimationFormat,
|
input: AnimationFormat,
|
||||||
output: AnimationFormat,
|
output: AnimationFormat,
|
||||||
bytes: Bytes,
|
bytes: Bytes,
|
||||||
) -> Result<impl AsyncRead + Unpin, MagickError> {
|
) -> Result<impl AsyncRead + Unpin, MagickError> {
|
||||||
let input_arg = format!("{}:-", input.magick_format());
|
convert(input.magick_format(), output.magick_format(), true, bytes).await
|
||||||
let output_arg = format!("{}:-", output.magick_format());
|
|
||||||
|
|
||||||
let process = Process::run("magick", &["-strip", &input_arg, "-coalesce", &output_arg])
|
|
||||||
.map_err(MagickError::Process)?;
|
|
||||||
|
|
||||||
Ok(process.bytes_read(bytes))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn convert_video(
|
pub(super) async fn convert_video(
|
||||||
input: AnimationFormat,
|
input: AnimationFormat,
|
||||||
output: OutputVideoFormat,
|
output: OutputVideoFormat,
|
||||||
bytes: Bytes,
|
bytes: Bytes,
|
||||||
) -> Result<impl AsyncRead + Unpin, MagickError> {
|
) -> Result<impl AsyncRead + Unpin, MagickError> {
|
||||||
let input_arg = format!("{}:-", input.magick_format());
|
convert(input.magick_format(), output.magick_format(), true, bytes).await
|
||||||
let output_arg = format!("{}:-", output.magick_format());
|
}
|
||||||
|
|
||||||
let process = Process::run("magick", &["-strip", &input_arg, "-coalesce", &output_arg])
|
async fn convert(
|
||||||
.map_err(MagickError::Process)?;
|
input: &'static str,
|
||||||
|
output: &'static str,
|
||||||
Ok(process.bytes_read(bytes))
|
coalesce: bool,
|
||||||
|
bytes: Bytes,
|
||||||
|
) -> Result<impl AsyncRead + Unpin, MagickError> {
|
||||||
|
let input_file = crate::tmp_file::tmp_file(None);
|
||||||
|
let input_file_str = input_file.to_str().ok_or(MagickError::Path)?;
|
||||||
|
|
||||||
|
crate::store::file_store::safe_create_parent(&input_file)
|
||||||
|
.await
|
||||||
|
.map_err(MagickError::CreateDir)?;
|
||||||
|
|
||||||
|
let mut tmp_one = crate::file::File::create(&input_file)
|
||||||
|
.await
|
||||||
|
.map_err(MagickError::CreateFile)?;
|
||||||
|
tmp_one
|
||||||
|
.write_from_bytes(bytes)
|
||||||
|
.await
|
||||||
|
.map_err(MagickError::Write)?;
|
||||||
|
tmp_one.close().await.map_err(MagickError::CloseFile)?;
|
||||||
|
|
||||||
|
let input_arg = format!("{input}:{input_file_str}");
|
||||||
|
let output_arg = format!("{output}:-");
|
||||||
|
|
||||||
|
let process = if coalesce {
|
||||||
|
Process::run(
|
||||||
|
"magick",
|
||||||
|
&[
|
||||||
|
"convert",
|
||||||
|
"-strip",
|
||||||
|
"-auto-orient",
|
||||||
|
&input_arg,
|
||||||
|
"-coalesce",
|
||||||
|
&output_arg,
|
||||||
|
],
|
||||||
|
)
|
||||||
|
.map_err(MagickError::Process)?
|
||||||
|
} else {
|
||||||
|
Process::run(
|
||||||
|
"magick",
|
||||||
|
&["convert", "-strip", "-auto-orient", &input_arg, &output_arg],
|
||||||
|
)
|
||||||
|
.map_err(MagickError::Process)?
|
||||||
|
};
|
||||||
|
|
||||||
|
let reader = process.read();
|
||||||
|
|
||||||
|
let clean_reader = crate::tmp_file::cleanup_tmpfile(reader, input_file);
|
||||||
|
|
||||||
|
Ok(Box::pin(clean_reader))
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue