// Copyright (C) 2026 Elias Wendland // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, version 3 exclusively. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . use crate::plugin::Plugin; use std::io::Write; use std::path::Path; pub struct PluginImpl; impl Plugin for PluginImpl { fn name(&self) -> &'static str { "magick" } fn from_formats(&self) -> Vec<&'static str> { vec![ "art", "avif", "bmp", "cmyk", "dpx", "eps", "fits", "gif", "gray", "heic", "ico", "j2k", "jp2", "jpeg", "jpg", "jxl", "mat", "miff", "mono", "pam", "pbm", "pcx", "pdf", "pgm", "pict", "png", "pnm", "ppm", "ps", "rgb", "rgba", "sgi", "sun", "svg", "tga", "tiff", "viff", "webp", "wmf", "xbm", "xcf", "xpm", "xwd", ] } fn to_formats(&self) -> Vec<&'static str> { vec![ "art", "avif", "bmp", "cmyk", "dpx", "eps", "fits", "gif", "gray", "heic", "ico", "j2k", "jp2", "jpeg", "jpg", "jxl", "mat", "miff", "mono", "pam", "pbm", "pcx", "pdf", "pgm", "pict", "png", "pnm", "ppm", "ps", "rgb", "rgba", "sgi", "sun", "svg", "tga", "tiff", "viff", "webp", "wmf", "xbm", "xcf", "xpm", "xwd", ] } #[tracing::instrument(skip(self))] fn is_available(&self) -> bool { tracing::trace!("Checking availability of magick for magick plugin"); let available = std::process::Command::new("magick") .arg("-version") .output() .is_ok(); tracing::debug!("magick plugin available: {}", available); if !available { tracing::warn!("magick plugin not available. Please install ImageMagick to use it."); } available } fn familiarity(&self, _from: &str, to: &str) -> u8 { match to { "png" | "jpeg" | "jpg" | "gif" => 255, "bmp" | "tiff" | "ico" | "tga" | "webp" => 240, "pdf" | "ps" | "eps" | "svg" => 200, "avif" | "heic" | "jxl" | "jp2" | "j2k" => 180, "pnm" | "ppm" | "pgm" | "pbm" | "pam" | "pcx" | "pict" | "dpx" | "miff" | "fits" | "xcf" => 150, _ => 128, } } fn quality(&self, _from: &str, to: &str) -> u8 { match to { "png" | "bmp" | "tiff" | "ico" | "tga" | "pnm" | "ppm" | "pgm" | "pbm" | "pam" => 250, "jpeg" | "jpg" | "webp" | "avif" | "heic" | "jxl" | "jp2" | "j2k" => 200, "gif" => 150, _ => 200, } } fn speed(&self, _from: &str, to: &str) -> u8 { match to { _ => 128, } } #[tracing::instrument(skip(self, input, temp_dir))] fn convert( &self, input: &[u8], _from: &str, to: &str, temp_dir: &Path, ) -> Result, String> { tracing::debug!("magick starting conversion: {} -> {}", _from, to); let mut temp_in = tempfile::Builder::new() .suffix(&format!(".{}", _from)) .tempfile_in(temp_dir) .map_err(|e| { tracing::error!("Failed to create temp input file: {}", e); e.to_string() })?; temp_in.write_all(input).map_err(|e| { tracing::error!("Failed to write to temp input file: {}", e); e.to_string() })?; let temp_out = tempfile::Builder::new() .suffix(&format!(".{}", to)) .tempfile_in(temp_dir) .map_err(|e| { tracing::error!("Failed to create temp output file: {}", e); e.to_string() })?; let temp_out_path = temp_out.into_temp_path(); let in_path = temp_in.path().to_path_buf(); tracing::trace!( "Temp files created. In: {:?}, Out: {:?}", in_path, temp_out_path ); let raw_args = vec![ in_path.to_str().unwrap(), temp_out_path.to_str().unwrap(), ]; tracing::debug!("Built magick arguments: {:?}", raw_args); let rt = tokio::runtime::Runtime::new().map_err(|e| { tracing::error!("Failed to create Tokio runtime: {}", e); e.to_string() })?; rt.block_on(async { tracing::trace!("Executing ImageMagick..."); let output = std::process::Command::new("magick") .args(raw_args) .output() .map_err(|e| { tracing::error!("ImageMagick execution failed: {}", e); e.to_string() })?; if !output.status.success() { let err_msg = String::from_utf8_lossy(&output.stderr); tracing::error!("ImageMagick failed with status {}. Stderr: {}", output.status, err_msg); return Err(format!("ImageMagick error: {}", err_msg)); } Ok(()) })?; tracing::debug!("ImageMagick execution succeeded. Reading output file..."); std::fs::read(&temp_out_path).map_err(|e| { tracing::error!("Failed to read output file {:?}: {}", temp_out_path, e); e.to_string() }) } }