#[derive(Debug, thiserror::Error)] pub(crate) enum MagickError { #[error("{0}")] IO(#[from] std::io::Error), #[error("Magick command failed")] Status, #[error("Magick semaphore is closed")] Closed, #[error("Invalid format")] Format, } pub(crate) enum ValidInputType { Mp4, Gif, Png, Jpeg, Webp, } static MAX_CONVERSIONS: once_cell::sync::OnceCell = once_cell::sync::OnceCell::new(); fn semaphore() -> &'static tokio::sync::Semaphore { MAX_CONVERSIONS .get_or_init(|| tokio::sync::Semaphore::new(num_cpus::get().saturating_sub(1).max(1))) } pub(crate) async fn convert_file( from: P1, to: P2, format: crate::config::Format, ) -> Result<(), MagickError> where P1: AsRef, P2: AsRef, { let mut output_file = std::ffi::OsString::new(); output_file.extend([format.to_magick_format().as_ref(), ":".as_ref()]); output_file.extend([to.as_ref().as_ref()]); let permit = semaphore().acquire().await?; let status = tokio::process::Command::new("magick") .arg("convert") .arg(&from.as_ref()) .arg(&output_file) .spawn()? .wait() .await?; drop(permit); if !status.success() { return Err(MagickError::Status); } Ok(()) } pub(crate) async fn input_type

(file: &P) -> Result where P: AsRef, { let permit = semaphore().acquire().await?; let output = tokio::process::Command::new("magick") .args([&"identify", &"-ping", &"-format", &"%m\n"]) .arg(&file.as_ref()) .output() .await?; drop(permit); let s = String::from_utf8_lossy(&output.stdout); let mut lines = s.lines(); let first = lines.next(); let opt = lines.fold(first, |acc, item| match acc { Some(prev) if prev == item => Some(prev), _ => None, }); match opt { Some("MP4") => Ok(ValidInputType::Mp4), Some("GIF") => Ok(ValidInputType::Gif), Some("PNG") => Ok(ValidInputType::Png), Some("JPEG") => Ok(ValidInputType::Jpeg), Some("WEBP") => Ok(ValidInputType::Webp), _ => Err(MagickError::Format), } } pub(crate) async fn process_image( input: P1, output: P2, args: Vec, format: crate::config::Format, ) -> Result<(), MagickError> where P1: AsRef, P2: AsRef, { let mut output_file = std::ffi::OsString::new(); output_file.extend([format!("{}:", format.to_magick_format()).as_ref()]); output_file.extend([output.as_ref().as_ref()]); let permit = semaphore().acquire().await?; let status = tokio::process::Command::new("magick") .arg(&"convert") .arg(&input.as_ref()) .args(args) .arg(&output.as_ref()) .spawn()? .wait() .await?; drop(permit); if !status.success() { return Err(MagickError::Status); } Ok(()) } impl From for MagickError { fn from(_: tokio::sync::AcquireError) -> MagickError { MagickError::Closed } }