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:
64
pixstrip-core/src/operations/rename.rs
Normal file
64
pixstrip-core/src/operations/rename.rs
Normal file
@@ -0,0 +1,64 @@
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
pub fn apply_template(
|
||||
template: &str,
|
||||
name: &str,
|
||||
ext: &str,
|
||||
counter: u32,
|
||||
dimensions: Option<(u32, u32)>,
|
||||
) -> String {
|
||||
let mut result = template.to_string();
|
||||
|
||||
result = result.replace("{name}", name);
|
||||
result = result.replace("{ext}", ext);
|
||||
|
||||
// Handle {counter:N} with padding
|
||||
if let Some(start) = result.find("{counter:") {
|
||||
if let Some(end) = result[start..].find('}') {
|
||||
let spec = &result[start + 9..start + end];
|
||||
if let Ok(padding) = spec.parse::<usize>() {
|
||||
let counter_str = format!("{:0>width$}", counter, width = padding);
|
||||
result = format!("{}{}{}", &result[..start], counter_str, &result[start + end + 1..]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
result = result.replace("{counter}", &counter.to_string());
|
||||
}
|
||||
|
||||
if let Some((w, h)) = dimensions {
|
||||
result = result.replace("{width}", &w.to_string());
|
||||
result = result.replace("{height}", &h.to_string());
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
pub fn resolve_collision(path: &Path) -> PathBuf {
|
||||
if !path.exists() {
|
||||
return path.to_path_buf();
|
||||
}
|
||||
|
||||
let parent = path.parent().unwrap_or(Path::new("."));
|
||||
let stem = path
|
||||
.file_stem()
|
||||
.and_then(|s| s.to_str())
|
||||
.unwrap_or("file");
|
||||
let ext = path
|
||||
.extension()
|
||||
.and_then(|e| e.to_str())
|
||||
.unwrap_or("");
|
||||
|
||||
for i in 1..1000 {
|
||||
let candidate = if ext.is_empty() {
|
||||
parent.join(format!("{}_{}", stem, i))
|
||||
} else {
|
||||
parent.join(format!("{}_{}.{}", stem, i, ext))
|
||||
};
|
||||
if !candidate.exists() {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback - should never happen with 1000 attempts
|
||||
parent.join(format!("{}_{}.{}", stem, "overflow", ext))
|
||||
}
|
||||
Reference in New Issue
Block a user