Skip to main content

flashraw_core/
metadata.rs

1//! Normalised camera/capture metadata and embedded preview descriptors.
2
3use crate::orientation::OrientationMetadata;
4use crate::types::{Compression, Orientation, RawFormat};
5
6/// Lens information.
7#[derive(Debug, Clone, Default, PartialEq)]
8#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
9#[non_exhaustive]
10pub struct LensInfo {
11    /// Lens manufacturer, when recorded.
12    pub make: Option<String>,
13    /// Lens model name, when recorded.
14    pub model: Option<String>,
15    /// Lens serial number, when recorded.
16    pub serial: Option<String>,
17    /// Min/max focal length in mm.
18    pub focal_range: Option<(f32, f32)>,
19    /// Max aperture (f-number) at min/max focal length.
20    pub aperture_range: Option<(f32, f32)>,
21}
22
23/// Exposure / capture parameters.
24#[derive(Debug, Clone, Default, PartialEq)]
25#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
26#[non_exhaustive]
27pub struct ExposureInfo {
28    /// ISO speed.
29    pub iso: Option<u32>,
30    /// Exposure time in seconds.
31    pub exposure_time: Option<f64>,
32    /// Aperture as an f-number (e.g. `2.8`).
33    pub f_number: Option<f32>,
34    /// Focal length in mm.
35    pub focal_length: Option<f32>,
36    /// 35 mm-equivalent focal length in mm (EXIF FocalLengthIn35mmFilm).
37    pub focal_length_35mm: Option<u32>,
38    /// Exposure compensation in EV.
39    pub exposure_bias: Option<f32>,
40    /// EXIF `Flash` tag value (bit field; bit 0 set when the flash fired).
41    pub flash: Option<u16>,
42    /// EXIF `MeteringMode` tag value.
43    pub metering_mode: Option<u16>,
44    /// EXIF `ExposureProgram` tag value.
45    pub exposure_program: Option<u16>,
46}
47
48/// GPS position.
49#[derive(Debug, Clone, Default, PartialEq)]
50#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
51#[non_exhaustive]
52pub struct GpsInfo {
53    /// Decimal degrees, negative for south.
54    pub latitude: Option<f64>,
55    /// Decimal degrees, negative for west.
56    pub longitude: Option<f64>,
57    /// Metres; negative below sea level.
58    pub altitude: Option<f64>,
59    /// GPS date and time in UTC, `YYYY-MM-DDTHH:MM:SSZ`.
60    pub timestamp: Option<String>,
61}
62
63/// Encoding of an embedded preview image.
64#[derive(Debug, Clone, Copy, PartialEq, Eq)]
65#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
66#[non_exhaustive]
67pub enum PreviewEncoding {
68    /// Baseline JPEG.
69    Jpeg,
70    /// Uncompressed 8-bit RGB, `width * height * 3` bytes.
71    Rgb8,
72    /// Uncompressed 8-bit YCbCr 4:2:2 (rare; some old Kodak/Sony files).
73    Ycbcr422,
74    /// JPEG XL (DNG 1.7 previews).
75    JpegXl,
76    /// HEIF/HEVC (e.g. CR3 HEIF previews, iPhone); exposed as bytes only.
77    Heif,
78}
79
80/// An embedded preview / thumbnail stored in the RAW container.
81#[derive(Debug, Clone, PartialEq)]
82#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
83#[non_exhaustive]
84pub struct PreviewInfo {
85    /// Byte offset of the preview data in the file.
86    pub offset: u64,
87    /// Length of the preview data in bytes.
88    pub length: u64,
89    /// Width in pixels as stored (before orientation); 0 if unknown.
90    pub width: u32,
91    /// Height in pixels as stored (before orientation); 0 if unknown.
92    pub height: u32,
93    /// How the preview bytes are encoded.
94    pub encoding: PreviewEncoding,
95    /// Orientation to apply to the preview's stored pixels: usually the file orientation.
96    /// Where vendor orientation records are resolved (Canon CR2, Nikon, Kodak) it is
97    /// resolved per preview: a preview carrying its own EXIF orientation uses it, and a
98    /// preview that was rendered already rotated is `Normal` even when the RAW image is not.
99    pub orientation: Orientation,
100    /// What the container labels this image as.
101    pub kind: PreviewKind,
102}
103
104impl PreviewInfo {
105    /// A preview of `length` bytes at `offset` in the file.
106    pub fn new(
107        offset: u64,
108        length: u64,
109        width: u32,
110        height: u32,
111        encoding: PreviewEncoding,
112        orientation: Orientation,
113        kind: PreviewKind,
114    ) -> Self {
115        PreviewInfo { offset, length, width, height, encoding, orientation, kind }
116    }
117}
118
119/// What the container labels a preview as.
120#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
121#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
122#[non_exhaustive]
123pub enum PreviewKind {
124    /// Small thumbnail.
125    Thumbnail,
126    /// Reduced-size preview.
127    Preview,
128    /// Full-resolution rendering of the image.
129    FullSize,
130}
131
132/// DNG camera-profile tags an editor needs to know about (presence and sizes; the tables
133/// themselves are not decoded).
134#[derive(Debug, Clone, Default, PartialEq)]
135#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
136#[non_exhaustive]
137pub struct DngProfileInfo {
138    /// ProfileName.
139    pub name: Option<String>,
140    /// ProfileEmbedPolicy (0 allow copying ... 3 no restrictions).
141    pub embed_policy: Option<u32>,
142    /// ProfileHueSatMapDims `[hue, sat, val]` when HueSatMap data exists.
143    pub hue_sat_map_dims: Option<[u32; 3]>,
144    /// Number of ProfileHueSatMapData tables present (0..=3).
145    pub hue_sat_map_count: u8,
146    /// ProfileLookTableDims `[hue, sat, val]` when ProfileLookTableData exists.
147    pub look_table_dims: Option<[u32; 3]>,
148    /// Whether ProfileToneCurve is present.
149    pub tone_curve: bool,
150    /// BaselineExposureOffset (stops).
151    pub baseline_exposure_offset: Option<f64>,
152}
153
154/// DNG rendering hints from IFD0 / the raw IFD that FlashRaw does not apply itself.
155#[derive(Debug, Clone, Default, PartialEq)]
156#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
157#[non_exhaustive]
158pub struct DngExtras {
159    /// BaselineExposure in stops (signed).
160    pub baseline_exposure: Option<f64>,
161    /// BaselineNoise.
162    pub baseline_noise: Option<f64>,
163    /// BaselineSharpness.
164    pub baseline_sharpness: Option<f64>,
165    /// LinearResponseLimit (fraction of white).
166    pub linear_response_limit: Option<f64>,
167    /// AsShotNeutral as stored (camera RGB of the neutral).
168    pub as_shot_neutral: Option<Vec<f64>>,
169    /// AsShotWhiteXY chromaticity.
170    pub as_shot_white_xy: Option<(f64, f64)>,
171    /// DefaultUserCrop as fractions of the default-cropped image: top, left, bottom, right.
172    pub default_user_crop: Option<[f64; 4]>,
173    /// DefaultScale (horizontal, vertical).
174    pub default_scale: Option<(f64, f64)>,
175    /// NoiseProfile (pairs of scale/offset per plane).
176    pub noise_profile: Option<Vec<f64>>,
177    /// Camera profile tags.
178    pub profile: DngProfileInfo,
179    /// The camera profile embedded in the file (forward matrices, tables, tone curve), when
180    /// it has one. Not serialised: the tables can be large; see [`DngProfileInfo`] for the
181    /// summary.
182    #[cfg_attr(feature = "serde", serde(skip))]
183    pub camera_profile: Option<crate::camera_profile::CameraProfile>,
184}
185
186/// Normalised metadata for a RAW file, available without decoding sensor data.
187#[derive(Debug, Clone, Default, PartialEq)]
188#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
189#[non_exhaustive]
190pub struct RawMetadata {
191    /// Make as written in the file.
192    pub make: String,
193    /// Model as written in the file.
194    pub model: String,
195    /// Normalised maker name ("Canon", "Nikon", "Sony", ...).
196    pub canonical_make: String,
197    /// Normalised model name (aliases resolved).
198    pub canonical_model: String,
199    /// DNG UniqueCameraModel or equivalent.
200    pub unique_camera_model: Option<String>,
201    /// Writing software (EXIF `Software` tag).
202    pub software: Option<String>,
203    /// Camera firmware version, from maker notes where available.
204    pub firmware: Option<String>,
205    /// Camera body serial number.
206    pub serial: Option<String>,
207    /// Artist / author.
208    pub artist: Option<String>,
209    /// Copyright notice.
210    pub copyright: Option<String>,
211    /// Lens information (fields are `None` when not recorded).
212    pub lens: LensInfo,
213    /// Exposure parameters (fields are `None` when not recorded).
214    pub exposure: ExposureInfo,
215    /// Capture timestamp, `YYYY-MM-DDTHH:MM:SS[.sss][±HH:MM]` when available.
216    pub capture_time: Option<String>,
217    /// GPS position; `None` unless a latitude or longitude is recorded.
218    pub gps: Option<GpsInfo>,
219    /// Image orientation (the resolved value of [`RawMetadata::orientation_info`]).
220    pub orientation: Orientation,
221    /// The orientation records found in the file (standard tag and vendor record) and
222    /// how the resolved [`RawMetadata::orientation`] was chosen.
223    pub orientation_info: OrientationMetadata,
224    /// Container format, when identified.
225    pub format: Option<RawFormat>,
226    /// Payload compression of the main RAW image, when identified.
227    pub compression: Option<Compression>,
228    /// Bits per sample of the encoded data.
229    pub bits_per_sample: u8,
230    /// Full sensor buffer width (including masked borders).
231    pub raw_width: u32,
232    /// Full sensor buffer height (including masked borders).
233    pub raw_height: u32,
234    /// Width after the default crop.
235    pub width: u32,
236    /// Height after the default crop.
237    pub height: u32,
238    /// DNG version bytes (e.g. `[1, 4, 0, 0]`); `None` for non-DNG files.
239    pub dng_version: Option<[u8; 4]>,
240    /// Free-form, vendor-specific values worth exposing (mode names, quality, crop mode...).
241    pub extra: Vec<(String, String)>,
242    /// Embedded previews and thumbnails.
243    pub previews: Vec<PreviewInfo>,
244    /// DNG rendering hints (`None` for non-DNG files).
245    pub dng: Option<DngExtras>,
246}
247
248/// [`RawMetadata::extra`] key: the camera's own image size as the file records it
249/// (`"<width>x<height>"`, from the EXIF pixel dimensions or a maker-note field).
250pub const EXTRA_IMAGE_SIZE: &str = "image_size";
251/// [`RawMetadata::extra`] key: where the default crop came from: `file` (the format's own
252/// crop field), `image-size` ([`EXTRA_IMAGE_SIZE`]), `preview` (the embedded full-size
253/// JPEG) or `camera-db`. Absent when the crop is the active area.
254pub const EXTRA_CROP_SOURCE: &str = "crop_source";
255
256impl RawMetadata {
257    /// The camera's own image size recorded by the file ([`EXTRA_IMAGE_SIZE`]).
258    pub fn image_size(&self) -> Option<(u32, u32)> {
259        let (w, h) = self.extra(EXTRA_IMAGE_SIZE)?.split_once('x')?;
260        Some((w.parse().ok()?, h.parse().ok()?)).filter(|&(w, h)| w > 0 && h > 0)
261    }
262    /// Record the camera's own image size ([`EXTRA_IMAGE_SIZE`]); zero sizes are ignored.
263    pub fn set_image_size(&mut self, width: u32, height: u32) {
264        if width > 0 && height > 0 {
265            self.set_extra(EXTRA_IMAGE_SIZE, format!("{width}x{height}"));
266        }
267    }
268
269    /// Metadata with the given make and model as written in the file; everything else
270    /// empty.
271    pub fn new(make: impl Into<String>, model: impl Into<String>) -> Self {
272        RawMetadata { make: make.into(), model: model.into(), ..Default::default() }
273    }
274
275    /// Set an entry of [`RawMetadata::extra`], replacing any existing value for `key`.
276    pub fn set_extra(&mut self, key: &str, value: impl ToString) {
277        let value = value.to_string();
278        if let Some(e) = self.extra.iter_mut().find(|(k, _)| k == key) {
279            e.1 = value;
280        } else {
281            self.extra.push((key.to_string(), value));
282        }
283    }
284    /// Look up an entry of [`RawMetadata::extra`] by key.
285    pub fn extra(&self, key: &str) -> Option<&str> {
286        self.extra.iter().find(|(k, _)| k == key).map(|(_, v)| v.as_str())
287    }
288    /// Human-friendly "Make Model" string using canonical names when known.
289    pub fn camera(&self) -> String {
290        let make = if self.canonical_make.is_empty() { &self.make } else { &self.canonical_make };
291        let model = if self.canonical_model.is_empty() { &self.model } else { &self.canonical_model };
292        format!("{make} {model}").trim().to_string()
293    }
294    /// Largest preview.
295    pub fn largest_preview(&self) -> Option<&PreviewInfo> {
296        self.previews.iter().max_by_key(|p| (p.width as u64 * p.height as u64, p.length))
297    }
298    /// Smallest preview (thumbnail).
299    pub fn smallest_preview(&self) -> Option<&PreviewInfo> {
300        self.previews.iter().min_by_key(|p| (p.width as u64 * p.height as u64, p.length))
301    }
302}