Let the copyright survive the export, and the GPS not
Carries the source's metadata all the way to the file the user hands over,
and proves in bytes that the coordinates do not come with it.
The privacy test was the piece that mattered and the piece that was wrong.
It searched the whole file for the two-byte hemisphere reference "N\0" or
"E\0", which is not a fingerprint of a GPS directory at all: sample 14 of the
sRGB tone curve inside the ICC profile every export embeds is 69, written as
`00 45`, and the next sample is below 256, so its high byte is `00`. Every
format would have failed a test about a colour profile. The needle is now the
twenty-four bytes a coordinate actually serialises to — three rationals, both
byte orders, since exif.rs writes little-endian and the tiff crate writes in
the host's — which cannot match by accident, and the retaining test asserts
the same needle is *present* so a search that could never find anything
cannot make the stripping test pass by being useless.
The batch exporter now hands the decoder's reading on to the encoder. It
already read the metadata for the orientation and the {date} token; passing
it through is what puts the camera, the lens and the rights statement into
the file. Nothing about privacy is decided there — dr-export takes that
decision once, from the settings.
The example passes it too, because it is the only place in the tree that
produces files a person can open in exiftool. A unit test can prove a GPS
directory is absent from a byte slice; only a real export proves a real
photograph comes out the far end still knowing which camera took it.
TRACES: FR-EXP-8
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -10,7 +10,7 @@
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
use dr_export::{export, Frame, NameContext};
|
||||
use dr_export::{export, Frame, NameContext, SourceMetadata};
|
||||
use dr_gpu::{AdjustPass, DemosaicedImage, Demosaicer, GpuContext};
|
||||
use dr_pipeline::EditGraph;
|
||||
use dr_types::{ColourSpace, ExportFormat, ExportSettings, OutputSharpening, SizingMode};
|
||||
@@ -96,6 +96,32 @@ fn main() {
|
||||
.map(|s| s.to_string_lossy().into_owned())
|
||||
.unwrap_or_else(|| "export".into());
|
||||
|
||||
// TRACES: FR-EXP-8
|
||||
// What the input said about itself, transcribed field by field into the
|
||||
// allowlist `dr-export` will write from. The example passes it because
|
||||
// this is the one place in the tree that produces files a person can open
|
||||
// in exiftool — a unit test can prove a GPS directory is absent from a
|
||||
// byte slice, but only a real export proves that a real photograph comes
|
||||
// out of the far end still knowing which camera took it.
|
||||
//
|
||||
// The defaults apply, so the files written here carry the camera, the
|
||||
// lens, the exposure and the rights statement, and carry no coordinates.
|
||||
let meta = dr_decode::metadata(&bytes).unwrap_or_default();
|
||||
let source_metadata = SourceMetadata {
|
||||
make: meta.make.clone(),
|
||||
model: meta.model.clone(),
|
||||
lens: meta.lens.clone(),
|
||||
shutter: meta.shutter,
|
||||
aperture: meta.aperture,
|
||||
iso: meta.iso,
|
||||
focal_length: meta.focal_length,
|
||||
captured_at: meta.captured_at,
|
||||
captured_offset: meta.captured_offset,
|
||||
artist: meta.artist.clone(),
|
||||
copyright: meta.copyright.clone(),
|
||||
location: meta.location,
|
||||
};
|
||||
|
||||
// One of each format, so the run exercises every encoder that exists.
|
||||
for (format, sizing, sharpening) in [
|
||||
(
|
||||
@@ -155,7 +181,7 @@ fn main() {
|
||||
.expect("a free name");
|
||||
|
||||
let t = std::time::Instant::now();
|
||||
let out = export(&frame, &settings, name).expect("export");
|
||||
let out = export(&frame, &settings, name, Some(&source_metadata)).expect("export");
|
||||
let path = out_dir.join(&out.name);
|
||||
std::fs::write(&path, &out.bytes).expect("write");
|
||||
println!(
|
||||
|
||||
Reference in New Issue
Block a user