Add ProcessingJob type with source management and output path resolution
All 6 pipeline tests passing.
This commit is contained in:
@@ -1 +1,83 @@
|
|||||||
// Processing pipeline
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::operations::*;
|
||||||
|
use crate::types::{ImageFormat, ImageSource};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct ProcessingJob {
|
||||||
|
pub input_dir: PathBuf,
|
||||||
|
pub output_dir: PathBuf,
|
||||||
|
#[serde(skip)]
|
||||||
|
pub sources: Vec<ImageSource>,
|
||||||
|
pub resize: Option<ResizeConfig>,
|
||||||
|
pub rotation: Option<Rotation>,
|
||||||
|
pub flip: Option<Flip>,
|
||||||
|
pub convert: Option<ConvertConfig>,
|
||||||
|
pub compress: Option<CompressConfig>,
|
||||||
|
pub metadata: Option<MetadataConfig>,
|
||||||
|
pub watermark: Option<WatermarkConfig>,
|
||||||
|
pub rename: Option<RenameConfig>,
|
||||||
|
pub preserve_directory_structure: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ProcessingJob {
|
||||||
|
pub fn new(input_dir: impl AsRef<Path>, output_dir: impl AsRef<Path>) -> Self {
|
||||||
|
Self {
|
||||||
|
input_dir: input_dir.as_ref().to_path_buf(),
|
||||||
|
output_dir: output_dir.as_ref().to_path_buf(),
|
||||||
|
sources: Vec::new(),
|
||||||
|
resize: None,
|
||||||
|
rotation: None,
|
||||||
|
flip: None,
|
||||||
|
convert: None,
|
||||||
|
compress: None,
|
||||||
|
metadata: None,
|
||||||
|
watermark: None,
|
||||||
|
rename: None,
|
||||||
|
preserve_directory_structure: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_source(&mut self, path: impl AsRef<Path>) {
|
||||||
|
self.sources.push(ImageSource::from_path(path));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn operation_count(&self) -> usize {
|
||||||
|
let mut count = 0;
|
||||||
|
if self.resize.is_some() { count += 1; }
|
||||||
|
if self.rotation.is_some() { count += 1; }
|
||||||
|
if self.flip.is_some() { count += 1; }
|
||||||
|
if self.convert.is_some() { count += 1; }
|
||||||
|
if self.compress.is_some() { count += 1; }
|
||||||
|
if self.metadata.is_some() { count += 1; }
|
||||||
|
if self.watermark.is_some() { count += 1; }
|
||||||
|
if self.rename.is_some() { count += 1; }
|
||||||
|
count
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn output_path_for(
|
||||||
|
&self,
|
||||||
|
source: &ImageSource,
|
||||||
|
output_format: Option<ImageFormat>,
|
||||||
|
) -> PathBuf {
|
||||||
|
let stem = source
|
||||||
|
.path
|
||||||
|
.file_stem()
|
||||||
|
.and_then(|s| s.to_str())
|
||||||
|
.unwrap_or("output");
|
||||||
|
|
||||||
|
let ext = output_format
|
||||||
|
.map(|f| f.extension())
|
||||||
|
.or_else(|| {
|
||||||
|
source
|
||||||
|
.path
|
||||||
|
.extension()
|
||||||
|
.and_then(|e| e.to_str())
|
||||||
|
})
|
||||||
|
.unwrap_or("bin");
|
||||||
|
|
||||||
|
self.output_dir.join(format!("{}.{}", stem, ext))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
61
pixstrip-core/tests/pipeline_tests.rs
Normal file
61
pixstrip-core/tests/pipeline_tests.rs
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
use pixstrip_core::pipeline::*;
|
||||||
|
use pixstrip_core::operations::*;
|
||||||
|
use pixstrip_core::types::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn processing_job_default_has_no_operations() {
|
||||||
|
let job = ProcessingJob::new("/tmp/input/", "/tmp/output/");
|
||||||
|
assert!(job.resize.is_none());
|
||||||
|
assert!(job.convert.is_none());
|
||||||
|
assert!(job.compress.is_none());
|
||||||
|
assert!(job.metadata.is_none());
|
||||||
|
assert!(job.watermark.is_none());
|
||||||
|
assert!(job.rename.is_none());
|
||||||
|
assert!(job.sources.is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn processing_job_add_sources() {
|
||||||
|
let mut job = ProcessingJob::new("/tmp/input/", "/tmp/output/");
|
||||||
|
job.add_source("/tmp/input/photo.jpg");
|
||||||
|
job.add_source("/tmp/input/image.png");
|
||||||
|
assert_eq!(job.sources.len(), 2);
|
||||||
|
assert_eq!(job.sources[0].original_format, Some(ImageFormat::Jpeg));
|
||||||
|
assert_eq!(job.sources[1].original_format, Some(ImageFormat::Png));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn processing_job_with_resize() {
|
||||||
|
let mut job = ProcessingJob::new("/tmp/input/", "/tmp/output/");
|
||||||
|
job.resize = Some(ResizeConfig::ByWidth(1200));
|
||||||
|
assert!(job.resize.is_some());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn processing_job_operation_count() {
|
||||||
|
let mut job = ProcessingJob::new("/tmp/input/", "/tmp/output/");
|
||||||
|
assert_eq!(job.operation_count(), 0);
|
||||||
|
job.resize = Some(ResizeConfig::ByWidth(1200));
|
||||||
|
assert_eq!(job.operation_count(), 1);
|
||||||
|
job.compress = Some(CompressConfig::Preset(QualityPreset::High));
|
||||||
|
assert_eq!(job.operation_count(), 2);
|
||||||
|
job.metadata = Some(MetadataConfig::StripAll);
|
||||||
|
assert_eq!(job.operation_count(), 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn processing_job_output_path() {
|
||||||
|
let job = ProcessingJob::new("/tmp/input/", "/tmp/output/");
|
||||||
|
let source = ImageSource::from_path("/tmp/input/photo.jpg");
|
||||||
|
let output = job.output_path_for(&source, None);
|
||||||
|
assert_eq!(output.to_str().unwrap(), "/tmp/output/photo.jpg");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn processing_job_output_path_with_format_change() {
|
||||||
|
let mut job = ProcessingJob::new("/tmp/input/", "/tmp/output/");
|
||||||
|
job.convert = Some(ConvertConfig::SingleFormat(ImageFormat::WebP));
|
||||||
|
let source = ImageSource::from_path("/tmp/input/photo.jpg");
|
||||||
|
let output = job.output_path_for(&source, Some(ImageFormat::WebP));
|
||||||
|
assert_eq!(output.to_str().unwrap(), "/tmp/output/photo.webp");
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user