feat: add GraphicsMagick and ImageMagick conversion plugins
Release / check-release (push) Successful in 18s
Release / build_gnu (push) Skipped
Release / build_musl (push) Skipped
Release / build_windows (push) Skipped
Release / package_gnu (deb) (push) Skipped
Release / package_gnu (rpm) (push) Skipped
Release / package_musl (deb) (push) Skipped
Release / package_musl (rpm) (push) Skipped
Release / publish-release (push) Skipped
CI / Test (push) Successful in 1m48s
CI / Build Linux (push) Successful in 2m1s
Release / check-release (push) Successful in 18s
Release / build_gnu (push) Skipped
Release / build_musl (push) Skipped
Release / build_windows (push) Skipped
Release / package_gnu (deb) (push) Skipped
Release / package_gnu (rpm) (push) Skipped
Release / package_musl (deb) (push) Skipped
Release / package_musl (rpm) (push) Skipped
Release / publish-release (push) Skipped
CI / Test (push) Successful in 1m48s
CI / Build Linux (push) Successful in 2m1s
This commit is contained in:
@@ -0,0 +1,160 @@
|
||||
// Copyright (C) 2026 Elias Wendland <eliaswendland@pm.me>
|
||||
//
|
||||
// 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
use crate::plugin::Plugin;
|
||||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
|
||||
pub struct PluginImpl;
|
||||
|
||||
impl Plugin for PluginImpl {
|
||||
fn name(&self) -> &'static str {
|
||||
"graphics_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 gm for graphics_magick plugin");
|
||||
let available = std::process::Command::new("gm")
|
||||
.arg("-version")
|
||||
.output()
|
||||
.is_ok();
|
||||
tracing::debug!("graphics_magick plugin available: {}", available);
|
||||
if !available {
|
||||
tracing::warn!(
|
||||
"graphics_magick plugin not available. Please install GraphicsMagick 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 {
|
||||
_ => 255,
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self, input, temp_dir))]
|
||||
fn convert(
|
||||
&self,
|
||||
input: &[u8],
|
||||
_from: &str,
|
||||
to: &str,
|
||||
temp_dir: &Path,
|
||||
) -> Result<Vec<u8>, String> {
|
||||
tracing::debug!("graphics_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![
|
||||
"convert",
|
||||
in_path.to_str().unwrap(),
|
||||
temp_out_path.to_str().unwrap(),
|
||||
];
|
||||
|
||||
tracing::debug!("Built graphics_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 GraphicsMagick...");
|
||||
let output = std::process::Command::new("gm")
|
||||
.args(raw_args)
|
||||
.output()
|
||||
.map_err(|e| {
|
||||
tracing::error!("GraphicsMagick execution failed: {}", e);
|
||||
e.to_string()
|
||||
})?;
|
||||
|
||||
if !output.status.success() {
|
||||
let err_msg = String::from_utf8_lossy(&output.stderr);
|
||||
tracing::error!("GraphicsMagick failed with status {}. Stderr: {}", output.status, err_msg);
|
||||
return Err(format!("GraphicsMagick error: {}", err_msg));
|
||||
}
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
tracing::debug!("GraphicsMagick 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()
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
// Copyright (C) 2026 Elias Wendland <eliaswendland@pm.me>
|
||||
//
|
||||
// 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
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<Vec<u8>, 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()
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user