Skip to main content

rawler/
exif.rs

1use serde::{Deserialize, Serialize};
2
3use crate::{
4  formats::tiff::{IFD, Rational, Result, SRational, Value},
5  lens::LensDescription,
6  tags::{ExifGpsTag, ExifTag},
7};
8
9use std::convert::TryInto;
10
11/// This struct contains the EXIF information.
12/// If a property accepts diffent data types, the type with
13/// the best accuracy is choosen.
14#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
15pub struct Exif {
16  pub orientation: Option<u16>,
17  pub copyright: Option<String>,
18  pub artist: Option<String>,
19  pub lens_spec: Option<[Rational; 4]>,
20  pub exposure_time: Option<Rational>,
21  pub fnumber: Option<Rational>,
22  pub aperture_value: Option<Rational>,
23  pub brightness_value: Option<SRational>,
24  pub iso_speed_ratings: Option<u16>,
25  pub iso_speed: Option<u32>,
26  pub recommended_exposure_index: Option<u32>,
27  pub sensitivity_type: Option<u16>,
28  pub exposure_bias: Option<SRational>,
29  pub date_time_original: Option<String>,
30  pub create_date: Option<String>,
31  pub modify_date: Option<String>,
32  pub exposure_program: Option<u16>,
33  pub timezone_offset: Option<Vec<i16>>,
34  pub offset_time: Option<String>,
35  pub offset_time_original: Option<String>,
36  pub offset_time_digitized: Option<String>,
37  pub sub_sec_time: Option<String>,
38  pub sub_sec_time_original: Option<String>,
39  pub sub_sec_time_digitized: Option<String>,
40  pub shutter_speed_value: Option<SRational>,
41  pub max_aperture_value: Option<Rational>,
42  pub subject_distance: Option<Rational>,
43  pub metering_mode: Option<u16>,
44  pub light_source: Option<u16>,
45  pub flash: Option<u16>,
46  pub focal_length: Option<Rational>,
47  pub image_number: Option<u32>,
48  pub color_space: Option<u16>,
49  pub flash_energy: Option<Rational>,
50  pub exposure_mode: Option<u16>,
51  pub white_balance: Option<u16>,
52  pub scene_capture_type: Option<u16>,
53  pub subject_distance_range: Option<u16>,
54  pub owner_name: Option<String>,
55  pub serial_number: Option<String>,
56  pub lens_serial_number: Option<String>,
57  pub lens_make: Option<String>,
58  pub lens_model: Option<String>,
59  pub gps: Option<ExifGPS>,
60  pub user_comment: Option<String>,
61  //pub makernotes: Option<Vec<u8>>,
62}
63
64#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
65pub struct ExifGPS {
66  pub gps_version_id: Option<[u8; 4]>,
67  pub gps_latitude_ref: Option<String>,
68  pub gps_latitude: Option<[Rational; 3]>,
69  pub gps_longitude_ref: Option<String>,
70  pub gps_longitude: Option<[Rational; 3]>,
71  pub gps_altitude_ref: Option<u8>,
72  pub gps_altitude: Option<Rational>,
73  pub gps_timestamp: Option<[Rational; 3]>,
74  pub gps_satellites: Option<String>,
75  pub gps_status: Option<String>,
76  pub gps_measure_mode: Option<String>,
77  pub gps_dop: Option<Rational>,
78  pub gps_speed_ref: Option<String>,
79  pub gps_speed: Option<Rational>,
80  pub gps_track_ref: Option<String>,
81  pub gps_track: Option<Rational>,
82  pub gps_img_direction_ref: Option<String>,
83  pub gps_img_direction: Option<Rational>,
84  pub gps_map_datum: Option<String>,
85  pub gps_dest_latitude_ref: Option<String>,
86  pub gps_dest_latitude: Option<[Rational; 3]>,
87  pub gps_dest_longitude_ref: Option<String>,
88  pub gps_dest_longitude: Option<[Rational; 3]>,
89  pub gps_dest_bearing_ref: Option<String>,
90  pub gps_dest_bearing: Option<Rational>,
91  pub gps_dest_distance_ref: Option<String>,
92  pub gps_dest_distance: Option<Rational>,
93  pub gps_processing_method: Option<Vec<u8>>,
94  pub gps_area_information: Option<Vec<u8>>,
95  pub gps_date_stamp: Option<String>,
96  pub gps_differential: Option<u16>,
97  pub gps_h_positioning_error: Option<Rational>,
98}
99
100impl Exif {
101  /// Read EXIF data. As some EXIF tags located in the root IFD,
102  /// we accept both IFDs here.
103  pub fn new(root_or_exif: &IFD) -> Result<Self> {
104    let mut ins = Self::default();
105    ins.extend_from_ifd(root_or_exif)?;
106    if let Some(exif_ifd) = root_or_exif.get_sub_ifd(ExifTag::ExifOffset) {
107      ins.extend_from_ifd(exif_ifd)?;
108    }
109    // Search for GPSInfo tag, usually it is located in IFD0
110    if let Some(gpsinfo_ifd) = root_or_exif.get_sub_ifd(ExifTag::GPSInfo) {
111      ins.extend_from_gps_ifd(gpsinfo_ifd)?;
112    }
113    Ok(ins)
114  }
115
116  /// Extend the EXIF info from this IFD. If the IFD contains a ExifIFD,
117  /// extend from this IFD, too.
118  pub fn extend_from_ifd(&mut self, ifd: &IFD) -> Result<()> {
119    let trim = |a: &String| -> String { a.trim().into() };
120    for (tag, entry) in ifd.entries().iter() {
121      // First try EXIF tags
122      if let Ok(tag) = ExifTag::try_from(*tag) {
123        match (tag, &entry.value) {
124          (ExifTag::Orientation, Value::Short(data)) => self.orientation = data.get(0).cloned(),
125          (ExifTag::Copyright, Value::Ascii(data)) => self.copyright = data.strings().get(0).map(trim),
126          (ExifTag::Artist, Value::Ascii(data)) => self.artist = data.strings().get(0).map(trim),
127          (ExifTag::ExposureTime, Value::Rational(data)) => self.exposure_time = data.get(0).cloned(),
128          (ExifTag::FNumber, Value::Rational(data)) => self.fnumber = data.get(0).cloned(),
129          (ExifTag::BrightnessValue, Value::SRational(data)) => self.brightness_value = data.get(0).cloned(),
130          (ExifTag::ApertureValue, Value::Rational(data)) => self.aperture_value = data.get(0).cloned(),
131          (ExifTag::ISOSpeedRatings, Value::Short(data)) => self.iso_speed_ratings = data.get(0).cloned(),
132          (ExifTag::ISOSpeed, Value::Long(data)) => self.iso_speed = data.get(0).cloned(),
133          (ExifTag::RecommendedExposureIndex, Value::Long(data)) => self.recommended_exposure_index = data.get(0).cloned(),
134          (ExifTag::SensitivityType, Value::Short(data)) => self.sensitivity_type = data.get(0).cloned(),
135          (ExifTag::ExposureBiasValue, Value::SRational(data)) => self.exposure_bias = data.get(0).cloned(),
136          (ExifTag::DateTimeOriginal, Value::Ascii(data)) => self.date_time_original = data.strings().get(0).cloned(),
137          (ExifTag::CreateDate, Value::Ascii(data)) => self.create_date = data.strings().get(0).cloned(),
138          (ExifTag::ModifyDate, Value::Ascii(data)) => self.modify_date = data.strings().get(0).cloned(),
139          (ExifTag::ExposureProgram, Value::Short(data)) => self.exposure_program = data.get(0).cloned(),
140          (ExifTag::TimeZoneOffset, Value::SShort(data)) => self.timezone_offset = Some(data.clone()),
141          (ExifTag::OffsetTime, Value::Ascii(data)) => self.offset_time = data.strings().get(0).cloned(),
142          (ExifTag::OffsetTimeOriginal, Value::Ascii(data)) => self.offset_time_original = data.strings().get(0).cloned(),
143          (ExifTag::OffsetTimeDigitized, Value::Ascii(data)) => self.offset_time_digitized = data.strings().get(0).cloned(),
144          (ExifTag::SubSecTime, Value::Ascii(data)) => self.sub_sec_time = data.strings().get(0).cloned(),
145          (ExifTag::SubSecTimeOriginal, Value::Ascii(data)) => self.sub_sec_time_original = data.strings().get(0).cloned(),
146          (ExifTag::SubSecTimeDigitized, Value::Ascii(data)) => self.sub_sec_time_digitized = data.strings().get(0).cloned(),
147          (ExifTag::ShutterSpeedValue, Value::SRational(data)) => self.shutter_speed_value = data.get(0).cloned(),
148          (ExifTag::MaxApertureValue, Value::Rational(data)) => self.max_aperture_value = data.get(0).cloned(),
149          (ExifTag::SubjectDistance, Value::Rational(data)) => self.subject_distance = data.get(0).cloned(),
150          (ExifTag::MeteringMode, Value::Short(data)) => self.metering_mode = data.get(0).cloned(),
151          (ExifTag::LightSource, Value::Short(data)) => self.light_source = data.get(0).cloned(),
152          (ExifTag::Flash, Value::Short(data)) => self.flash = data.get(0).cloned(),
153          (ExifTag::FocalLength, Value::Rational(data)) => self.focal_length = data.get(0).cloned(),
154          (ExifTag::ImageNumber, Value::Long(data)) => self.image_number = data.get(0).cloned(),
155          (ExifTag::ColorSpace, Value::Short(data)) => self.color_space = data.get(0).cloned(),
156          (ExifTag::FlashEnergy, Value::Rational(data)) => self.flash_energy = data.get(0).cloned(),
157          (ExifTag::ExposureMode, Value::Short(data)) => self.exposure_mode = data.get(0).cloned(),
158          (ExifTag::WhiteBalance, Value::Short(data)) => self.white_balance = data.get(0).cloned(),
159          (ExifTag::SceneCaptureType, Value::Short(data)) => self.scene_capture_type = data.get(0).cloned(),
160          (ExifTag::SubjectDistanceRange, Value::Short(data)) => self.subject_distance_range = data.get(0).cloned(),
161          (ExifTag::OwnerName, Value::Ascii(data)) => self.owner_name = data.strings().get(0).map(trim),
162          (ExifTag::SerialNumber, Value::Ascii(data)) => self.serial_number = data.strings().get(0).map(trim),
163          (ExifTag::LensSerialNumber, Value::Ascii(data)) => self.lens_serial_number = data.strings().get(0).map(trim),
164          (ExifTag::UserComment, Value::Ascii(data)) => self.user_comment = data.strings().get(0).map(trim),
165          //(ExifTag::MakerNotes, Value::Undefined(data)) => self.makernotes = Some(data.clone()),
166          (tag, _value) => {
167            log::debug!("Ignoring EXIF tag: {:?}", tag);
168          }
169        }
170      }
171    }
172    Ok(())
173  }
174
175  /// Extend the EXIF info from this IFD. If the IFD contains a ExifIFD,
176  /// extend from this IFD, too.
177  pub fn extend_from_gps_ifd(&mut self, ifd: &IFD) -> Result<()> {
178    for (tag, entry) in ifd.entries().iter() {
179      if let Ok(tag) = ExifGpsTag::try_from(*tag) {
180        // We hit a GPS tag, make sure the gps property is initialized.
181        if self.gps.is_none() {
182          self.gps = Some(ExifGPS::default());
183        }
184        if let Some(gps) = &mut self.gps {
185          match (tag, &entry.value) {
186            (ExifGpsTag::GPSVersionID, Value::Byte(data)) => gps.gps_version_id = data.clone().try_into().ok(),
187            (ExifGpsTag::GPSLatitudeRef, Value::Ascii(data)) => gps.gps_latitude_ref = data.strings().get(0).cloned(),
188            (ExifGpsTag::GPSLatitude, Value::Rational(data)) => gps.gps_latitude = data.clone().try_into().ok(),
189            (ExifGpsTag::GPSLongitudeRef, Value::Ascii(data)) => gps.gps_longitude_ref = data.strings().get(0).cloned(),
190            (ExifGpsTag::GPSLongitude, Value::Rational(data)) => gps.gps_longitude = data.clone().try_into().ok(),
191            (ExifGpsTag::GPSAltitudeRef, Value::Byte(data)) => gps.gps_altitude_ref = data.get(0).cloned(),
192            (ExifGpsTag::GPSAltitude, Value::Rational(data)) => gps.gps_altitude = data.get(0).cloned(),
193            (ExifGpsTag::GPSTimeStamp, Value::Rational(data)) => gps.gps_timestamp = data.clone().try_into().ok(),
194            (ExifGpsTag::GPSSatellites, Value::Ascii(data)) => gps.gps_satellites = data.strings().get(0).cloned(),
195            (ExifGpsTag::GPSStatus, Value::Ascii(data)) => gps.gps_status = data.strings().get(0).cloned(),
196            (ExifGpsTag::GPSMeasureMode, Value::Ascii(data)) => gps.gps_measure_mode = data.strings().get(0).cloned(),
197            (ExifGpsTag::GPSDOP, Value::Rational(data)) => gps.gps_dop = data.get(0).cloned(),
198            (ExifGpsTag::GPSSpeedRef, Value::Ascii(data)) => gps.gps_speed_ref = data.strings().get(0).cloned(),
199            (ExifGpsTag::GPSSpeed, Value::Rational(data)) => gps.gps_speed = data.get(0).cloned(),
200            (ExifGpsTag::GPSTrackRef, Value::Ascii(data)) => gps.gps_track_ref = data.strings().get(0).cloned(),
201            (ExifGpsTag::GPSTrack, Value::Rational(data)) => gps.gps_track = data.get(0).cloned(),
202            (ExifGpsTag::GPSImgDirectionRef, Value::Ascii(data)) => gps.gps_img_direction_ref = data.strings().get(0).cloned(),
203            (ExifGpsTag::GPSImgDirection, Value::Rational(data)) => gps.gps_img_direction = data.get(0).cloned(),
204            (ExifGpsTag::GPSMapDatum, Value::Ascii(data)) => gps.gps_map_datum = data.strings().get(0).cloned(),
205            (ExifGpsTag::GPSDestLatitudeRef, Value::Ascii(data)) => gps.gps_dest_latitude_ref = data.strings().get(0).cloned(),
206            (ExifGpsTag::GPSDestLatitude, Value::Rational(data)) => gps.gps_dest_latitude = data.clone().try_into().ok(),
207            (ExifGpsTag::GPSDestLongitudeRef, Value::Ascii(data)) => gps.gps_dest_longitude_ref = data.strings().get(0).cloned(),
208            (ExifGpsTag::GPSDestLongitude, Value::Rational(data)) => gps.gps_dest_longitude = data.clone().try_into().ok(),
209            (ExifGpsTag::GPSDestBearingRef, Value::Ascii(data)) => gps.gps_dest_bearing_ref = data.strings().get(0).cloned(),
210            (ExifGpsTag::GPSDestBearing, Value::Rational(data)) => gps.gps_dest_bearing = data.get(0).cloned(),
211            (ExifGpsTag::GPSDestDistanceRef, Value::Ascii(data)) => gps.gps_dest_distance_ref = data.strings().get(0).cloned(),
212            (ExifGpsTag::GPSDestDistance, Value::Rational(data)) => gps.gps_dest_distance = data.get(0).cloned(),
213            (ExifGpsTag::GPSProcessingMethod, Value::Undefined(data)) => gps.gps_processing_method = Some(data.clone()),
214            (ExifGpsTag::GPSAreaInformation, Value::Undefined(data)) => gps.gps_area_information = Some(data.clone()),
215            (ExifGpsTag::GPSDateStamp, Value::Ascii(data)) => gps.gps_date_stamp = data.strings().get(0).cloned(),
216            (ExifGpsTag::GPSDifferential, Value::Short(data)) => gps.gps_differential = data.get(0).cloned(),
217            (ExifGpsTag::GPSHPositioningError, Value::Rational(data)) => gps.gps_h_positioning_error = data.get(0).cloned(),
218            (tag, _value) => {
219              log::debug!("Ignoring EXIF GPS tag: {:?}", tag);
220            }
221          }
222        }
223      }
224    }
225    Ok(())
226  }
227
228  pub(crate) fn extend_from_lens(&mut self, lens: &LensDescription) {
229    let lens_info: [Rational; 4] = [lens.focal_range[0], lens.focal_range[1], lens.aperture_range[0], lens.aperture_range[1]];
230    self.lens_spec = Some(lens_info);
231    self.lens_make = Some(lens.lens_make.clone());
232    self.lens_model = Some(lens.lens_model.clone());
233  }
234}