//! TRACES: FR-EXP-8 //! What an export is allowed to say about where it came from. //! //! # An allowlist, not a filter //! //! [`SourceMetadata`] is the whole of what can reach a file this crate writes. //! It is populated field by field from whatever the caller decoded, and //! nothing else travels — not because each unwanted tag is removed, but //! because there is nowhere in this type for one to sit. That is the //! difference between "we strip GPS" and "GPS cannot be written unless //! [`SourceMetadata::location`] is `Some`", and only the second survives //! somebody adding a field to the decoder next year. //! //! # What is deliberately not here //! //! **The maker note** (EXIF `0x927C`). It is an opaque vendor blob with no //! public format, and its contents differ by body and firmware. Canon's //! carries the body serial number and the shutter count; several bodies put a //! *duplicate copy of the GPS fix* inside it, which is the specific reason it //! cannot be passed through as an unexamined byte range: an export that //! stripped the GPS directory and copied the maker note would have published //! the coordinates anyway, while reporting itself as private. Parsing it per //! vendor to decide what is safe is a research project with a permanent //! maintenance cost, and the value on the other side is a few tags a //! photographer rarely misses. So it is dropped, in both directions, whatever //! the settings say. //! //! **Serial numbers and owner name** (`BodySerialNumber` 0xA431, //! `LensSerialNumber` 0xA435, `CameraOwnerName` 0xA430). These identify a //! person and a specific piece of equipment, and a serial number in a //! published file links every photograph that person has ever posted. They //! have no field here, so no export writes them. //! //! **IPTC and XMP.** FR-EXP-8 names both. Neither is read by `dr-decode` //! today, so there is nothing to carry through; when there is, it arrives as //! fields on this type and is written from them, and the same allowlist //! reasoning applies unchanged. use dr_types::Location; /// TRACES: FR-EXP-8 /// The source metadata an export may carry. /// /// Every field is optional because every field is genuinely absent from some /// real file: scanner output has no aperture, a JPEG from a phone has no lens /// model, and most photographs have no copyright statement at all. /// /// Built by the caller, which is the only place that has both the decoded /// source and the crate that decoded it — `dr-export` deliberately depends on /// no decoder (see the crate docs), so the copy is made one field at a time /// where both types are in scope. That transcription is a feature: it is the /// point where somebody has to decide, in writing, that a newly-parsed piece /// of the source is allowed to leave the machine. #[derive(Debug, Clone, Default, PartialEq)] pub struct SourceMetadata { pub make: Option, pub model: Option, pub lens: Option, /// Exposure time in seconds. pub shutter: Option, /// The f-number, as in f/2.8. pub aperture: Option, pub iso: Option, /// Millimetres, as marked on the lens rather than 35 mm equivalent. pub focal_length: Option, /// When the shutter fired, as Unix seconds read as a wall clock. pub captured_at: Option, /// Minutes east of UTC, where the camera recorded a zone. pub captured_offset: Option, /// Who made the photograph. pub artist: Option, /// The rights statement. pub copyright: Option, /// TRACES: FR-EXP-8 /// Where the shutter fired. /// /// The one field the strip option is about. It is carried this far so that /// a photographer who *wants* their coordinates can have them; by the time /// the encoder sees the record this field has already been through /// [`Self::sanitised`], and is `None` unless the user turned stripping /// off. pub location: Option, } impl SourceMetadata { /// This record as the settings permit it to be written. /// /// **The single place stripping happens.** The encoders below take a /// record and write what is in it, with no view on privacy; concentrating /// the decision here means there is one function to read to know what an /// export can disclose, and no format can quietly disagree with the /// others — the failure mode where JPEG honours the setting and TIFF, five /// hundred lines away, does not. /// /// Stripping empties the field rather than blanking it. A `GPSLatitude` of /// `0/0` still announces that the camera had a fix and that this file has /// been through a scrubber; an absent directory says nothing at all, and /// says it in the same shape as the millions of files that never had one. pub(crate) fn sanitised(&self, strip_location: bool) -> Self { let mut out = self.clone(); if strip_location { out.location = None; } out } }