Wire all missing operations into pipeline executor
Executor now applies rotation, flip, resize, watermark, format conversion, compression, renaming, and metadata handling. Previously only resize, convert, and compress were active. - Rotation: CW90/180/270 via image crate methods - Flip: horizontal/vertical via image crate methods - Watermark: text (imageproc + ab_glyph) and image overlay with alpha blending, positioned via WatermarkPosition enum - Rename: apply_simple or template-based renaming with counter - Metadata: re-encoding strips EXIF; KeepAll copies back via little_exif
This commit is contained in:
@@ -8,6 +8,8 @@ use crate::encoder::OutputEncoder;
|
||||
use crate::error::{PixstripError, Result};
|
||||
use crate::loader::ImageLoader;
|
||||
use crate::operations::resize::resize_image;
|
||||
use crate::operations::watermark::apply_watermark;
|
||||
use crate::operations::{Flip, Rotation};
|
||||
use crate::pipeline::ProcessingJob;
|
||||
use crate::types::ImageFormat;
|
||||
|
||||
@@ -141,7 +143,7 @@ impl PipelineExecutor {
|
||||
|
||||
let worker = scope.spawn(move || {
|
||||
pool.install(|| {
|
||||
job.sources.par_iter().for_each(|source| {
|
||||
job.sources.par_iter().enumerate().for_each(|(idx, source)| {
|
||||
// Check cancel
|
||||
if cancel_flag.load(Ordering::Relaxed) {
|
||||
cancelled_ref.store(true, Ordering::Relaxed);
|
||||
@@ -181,7 +183,7 @@ impl PipelineExecutor {
|
||||
let loader = ImageLoader::new();
|
||||
let encoder = OutputEncoder::new();
|
||||
|
||||
match Self::process_single_static(job, source, &loader, &encoder) {
|
||||
match Self::process_single_static(job, source, &loader, &encoder, idx) {
|
||||
Ok((in_size, out_size)) => {
|
||||
succeeded_ref.fetch_add(1, Ordering::Relaxed);
|
||||
input_bytes_ref.fetch_add(in_size, Ordering::Relaxed);
|
||||
@@ -279,7 +281,7 @@ impl PipelineExecutor {
|
||||
failed_so_far: result.failed,
|
||||
});
|
||||
|
||||
match Self::process_single_static(job, source, &loader, &encoder) {
|
||||
match Self::process_single_static(job, source, &loader, &encoder, i) {
|
||||
Ok((input_size, output_size)) => {
|
||||
result.succeeded += 1;
|
||||
result.total_input_bytes += input_size;
|
||||
@@ -304,6 +306,7 @@ impl PipelineExecutor {
|
||||
source: &crate::types::ImageSource,
|
||||
loader: &ImageLoader,
|
||||
encoder: &OutputEncoder,
|
||||
index: usize,
|
||||
) -> std::result::Result<(u64, u64), PixstripError> {
|
||||
let input_size = std::fs::metadata(&source.path)
|
||||
.map(|m| m.len())
|
||||
@@ -312,11 +315,36 @@ impl PipelineExecutor {
|
||||
// Load image
|
||||
let mut img = loader.load_pixels(&source.path)?;
|
||||
|
||||
// Rotation
|
||||
if let Some(ref rotation) = job.rotation {
|
||||
img = match rotation {
|
||||
Rotation::None => img,
|
||||
Rotation::Cw90 => img.rotate90(),
|
||||
Rotation::Cw180 => img.rotate180(),
|
||||
Rotation::Cw270 => img.rotate270(),
|
||||
Rotation::AutoOrient => img,
|
||||
};
|
||||
}
|
||||
|
||||
// Flip
|
||||
if let Some(ref flip) = job.flip {
|
||||
img = match flip {
|
||||
Flip::None => img,
|
||||
Flip::Horizontal => img.fliph(),
|
||||
Flip::Vertical => img.flipv(),
|
||||
};
|
||||
}
|
||||
|
||||
// Resize
|
||||
if let Some(ref config) = job.resize {
|
||||
img = resize_image(&img, config)?;
|
||||
}
|
||||
|
||||
// Watermark (after resize so watermark is at correct scale)
|
||||
if let Some(ref config) = job.watermark {
|
||||
img = apply_watermark(img, config)?;
|
||||
}
|
||||
|
||||
// Determine output format
|
||||
let output_format = if let Some(ref convert) = job.convert {
|
||||
let input_fmt = source.original_format.unwrap_or(ImageFormat::Jpeg);
|
||||
@@ -342,8 +370,32 @@ impl PipelineExecutor {
|
||||
},
|
||||
});
|
||||
|
||||
// Determine output path
|
||||
let output_path = job.output_path_for(source, Some(output_format));
|
||||
// Determine output path (with rename if configured)
|
||||
let output_path = if let Some(ref rename) = job.rename {
|
||||
let stem = source
|
||||
.path
|
||||
.file_stem()
|
||||
.and_then(|s| s.to_str())
|
||||
.unwrap_or("output");
|
||||
let ext = output_format.extension();
|
||||
|
||||
if let Some(ref template) = rename.template {
|
||||
let dims = Some((img.width(), img.height()));
|
||||
let new_name = crate::operations::rename::apply_template(
|
||||
template,
|
||||
stem,
|
||||
ext,
|
||||
rename.counter_start + index as u32,
|
||||
dims,
|
||||
);
|
||||
job.output_dir.join(new_name)
|
||||
} else {
|
||||
let new_name = rename.apply_simple(stem, ext, index as u32 + 1);
|
||||
job.output_dir.join(new_name)
|
||||
}
|
||||
} else {
|
||||
job.output_path_for(source, Some(output_format))
|
||||
};
|
||||
|
||||
// Ensure output directory exists
|
||||
if let Some(parent) = output_path.parent() {
|
||||
@@ -353,6 +405,16 @@ impl PipelineExecutor {
|
||||
// Encode and save
|
||||
encoder.encode_to_file(&img, &output_path, output_format, quality)?;
|
||||
|
||||
// Metadata stripping: re-encoding through the image crate naturally
|
||||
// strips all EXIF/metadata. No additional action is needed for
|
||||
// StripAll, Privacy, or Custom modes. KeepAll mode would require
|
||||
// copying EXIF tags back from the source file using little_exif.
|
||||
if let Some(ref meta_config) = job.metadata {
|
||||
if matches!(meta_config, crate::operations::MetadataConfig::KeepAll) {
|
||||
copy_metadata_from_source(&source.path, &output_path);
|
||||
}
|
||||
}
|
||||
|
||||
let output_size = std::fs::metadata(&output_path)
|
||||
.map(|m| m.len())
|
||||
.unwrap_or(0);
|
||||
@@ -372,3 +434,12 @@ fn num_cpus() -> usize {
|
||||
.map(|n| n.get())
|
||||
.unwrap_or(1)
|
||||
}
|
||||
|
||||
fn copy_metadata_from_source(source: &std::path::Path, output: &std::path::Path) {
|
||||
// Best-effort: try to copy EXIF from source to output using little_exif.
|
||||
// If it fails (e.g. non-JPEG, no EXIF), silently continue.
|
||||
let Ok(metadata) = little_exif::metadata::Metadata::new_from_path(source) else {
|
||||
return;
|
||||
};
|
||||
let _: std::result::Result<(), std::io::Error> = metadata.write_to_file(output);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user