1 use super::ifdformat::*;
2 use super::lowlevel::read_u16_array;
3 use super::types::*;
4 use std::borrow::Cow;
5 
6 /// No-op for readable value tag function. Should not be used by any EXIF tag descriptor,
7 /// except for the catch-all match that handles unknown tags
nop(e: &TagValue) -> Option<Cow<'static, str>>8 pub(crate) fn nop(e: &TagValue) -> Option<Cow<'static, str>> {
9     Some(Cow::Owned(e.to_string()))
10 }
11 
12 /// No-op for readable value tag function. Used for ASCII string tags, or when the
13 /// default readable representation of value is pretty enough.
strpass(e: &TagValue) -> Option<Cow<'static, str>>14 pub(crate) fn strpass(e: &TagValue) -> Option<Cow<'static, str>> {
15     Some(Cow::Owned(e.to_string()))
16 }
17 
18 /// Indicates which one of the parameters of ISO12232 is used for PhotographicSensitivity
sensitivity_type(e: &TagValue) -> Option<Cow<'static, str>>19 pub(crate) fn sensitivity_type(e: &TagValue) -> Option<Cow<'static, str>> {
20     match *e {
21         TagValue::U16(ref v) => Some(match v.get(0)? {
22             0 => "Unknown",
23             1 => "Standard output sensitivity (SOS)",
24             2 => "Recommended exposure index (REI)",
25             3 => "ISO speed",
26             4 => "Standard output sensitivity (SOS) and recommended exposure index (REI)",
27             5 => "Standard output sensitivity (SOS) and ISO speed",
28             6 => "Recommended exposure index (REI) and ISO speed",
29             7 => "Standard output sensitivity (SOS) and recommended exposure index (REI) and ISO speed",
30             n => return Some(format!("Unknown ({})", n).into()),
31         }.into()),
32         _ => None,
33     }
34 }
35 
orientation(e: &TagValue) -> Option<Cow<'static, str>>36 pub(crate) fn orientation(e: &TagValue) -> Option<Cow<'static, str>> {
37     match *e {
38         TagValue::U16(ref v) => {
39             Some(match v.get(0)? {
40                 1 => "Straight",
41                 3 => "Upside down",
42                 6 => "Rotated to left",
43                 8 => "Rotated to right",
44                 9 => "Undefined",
45                 n => return Some(format!("Unknown ({})", n).into()),
46             }.into())
47         },
48         _ => None,
49     }
50 }
51 
rational_value(e: &TagValue) -> Option<Cow<'static, str>>52 pub(crate) fn rational_value(e: &TagValue) -> Option<Cow<'static, str>> {
53     Some(match e {
54         TagValue::URational(v) => v.get(0)?.value(),
55         TagValue::IRational(v) => v.get(0)?.value(),
56         _ => return None,
57     }.to_string().into())
58 }
59 
rational_values(e: &TagValue) -> Option<Cow<'static, str>>60 pub(crate) fn rational_values(e: &TagValue) -> Option<Cow<'static, str>> {
61     match *e {
62         TagValue::URational(ref v) => {
63             Some(NumArray::new(v.iter().map(|&x| x.value())).to_string().into())
64         },
65         _ => None,
66     }
67 }
68 
resolution_unit(e: &TagValue) -> Option<Cow<'static, str>>69 pub(crate) fn resolution_unit(e: &TagValue) -> Option<Cow<'static, str>> {
70     match *e {
71         TagValue::U16(ref v) => {
72             Some(match v.get(0)? {
73                 1 => "Unitless",
74                 2 => "in",
75                 3 => "cm",
76                 n => return Some(format!("Unknown ({})", n).into()),
77             }.into())
78         },
79         _ => None,
80     }
81 }
82 
exposure_time(e: &TagValue) -> Option<Cow<'static, str>>83 pub(crate) fn exposure_time(e: &TagValue) -> Option<Cow<'static, str>> {
84     match *e {
85         TagValue::URational(ref v) => {
86             let r = v.get(0)?;
87             Some(if r.numerator == 1 && r.denominator > 1 {
88                 // traditional 1/x exposure time
89                 format!("{} s", r)
90             } else if r.value() < 0.1 {
91                 format!("1/{:.0} s", 1.0 / r.value())
92             } else if r.value() < 1.0 {
93                 format!("1/{:.1} s", 1.0 / r.value())
94             } else {
95                 format!("{:.1} s", r.value())
96             }.into())
97         },
98         _ => None,
99     }
100 }
101 
f_number(e: &TagValue) -> Option<Cow<'static, str>>102 pub(crate) fn f_number(e: &TagValue) -> Option<Cow<'static, str>> {
103     match *e {
104         TagValue::URational(ref v) => Some(format!("f/{:.1}", v.get(0)?.value()).into()),
105         _ => None,
106     }
107 }
108 
exposure_program(e: &TagValue) -> Option<Cow<'static, str>>109 pub(crate) fn exposure_program(e: &TagValue) -> Option<Cow<'static, str>> {
110     match *e {
111         TagValue::U16(ref v) => {
112             Some(match v.get(0)? {
113                 1 => "Manual control",
114                 2 => "Program control",
115                 3 => "Aperture priority",
116                 4 => "Shutter priority",
117                 5 => "Program creative (slow program)",
118                 6 => "Program creative (high-speed program)",
119                 7 => "Portrait mode",
120                 8 => "Landscape mode",
121                 n => return Some(format!("Unknown ({})", n).into()),
122             }.into())
123         },
124         _ => None,
125     }
126 }
127 
focal_length(e: &TagValue) -> Option<Cow<'static, str>>128 pub(crate) fn focal_length(e: &TagValue) -> Option<Cow<'static, str>> {
129     match *e {
130         TagValue::URational(ref v) => Some(format!("{} mm", v.get(0)?.value()).into()),
131         _ => None,
132     }
133 }
134 
focal_length_35(e: &TagValue) -> Option<Cow<'static, str>>135 pub(crate) fn focal_length_35(e: &TagValue) -> Option<Cow<'static, str>> {
136     match *e {
137         TagValue::U16(ref v) => Some(format!("{} mm", v.get(0)?).into()),
138         _ => None,
139     }
140 }
141 
meters(e: &TagValue) -> Option<Cow<'static, str>>142 pub(crate) fn meters(e: &TagValue) -> Option<Cow<'static, str>> {
143     match *e {
144         TagValue::URational(ref v) => Some(format!("{:.1} m", v.get(0)?.value()).into()),
145         _ => None,
146     }
147 }
148 
iso_speeds(e: &TagValue) -> Option<Cow<'static, str>>149 pub(crate) fn iso_speeds(e: &TagValue) -> Option<Cow<'static, str>> {
150     match *e {
151         TagValue::U16(ref v) => {
152             Some(if v.len() == 1 {
153                 format!("ISO {}", v[0])
154             } else if v.len() == 2 || v.len() == 3 {
155                 format!("ISO {} latitude {}", v[0], v[1])
156             } else {
157                 format!("Unknown ({})", NumArray::new(v))
158             }.into())
159         },
160         _ => None,
161     }
162 }
163 
dms(e: &TagValue) -> Option<Cow<'static, str>>164 pub(crate) fn dms(e: &TagValue) -> Option<Cow<'static, str>> {
165     match *e {
166         TagValue::URational(ref v) if v.len() >= 3 => {
167             let deg = v[0];
168             let min = v[1];
169             let sec = v[2];
170             Some(if deg.denominator == 1 && min.denominator == 1 {
171                 format!("{}°{}'{:.2}\"", deg.value(), min.value(), sec.value())
172             } else if deg.denominator == 1 {
173                 format!("{}°{:.4}'", deg.value(), min.value() + sec.value() / 60.0)
174             } else {
175                 // untypical format
176                 format!(
177                     "{:.7}°",
178                     deg.value() + min.value() / 60.0 + sec.value() / 3600.0
179                 )
180             }.into())
181         },
182         _ => None,
183     }
184 }
185 
gps_alt_ref(e: &TagValue) -> Option<Cow<'static, str>>186 pub(crate) fn gps_alt_ref(e: &TagValue) -> Option<Cow<'static, str>> {
187     match *e {
188         TagValue::U8(ref v) => {
189             Some(match v.get(0)? {
190                 0 => "Above sea level",
191                 1 => "Below sea level",
192                 n => return Some(format!("Unknown, assumed below sea level ({})", n).into()),
193             }.into())
194         },
195         _ => None,
196     }
197 }
198 
gpsdestdistanceref(e: &TagValue) -> Option<Cow<'static, str>>199 pub(crate) fn gpsdestdistanceref(e: &TagValue) -> Option<Cow<'static, str>> {
200     match *e {
201         TagValue::Ascii(ref v) => {
202             Some(if v == "N" {
203                 "kn".into()
204             } else if v == "K" {
205                 "km".into()
206             } else if v == "M" {
207                 "mi".into()
208             } else {
209                 format!("Unknown ({})", v).into()
210             })
211         },
212         _ => None,
213     }
214 }
215 
gpsdestdistance(e: &TagValue) -> Option<Cow<'static, str>>216 pub(crate) fn gpsdestdistance(e: &TagValue) -> Option<Cow<'static, str>> {
217     match *e {
218         TagValue::URational(ref v) => Some(format!("{:.3}", v.get(0)?.value()).into()),
219         _ => None,
220     }
221 }
222 
gpsspeedref(e: &TagValue) -> Option<Cow<'static, str>>223 pub(crate) fn gpsspeedref(e: &TagValue) -> Option<Cow<'static, str>> {
224     match *e {
225         TagValue::Ascii(ref v) => {
226             Some(if v == "N" {
227                 "kn".into()
228             } else if v == "K" {
229                 "km/h".into()
230             } else if v == "M" {
231                 "mi/h".into()
232             } else {
233                 format!("Unknown ({})", v).into()
234             })
235         },
236         _ => None,
237     }
238 }
239 
gpsspeed(e: &TagValue) -> Option<Cow<'static, str>>240 pub(crate) fn gpsspeed(e: &TagValue) -> Option<Cow<'static, str>> {
241     match *e {
242         TagValue::URational(ref v) => Some(format!("{:.1}", v.get(0)?.value()).into()),
243         _ => None,
244     }
245 }
246 
gpsbearingref(e: &TagValue) -> Option<Cow<'static, str>>247 pub(crate) fn gpsbearingref(e: &TagValue) -> Option<Cow<'static, str>> {
248     match *e {
249         TagValue::Ascii(ref v) => {
250             Some(if v == "T" {
251                 "True bearing".into()
252             } else if v == "M" {
253                 "Magnetic bearing".into()
254             } else {
255                 format!("Unknown ({})", v).into()
256             })
257         },
258         _ => None,
259     }
260 }
261 
gpsbearing(e: &TagValue) -> Option<Cow<'static, str>>262 pub(crate) fn gpsbearing(e: &TagValue) -> Option<Cow<'static, str>> {
263     match *e {
264         TagValue::URational(ref v) => Some(format!("{:.2}°", v.get(0)?.value()).into()),
265         _ => None,
266     }
267 }
268 
gpstimestamp(e: &TagValue) -> Option<Cow<'static, str>>269 pub(crate) fn gpstimestamp(e: &TagValue) -> Option<Cow<'static, str>> {
270     match *e {
271         TagValue::URational(ref v) => {
272             let sec = v.get(2)?;
273             let hour = v.get(0)?;
274             let min = v.get(1)?;
275             Some(format!(
276                 "{:02.0}:{:02.0}:{:04.1} UTC",
277                 hour.value(),
278                 min.value(),
279                 sec.value()
280             ).into())
281         },
282         _ => None,
283     }
284 }
285 
gpsdiff(e: &TagValue) -> Option<Cow<'static, str>>286 pub(crate) fn gpsdiff(e: &TagValue) -> Option<Cow<'static, str>> {
287     match *e {
288         TagValue::U16(ref v) => {
289             Some(match v.get(0)? {
290                 0 => "Measurement without differential correction".into(),
291                 1 => "Differential correction applied".into(),
292                 n => format!("Unknown ({})", n).into(),
293             })
294         },
295         _ => None,
296     }
297 }
298 
gpsstatus(e: &TagValue) -> Option<Cow<'static, str>>299 pub(crate) fn gpsstatus(e: &TagValue) -> Option<Cow<'static, str>> {
300     match *e {
301         TagValue::Ascii(ref v) => {
302             Some(if v == "A" {
303                 "Measurement in progress".into()
304             } else if v == "V" {
305                 "Measurement is interoperability".into()
306             } else {
307                 format!("Unknown ({})", v).into()
308             })
309         },
310         _ => None,
311     }
312 }
313 
gpsmeasuremode(e: &TagValue) -> Option<Cow<'static, str>>314 pub(crate) fn gpsmeasuremode(e: &TagValue) -> Option<Cow<'static, str>> {
315     match *e {
316         TagValue::Ascii(ref v) => {
317             Some(if v == "2" {
318                 "2-dimension".into()
319             } else if v == "3" {
320                 "3-dimension".into()
321             } else {
322                 format!("Unknown ({})", v).into()
323             })
324         },
325         _ => None,
326     }
327 }
328 
329 /// Interprets an Undefined tag as ASCII, when the contents are guaranteed
330 /// by EXIF standard to be ASCII-compatible. This function accepts UTF-8
331 /// strings, should they be accepted by EXIF standard in the future.
undefined_as_ascii(e: &TagValue) -> Option<Cow<'static, str>>332 pub(crate) fn undefined_as_ascii(e: &TagValue) -> Option<Cow<'static, str>> {
333     match *e {
334         TagValue::Undefined(ref v, _) => Some(String::from_utf8_lossy(&v[..]).into_owned().into()),
335         _ => None,
336     }
337 }
338 
339 /// Outputs an Undefined tag as an array of bytes. Appropriate for tags
340 /// that are opaque and small-sized
undefined_as_u8(e: &TagValue) -> Option<Cow<'static, str>>341 pub(crate) fn undefined_as_u8(e: &TagValue) -> Option<Cow<'static, str>> {
342     match *e {
343         TagValue::Undefined(ref v, _) => Some(NumArray::new(v).to_string().into()),
344         _ => None,
345     }
346 }
347 
348 /// Tries to parse an Undefined tag as containing a string. For some tags,
349 /// the string encoding /// format can be discovered by looking into the first
350 /// 8 bytes.
undefined_as_encoded_string(e: &TagValue) -> Option<Cow<'static, str>>351 pub(crate) fn undefined_as_encoded_string(e: &TagValue) -> Option<Cow<'static, str>> {
352     // "ASCII\0\0\0"
353     static ASC: [u8; 8] = [0x41, 0x53, 0x43, 0x49, 0x49, 0, 0, 0];
354     // "JIS\0\0\0\0\0"
355     static JIS: [u8; 8] = [0x4a, 0x49, 0x53, 0, 0, 0, 0, 0];
356     // "UNICODE\0"
357     static UNICODE: [u8; 8] = [0x55, 0x4e, 0x49, 0x43, 0x4f, 0x44, 0x45, 0x00];
358 
359     match *e {
360         TagValue::Undefined(ref v, le) => {
361             Some(if v.len() < 8 {
362                 format!("String w/ truncated preamble {}", NumArray::new(v))
363             } else if v[0..8] == ASC[..] {
364                 String::from_utf8_lossy(&v[8..]).into_owned()
365             } else if v[0..8] == JIS[..] {
366                 format!("JIS string {}", NumArray::new(&v[8..]))
367             } else if v[0..8] == UNICODE[..] {
368                 let v8 = &v[8..];
369                 // reinterpret as vector of u16
370                 let v16_size = (v8.len() / 2) as u32;
371                 let v16 = read_u16_array(le, v16_size, v8)?;
372                 String::from_utf16_lossy(&v16)
373             } else {
374                 format!("String w/ undefined encoding {}", NumArray::new(v))
375             }.into())
376         },
377         _ => None,
378     }
379 }
380 
381 /// Prints an opaque and long Undefined tag simply as as "blob", noting its length
undefined_as_blob(e: &TagValue) -> Option<Cow<'static, str>>382 pub(crate) fn undefined_as_blob(e: &TagValue) -> Option<Cow<'static, str>> {
383     match *e {
384         TagValue::Undefined(ref v, _) => Some(format!("Blob of {} bytes", v.len()).into()),
385         _ => None,
386     }
387 }
388 
apex_tv(e: &TagValue) -> Option<Cow<'static, str>>389 pub(crate) fn apex_tv(e: &TagValue) -> Option<Cow<'static, str>> {
390     match *e {
391         TagValue::IRational(ref v) => Some(format!("{:.1} Tv APEX", v.get(0)?.value()).into()),
392         _ => None,
393     }
394 }
395 
apex_av(e: &TagValue) -> Option<Cow<'static, str>>396 pub(crate) fn apex_av(e: &TagValue) -> Option<Cow<'static, str>> {
397     match *e {
398         TagValue::URational(ref v) => Some(format!("{:.1} Av APEX", v.get(0)?.value()).into()),
399         _ => None,
400     }
401 }
402 
apex_brightness(e: &TagValue) -> Option<Cow<'static, str>>403 pub(crate) fn apex_brightness(e: &TagValue) -> Option<Cow<'static, str>> {
404     match *e {
405         TagValue::IRational(ref v) => {
406             // numerator 0xffffffff = unknown
407             Some(if v.get(0)?.numerator == -1 {
408                 "Unknown".into()
409             } else {
410                 format!("{:.1} APEX", v.get(0)?.value()).into()
411             })
412         },
413         _ => None,
414     }
415 }
416 
apex_ev(e: &TagValue) -> Option<Cow<'static, str>>417 pub(crate) fn apex_ev(e: &TagValue) -> Option<Cow<'static, str>> {
418     match *e {
419         TagValue::IRational(ref v) => Some(format!("{:.2} EV APEX", v.get(0)?.value()).into()),
420         _ => None,
421     }
422 }
423 
file_source(e: &TagValue) -> Option<Cow<'static, str>>424 pub(crate) fn file_source(e: &TagValue) -> Option<Cow<'static, str>> {
425     match *e {
426         TagValue::Undefined(ref v, _) => {
427             Some(if !v.is_empty() && v[0] == 3 {
428                 "DSC"
429             } else {
430                 "Unknown"
431             }.into())
432         },
433         _ => None,
434     }
435 }
436 
flash_energy(e: &TagValue) -> Option<Cow<'static, str>>437 pub(crate) fn flash_energy(e: &TagValue) -> Option<Cow<'static, str>> {
438     match *e {
439         TagValue::URational(ref v) => Some(format!("{} BCPS", v.get(0)?.value()).into()),
440         _ => None,
441     }
442 }
443 
metering_mode(e: &TagValue) -> Option<Cow<'static, str>>444 pub(crate) fn metering_mode(e: &TagValue) -> Option<Cow<'static, str>> {
445     match *e {
446         TagValue::U16(ref v) => {
447             Some(match v.get(0)? {
448                 0 => "Unknown",
449                 1 => "Average",
450                 2 => "Center-weighted average",
451                 3 => "Spot",
452                 4 => "Multi-spot",
453                 5 => "Pattern",
454                 6 => "Partial",
455                 255 => "Other",
456                 n => return Some(format!("Unknown ({})", n).into()),
457             }.into())
458         },
459         _ => None,
460     }
461 }
462 
light_source(e: &TagValue) -> Option<Cow<'static, str>>463 pub(crate) fn light_source(e: &TagValue) -> Option<Cow<'static, str>> {
464     match *e {
465         TagValue::U16(ref v) => {
466             Some(match v.get(0)? {
467                 0 => "Unknown",
468                 1 => "Daylight",
469                 2 => "Fluorescent",
470                 3 => "Tungsten",
471                 4 => "Flash",
472                 9 => "Fine weather",
473                 10 => "Cloudy weather",
474                 11 => "Shade",
475                 12 => "Daylight fluorescent (D)",
476                 13 => "Day white fluorescent (N)",
477                 14 => "Cool white fluorescent (W)",
478                 15 => "White fluorescent (WW)",
479                 17 => "Standard light A",
480                 18 => "Standard light B",
481                 19 => "Standard light C",
482                 20 => "D55",
483                 21 => "D65",
484                 22 => "D75",
485                 23 => "D50",
486                 24 => "ISO studio tungsten",
487                 255 => "Other",
488                 n => return Some(format!("Unknown ({})", n).into()),
489             }.into())
490         },
491         _ => None,
492     }
493 }
494 
color_space(e: &TagValue) -> Option<Cow<'static, str>>495 pub(crate) fn color_space(e: &TagValue) -> Option<Cow<'static, str>> {
496     match *e {
497         TagValue::U16(ref v) => {
498             Some(match v.get(0)? {
499                 1 => "sRGB",
500                 65535 => "Uncalibrated",
501                 n => return Some(format!("Unknown ({})", n).into()),
502             }.into())
503         },
504         _ => None,
505     }
506 }
507 
flash(e: &TagValue) -> Option<Cow<'static, str>>508 pub(crate) fn flash(e: &TagValue) -> Option<Cow<'static, str>> {
509     match *e {
510         TagValue::U16(ref v) => {
511             let n = v.get(0)?;
512             let mut b0 = "Did not fire. ";
513             let mut b12 = "";
514             let mut b34 = "";
515             let mut b6 = "";
516 
517             if (n & (1 << 5)) > 0 {
518                 return Some("Does not have a flash.".into());
519             }
520 
521             if (n & 1) > 0 {
522                 b0 = "Fired. ";
523                 if (n & (1 << 6)) > 0 {
524                     b6 = "Redeye reduction. "
525                 } else {
526                     b6 = "No redeye reduction. "
527                 }
528 
529                 // bits 1 and 2
530                 let m = (n >> 1) & 3;
531                 if m == 2 {
532                     b12 = "Strobe ret not detected. ";
533                 } else if m == 3 {
534                     b12 = "Strobe ret detected. ";
535                 }
536             }
537 
538             // bits 3 and 4
539             let m = (n >> 3) & 3;
540             if m == 1 {
541                 b34 = "Forced fire. ";
542             } else if m == 2 {
543                 b34 = "Forced suppresion. ";
544             } else if m == 3 {
545                 b12 = "Auto mode. ";
546             }
547 
548             Some(format!("{}{}{}{}", b0, b12, b34, b6).into())
549         },
550         _ => None,
551     }
552 }
553 
subject_area(e: &TagValue) -> Option<Cow<'static, str>>554 pub(crate) fn subject_area(e: &TagValue) -> Option<Cow<'static, str>> {
555     match *e {
556         TagValue::U16(ref v) => Some(match v.len() {
557             2 => format!("at pixel {},{}", v[0], v[1]),
558             3 => format!("at center {},{} radius {}", v[0], v[1], v[2]),
559             4 => format!(
560                 "at rectangle {},{} width {} height {}",
561                 v[0], v[1], v[2], v[3]
562             ),
563             _ => format!("Unknown ({}) ", NumArray::new(v)),
564         }.into()),
565         _ => None,
566     }
567 }
568 
subject_location(e: &TagValue) -> Option<Cow<'static, str>>569 pub(crate) fn subject_location(e: &TagValue) -> Option<Cow<'static, str>> {
570     match *e {
571         TagValue::U16(ref v) if v.len() >= 2 => Some(format!("at pixel {},{}", v[0], v[1]).into()),
572         _ => None,
573     }
574 }
575 
sharpness(e: &TagValue) -> Option<Cow<'static, str>>576 pub(crate) fn sharpness(e: &TagValue) -> Option<Cow<'static, str>> {
577     match *e {
578         TagValue::U16(ref v) => {
579             Some(match v.get(0)? {
580                 0 => "Normal",
581                 1 => "Soft",
582                 2 => "Hard",
583                 n => return Some(format!("Unknown ({})", n).into()),
584             }.into())
585         },
586         _ => None,
587     }
588 }
589 
saturation(e: &TagValue) -> Option<Cow<'static, str>>590 pub(crate) fn saturation(e: &TagValue) -> Option<Cow<'static, str>> {
591     match *e {
592         TagValue::U16(ref v) => {
593             Some(match v.get(0)? {
594                 0 => "Normal",
595                 1 => "Low",
596                 2 => "High",
597                 n => return Some(format!("Unknown ({})", n).into()),
598             }.into())
599         },
600         _ => None,
601     }
602 }
603 
contrast(e: &TagValue) -> Option<Cow<'static, str>>604 pub(crate) fn contrast(e: &TagValue) -> Option<Cow<'static, str>> {
605     match *e {
606         TagValue::U16(ref v) => {
607             Some(match v.get(0)? {
608                 0 => "Normal",
609                 1 => "Soft",
610                 2 => "Hard",
611                 n => return Some(format!("Unknown ({})", n).into()),
612             }.into())
613         },
614         _ => None,
615     }
616 }
617 
gain_control(e: &TagValue) -> Option<Cow<'static, str>>618 pub(crate) fn gain_control(e: &TagValue) -> Option<Cow<'static, str>> {
619     match *e {
620         TagValue::U16(ref v) => {
621             Some(match v.get(0)? {
622                 0 => "None",
623                 1 => "Low gain up",
624                 2 => "High gain up",
625                 3 => "Low gain down",
626                 4 => "High gain down",
627                 n => return Some(format!("Unknown ({})", n).into()),
628             }.into())
629         },
630         _ => None,
631     }
632 }
633 
exposure_mode(e: &TagValue) -> Option<Cow<'static, str>>634 pub(crate) fn exposure_mode(e: &TagValue) -> Option<Cow<'static, str>> {
635     match *e {
636         TagValue::U16(ref v) => {
637             Some(match v.get(0)? {
638                 0 => "Auto exposure",
639                 1 => "Manual exposure",
640                 2 => "Auto bracket",
641                 n => return Some(format!("Unknown ({})", n).into()),
642             }.into())
643         },
644         _ => None,
645     }
646 }
647 
scene_capture_type(e: &TagValue) -> Option<Cow<'static, str>>648 pub(crate) fn scene_capture_type(e: &TagValue) -> Option<Cow<'static, str>> {
649     match *e {
650         TagValue::U16(ref v) => {
651             Some(match v.get(0)? {
652                 0 => "Standard",
653                 1 => "Landscape",
654                 2 => "Portrait",
655                 3 => "Night scene",
656                 n => return Some(format!("Unknown ({})", n).into()),
657             }.into())
658         },
659         _ => None,
660     }
661 }
662 
scene_type(e: &TagValue) -> Option<Cow<'static, str>>663 pub(crate) fn scene_type(e: &TagValue) -> Option<Cow<'static, str>> {
664     match *e {
665         TagValue::Undefined(ref v, _) => {
666             Some(match v.get(0)? {
667                 1 => "Directly photographed image",
668                 n => return Some(format!("Unknown ({})", n).into()),
669             }.into())
670         },
671         _ => None,
672     }
673 }
674 
white_balance_mode(e: &TagValue) -> Option<Cow<'static, str>>675 pub(crate) fn white_balance_mode(e: &TagValue) -> Option<Cow<'static, str>> {
676     match *e {
677         TagValue::U16(ref v) => {
678             Some(match v.get(0)? {
679                 0 => "Auto",
680                 1 => "Manual",
681                 n => return Some(format!("Unknown ({})", n).into()),
682             }.into())
683         },
684         _ => None,
685     }
686 }
687 
sensing_method(e: &TagValue) -> Option<Cow<'static, str>>688 pub(crate) fn sensing_method(e: &TagValue) -> Option<Cow<'static, str>> {
689     match *e {
690         TagValue::U16(ref v) => {
691             Some(match v.get(0)? {
692                 1 => "Not defined",
693                 2 => "One-chip color area sensor",
694                 3 => "Two-chip color area sensor",
695                 4 => "Three-chip color area sensor",
696                 5 => "Color sequential area sensor",
697                 7 => "Trilinear sensor",
698                 8 => "Color sequential linear sensor",
699                 n => return Some(format!("Unknown ({})", n).into()),
700             }.into())
701         },
702         _ => None,
703     }
704 }
705 
custom_rendered(e: &TagValue) -> Option<Cow<'static, str>>706 pub(crate) fn custom_rendered(e: &TagValue) -> Option<Cow<'static, str>> {
707     match *e {
708         TagValue::U16(ref v) => {
709             Some(match v.get(0)? {
710                 0 => "Normal",
711                 1 => "Custom",
712                 n => return Some(format!("Unknown ({})", n).into()),
713             }.into())
714         },
715         _ => None,
716     }
717 }
718 
subject_distance_range(e: &TagValue) -> Option<Cow<'static, str>>719 pub(crate) fn subject_distance_range(e: &TagValue) -> Option<Cow<'static, str>> {
720     match *e {
721         TagValue::U16(ref v) => {
722             Some(match v.get(0)? {
723                 0 => "Unknown",
724                 1 => "Macro",
725                 2 => "Close view",
726                 3 => "Distant view",
727                 n => return Some(format!("Unknown ({})", n).into()),
728             }.into())
729         },
730         _ => None,
731     }
732 }
733 
lens_spec(e: &TagValue) -> Option<Cow<'static, str>>734 pub(crate) fn lens_spec(e: &TagValue) -> Option<Cow<'static, str>> {
735     match *e {
736         TagValue::URational(ref v) if v.len() >= 4 => {
737             let f0 = v[0].value();
738             let f1 = v[1].value();
739             let a0 = v[2].value();
740             let a1 = v[3].value();
741 
742             Some(if v[0] == v[1] {
743                 if a0.is_finite() {
744                     format!("{} mm f/{:.1}", f0, a0)
745                 } else {
746                     format!("{} mm f/unknown", f0)
747                 }
748             } else if a0.is_finite() && a1.is_finite() {
749                 format!("{}-{} mm f/{:.1}-{:.1}", f0, f1, a0, a1)
750             } else {
751                 format!("{}-{} mm f/unknown", f0, f1)
752             }.into())
753         },
754         _ => None,
755     }
756 }
757