Add metadata stripping, watermark positioning, and rename template modules
Metadata: JPEG EXIF stripping (APP1 segment removal).
Watermark: 9-position grid calculation with margin support.
Rename: template parser with {name}, {ext}, {counter:N}, {width}, {height}
and collision resolution with auto-suffix.
Phase 4 complete - 79 tests passing, zero clippy warnings.
This commit is contained in:
44
pixstrip-core/tests/metadata_tests.rs
Normal file
44
pixstrip-core/tests/metadata_tests.rs
Normal file
@@ -0,0 +1,44 @@
|
||||
use pixstrip_core::operations::MetadataConfig;
|
||||
use pixstrip_core::operations::metadata::strip_metadata;
|
||||
use std::path::Path;
|
||||
|
||||
fn create_test_jpeg(path: &Path) {
|
||||
let img = image::RgbImage::from_fn(100, 80, |x, y| {
|
||||
image::Rgb([(x % 256) as u8, (y % 256) as u8, 128])
|
||||
});
|
||||
img.save_with_format(path, image::ImageFormat::Jpeg).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn strip_all_metadata_produces_file() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let input = dir.path().join("test.jpg");
|
||||
let output = dir.path().join("stripped.jpg");
|
||||
create_test_jpeg(&input);
|
||||
|
||||
strip_metadata(&input, &output, &MetadataConfig::StripAll).unwrap();
|
||||
assert!(output.exists());
|
||||
assert!(std::fs::metadata(&output).unwrap().len() > 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keep_all_metadata_copies_file() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let input = dir.path().join("test.jpg");
|
||||
let output = dir.path().join("kept.jpg");
|
||||
create_test_jpeg(&input);
|
||||
|
||||
strip_metadata(&input, &output, &MetadataConfig::KeepAll).unwrap();
|
||||
assert!(output.exists());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn privacy_mode_strips_gps() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let input = dir.path().join("test.jpg");
|
||||
let output = dir.path().join("privacy.jpg");
|
||||
create_test_jpeg(&input);
|
||||
|
||||
strip_metadata(&input, &output, &MetadataConfig::Privacy).unwrap();
|
||||
assert!(output.exists());
|
||||
}
|
||||
95
pixstrip-core/tests/rename_tests.rs
Normal file
95
pixstrip-core/tests/rename_tests.rs
Normal file
@@ -0,0 +1,95 @@
|
||||
use pixstrip_core::operations::rename::{apply_template, resolve_collision};
|
||||
|
||||
#[test]
|
||||
fn template_basic_variables() {
|
||||
let result = apply_template(
|
||||
"{name}_{counter:3}.{ext}",
|
||||
"sunset",
|
||||
"jpg",
|
||||
1,
|
||||
None,
|
||||
);
|
||||
assert_eq!(result, "sunset_001.jpg");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn template_with_prefix() {
|
||||
let result = apply_template(
|
||||
"blog_{name}.{ext}",
|
||||
"photo",
|
||||
"webp",
|
||||
1,
|
||||
None,
|
||||
);
|
||||
assert_eq!(result, "blog_photo.webp");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn template_counter_padding() {
|
||||
let result = apply_template(
|
||||
"{name}_{counter:4}.{ext}",
|
||||
"img",
|
||||
"png",
|
||||
42,
|
||||
None,
|
||||
);
|
||||
assert_eq!(result, "img_0042.png");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn template_counter_no_padding() {
|
||||
let result = apply_template(
|
||||
"{name}_{counter}.{ext}",
|
||||
"img",
|
||||
"png",
|
||||
42,
|
||||
None,
|
||||
);
|
||||
assert_eq!(result, "img_42.png");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn template_width_height() {
|
||||
let result = apply_template(
|
||||
"{name}_{width}x{height}.{ext}",
|
||||
"photo",
|
||||
"jpg",
|
||||
1,
|
||||
Some((1920, 1080)),
|
||||
);
|
||||
assert_eq!(result, "photo_1920x1080.jpg");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn collision_adds_suffix() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let base = dir.path().join("photo.jpg");
|
||||
std::fs::write(&base, b"exists").unwrap();
|
||||
|
||||
let resolved = resolve_collision(&base);
|
||||
assert_eq!(
|
||||
resolved.file_name().unwrap().to_str().unwrap(),
|
||||
"photo_1.jpg"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn collision_increments() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
std::fs::write(dir.path().join("photo.jpg"), b"exists").unwrap();
|
||||
std::fs::write(dir.path().join("photo_1.jpg"), b"exists").unwrap();
|
||||
|
||||
let resolved = resolve_collision(&dir.path().join("photo.jpg"));
|
||||
assert_eq!(
|
||||
resolved.file_name().unwrap().to_str().unwrap(),
|
||||
"photo_2.jpg"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_collision_returns_same() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let path = dir.path().join("unique.jpg");
|
||||
let resolved = resolve_collision(&path);
|
||||
assert_eq!(resolved, path);
|
||||
}
|
||||
111
pixstrip-core/tests/watermark_tests.rs
Normal file
111
pixstrip-core/tests/watermark_tests.rs
Normal file
@@ -0,0 +1,111 @@
|
||||
use pixstrip_core::operations::watermark::calculate_position;
|
||||
use pixstrip_core::operations::WatermarkPosition;
|
||||
use pixstrip_core::types::Dimensions;
|
||||
|
||||
#[test]
|
||||
fn position_top_left() {
|
||||
let (x, y) = calculate_position(
|
||||
WatermarkPosition::TopLeft,
|
||||
Dimensions { width: 1920, height: 1080 },
|
||||
Dimensions { width: 200, height: 50 },
|
||||
10,
|
||||
);
|
||||
assert_eq!(x, 10);
|
||||
assert_eq!(y, 10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn position_center() {
|
||||
let (x, y) = calculate_position(
|
||||
WatermarkPosition::Center,
|
||||
Dimensions { width: 1920, height: 1080 },
|
||||
Dimensions { width: 200, height: 50 },
|
||||
10,
|
||||
);
|
||||
assert_eq!(x, 860); // (1920 - 200) / 2
|
||||
assert_eq!(y, 515); // (1080 - 50) / 2
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn position_bottom_right() {
|
||||
let (x, y) = calculate_position(
|
||||
WatermarkPosition::BottomRight,
|
||||
Dimensions { width: 1920, height: 1080 },
|
||||
Dimensions { width: 200, height: 50 },
|
||||
10,
|
||||
);
|
||||
assert_eq!(x, 1710); // 1920 - 200 - 10
|
||||
assert_eq!(y, 1020); // 1080 - 50 - 10
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn position_top_center() {
|
||||
let (x, y) = calculate_position(
|
||||
WatermarkPosition::TopCenter,
|
||||
Dimensions { width: 1920, height: 1080 },
|
||||
Dimensions { width: 200, height: 50 },
|
||||
10,
|
||||
);
|
||||
assert_eq!(x, 860); // (1920 - 200) / 2
|
||||
assert_eq!(y, 10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn position_bottom_center() {
|
||||
let (x, y) = calculate_position(
|
||||
WatermarkPosition::BottomCenter,
|
||||
Dimensions { width: 1920, height: 1080 },
|
||||
Dimensions { width: 200, height: 50 },
|
||||
10,
|
||||
);
|
||||
assert_eq!(x, 860);
|
||||
assert_eq!(y, 1020);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn position_middle_left() {
|
||||
let (x, y) = calculate_position(
|
||||
WatermarkPosition::MiddleLeft,
|
||||
Dimensions { width: 1920, height: 1080 },
|
||||
Dimensions { width: 200, height: 50 },
|
||||
10,
|
||||
);
|
||||
assert_eq!(x, 10);
|
||||
assert_eq!(y, 515);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn position_middle_right() {
|
||||
let (x, y) = calculate_position(
|
||||
WatermarkPosition::MiddleRight,
|
||||
Dimensions { width: 1920, height: 1080 },
|
||||
Dimensions { width: 200, height: 50 },
|
||||
10,
|
||||
);
|
||||
assert_eq!(x, 1710);
|
||||
assert_eq!(y, 515);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn position_top_right() {
|
||||
let (x, y) = calculate_position(
|
||||
WatermarkPosition::TopRight,
|
||||
Dimensions { width: 1920, height: 1080 },
|
||||
Dimensions { width: 200, height: 50 },
|
||||
10,
|
||||
);
|
||||
assert_eq!(x, 1710);
|
||||
assert_eq!(y, 10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn position_bottom_left() {
|
||||
let (x, y) = calculate_position(
|
||||
WatermarkPosition::BottomLeft,
|
||||
Dimensions { width: 1920, height: 1080 },
|
||||
Dimensions { width: 200, height: 50 },
|
||||
10,
|
||||
);
|
||||
assert_eq!(x, 10);
|
||||
assert_eq!(y, 1020);
|
||||
}
|
||||
Reference in New Issue
Block a user