exif_oxide/implementations/
value_conv.rs1use crate::types::{ExifError, Result, TagValue};
10
11pub fn gps_coordinate_value_conv(value: &TagValue) -> Result<TagValue> {
21 match value {
22 TagValue::RationalArray(coords) if coords.len() >= 3 => {
23 let degrees = if coords[0].1 != 0 {
29 coords[0].0 as f64 / coords[0].1 as f64
30 } else {
31 0.0 };
33
34 let minutes = if coords.len() > 1 && coords[1].1 != 0 {
36 coords[1].0 as f64 / coords[1].1 as f64
37 } else {
38 0.0 };
40
41 let seconds = if coords.len() > 2 && coords[2].1 != 0 {
43 coords[2].0 as f64 / coords[2].1 as f64
44 } else {
45 0.0 };
47
48 let decimal_degrees = degrees + ((minutes + seconds / 60.0) / 60.0);
50
51 Ok(TagValue::F64(decimal_degrees))
52 }
53 _ => Err(ExifError::ParseError(
54 "GPS coordinate conversion requires rational array with at least 3 elements"
55 .to_string(),
56 )),
57 }
58}
59
60pub fn apex_shutter_speed_value_conv(value: &TagValue) -> Result<TagValue> {
65 match value.as_f64() {
66 Some(apex_val) => {
67 let shutter_speed = (-apex_val).exp2(); Ok(TagValue::F64(shutter_speed))
69 }
70 None => Err(ExifError::ParseError(
71 "APEX shutter speed conversion requires numeric value".to_string(),
72 )),
73 }
74}
75
76pub fn apex_aperture_value_conv(value: &TagValue) -> Result<TagValue> {
81 match value.as_f64() {
82 Some(apex_val) => {
83 let f_number = (apex_val / 2.0).exp2(); Ok(TagValue::F64(f_number))
85 }
86 None => Err(ExifError::ParseError(
87 "APEX aperture conversion requires numeric value".to_string(),
88 )),
89 }
90}
91
92pub fn apex_exposure_compensation_value_conv(value: &TagValue) -> Result<TagValue> {
97 match value.as_f64() {
100 Some(ev_value) => Ok(TagValue::F64(ev_value)),
101 None => Ok(value.clone()), }
103}
104
105pub fn fnumber_value_conv(value: &TagValue) -> Result<TagValue> {
110 match value {
111 TagValue::Rational(num, denom) => {
112 if *denom != 0 {
113 let f_number = *num as f64 / *denom as f64;
114 Ok(TagValue::F64(f_number))
115 } else {
116 Err(ExifError::ParseError(
117 "FNumber has zero denominator".to_string(),
118 ))
119 }
120 }
121 _ => Ok(value.clone()),
123 }
124}
125
126pub fn gpstimestamp_value_conv(value: &TagValue) -> Result<TagValue> {
131 match value {
132 TagValue::RationalArray(rationals) if rationals.len() >= 3 => {
133 let hours = if rationals[0].1 != 0 {
134 rationals[0].0 / rationals[0].1
135 } else {
136 0
137 };
138
139 let minutes = if rationals[1].1 != 0 {
140 rationals[1].0 / rationals[1].1
141 } else {
142 0
143 };
144
145 let seconds = if rationals[2].1 != 0 {
146 rationals[2].0 / rationals[2].1
147 } else {
148 0
149 };
150
151 let time_string = format!("{hours:02}:{minutes:02}:{seconds:02}");
153 Ok(TagValue::String(time_string))
154 }
155 _ => Err(ExifError::ParseError(
156 "GPS timestamp conversion requires rational array with at least 3 elements".to_string(),
157 )),
158 }
159}
160
161pub fn gpsdatestamp_value_conv(value: &TagValue) -> Result<TagValue> {
166 Ok(value.clone())
168}
169
170pub fn whitebalance_value_conv(value: &TagValue) -> Result<TagValue> {
175 Ok(value.clone())
177}
178
179pub fn exposuretime_value_conv(value: &TagValue) -> Result<TagValue> {
182 match value {
183 TagValue::Rational(num, denom) => {
184 if *denom != 0 {
185 let exposure_time = *num as f64 / *denom as f64;
186 Ok(TagValue::F64(exposure_time))
187 } else {
188 Err(ExifError::ParseError(
189 "ExposureTime has zero denominator".to_string(),
190 ))
191 }
192 }
193 _ => Ok(value.clone()),
195 }
196}
197
198pub fn focallength_value_conv(value: &TagValue) -> Result<TagValue> {
201 match value {
202 TagValue::Rational(num, denom) => {
203 if *denom != 0 {
204 let focal_length = *num as f64 / *denom as f64;
205 Ok(TagValue::F64(focal_length))
206 } else {
207 Err(ExifError::ParseError(
208 "FocalLength has zero denominator".to_string(),
209 ))
210 }
211 }
212 _ => Ok(value.clone()),
214 }
215}
216
217#[cfg(test)]
218mod tests {
219 use super::*;
220
221 #[test]
222 fn test_gps_coordinate_conversion() {
223 let coords = vec![(40, 1), (26, 1), (468, 10)]; let coord_value = TagValue::RationalArray(coords);
226
227 let result = gps_coordinate_value_conv(&coord_value).unwrap();
228 if let TagValue::F64(decimal) = result {
229 assert!((decimal - 40.446333333).abs() < 0.000001);
231 } else {
232 panic!("Expected F64 result");
233 }
234 }
235
236 #[test]
237 fn test_gps_coordinate_precision() {
238 let coords = vec![(12, 1), (34, 1), (56789, 1000)]; let coord_value = TagValue::RationalArray(coords);
241
242 let result = gps_coordinate_value_conv(&coord_value).unwrap();
243 if let TagValue::F64(decimal) = result {
244 let expected = 12.0 + 34.0 / 60.0 + 56.789 / 3600.0;
246 assert!((decimal - expected).abs() < 0.0000001);
247 } else {
248 panic!("Expected F64 result");
249 }
250 }
251
252 #[test]
253 fn test_gps_coordinate_zero_values() {
254 let coords = vec![(0, 1), (0, 1), (0, 1)];
256 let coord_value = TagValue::RationalArray(coords);
257
258 let result = gps_coordinate_value_conv(&coord_value).unwrap();
259 if let TagValue::F64(decimal) = result {
260 assert_eq!(decimal, 0.0);
261 } else {
262 panic!("Expected F64 result");
263 }
264 }
265
266 #[test]
267 fn test_gps_coordinate_only_degrees() {
268 let coords = vec![(45, 1), (0, 1), (0, 1)];
270 let coord_value = TagValue::RationalArray(coords);
271
272 let result = gps_coordinate_value_conv(&coord_value).unwrap();
273 if let TagValue::F64(decimal) = result {
274 assert_eq!(decimal, 45.0);
275 } else {
276 panic!("Expected F64 result");
277 }
278 }
279
280 #[test]
281 fn test_gps_coordinate_zero_denominators() {
282 let coords = vec![(40, 1), (30, 0), (45, 1)]; let coord_value = TagValue::RationalArray(coords);
285
286 let result = gps_coordinate_value_conv(&coord_value).unwrap();
287 if let TagValue::F64(decimal) = result {
288 assert!((decimal - 40.0125).abs() < 0.0001);
290 } else {
291 panic!("Expected F64 result");
292 }
293 }
294
295 #[test]
296 fn test_gps_coordinate_invalid_input() {
297 let value = TagValue::String("40.446333".to_string());
299 let result = gps_coordinate_value_conv(&value);
300 assert!(matches!(result, Err(ExifError::ParseError(_))));
301
302 let coords = vec![(40, 1), (26, 1)]; let coord_value = TagValue::RationalArray(coords);
305 let result = gps_coordinate_value_conv(&coord_value);
306 assert!(matches!(result, Err(ExifError::ParseError(_))));
307
308 let coords = vec![];
310 let coord_value = TagValue::RationalArray(coords);
311 let result = gps_coordinate_value_conv(&coord_value);
312 assert!(matches!(result, Err(ExifError::ParseError(_))));
313 }
314
315 #[test]
316 fn test_apex_shutter_speed() {
317 let apex_value = TagValue::F64(11.0);
319 let result = apex_shutter_speed_value_conv(&apex_value).unwrap();
320
321 if let TagValue::F64(speed) = result {
322 assert!((speed - 0.00048828125).abs() < 0.000001);
323 } else {
324 panic!("Expected F64 result");
325 }
326 }
327
328 #[test]
329 fn test_apex_aperture() {
330 let apex_value = TagValue::F64(4.0);
332 let result = apex_aperture_value_conv(&apex_value).unwrap();
333
334 if let TagValue::F64(f_number) = result {
335 assert!((f_number - 4.0).abs() < 0.001);
336 } else {
337 panic!("Expected F64 result");
338 }
339 }
340
341 #[test]
342 fn test_fnumber_conversion() {
343 let fnumber_rational = TagValue::Rational(4, 1);
345 let result = fnumber_value_conv(&fnumber_rational).unwrap();
346
347 if let TagValue::F64(f_number) = result {
348 assert!((f_number - 4.0).abs() < 0.001);
349 } else {
350 panic!("Expected F64 result");
351 }
352 }
353}