1use crate::metadata::{GpsInfo, RawMetadata};
7use crate::orientation::{OrientationMetadata, OrientationPolicy};
8use crate::tiff::{Ifd, tags};
9use crate::types::Orientation;
10
11pub fn read_ifd0(ifd: &Ifd, meta: &mut RawMetadata) {
13 if meta.make.is_empty() {
14 if let Some(s) = ifd.string(tags::MAKE) {
15 meta.make = s;
16 }
17 }
18 if meta.model.is_empty() {
19 if let Some(s) = ifd.string(tags::MODEL) {
20 meta.model = s;
21 }
22 }
23 if let Some(o) = ifd.u32(tags::ORIENTATION) {
24 meta.orientation = Orientation::from_exif(o);
25 meta.orientation_info =
26 OrientationMetadata::resolve(Orientation::from_exif_checked(o), None, None, OrientationPolicy::ExifOnly);
27 }
28 if meta.software.is_none() {
29 meta.software = ifd.string(tags::SOFTWARE);
30 }
31 if meta.artist.is_none() {
32 meta.artist = ifd.string(tags::ARTIST);
33 }
34 if meta.copyright.is_none() {
35 meta.copyright = ifd.string(tags::COPYRIGHT);
36 }
37 if meta.capture_time.is_none() {
38 meta.capture_time = ifd.string(tags::DATE_TIME).and_then(|s| normalize_datetime(&s, None, None));
39 }
40 if let Some(e) = &ifd.exif {
41 read_exif(e, meta);
42 }
43 if let Some(g) = &ifd.gps {
44 meta.gps = read_gps(g);
45 }
46 read_exif(ifd, meta);
48}
49
50pub fn read_exif(e: &Ifd, meta: &mut RawMetadata) {
52 let x = &mut meta.exposure;
53 if x.exposure_time.is_none() {
54 x.exposure_time = e.f64(tags::EXPOSURE_TIME).filter(|v| *v > 0.0);
55 }
56 if x.f_number.is_none() {
57 x.f_number = e.f64(tags::F_NUMBER).map(|v| v as f32).filter(|v| *v > 0.0);
58 }
59 if x.iso.is_none() {
60 x.iso = e.get(tags::ISO).and_then(|v| v.u32(0)).filter(|v| *v > 0);
61 }
62 if x.focal_length.is_none() {
63 x.focal_length = e.f64(tags::FOCAL_LENGTH).map(|v| v as f32).filter(|v| *v > 0.0);
64 }
65 if x.focal_length_35mm.is_none() {
66 x.focal_length_35mm = e.u32(tags::FOCAL_LENGTH_35MM).filter(|v| *v > 0);
67 }
68 if x.exposure_bias.is_none() {
69 x.exposure_bias = e.f64(tags::EXPOSURE_BIAS).map(|v| v as f32);
70 }
71 if x.flash.is_none() {
72 x.flash = e.u32(tags::FLASH).map(|v| v as u16);
73 }
74 if x.metering_mode.is_none() {
75 x.metering_mode = e.u32(tags::METERING_MODE).map(|v| v as u16);
76 }
77 if x.exposure_program.is_none() {
78 x.exposure_program = e.u32(tags::EXPOSURE_PROGRAM).map(|v| v as u16);
79 }
80 if let Some(s) = e.string(tags::DATE_TIME_ORIGINAL) {
81 let sub = e.string(tags::SUBSEC_TIME_ORIGINAL);
82 let off = e.string(tags::OFFSET_TIME_ORIGINAL);
83 if let Some(t) = normalize_datetime(&s, sub.as_deref(), off.as_deref()) {
84 meta.capture_time = Some(t);
85 }
86 }
87 if meta.serial.is_none() {
88 meta.serial = e.string(tags::BODY_SERIAL);
89 }
90 if meta.image_size().is_none() {
93 if let (Some(w), Some(h)) = (e.u32(tags::PIXEL_X_DIMENSION), e.u32(tags::PIXEL_Y_DIMENSION)) {
94 meta.set_image_size(w, h);
95 }
96 }
97 let l = &mut meta.lens;
98 if l.make.is_none() {
99 l.make = e.string(tags::LENS_MAKE);
100 }
101 if l.model.is_none() {
102 l.model = e.string(tags::LENS_MODEL);
103 }
104 if l.serial.is_none() {
105 l.serial = e.string(tags::LENS_SERIAL);
106 }
107 if l.focal_range.is_none() {
108 if let Some(spec) = e.get(tags::LENS_SPEC) {
109 let v = spec.f64s();
110 if v.len() >= 4 && v[0] > 0.0 {
111 l.focal_range = Some((v[0] as f32, v[1] as f32));
112 if v[2] > 0.0 {
113 l.aperture_range = Some((v[2] as f32, v[3] as f32));
114 }
115 }
116 }
117 }
118}
119
120pub fn read_gps(g: &Ifd) -> Option<GpsInfo> {
122 let dms = |tag: u16| -> Option<f64> {
123 let v = g.get(tag)?.f64s();
124 (v.len() >= 3).then(|| v[0] + v[1] / 60.0 + v[2] / 3600.0)
125 };
126 let mut info = GpsInfo::default();
127 if let Some(lat) = dms(0x0002) {
128 let r = g.string(0x0001).unwrap_or_default();
129 info.latitude = Some(if r.starts_with('S') { -lat } else { lat });
130 }
131 if let Some(lon) = dms(0x0004) {
132 let r = g.string(0x0003).unwrap_or_default();
133 info.longitude = Some(if r.starts_with('W') { -lon } else { lon });
134 }
135 if let Some(alt) = g.f64(0x0006) {
136 let below = g.get(0x0005).and_then(|e| e.u32(0)).unwrap_or(0) == 1;
137 info.altitude = Some(if below { -alt } else { alt });
138 }
139 if let (Some(d), Some(t)) = (g.string(0x001D), g.get(0x0007)) {
140 let t = t.f64s();
141 if t.len() >= 3 {
142 info.timestamp =
143 Some(format!("{}T{:02}:{:02}:{:02}Z", d.replace(':', "-"), t[0] as u32, t[1] as u32, t[2] as u32));
144 }
145 }
146 (info.latitude.is_some() || info.longitude.is_some()).then_some(info)
147}
148
149pub fn normalize_datetime(s: &str, subsec: Option<&str>, offset: Option<&str>) -> Option<String> {
151 let s = s.trim();
152 let b = s.as_bytes();
153 if b.len() < 19 || !b[..4].iter().all(|c| c.is_ascii_digit()) || s.starts_with("0000") {
154 return None;
155 }
156 let mut out = format!("{}-{}-{}T{}", &s[0..4], &s[5..7], &s[8..10], &s[11..19]);
157 if let Some(ss) = subsec.map(str::trim).filter(|v| !v.is_empty() && v.bytes().all(|c| c.is_ascii_digit())) {
158 out.push('.');
159 out.push_str(ss);
160 }
161 if let Some(o) = offset.map(str::trim).filter(|v| v.len() == 6 && (v.starts_with('+') || v.starts_with('-'))) {
162 out.push_str(o);
163 }
164 Some(out)
165}
166
167#[cfg(test)]
168mod tests {
169 use super::*;
170
171 #[test]
172 fn datetime() {
173 assert_eq!(
174 normalize_datetime("2021:03:04 05:06:07", Some("12"), Some("+02:00")).unwrap(),
175 "2021-03-04T05:06:07.12+02:00"
176 );
177 assert!(normalize_datetime("0000:00:00 00:00:00", None, None).is_none());
178 assert!(normalize_datetime("bad", None, None).is_none());
179 }
180}