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}
180
181/// Normalised metadata for a RAW file, available without decoding sensor data.
182#[derive(Debug, Clone, Default, PartialEq)]
183#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
184#[non_exhaustive]
185pub struct RawMetadata {
186 /// Make as written in the file.
187 pub make: String,
188 /// Model as written in the file.
189 pub model: String,
190 /// Normalised maker name ("Canon", "Nikon", "Sony", ...).
191 pub canonical_make: String,
192 /// Normalised model name (aliases resolved).
193 pub canonical_model: String,
194 /// DNG UniqueCameraModel or equivalent.
195 pub unique_camera_model: Option<String>,
196 /// Writing software (EXIF `Software` tag).
197 pub software: Option<String>,
198 /// Camera firmware version, from maker notes where available.
199 pub firmware: Option<String>,
200 /// Camera body serial number.
201 pub serial: Option<String>,
202 /// Artist / author.
203 pub artist: Option<String>,
204 /// Copyright notice.
205 pub copyright: Option<String>,
206 /// Lens information (fields are `None` when not recorded).
207 pub lens: LensInfo,
208 /// Exposure parameters (fields are `None` when not recorded).
209 pub exposure: ExposureInfo,
210 /// Capture timestamp, `YYYY-MM-DDTHH:MM:SS[.sss][±HH:MM]` when available.
211 pub capture_time: Option<String>,
212 /// GPS position; `None` unless a latitude or longitude is recorded.
213 pub gps: Option<GpsInfo>,
214 /// Image orientation (the resolved value of [`RawMetadata::orientation_info`]).
215 pub orientation: Orientation,
216 /// The orientation records found in the file (standard tag and vendor record) and
217 /// how the resolved [`RawMetadata::orientation`] was chosen.
218 pub orientation_info: OrientationMetadata,
219 /// Container format, when identified.
220 pub format: Option<RawFormat>,
221 /// Payload compression of the main RAW image, when identified.
222 pub compression: Option<Compression>,
223 /// Bits per sample of the encoded data.
224 pub bits_per_sample: u8,
225 /// Full sensor buffer width (including masked borders).
226 pub raw_width: u32,
227 /// Full sensor buffer height (including masked borders).
228 pub raw_height: u32,
229 /// Width after the default crop.
230 pub width: u32,
231 /// Height after the default crop.
232 pub height: u32,
233 /// DNG version bytes (e.g. `[1, 4, 0, 0]`); `None` for non-DNG files.
234 pub dng_version: Option<[u8; 4]>,
235 /// Free-form, vendor-specific values worth exposing (mode names, quality, crop mode...).
236 pub extra: Vec<(String, String)>,
237 /// Embedded previews and thumbnails.
238 pub previews: Vec<PreviewInfo>,
239 /// DNG rendering hints (`None` for non-DNG files).
240 pub dng: Option<DngExtras>,
241}
242
243/// [`RawMetadata::extra`] key: the camera's own image size as the file records it
244/// (`"<width>x<height>"`, from the EXIF pixel dimensions or a maker-note field).
245pub const EXTRA_IMAGE_SIZE: &str = "image_size";
246/// [`RawMetadata::extra`] key: where the default crop came from: `file` (the format's own
247/// crop field), `image-size` ([`EXTRA_IMAGE_SIZE`]), `preview` (the embedded full-size
248/// JPEG) or `camera-db`. Absent when the crop is the active area.
249pub const EXTRA_CROP_SOURCE: &str = "crop_source";
250
251impl RawMetadata {
252 /// The camera's own image size recorded by the file ([`EXTRA_IMAGE_SIZE`]).
253 pub fn image_size(&self) -> Option<(u32, u32)> {
254 let (w, h) = self.extra(EXTRA_IMAGE_SIZE)?.split_once('x')?;
255 Some((w.parse().ok()?, h.parse().ok()?)).filter(|&(w, h)| w > 0 && h > 0)
256 }
257 /// Record the camera's own image size ([`EXTRA_IMAGE_SIZE`]); zero sizes are ignored.
258 pub fn set_image_size(&mut self, width: u32, height: u32) {
259 if width > 0 && height > 0 {
260 self.set_extra(EXTRA_IMAGE_SIZE, format!("{width}x{height}"));
261 }
262 }
263
264 /// Metadata with the given make and model as written in the file; everything else
265 /// empty.
266 pub fn new(make: impl Into<String>, model: impl Into<String>) -> Self {
267 RawMetadata { make: make.into(), model: model.into(), ..Default::default() }
268 }
269
270 /// Set an entry of [`RawMetadata::extra`], replacing any existing value for `key`.
271 pub fn set_extra(&mut self, key: &str, value: impl ToString) {
272 let value = value.to_string();
273 if let Some(e) = self.extra.iter_mut().find(|(k, _)| k == key) {
274 e.1 = value;
275 } else {
276 self.extra.push((key.to_string(), value));
277 }
278 }
279 /// Look up an entry of [`RawMetadata::extra`] by key.
280 pub fn extra(&self, key: &str) -> Option<&str> {
281 self.extra.iter().find(|(k, _)| k == key).map(|(_, v)| v.as_str())
282 }
283 /// Human-friendly "Make Model" string using canonical names when known.
284 pub fn camera(&self) -> String {
285 let make = if self.canonical_make.is_empty() { &self.make } else { &self.canonical_make };
286 let model = if self.canonical_model.is_empty() { &self.model } else { &self.canonical_model };
287 format!("{make} {model}").trim().to_string()
288 }
289 /// Largest preview.
290 pub fn largest_preview(&self) -> Option<&PreviewInfo> {
291 self.previews.iter().max_by_key(|p| (p.width as u64 * p.height as u64, p.length))
292 }
293 /// Smallest preview (thumbnail).
294 pub fn smallest_preview(&self) -> Option<&PreviewInfo> {
295 self.previews.iter().min_by_key(|p| (p.width as u64 * p.height as u64, p.length))
296 }
297}