diff --git a/src/discover/exiftool.rs b/src/discover/exiftool.rs index 672ce10..96184cd 100644 --- a/src/discover/exiftool.rs +++ b/src/discover/exiftool.rs @@ -42,7 +42,7 @@ pub(super) async fn check_reorient( #[tracing::instrument(level = "trace", skip(input))] async fn needs_reorienting(input: Bytes, timeout: u64) -> Result { - let process = Process::run("exiftool", &["-n", "-Orientation", "-"], timeout)?; + let process = Process::run("exiftool", &["-n", "-Orientation", "-"], &[], timeout)?; let mut reader = process.bytes_read(input); let mut buf = String::new(); diff --git a/src/discover/ffmpeg.rs b/src/discover/ffmpeg.rs index a1cfbda..b5c8b99 100644 --- a/src/discover/ffmpeg.rs +++ b/src/discover/ffmpeg.rs @@ -201,7 +201,6 @@ where Fut: std::future::Future>, { let input_file = tmp_dir.tmp_file(None); - let input_file_str = input_file.to_str().ok_or(FfMpegError::Path)?; crate::store::file_store::safe_create_parent(&input_file) .await .map_err(FfMpegError::CreateDir)?; @@ -215,17 +214,18 @@ where let process = Process::run( "ffprobe", &[ - "-v", - "quiet", - "-count_frames", - "-show_entries", - "stream=width,height,nb_read_frames,codec_name,pix_fmt:format=format_name", - "-of", - "default=noprint_wrappers=1:nokey=1", - "-print_format", - "json", - input_file_str, + "-v".as_ref(), + "quiet".as_ref(), + "-count_frames".as_ref(), + "-show_entries".as_ref(), + "stream=width,height,nb_read_frames,codec_name,pix_fmt:format=format_name".as_ref(), + "-of".as_ref(), + "default=noprint_wrappers=1:nokey=1".as_ref(), + "-print_format".as_ref(), + "json".as_ref(), + input_file.as_os_str(), ], + &[], timeout, )?; @@ -235,9 +235,8 @@ where .read_to_end(&mut output) .await .map_err(FfMpegError::Read)?; - tokio::fs::remove_file(input_file_str) - .await - .map_err(FfMpegError::RemoveFile)?; + + drop(input_file); let output: FfMpegDiscovery = serde_json::from_slice(&output).map_err(FfMpegError::Json)?; @@ -273,6 +272,7 @@ async fn alpha_pixel_formats(timeout: u64) -> Result, FfMpegErro "-print_format", "json", ], + &[], timeout, )?; diff --git a/src/discover/magick.rs b/src/discover/magick.rs index 7adbe06..46ba507 100644 --- a/src/discover/magick.rs +++ b/src/discover/magick.rs @@ -7,7 +7,7 @@ use tokio::io::AsyncReadExt; use crate::{ discover::DiscoverError, formats::{AnimationFormat, ImageFormat, ImageInput, InputFile}, - magick::MagickError, + magick::{MagickError, MAGICK_TEMPORARY_PATH}, process::Process, tmp_file::TmpDir, }; @@ -99,8 +99,12 @@ where F: FnOnce(crate::file::File) -> Fut, Fut: std::future::Future>, { + let temporary_path = tmp_dir + .tmp_folder() + .await + .map_err(MagickError::CreateTemporaryDirectory)?; + let input_file = tmp_dir.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)?; @@ -111,9 +115,17 @@ where let tmp_one = (f)(tmp_one).await?; tmp_one.close().await.map_err(MagickError::CloseFile)?; + let envs = [(MAGICK_TEMPORARY_PATH, temporary_path.as_os_str())]; + let process = Process::run( "magick", - &["convert", "-ping", input_file_str, "INFO:"], + &[ + "convert".as_ref(), + "-ping".as_ref(), + input_file.as_os_str(), + "INFO:".as_ref(), + ], + &envs, timeout, )?; @@ -123,9 +135,9 @@ where .read_to_string(&mut output) .await .map_err(MagickError::Read)?; - tokio::fs::remove_file(input_file_str) - .await - .map_err(MagickError::RemoveFile)?; + + drop(input_file); + drop(temporary_path); if output.is_empty() { return Err(MagickError::Empty); @@ -154,8 +166,12 @@ where F: FnOnce(crate::file::File) -> Fut, Fut: std::future::Future>, { + let temporary_path = tmp_dir + .tmp_folder() + .await + .map_err(MagickError::CreateTemporaryDirectory)?; + let input_file = tmp_dir.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)?; @@ -166,9 +182,17 @@ where let tmp_one = (f)(tmp_one).await?; tmp_one.close().await.map_err(MagickError::CloseFile)?; + let envs = [(MAGICK_TEMPORARY_PATH, temporary_path.as_os_str())]; + let process = Process::run( "magick", - &["convert", "-ping", input_file_str, "JSON:"], + &[ + "convert".as_ref(), + "-ping".as_ref(), + input_file.as_os_str(), + "JSON:".as_ref(), + ], + &envs, timeout, )?; @@ -178,9 +202,8 @@ where .read_to_end(&mut output) .await .map_err(MagickError::Read)?; - tokio::fs::remove_file(input_file_str) - .await - .map_err(MagickError::RemoveFile)?; + + drop(input_file); if output.is_empty() { return Err(MagickError::Empty); diff --git a/src/exiftool.rs b/src/exiftool.rs index 2000ff3..68f2789 100644 --- a/src/exiftool.rs +++ b/src/exiftool.rs @@ -45,7 +45,7 @@ impl ExifError { #[tracing::instrument(level = "trace", skip(input))] pub(crate) async fn needs_reorienting(timeout: u64, input: Bytes) -> Result { - let process = Process::run("exiftool", &["-n", "-Orientation", "-"], timeout)?; + let process = Process::run("exiftool", &["-n", "-Orientation", "-"], &[], timeout)?; let mut reader = process.bytes_read(input); let mut buf = String::new(); @@ -62,7 +62,7 @@ pub(crate) fn clear_metadata_bytes_read( timeout: u64, input: Bytes, ) -> Result { - let process = Process::run("exiftool", &["-all=", "-", "-out", "-"], timeout)?; + let process = Process::run("exiftool", &["-all=", "-", "-out", "-"], &[], timeout)?; Ok(process.bytes_read(input)) } diff --git a/src/ffmpeg.rs b/src/ffmpeg.rs index 6d5bdc9..330ba28 100644 --- a/src/ffmpeg.rs +++ b/src/ffmpeg.rs @@ -29,17 +29,11 @@ pub(crate) enum FfMpegError { #[error("Error closing file")] CloseFile(#[source] std::io::Error), - #[error("Error removing file")] - RemoveFile(#[source] std::io::Error), - #[error("Error in store")] Store(#[source] StoreError), #[error("Invalid media file provided")] CommandFailed(ProcessError), - - #[error("Invalid file path")] - Path, } impl From for FfMpegError { @@ -64,9 +58,7 @@ impl FfMpegError { | Self::ReadFile(_) | Self::OpenFile(_) | Self::CreateFile(_) - | Self::CloseFile(_) - | Self::RemoveFile(_) - | Self::Path => ErrorCode::COMMAND_ERROR, + | Self::CloseFile(_) => ErrorCode::COMMAND_ERROR, } } diff --git a/src/generate/ffmpeg.rs b/src/generate/ffmpeg.rs index 4115f9f..082b987 100644 --- a/src/generate/ffmpeg.rs +++ b/src/generate/ffmpeg.rs @@ -55,13 +55,11 @@ pub(super) async fn thumbnail( timeout: u64, ) -> Result, FfMpegError> { let input_file = tmp_dir.tmp_file(Some(input_format.file_extension())); - let input_file_str = input_file.to_str().ok_or(FfMpegError::Path)?; crate::store::file_store::safe_create_parent(&input_file) .await .map_err(FfMpegError::CreateDir)?; let output_file = tmp_dir.tmp_file(Some(format.to_file_extension())); - let output_file_str = output_file.to_str().ok_or(FfMpegError::Path)?; crate::store::file_store::safe_create_parent(&output_file) .await .map_err(FfMpegError::CreateDir)?; @@ -82,26 +80,25 @@ pub(super) async fn thumbnail( let process = Process::run( "ffmpeg", &[ - "-hide_banner", - "-v", - "warning", - "-i", - input_file_str, - "-frames:v", - "1", - "-codec", - format.as_ffmpeg_codec(), - "-f", - format.as_ffmpeg_format(), - output_file_str, + "-hide_banner".as_ref(), + "-v".as_ref(), + "warning".as_ref(), + "-i".as_ref(), + input_file.as_os_str(), + "-frames:v".as_ref(), + "1".as_ref(), + "-codec".as_ref(), + format.as_ffmpeg_codec().as_ref(), + "-f".as_ref(), + format.as_ffmpeg_format().as_ref(), + output_file.as_os_str(), ], + &[], timeout, )?; process.wait().await?; - tokio::fs::remove_file(input_file) - .await - .map_err(FfMpegError::RemoveFile)?; + drop(input_file); let tmp_two = crate::file::File::open(&output_file) .await @@ -111,7 +108,7 @@ pub(super) async fn thumbnail( .await .map_err(FfMpegError::ReadFile)?; let reader = tokio_util::io::StreamReader::new(stream); - let clean_reader = crate::tmp_file::cleanup_tmpfile(reader, output_file); + let clean_reader = output_file.reader(reader); Ok(Box::pin(clean_reader)) } diff --git a/src/generate/magick.rs b/src/generate/magick.rs index bb89183..e0a3236 100644 --- a/src/generate/magick.rs +++ b/src/generate/magick.rs @@ -1,7 +1,11 @@ -use std::sync::Arc; +use std::{ffi::OsStr, sync::Arc}; use crate::{ - formats::ProcessableFormat, magick::MagickError, process::Process, read::BoxRead, store::Store, + formats::ProcessableFormat, + magick::{MagickError, MAGICK_TEMPORARY_PATH}, + process::Process, + read::BoxRead, + store::Store, tmp_file::TmpDir, }; @@ -17,8 +21,12 @@ where F: FnOnce(crate::file::File) -> Fut, Fut: std::future::Future>, { + let temporary_path = tmp_dir + .tmp_folder() + .await + .map_err(MagickError::CreateTemporaryDirectory)?; + let input_file = tmp_dir.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)?; @@ -29,26 +37,33 @@ where let tmp_one = (write_file)(tmp_one).await?; tmp_one.close().await.map_err(MagickError::CloseFile)?; - let input_arg = format!("{}:{input_file_str}[0]", input_format.magick_format()); + let input_arg = [ + input_format.magick_format().as_ref(), + input_file.as_os_str(), + ] + .join(":".as_ref()); let output_arg = format!("{}:-", format.magick_format()); let quality = quality.map(|q| q.to_string()); let len = 3 + if format.coalesce() { 1 } else { 0 } + if quality.is_some() { 1 } else { 0 }; - let mut args: Vec<&str> = Vec::with_capacity(len); - args.push("convert"); + let mut args: Vec<&OsStr> = Vec::with_capacity(len); + args.push("convert".as_ref()); args.push(&input_arg); if format.coalesce() { - args.push("-coalesce"); + args.push("-coalesce".as_ref()); } if let Some(quality) = &quality { - args.extend(["-quality", quality]); + args.extend(["-quality".as_ref(), quality.as_ref()] as [&OsStr; 2]); } - args.push(&output_arg); + args.push(output_arg.as_ref()); - let reader = Process::run("magick", &args, timeout)?.read(); + let envs = [(MAGICK_TEMPORARY_PATH, temporary_path.as_os_str())]; - let clean_reader = crate::tmp_file::cleanup_tmpfile(reader, input_file); + let reader = Process::run("magick", &args, &envs, timeout)?.read(); + + let clean_reader = input_file.reader(reader); + let clean_reader = temporary_path.reader(clean_reader); Ok(Box::pin(clean_reader)) } diff --git a/src/magick.rs b/src/magick.rs index 79ed481..bb58315 100644 --- a/src/magick.rs +++ b/src/magick.rs @@ -1,4 +1,4 @@ -use std::sync::Arc; +use std::{ffi::OsStr, sync::Arc}; use crate::{ error_code::ErrorCode, @@ -11,6 +11,8 @@ use crate::{ use tokio::io::AsyncRead; +pub(crate) const MAGICK_TEMPORARY_PATH: &str = "MAGICK_TEMPORARY_PATH"; + #[derive(Debug, thiserror::Error)] pub(crate) enum MagickError { #[error("Error in imagemagick process")] @@ -34,12 +36,12 @@ pub(crate) enum MagickError { #[error("Error creating directory")] CreateDir(#[source] crate::store::file_store::FileError), + #[error("Error creating temporary directory")] + CreateTemporaryDirectory(#[source] std::io::Error), + #[error("Error closing file")] CloseFile(#[source] std::io::Error), - #[error("Error removing file")] - RemoveFile(#[source] std::io::Error), - #[error("Error in metadata discovery")] Discover(#[source] crate::discover::DiscoverError), @@ -48,9 +50,6 @@ pub(crate) enum MagickError { #[error("Command output is empty")] Empty, - - #[error("Invalid file path")] - Path, } impl From for MagickError { @@ -73,11 +72,10 @@ impl MagickError { | Self::Write(_) | Self::CreateFile(_) | Self::CreateDir(_) + | Self::CreateTemporaryDirectory(_) | Self::CloseFile(_) - | Self::RemoveFile(_) | Self::Discover(_) - | Self::Empty - | Self::Path => ErrorCode::COMMAND_ERROR, + | Self::Empty => ErrorCode::COMMAND_ERROR, } } @@ -103,8 +101,12 @@ where F: FnOnce(crate::file::File) -> Fut, Fut: std::future::Future>, { + let temporary_path = tmp_dir + .tmp_folder() + .await + .map_err(MagickError::CreateTemporaryDirectory)?; + let input_file = tmp_dir.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)?; @@ -115,7 +117,11 @@ where 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 input_arg = [ + input_format.magick_format().as_ref(), + input_file.as_os_str(), + ] + .join(":".as_ref()); let output_arg = format!("{}:-", format.magick_format()); let quality = quality.map(|q| q.to_string()); @@ -124,21 +130,24 @@ where + if quality.is_some() { 1 } else { 0 } + process_args.len(); - let mut args: Vec<&str> = Vec::with_capacity(len); - args.push("convert"); + let mut args: Vec<&OsStr> = Vec::with_capacity(len); + args.push("convert".as_ref()); args.push(&input_arg); if input_format.coalesce() { - args.push("-coalesce"); + args.push("-coalesce".as_ref()); } - args.extend(process_args.iter().map(|s| s.as_str())); + args.extend(process_args.iter().map(|s| AsRef::::as_ref(s))); if let Some(quality) = &quality { - args.extend(["-quality", quality]); + args.extend(["-quality".as_ref(), quality.as_ref()] as [&OsStr; 2]); } - args.push(&output_arg); + args.push(output_arg.as_ref()); - let reader = Process::run("magick", &args, timeout)?.read(); + let envs = [(MAGICK_TEMPORARY_PATH, temporary_path.as_os_str())]; - let clean_reader = crate::tmp_file::cleanup_tmpfile(reader, input_file); + let reader = Process::run("magick", &args, &envs, timeout)?.read(); + + let clean_reader = input_file.reader(reader); + let clean_reader = temporary_path.reader(clean_reader); Ok(Box::pin(clean_reader)) } diff --git a/src/process.rs b/src/process.rs index e79aaa2..138a3a8 100644 --- a/src/process.rs +++ b/src/process.rs @@ -1,6 +1,7 @@ use actix_web::web::Bytes; use flume::r#async::RecvFut; use std::{ + ffi::OsStr, future::Future, pin::Pin, process::{ExitStatus, Stdio}, @@ -115,9 +116,24 @@ impl ProcessError { } impl Process { - pub(crate) fn run(command: &str, args: &[&str], timeout: u64) -> Result { - let res = tracing::trace_span!(parent: None, "Create command", %command) - .in_scope(|| Self::spawn(command, Command::new(command).args(args), timeout)); + pub(crate) fn run( + command: &str, + args: &[T], + envs: &[(&str, &OsStr)], + timeout: u64, + ) -> Result + where + T: AsRef, + { + let res = tracing::trace_span!(parent: None, "Create command", %command).in_scope(|| { + Self::spawn( + command, + Command::new(command) + .args(args) + .envs(envs.into_iter().copied()), + timeout, + ) + }); match res { Ok(this) => Ok(this), diff --git a/src/tmp_file.rs b/src/tmp_file.rs index 793165c..c1d208f 100644 --- a/src/tmp_file.rs +++ b/src/tmp_file.rs @@ -1,4 +1,8 @@ -use std::{path::PathBuf, sync::Arc}; +use std::{ + ops::Deref, + path::{Path, PathBuf}, + sync::Arc, +}; use tokio::io::AsyncRead; use uuid::Uuid; @@ -16,20 +20,33 @@ impl TmpDir { Ok(Arc::new(TmpDir { path: Some(path) })) } - pub(crate) fn tmp_file(&self, ext: Option<&str>) -> PathBuf { + fn build_tmp_file(&self, ext: Option<&str>) -> Arc { if let Some(ext) = ext { - self.path - .as_ref() - .expect("tmp path exists") - .join(format!("{}{}", Uuid::new_v4(), ext)) + Arc::from(self.path.as_ref().expect("tmp path exists").join(format!( + "{}{}", + Uuid::new_v4(), + ext + ))) } else { - self.path - .as_ref() - .expect("tmp path exists") - .join(Uuid::new_v4().to_string()) + Arc::from( + self.path + .as_ref() + .expect("tmp path exists") + .join(Uuid::new_v4().to_string()), + ) } } + pub(crate) fn tmp_file(&self, ext: Option<&str>) -> TmpFile { + TmpFile(self.build_tmp_file(ext)) + } + + pub(crate) async fn tmp_folder(&self) -> std::io::Result { + let path = self.build_tmp_file(None); + tokio::fs::create_dir(&path).await?; + Ok(TmpFolder(path)) + } + pub(crate) async fn cleanup(self: Arc) -> std::io::Result<()> { if let Some(path) = Arc::into_inner(self).and_then(|mut this| this.path.take()) { tokio::fs::remove_dir_all(path).await?; @@ -47,7 +64,66 @@ impl Drop for TmpDir { } } -struct TmpFile(PathBuf); +#[must_use] +pub(crate) struct TmpFolder(Arc); + +impl TmpFolder { + pub(crate) fn reader(self, reader: R) -> TmpFolderCleanup { + TmpFolderCleanup { + inner: reader, + folder: self, + } + } +} + +impl AsRef for TmpFolder { + fn as_ref(&self) -> &Path { + &self.0 + } +} + +impl Deref for TmpFolder { + type Target = Path; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl Drop for TmpFolder { + fn drop(&mut self) { + crate::sync::spawn( + "remove-tmpfolder", + tokio::fs::remove_dir_all(self.0.clone()), + ); + } +} + +#[must_use] +pub(crate) struct TmpFile(Arc); + +impl TmpFile { + pub(crate) fn reader(self, reader: R) -> TmpFileCleanup { + TmpFileCleanup { + inner: reader, + file: self, + } + } +} + +impl AsRef for TmpFile { + fn as_ref(&self) -> &Path { + &self.0 + } +} + +impl Deref for TmpFile { + type Target = Path; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} impl Drop for TmpFile { fn drop(&mut self) { @@ -64,10 +140,12 @@ pin_project_lite::pin_project! { } } -pub(crate) fn cleanup_tmpfile(reader: R, file: PathBuf) -> TmpFileCleanup { - TmpFileCleanup { - inner: reader, - file: TmpFile(file), +pin_project_lite::pin_project! { + pub(crate) struct TmpFolderCleanup { + #[pin] + inner: R, + + folder: TmpFolder, } } @@ -82,3 +160,15 @@ impl AsyncRead for TmpFileCleanup { this.inner.poll_read(cx, buf) } } + +impl AsyncRead for TmpFolderCleanup { + fn poll_read( + mut self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + buf: &mut tokio::io::ReadBuf<'_>, + ) -> std::task::Poll> { + let this = self.as_mut().project(); + + this.inner.poll_read(cx, buf) + } +} diff --git a/src/validate/exiftool.rs b/src/validate/exiftool.rs index ef5be40..eae08f9 100644 --- a/src/validate/exiftool.rs +++ b/src/validate/exiftool.rs @@ -7,7 +7,7 @@ pub(crate) fn clear_metadata_bytes_read( input: Bytes, timeout: u64, ) -> Result, ExifError> { - let process = Process::run("exiftool", &["-all=", "-", "-out", "-"], timeout)?; + let process = Process::run("exiftool", &["-all=", "-", "-out", "-"], &[], timeout)?; Ok(Box::pin(process.bytes_read(input))) } diff --git a/src/validate/ffmpeg.rs b/src/validate/ffmpeg.rs index da1bd9a..b093aa1 100644 --- a/src/validate/ffmpeg.rs +++ b/src/validate/ffmpeg.rs @@ -1,3 +1,5 @@ +use std::ffi::OsStr; + use actix_web::web::Bytes; use crate::{ @@ -17,7 +19,6 @@ pub(super) async fn transcode_bytes( bytes: Bytes, ) -> Result, FfMpegError> { let input_file = tmp_dir.tmp_file(None); - let input_file_str = input_file.to_str().ok_or(FfMpegError::Path)?; crate::store::file_store::safe_create_parent(&input_file) .await .map_err(FfMpegError::CreateDir)?; @@ -32,12 +33,11 @@ pub(super) async fn transcode_bytes( tmp_one.close().await.map_err(FfMpegError::CloseFile)?; let output_file = tmp_dir.tmp_file(None); - let output_file_str = output_file.to_str().ok_or(FfMpegError::Path)?; transcode_files( - input_file_str, + input_file.as_os_str(), input_format, - output_file_str, + output_file.as_os_str(), output_format, crf, timeout, @@ -52,15 +52,15 @@ pub(super) async fn transcode_bytes( .await .map_err(FfMpegError::ReadFile)?; let reader = tokio_util::io::StreamReader::new(stream); - let clean_reader = crate::tmp_file::cleanup_tmpfile(reader, output_file); + let clean_reader = output_file.reader(reader); Ok(Box::pin(clean_reader)) } async fn transcode_files( - input_path: &str, + input_path: &OsStr, input_format: InputVideoFormat, - output_path: &str, + output_path: &OsStr, output_format: OutputVideo, crf: u8, timeout: u64, @@ -74,47 +74,51 @@ async fn transcode_files( } = output_format; let mut args = vec![ - "-hide_banner", - "-v", - "warning", - "-f", - input_format.ffmpeg_format(), - "-i", + "-hide_banner".as_ref(), + "-v".as_ref(), + "warning".as_ref(), + "-f".as_ref(), + input_format.ffmpeg_format().as_ref(), + "-i".as_ref(), input_path, ]; if transcode_video { args.extend([ - "-pix_fmt", - output_format.pix_fmt(), - "-vf", - "scale=trunc(iw/2)*2:trunc(ih/2)*2", - "-c:v", - output_format.ffmpeg_video_codec(), - "-crf", - &crf, - ]); + "-pix_fmt".as_ref(), + output_format.pix_fmt().as_ref(), + "-vf".as_ref(), + "scale=trunc(iw/2)*2:trunc(ih/2)*2".as_ref(), + "-c:v".as_ref(), + output_format.ffmpeg_video_codec().as_ref(), + "-crf".as_ref(), + &crf.as_ref(), + ] as [&OsStr; 8]); if output_format.is_vp9() { - args.extend(["-b:v", "0"]); + args.extend(["-b:v".as_ref(), "0".as_ref()] as [&OsStr; 2]); } } else { - args.extend(["-c:v", "copy"]); + args.extend(["-c:v".as_ref(), "copy".as_ref()] as [&OsStr; 2]); } if transcode_audio { if let Some(audio_codec) = output_format.ffmpeg_audio_codec() { - args.extend(["-c:a", audio_codec]); + args.extend(["-c:a".as_ref(), audio_codec.as_ref()] as [&OsStr; 2]); } else { - args.push("-an") + args.push("-an".as_ref()) } } else { - args.extend(["-c:a", "copy"]); + args.extend(["-c:a".as_ref(), "copy".as_ref()] as [&OsStr; 2]); } - args.extend(["-f", output_format.ffmpeg_format(), output_path]); + args.extend([ + "-f".as_ref(), + output_format.ffmpeg_format().as_ref(), + output_path, + ]); - Process::run("ffmpeg", &args, timeout)?.wait().await?; + Process::run("ffmpeg", &args, &[], timeout)?.wait().await?; Ok(()) } diff --git a/src/validate/magick.rs b/src/validate/magick.rs index b087674..249fc6d 100644 --- a/src/validate/magick.rs +++ b/src/validate/magick.rs @@ -1,8 +1,10 @@ +use std::ffi::OsStr; + use actix_web::web::Bytes; use crate::{ formats::{AnimationFormat, ImageFormat}, - magick::MagickError, + magick::{MagickError, MAGICK_TEMPORARY_PATH}, process::Process, read::BoxRead, tmp_file::TmpDir, @@ -57,8 +59,12 @@ async fn convert( timeout: u64, bytes: Bytes, ) -> Result, MagickError> { + let temporary_path = tmp_dir + .tmp_folder() + .await + .map_err(MagickError::CreateTemporaryDirectory)?; + let input_file = tmp_dir.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 @@ -73,27 +79,30 @@ async fn convert( .map_err(MagickError::Write)?; tmp_one.close().await.map_err(MagickError::CloseFile)?; - let input_arg = format!("{input}:{input_file_str}"); + let input_arg = [input.as_ref(), input_file.as_os_str()].join(":".as_ref()); let output_arg = format!("{output}:-"); let quality = quality.map(|q| q.to_string()); - let mut args = vec!["convert"]; + let mut args: Vec<&OsStr> = vec!["convert".as_ref()]; if coalesce { - args.push("-coalesce"); + args.push("-coalesce".as_ref()); } - args.extend(["-strip", "-auto-orient", &input_arg]); + args.extend(["-strip".as_ref(), "-auto-orient".as_ref(), &input_arg] as [&OsStr; 3]); if let Some(quality) = &quality { - args.extend(["-quality", quality]); + args.extend(["-quality".as_ref(), quality.as_ref()] as [&OsStr; 2]); } - args.push(&output_arg); + args.push(output_arg.as_ref()); - let reader = Process::run("magick", &args, timeout)?.read(); + let envs = [(MAGICK_TEMPORARY_PATH, temporary_path.as_os_str())]; - let clean_reader = crate::tmp_file::cleanup_tmpfile(reader, input_file); + let reader = Process::run("magick", &args, &envs, timeout)?.read(); + + let clean_reader = input_file.reader(reader); + let clean_reader = temporary_path.reader(clean_reader); Ok(Box::pin(clean_reader)) }