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.
92 lines
2.6 KiB
Rust
92 lines
2.6 KiB
Rust
use std::path::Path;
|
|
|
|
use crate::error::{PixstripError, Result};
|
|
|
|
use super::MetadataConfig;
|
|
|
|
pub fn strip_metadata(
|
|
input: &Path,
|
|
output: &Path,
|
|
config: &MetadataConfig,
|
|
) -> Result<()> {
|
|
match config {
|
|
MetadataConfig::KeepAll => {
|
|
std::fs::copy(input, output).map_err(PixstripError::Io)?;
|
|
}
|
|
MetadataConfig::StripAll => {
|
|
strip_all_exif(input, output)?;
|
|
}
|
|
MetadataConfig::Privacy => {
|
|
// Privacy mode strips GPS and camera info but keeps copyright.
|
|
// For now, we strip all EXIF as a safe default.
|
|
// Selective tag preservation requires full EXIF parsing.
|
|
strip_all_exif(input, output)?;
|
|
}
|
|
MetadataConfig::Custom { .. } => {
|
|
// Custom selective stripping - simplified to strip-all for now.
|
|
// Full selective stripping requires per-tag EXIF manipulation.
|
|
strip_all_exif(input, output)?;
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn strip_all_exif(input: &Path, output: &Path) -> Result<()> {
|
|
let data = std::fs::read(input).map_err(PixstripError::Io)?;
|
|
let cleaned = remove_exif_from_jpeg(&data);
|
|
std::fs::write(output, cleaned).map_err(PixstripError::Io)?;
|
|
Ok(())
|
|
}
|
|
|
|
fn remove_exif_from_jpeg(data: &[u8]) -> Vec<u8> {
|
|
// Only process JPEG files (starts with FF D8)
|
|
if data.len() < 2 || data[0] != 0xFF || data[1] != 0xD8 {
|
|
return data.to_vec();
|
|
}
|
|
|
|
let mut result = Vec::with_capacity(data.len());
|
|
result.push(0xFF);
|
|
result.push(0xD8);
|
|
|
|
let mut i = 2;
|
|
while i + 1 < data.len() {
|
|
if data[i] != 0xFF {
|
|
result.extend_from_slice(&data[i..]);
|
|
break;
|
|
}
|
|
|
|
let marker = data[i + 1];
|
|
|
|
// APP1 (0xE1) contains EXIF - skip it
|
|
if marker == 0xE1 && i + 3 < data.len() {
|
|
let len = ((data[i + 2] as usize) << 8) | (data[i + 3] as usize);
|
|
i += 2 + len;
|
|
continue;
|
|
}
|
|
|
|
// SOS (0xDA) - start of scan, copy everything from here
|
|
if marker == 0xDA {
|
|
result.extend_from_slice(&data[i..]);
|
|
break;
|
|
}
|
|
|
|
// Other markers - keep them
|
|
if i + 3 < data.len() {
|
|
let len = ((data[i + 2] as usize) << 8) | (data[i + 3] as usize);
|
|
let end = i + 2 + len;
|
|
if end <= data.len() {
|
|
result.extend_from_slice(&data[i..end]);
|
|
i = end;
|
|
} else {
|
|
result.extend_from_slice(&data[i..]);
|
|
break;
|
|
}
|
|
} else {
|
|
result.extend_from_slice(&data[i..]);
|
|
break;
|
|
}
|
|
}
|
|
|
|
result
|
|
}
|