1use byteorder::{BigEndian, ByteOrder, LittleEndian};
7
8use std::cell::Cell;
9
10use crate::error::{Error, Result};
11use crate::tag::{Tag, TagGroup, TagId};
12use crate::tags::exif as exif_tags;
13use crate::value::Value;
14
15thread_local! {
16 static SHOW_UNKNOWN: Cell<u8> = const { Cell::new(0) };
17}
18
19pub fn set_show_unknown(level: u8) {
21 SHOW_UNKNOWN.with(|s| s.set(level));
22}
23
24pub fn get_show_unknown() -> u8 {
26 SHOW_UNKNOWN.with(|s| s.get())
27}
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq)]
31pub enum ByteOrderMark {
32 LittleEndian,
33 BigEndian,
34}
35
36#[derive(Debug)]
38pub struct TiffHeader {
39 pub byte_order: ByteOrderMark,
40 pub ifd0_offset: u32,
41}
42
43#[derive(Debug)]
45struct IfdEntry {
46 tag: u16,
47 data_type: u16,
48 count: u32,
49 value_offset: u32,
50 inline_data: [u8; 4],
52}
53
54fn type_size(data_type: u16) -> Option<usize> {
56 match data_type {
57 1 => Some(1), 2 => Some(1), 3 => Some(2), 4 => Some(4), 5 => Some(8), 6 => Some(1), 7 => Some(1), 8 => Some(2), 9 => Some(4), 10 => Some(8), 11 => Some(4), 12 => Some(8), 13 => Some(4), _ => None,
71 }
72}
73
74pub fn parse_tiff_header(data: &[u8]) -> Result<TiffHeader> {
76 if data.len() < 8 {
77 return Err(Error::InvalidTiffHeader);
78 }
79
80 let byte_order = match (data[0], data[1]) {
81 (b'I', b'I') => ByteOrderMark::LittleEndian,
82 (b'M', b'M') => ByteOrderMark::BigEndian,
83 _ => return Err(Error::InvalidTiffHeader),
84 };
85
86 let magic = match byte_order {
87 ByteOrderMark::LittleEndian => LittleEndian::read_u16(&data[2..4]),
88 ByteOrderMark::BigEndian => BigEndian::read_u16(&data[2..4]),
89 };
90
91 if magic != 42 {
92 return Err(Error::InvalidTiffHeader);
93 }
94
95 let ifd0_offset = match byte_order {
96 ByteOrderMark::LittleEndian => LittleEndian::read_u32(&data[4..8]),
97 ByteOrderMark::BigEndian => BigEndian::read_u32(&data[4..8]),
98 };
99
100 Ok(TiffHeader {
101 byte_order,
102 ifd0_offset,
103 })
104}
105
106pub(crate) const EXIF_PRIMARY_TAGS: &[&str] = &[
110 "ThumbnailOffset",
111 "ThumbnailLength",
112 "ThumbnailImage",
113 "StripOffsets",
114 "StripByteCounts",
115 "PreviewImageStart",
116 "PreviewImageLength",
117 "PreviewImage",
118 "ImageWidth",
119 "ImageHeight",
120 "BitsPerSample",
121 "Compression",
122 "PhotometricInterpretation",
123 "SamplesPerPixel",
124 "RowsPerStrip",
125 "PlanarConfiguration",
126 "XResolution",
127 "YResolution",
128 "ResolutionUnit",
129 "Orientation",
130 "Make",
131 "Model",
132 "Software",
133 "ExifByteOrder",
134 "CR2CFAPattern",
135 "RawImageSegmentation",
136 "ColorSpace",
137 "ExifVersion",
138 "FlashpixVersion",
139 "ExifImageWidth",
140 "ExifImageHeight",
141 "InteropIndex",
142 "InteropVersion",
143 "DateTimeOriginal",
144 "CreateDate",
145 "ModifyDate",
146 "DateTime",
147 "FocalPlaneXResolution",
148 "FocalPlaneYResolution",
149 "FocalPlaneResolutionUnit",
150 "CustomRendered",
151 "ExposureMode",
152 "SceneCaptureType",
153 "PrintIMVersion",
155 "Flash",
156 "FocalLength",
157 "ISO",
158 "ExposureTime",
159 "ExposureProgram",
160 "FNumber",
161 "ShutterSpeedValue",
162 "ApertureValue",
163 "ComponentsConfiguration",
164 "UserComment",
165 "Contrast",
168 "Saturation",
169 "Sharpness",
170];
171
172pub struct ExifReader;
174
175impl ExifReader {
176 pub fn read(data: &[u8]) -> Result<Vec<Tag>> {
178 Self::read_with_base(data, 0)
179 }
180
181 pub fn read_with_base(data: &[u8], base: usize) -> Result<Vec<Tag>> {
184 let mut tags = Self::read_inner(data, base)?;
185 if base != 0 {
186 const OFFSET_TAGS: &[&str] = &[
188 "ThumbnailOffset",
189 "PreviewImageStart",
190 "JpgFromRawStart",
191 "OtherImageStart",
192 "StripOffsets",
193 ];
194 for t in tags.iter_mut() {
195 if OFFSET_TAGS.contains(&t.name.as_str()) {
196 if let Some(off) = t.raw_value.as_u64() {
197 let abs = off + base as u64;
198 t.raw_value = Value::U32(abs as u32);
199 t.print_value = abs.to_string();
200 }
201 }
202 }
203 }
204 Ok(tags)
205 }
206
207 fn read_inner(data: &[u8], exif_base: usize) -> Result<Vec<Tag>> {
208 let header = parse_tiff_header(data)?;
209 let mut tags = Vec::new();
210
211 let bo_str = match header.byte_order {
213 ByteOrderMark::LittleEndian => "Little-endian (Intel, II)",
214 ByteOrderMark::BigEndian => "Big-endian (Motorola, MM)",
215 };
216 tags.push(Tag {
217 id: TagId::Text("ExifByteOrder".to_string()),
218 name: "ExifByteOrder".to_string(),
219 description: "Exif Byte Order".to_string(),
220 group: TagGroup {
221 family0: "EXIF".to_string(),
222 family1: "IFD0".to_string(),
223 family2: "ExifTool".to_string(),
224 },
225 raw_value: Value::String(bo_str.to_string()),
226 print_value: bo_str.to_string(),
227 priority: 0,
228 });
229
230 let is_cr2 = data.len() > 10 && &data[8..10] == b"CR";
232
233 Self::read_ifd(data, &header, header.ifd0_offset, "IFD0", &mut tags)?;
235
236 if is_cr2 {
239 for tag in tags.iter_mut() {
241 if tag.group.family1 == "IFD0" {
242 if tag.name == "StripOffsets" {
243 tag.name = "PreviewImageStart".to_string();
244 tag.description = "Preview Image Start".to_string();
245 tag.id = TagId::Text("PreviewImageStart".to_string());
246 } else if tag.name == "StripByteCounts" {
247 tag.name = "PreviewImageLength".to_string();
248 tag.description = "Preview Image Length".to_string();
249 tag.id = TagId::Text("PreviewImageLength".to_string());
250 }
251 }
252 }
253 let preview_start = tags
255 .iter()
256 .find(|t| t.name == "PreviewImageStart" && t.group.family1 == "IFD0")
257 .and_then(|t| t.raw_value.as_u64())
258 .map(|v| v as usize);
259 let preview_len = tags
260 .iter()
261 .find(|t| t.name == "PreviewImageLength" && t.group.family1 == "IFD0")
262 .and_then(|t| t.raw_value.as_u64())
263 .map(|v| v as usize);
264 if let (Some(start), Some(len)) = (preview_start, preview_len) {
265 if len > 0 && start + len <= data.len() {
266 let img_data = data[start..start + len].to_vec();
267 let pv = format!("(Binary data {} bytes, use -b option to extract)", len);
268 tags.push(Tag {
269 id: TagId::Text("PreviewImage".to_string()),
270 name: "PreviewImage".to_string(),
271 description: "Preview Image".to_string(),
272 group: TagGroup {
273 family0: "EXIF".to_string(),
274 family1: "IFD0".to_string(),
275 family2: "Preview".to_string(),
276 },
277 raw_value: Value::Binary(img_data),
278 print_value: pv,
279 priority: 0,
280 });
281 }
282 }
283 }
284
285 let make = tags
287 .iter()
288 .find(|t| t.name == "Make")
289 .map(|t| t.print_value.clone())
290 .unwrap_or_default();
291
292 let model = tags
293 .iter()
294 .find(|t| t.name == "Model")
295 .map(|t| t.print_value.clone())
296 .unwrap_or_default();
297
298 let make_and_model = if model.is_empty() {
300 make.clone()
301 } else {
302 model
303 };
304
305 let mn_info: Option<(usize, usize)> = {
308 let mut result = None;
310 Self::find_makernote(data, &header, &mut result);
311 result
312 };
313
314 if let Some((mn_offset, mn_size)) = mn_info {
315 let mn_tags = crate::metadata::makernotes::parse_makernotes_exif_base(
316 data,
317 mn_offset,
318 mn_size,
319 &make,
320 &make_and_model,
321 header.byte_order,
322 exif_base,
323 );
324 tags.retain(|t| t.name != "MakerNote");
326 {
330 let exif_primary: &[&str] = EXIF_PRIMARY_TAGS;
332 let mn_name_set: std::collections::HashSet<String> = mn_tags
336 .iter()
337 .filter(|t| t.priority >= 0)
338 .map(|t| t.name.clone())
339 .collect();
340 let exif_has: std::collections::HashSet<String> =
341 tags.iter().map(|t| t.name.clone()).collect();
342 tags.retain(|t| {
344 !mn_name_set.contains(&t.name) || exif_primary.contains(&t.name.as_str())
345 });
346 for mn_tag in mn_tags {
351 let authoritative = mn_tag.group.family1 == "Kodak"
352 && matches!(mn_tag.name.as_str(), "FNumber" | "ExposureTime");
353 if !authoritative
354 && exif_primary.contains(&mn_tag.name.as_str())
355 && exif_has.contains(&mn_tag.name)
356 {
357 continue;
359 }
360 tags.push(mn_tag);
361 }
362 }
363 }
364
365 if mn_info.is_none() {
367 Self::parse_dng_private_data(data, &header, &make, &make_and_model, &mut tags);
370 }
371
372 {
375 let iptc_data: Option<Vec<u8>> =
376 tags.iter().find(|t| t.name == "IPTC-NAA").and_then(|t| {
377 match &t.raw_value {
378 Value::Undefined(bytes) => Some(bytes.clone()),
379 Value::Binary(bytes) => Some(bytes.clone()),
380 Value::List(items) => {
381 let mut bytes = Vec::with_capacity(items.len() * 4);
383 for item in items {
384 if let Value::U32(v) = item {
385 bytes.extend_from_slice(&v.to_be_bytes())
386 }
387 }
388 if bytes.is_empty() {
389 None
390 } else {
391 Some(bytes)
392 }
393 }
394 _ => None,
395 }
396 });
397
398 if let Some(iptc_bytes) = iptc_data {
399 let md5_hex = crate::md5::md5_hex(&iptc_bytes);
401
402 if let Ok(iptc_tags) = crate::metadata::IptcReader::read(&iptc_bytes) {
403 tags.retain(|t| t.name != "IPTC-NAA");
405 tags.extend(iptc_tags);
406 }
407
408 tags.push(crate::tag::Tag {
410 id: crate::tag::TagId::Text("CurrentIPTCDigest".into()),
411 name: "CurrentIPTCDigest".into(),
412 description: "Current IPTC Digest".into(),
413 group: crate::tag::TagGroup {
414 family0: "IPTC".into(),
415 family1: "IPTC".into(),
416 family2: "Other".into(),
417 },
418 raw_value: Value::String(md5_hex.clone()),
419 print_value: md5_hex,
420 priority: 0,
421 });
422 }
423 }
424
425 {
427 let icc_data: Option<Vec<u8>> =
428 tags.iter()
429 .find(|t| t.name == "ICC_Profile")
430 .and_then(|t| match &t.raw_value {
431 Value::Undefined(bytes) => Some(bytes.clone()),
432 Value::Binary(bytes) => Some(bytes.clone()),
433 _ => None,
434 });
435
436 if let Some(icc_bytes) = icc_data {
437 if let Ok(icc_tags) = crate::formats::icc::read_icc(&icc_bytes) {
438 tags.retain(|t| t.name != "ICC_Profile");
440 tags.extend(icc_tags);
441 }
442 }
443 }
444
445 process_geotiff_keys(&mut tags);
447
448 {
453 let mn_tags_start = tags
455 .iter()
456 .position(|t| t.group.family0 == "MakerNotes")
457 .unwrap_or(tags.len());
458 if mn_tags_start < tags.len() {
459 let mut last_idx: std::collections::HashMap<&str, usize> =
461 std::collections::HashMap::new();
462 for (i, t) in tags[mn_tags_start..].iter().enumerate() {
463 last_idx.insert(t.name.as_str(), i + mn_tags_start);
464 }
465 let mut seen: std::collections::HashSet<&str> = std::collections::HashSet::new();
467 let mut keep = vec![false; tags.len()];
469 for (i, t) in tags.iter().enumerate().rev() {
470 if t.group.family0 != "MakerNotes" {
471 keep[i] = true;
472 continue;
473 }
474 if seen.insert(t.name.as_str()) {
475 keep[i] = true; }
477 }
478 let mut iter = keep.iter();
479 tags.retain(|_| *iter.next().unwrap_or(&true));
480 }
481 }
482
483 for (coord, reftag) in [
486 ("GPSLatitude", "GPSLatitudeRef"),
487 ("GPSLongitude", "GPSLongitudeRef"),
488 ] {
489 let r = tags
490 .iter()
491 .find(|t| t.name == reftag)
492 .map(|t| t.raw_value.to_display_string())
493 .and_then(|s| s.chars().next())
494 .filter(|c| "NSEW".contains(*c));
495 if let Some(refc) = r {
496 if let Some(t) = tags.iter_mut().find(|t| t.name == coord) {
497 let parts: Vec<f64> = t
498 .raw_value
499 .to_display_string()
500 .split_whitespace()
501 .filter_map(|s| s.parse::<f64>().ok())
502 .collect();
503 if parts.len() == 3 {
504 let dec = parts[0] + parts[1] / 60.0 + parts[2] / 3600.0;
505 let deg = dec.floor();
506 let rem = (dec - deg) * 60.0;
507 let min = rem.floor();
508 let sec = (rem - min) * 60.0;
509 t.print_value =
510 format!("{} deg {}' {:.2}\" {}", deg as i64, min as i64, sec, refc);
511 }
512 }
513 }
514 if let Some(t) = tags.iter_mut().find(|t| t.name == coord) {
516 if t.print_value.split_whitespace().all(|p| p == "undef") {
517 t.print_value = String::new();
518 }
519 }
520 }
521
522 let fullres_group = tags
528 .iter()
529 .find(|t| {
530 t.name == "SubfileType"
531 && t.print_value == "Full-resolution image"
532 && t.group.family1 != "IFD0"
533 })
534 .map(|t| t.group.family1.clone());
535 if let Some(group) = fullres_group {
536 for dim in ["ImageHeight", "ImageWidth"] {
537 if let Some(pos) = tags
538 .iter()
539 .position(|t| t.name == dim && t.group.family1 == group)
540 {
541 let t = tags.remove(pos);
542 tags.insert(0, t);
543 }
544 }
545 }
546
547 Ok(tags)
548 }
549
550 fn parse_dng_private_data(
553 data: &[u8],
554 header: &TiffHeader,
555 make: &str,
556 model: &str,
557 tags: &mut Vec<Tag>,
558 ) {
559 let ifd0_offset = header.ifd0_offset as usize;
561 if ifd0_offset + 2 > data.len() {
562 return;
563 }
564 let entry_count = read_u16(data, ifd0_offset, header.byte_order) as usize;
565 let entries_start = ifd0_offset + 2;
566 for i in 0..entry_count {
567 let eoff = entries_start + i * 12;
568 if eoff + 12 > data.len() {
569 break;
570 }
571 let tag = read_u16(data, eoff, header.byte_order);
572 if tag == 0xC634 {
573 let dtype = read_u16(data, eoff + 2, header.byte_order);
574 let count = read_u32(data, eoff + 4, header.byte_order) as usize;
575 let elem_size = match dtype {
576 1 | 7 => 1,
577 _ => 0,
578 };
579 let total = elem_size * count;
580 if total < 14 {
581 continue;
582 }
583 let off = read_u32(data, eoff + 8, header.byte_order) as usize;
584 if off + total > data.len() {
585 continue;
586 }
587 let pdata = &data[off..off + total];
588 if !pdata.starts_with(b"Adobe\0") {
590 continue;
591 }
592 let mut bpos = 6;
593 while bpos + 8 <= pdata.len() {
594 let btag = &pdata[bpos..bpos + 4];
595 let bsize = u32::from_be_bytes([
596 pdata[bpos + 4],
597 pdata[bpos + 5],
598 pdata[bpos + 6],
599 pdata[bpos + 7],
600 ]) as usize;
601 bpos += 8;
602 if bpos + bsize > pdata.len() {
603 break;
604 }
605 if btag == b"MakN" && bsize > 6 {
606 let mn_block = &pdata[bpos..bpos + bsize];
607 let mn_bo = if &mn_block[0..2] == b"II" {
608 ByteOrderMark::LittleEndian
609 } else {
610 ByteOrderMark::BigEndian
611 };
612 let mut mn_start = 6; if bsize >= 18 && &mn_block[6..10] == b"\0\0\0\x01" {
615 mn_start += 12;
616 }
617 if mn_start < bsize {
618 let mn_bo_str = if mn_bo == ByteOrderMark::LittleEndian {
620 "Little-endian (Intel, II)"
621 } else {
622 "Big-endian (Motorola, MM)"
623 };
624 tags.push(Tag {
625 id: TagId::Text("MakerNoteByteOrder".into()),
626 name: "MakerNoteByteOrder".into(),
627 description: "Maker Note Byte Order".into(),
628 group: TagGroup {
629 family0: "File".into(),
630 family1: "File".into(),
631 family2: "Image".into(),
632 },
633 raw_value: Value::String(mn_bo_str.into()),
634 print_value: mn_bo_str.into(),
635 priority: 0,
636 });
637 let mn_data_in_block = &mn_block[mn_start..];
641 let mn_abs_offset = off + (bpos - 8 + 8) + mn_start; let fix_base = if mn_data_in_block.len() > 8 {
644 let footer = &mn_data_in_block[mn_data_in_block.len() - 8..];
645 if (footer[0..2] == *b"II" || footer[0..2] == *b"MM")
646 && (footer[2..4] == *b"\x2a\x00"
647 || footer[2..4] == *b"\x00\x2a")
648 {
649 let old_off = if footer[0] == b'I' {
650 u32::from_le_bytes([
651 footer[4], footer[5], footer[6], footer[7],
652 ])
653 } else {
654 u32::from_be_bytes([
655 footer[4], footer[5], footer[6], footer[7],
656 ])
657 } as usize;
658 if old_off > 0 && mn_abs_offset > old_off {
659 mn_abs_offset as isize - old_off as isize
660 } else {
661 0
662 }
663 } else {
664 0
665 }
666 } else {
667 0
668 };
669
670 let mn_tags = if fix_base != 0 {
671 crate::metadata::makernotes::parse_makernotes_with_base(
673 data,
674 mn_abs_offset,
675 mn_data_in_block.len(),
676 make,
677 model,
678 mn_bo,
679 fix_base,
680 )
681 } else {
682 crate::metadata::makernotes::parse_makernotes(
683 mn_data_in_block,
684 0,
685 mn_data_in_block.len(),
686 make,
687 model,
688 mn_bo,
689 )
690 };
691 let dng_suppress = [
693 "AESetting",
694 "CameraISO",
695 "ImageStabilization",
696 "SpotMeteringMode",
697 "RawJpgSize",
698 "Warning",
699 ];
700 for mn_tag in mn_tags {
701 if dng_suppress.contains(&mn_tag.name.as_str()) {
702 continue;
703 }
704 let exists = tags.iter().any(|t| t.name == mn_tag.name);
705 if exists {
706 if EXIF_PRIMARY_TAGS.contains(&mn_tag.name.as_str()) {
710 continue;
711 }
712 tags.retain(|t| t.name != mn_tag.name);
713 }
714 tags.push(mn_tag);
715 }
716 }
717 }
718 bpos += bsize;
719 }
720 break;
721 }
722 }
723 }
724
725 fn find_makernote(data: &[u8], header: &TiffHeader, result: &mut Option<(usize, usize)>) {
726 let ifd0_offset = header.ifd0_offset as usize;
728 if ifd0_offset + 2 > data.len() {
729 return;
730 }
731 let entry_count = read_u16(data, ifd0_offset, header.byte_order) as usize;
732 let entries_start = ifd0_offset + 2;
733
734 for i in 0..entry_count {
735 let eoff = entries_start + i * 12;
736 if eoff + 12 > data.len() {
737 break;
738 }
739 let tag = read_u16(data, eoff, header.byte_order);
740 if tag == 0x8769 {
741 let exif_offset = read_u32(data, eoff + 8, header.byte_order) as usize;
743 Self::find_makernote_in_ifd(data, header, exif_offset, result);
744 break;
745 }
746 }
747 }
748
749 fn find_makernote_in_ifd(
750 data: &[u8],
751 header: &TiffHeader,
752 ifd_offset: usize,
753 result: &mut Option<(usize, usize)>,
754 ) {
755 if ifd_offset + 2 > data.len() {
756 return;
757 }
758 let entry_count = read_u16(data, ifd_offset, header.byte_order) as usize;
759 let entries_start = ifd_offset + 2;
760
761 for i in 0..entry_count {
762 let eoff = entries_start + i * 12;
763 if eoff + 12 > data.len() {
764 break;
765 }
766 let tag = read_u16(data, eoff, header.byte_order);
767 if tag == 0x927C {
768 let data_type = read_u16(data, eoff + 2, header.byte_order);
769 let count = read_u32(data, eoff + 4, header.byte_order) as usize;
770 let type_size = match data_type {
771 1 | 2 | 6 | 7 => 1,
772 3 | 8 => 2,
773 4 | 9 | 11 | 13 => 4,
774 5 | 10 | 12 => 8,
775 _ => 1,
776 };
777 let total_size = type_size * count;
778
779 if total_size <= 4 {
780 break;
782 }
783 let offset = read_u32(data, eoff + 8, header.byte_order) as usize;
784 if offset + total_size <= data.len() {
785 *result = Some((offset, total_size));
786 }
787 break;
788 }
789 }
790 }
791
792 fn read_ifd(
794 data: &[u8],
795 header: &TiffHeader,
796 offset: u32,
797 ifd_name: &str,
798 tags: &mut Vec<Tag>,
799 ) -> Result<Option<u32>> {
800 let offset = offset as usize;
801 if offset + 2 > data.len() {
802 return Err(Error::InvalidExif(format!(
803 "{} offset {} beyond data length {}",
804 ifd_name,
805 offset,
806 data.len()
807 )));
808 }
809
810 let entry_count = read_u16(data, offset, header.byte_order) as usize;
811 let entries_start = offset + 2;
812 let _entries_end = entries_start + entry_count * 12;
813
814 if entries_start + 12 > data.len() && entry_count > 0 {
816 return Err(Error::InvalidExif(format!(
817 "{} entries extend beyond data (need {}, have {})",
818 ifd_name,
819 entries_start + 12,
820 data.len()
821 )));
822 }
823 let entry_count = entry_count.min((data.len().saturating_sub(entries_start)) / 12);
825 let entries_end = entries_start + entry_count * 12;
826
827 for i in 0..entry_count {
828 let entry_offset = entries_start + i * 12;
829 let entry = parse_ifd_entry(data, entry_offset, header.byte_order);
830
831 match entry.tag {
833 0x8769 => {
834 let sub_offset = entry.value_offset;
836 if (sub_offset as usize) < data.len() {
837 let _ = Self::read_ifd(data, header, sub_offset, "ExifIFD", tags);
838 }
839 continue;
840 }
841 0x8825 => {
842 let sub_offset = entry.value_offset;
844 if (sub_offset as usize) < data.len() {
845 let _ = Self::read_ifd(data, header, sub_offset, "GPS", tags);
846 }
847 continue;
848 }
849 0xA005 => {
850 let sub_offset = entry.value_offset;
852 if (sub_offset as usize) < data.len() {
853 let _ = Self::read_ifd(data, header, sub_offset, "InteropIFD", tags);
854 }
855 continue;
856 }
857 0xC4A5 => {
859 let total_size = match entry.data_type {
860 1 | 2 | 6 | 7 => entry.count as usize,
861 _ => 0,
862 };
863 if total_size >= 12 {
864 let off = entry.value_offset as usize;
865 if off + 12 <= data.len() && &data[off..off + 7] == b"PrintIM" {
866 let ver =
868 crate::encoding::decode_utf8_or_latin1(&data[off + 8..off + 12])
869 .trim_end_matches('\0')
870 .to_string();
871 tags.push(Tag {
872 id: TagId::Text("PrintIMVersion".into()),
873 name: "PrintIMVersion".into(),
874 description: "PrintIM Version".into(),
875 group: TagGroup {
876 family0: "PrintIM".into(),
877 family1: "PrintIM".into(),
878 family2: "Printing".into(),
879 },
880 raw_value: Value::String(ver.clone()),
881 print_value: ver,
882 priority: 0,
883 });
884 }
885 }
886 continue; }
888 0x0006 if ifd_name == "GPS" => {
890 if let Some(Value::URational(0, 0)) =
891 read_ifd_value(data, &entry, header.byte_order)
892 {
893 continue;
894 }
895 }
896 0x0201 if ifd_name.starts_with("SubIFD") => {
898 if let Some(val) = read_ifd_value(data, &entry, header.byte_order) {
899 let pv = val.to_display_string();
900 tags.push(Tag {
901 id: TagId::Numeric(entry.tag),
902 name: "JpgFromRawStart".into(),
903 description: "Jpg From Raw Start".into(),
904 group: TagGroup {
905 family0: "EXIF".into(),
906 family1: ifd_name.to_string(),
907 family2: "Image".into(),
908 },
909 raw_value: val,
910 print_value: pv,
911 priority: 0,
912 });
913 }
914 continue;
915 }
916 0x0202 if ifd_name.starts_with("SubIFD") => {
918 if let Some(val) = read_ifd_value(data, &entry, header.byte_order) {
919 let pv = val.to_display_string();
920 tags.push(Tag {
921 id: TagId::Numeric(entry.tag),
922 name: "JpgFromRawLength".into(),
923 description: "Jpg From Raw Length".into(),
924 group: TagGroup {
925 family0: "EXIF".into(),
926 family1: ifd_name.to_string(),
927 family2: "Image".into(),
928 },
929 raw_value: val,
930 print_value: pv,
931 priority: 0,
932 });
933 }
934 continue;
935 }
936 0x014A if ifd_name == "IFD0" => {
938 if let Some(val) = read_ifd_value(data, &entry, header.byte_order) {
940 let offsets: Vec<u32> = match &val {
941 Value::U32(v) => vec![*v],
942 Value::List(items) => items
943 .iter()
944 .filter_map(|v| {
945 if let Value::U32(o) = v {
946 Some(*o)
947 } else {
948 None
949 }
950 })
951 .collect(),
952 _ => vec![],
953 };
954 for (idx, &off) in offsets.iter().enumerate() {
955 if (off as usize) < data.len() {
956 let sub_name = format!("SubIFD{}", idx);
957 let before_idx = tags.len();
958 let _ = Self::read_ifd(data, header, off, &sub_name, tags);
959
960 let is_jpeg = tags[before_idx..].iter().any(|t| {
962 t.name == "Compression"
963 && (t.print_value.contains("JPEG")
964 || t.raw_value.as_u64() == Some(6))
965 });
966
967 if is_jpeg {
968 let (start_name, len_name, img_name) = if idx == 2 {
971 ("JpgFromRawStart", "JpgFromRawLength", "JpgFromRaw")
972 } else {
973 ("PreviewImageStart", "PreviewImageLength", "PreviewImage")
974 };
975 let strip_off = tags[before_idx..]
977 .iter()
978 .find(|t| t.name == "StripOffsets")
979 .and_then(|t| t.raw_value.as_u64());
980 let strip_len = tags[before_idx..]
981 .iter()
982 .find(|t| t.name == "StripByteCounts")
983 .and_then(|t| t.raw_value.as_u64());
984 if let (Some(s), Some(l)) = (strip_off, strip_len) {
985 tags.push(Tag {
986 id: TagId::Text(start_name.into()),
987 name: start_name.into(),
988 description: start_name.into(),
989 group: TagGroup {
990 family0: "EXIF".into(),
991 family1: sub_name.clone(),
992 family2: "Preview".into(),
993 },
994 raw_value: Value::U32(s as u32),
995 print_value: s.to_string(),
996 priority: 0,
997 });
998 tags.push(Tag {
999 id: TagId::Text(len_name.into()),
1000 name: len_name.into(),
1001 description: len_name.into(),
1002 group: TagGroup {
1003 family0: "EXIF".into(),
1004 family1: sub_name.clone(),
1005 family2: "Preview".into(),
1006 },
1007 raw_value: Value::U32(l as u32),
1008 print_value: l.to_string(),
1009 priority: 0,
1010 });
1011 let s = s as usize;
1013 let l = l as usize;
1014 if l > 0 && s + l <= data.len() {
1015 let pv = format!(
1016 "(Binary data {} bytes, use -b option to extract)",
1017 l
1018 );
1019 tags.push(Tag {
1020 id: TagId::Text(img_name.into()),
1021 name: img_name.into(),
1022 description: img_name.into(),
1023 group: TagGroup {
1024 family0: "EXIF".into(),
1025 family1: sub_name.clone(),
1026 family2: "Preview".into(),
1027 },
1028 raw_value: Value::Binary(data[s..s + l].to_vec()),
1029 print_value: pv,
1030 priority: 0,
1031 });
1032 }
1033 }
1034 }
1035
1036 let jpg_start = tags[before_idx..]
1038 .iter()
1039 .find(|t| t.name == "JpgFromRawStart")
1040 .and_then(|t| t.raw_value.as_u64());
1041 let jpg_len = tags[before_idx..]
1042 .iter()
1043 .find(|t| t.name == "JpgFromRawLength")
1044 .and_then(|t| t.raw_value.as_u64());
1045 if let (Some(start), Some(len)) = (jpg_start, jpg_len) {
1046 let start = start as usize;
1047 let len = len as usize;
1048 if len > 0 && start + len <= data.len() {
1049 let pv = format!(
1050 "(Binary data {} bytes, use -b option to extract)",
1051 len
1052 );
1053 tags.push(Tag {
1054 id: TagId::Text("JpgFromRaw".into()),
1055 name: "JpgFromRaw".into(),
1056 description: "Jpg From Raw".into(),
1057 group: TagGroup {
1058 family0: "EXIF".into(),
1059 family1: sub_name,
1060 family2: "Preview".into(),
1061 },
1062 raw_value: Value::Binary(
1063 data[start..start + len].to_vec(),
1064 ),
1065 print_value: pv,
1066 priority: 0,
1067 });
1068 }
1069 }
1070 }
1071 }
1072 }
1073 continue;
1074 }
1075 0x0100 | 0x0101 | 0x0102 | 0x0103 | 0x0111 | 0x0117 if ifd_name == "IFD2" => {
1080 continue;
1081 }
1082 0x0103 if ifd_name == "IFD3" => {
1084 continue;
1085 }
1086 _ => {}
1087 }
1088
1089 if let Some(mut value) = read_ifd_value(data, &entry, header.byte_order) {
1090 if ifd_name == "GPS" && entry.tag == 0x0007 {
1093 if let Value::List(ref mut items) = value {
1094 for item in items.iter_mut() {
1095 if matches!(item, Value::URational(0, 0)) {
1096 *item = Value::URational(0, 1);
1097 }
1098 }
1099 }
1100 }
1101 let tag_info = exif_tags::lookup(ifd_name, entry.tag);
1102 let (name, description, family2) = match tag_info {
1103 Some(info) => (
1104 info.name.to_string(),
1105 info.description.to_string(),
1106 info.family2.to_string(),
1107 ),
1108 None => {
1109 if matches!(
1111 entry.tag,
1112 0xC634 ) {
1116 continue;
1117 }
1118 match exif_tags::lookup_generated(entry.tag) {
1120 Some((n, d)) => (n.to_string(), d.to_string(), "Other".to_string()),
1121 None => {
1122 continue;
1124 }
1125 }
1126 }
1127 };
1128
1129 if name == "ApplicationNotes" {
1131 if let Value::Binary(ref xmp_bytes) = value {
1132 if let Ok(xmp_tags) = crate::metadata::XmpReader::read(xmp_bytes) {
1133 tags.extend(xmp_tags);
1134 }
1135 }
1136 continue;
1137 }
1138 if matches!(
1140 name.as_str(),
1141 "MinSampleValue" | "MaxSampleValue" | "ProcessingSoftware" | "PanasonicTitle" | "PanasonicTitle2" ) {
1145 continue;
1146 }
1147
1148 let print_value = if name.starts_with("Tag0x") && get_show_unknown() >= 2 {
1149 match &value {
1151 Value::Binary(bytes) | Value::Undefined(bytes) => bytes
1152 .iter()
1153 .map(|b| format!("{:02x}", b))
1154 .collect::<Vec<_>>()
1155 .join(" "),
1156 _ => value.to_display_string(),
1157 }
1158 } else if name.starts_with("Tag0x") {
1159 value.to_display_string()
1161 } else {
1162 exif_tags::print_conv(ifd_name, entry.tag, &value)
1163 .or_else(|| {
1164 value
1166 .as_u64()
1167 .and_then(|v| {
1168 crate::tags::print_conv_generated::print_conv_by_name(
1169 &name, v as i64,
1170 )
1171 })
1172 .map(|s| s.to_string())
1173 })
1174 .unwrap_or_else(|| value.to_display_string())
1175 };
1176
1177 tags.push(Tag {
1178 id: TagId::Numeric(entry.tag),
1179 name,
1180 description,
1181 group: TagGroup {
1182 family0: "EXIF".to_string(),
1183 family1: ifd_name.to_string(),
1184 family2,
1185 },
1186 raw_value: value,
1187 print_value,
1188 priority: 0,
1189 });
1190 }
1191 }
1192
1193 let next_ifd_offset = if entries_end + 4 <= data.len() {
1195 read_u32(data, entries_end, header.byte_order)
1196 } else {
1197 0
1198 };
1199 if next_ifd_offset != 0 && ifd_name == "IFD0" {
1200 let ifd1_start_idx = tags.len();
1202 let ifd1_next = Self::read_ifd(data, header, next_ifd_offset, "IFD1", tags)
1203 .ok()
1204 .flatten();
1205 {
1208 let ifd0_names: std::collections::HashSet<String> = tags[..ifd1_start_idx]
1209 .iter()
1210 .map(|t| t.name.clone())
1211 .collect();
1212 let thumbnail_tags = [
1213 "ThumbnailOffset",
1214 "ThumbnailLength",
1215 "ThumbnailImage",
1216 "Compression",
1217 "PhotometricInterpretation",
1218 "JPEGInterchangeFormat",
1219 "JPEGInterchangeFormatLength",
1220 "SubfileType",
1221 "StripOffsets",
1222 "StripByteCounts",
1223 ];
1224 tags.retain(|t| {
1225 if t.group.family1 != "IFD1" {
1226 return true;
1227 }
1228 if thumbnail_tags.contains(&t.name.as_str()) {
1230 return true;
1231 }
1232 !ifd0_names.contains(&t.name)
1234 });
1235 }
1236
1237 let thumb_offset = tags
1239 .iter()
1240 .find(|t| t.name == "ThumbnailOffset" && t.group.family1 == "IFD1")
1241 .and_then(|t| t.raw_value.as_u64());
1242 let thumb_length = tags
1243 .iter()
1244 .find(|t| t.name == "ThumbnailLength" && t.group.family1 == "IFD1")
1245 .and_then(|t| t.raw_value.as_u64());
1246
1247 if let (Some(off), Some(len)) = (thumb_offset, thumb_length) {
1248 let off = off as usize;
1249 let len = len as usize;
1250 if off + len <= data.len() && len > 0 {
1251 tags.push(Tag {
1252 id: TagId::Text("ThumbnailImage".into()),
1253 name: "ThumbnailImage".into(),
1254 description: "Thumbnail Image".into(),
1255 group: TagGroup {
1256 family0: "EXIF".into(),
1257 family1: "IFD1".into(),
1258 family2: "Image".into(),
1259 },
1260 raw_value: Value::Binary(data[off..off + len].to_vec()),
1261 print_value: format!(
1262 "(Binary data {} bytes, use -b option to extract)",
1263 len
1264 ),
1265 priority: 0,
1266 });
1267 }
1268 }
1269
1270 let is_cr2 = data.len() > 10 && &data[8..10] == b"CR";
1273 if is_cr2 {
1274 if let Some(ifd2_offset) = ifd1_next {
1275 let ifd2_next = Self::read_ifd(data, header, ifd2_offset, "IFD2", tags)
1277 .ok()
1278 .flatten();
1279 if let Some(ifd3_offset) = ifd2_next {
1281 let _ = Self::read_ifd(data, header, ifd3_offset, "IFD3", tags);
1282 }
1283 }
1284 }
1285 }
1286
1287 Ok(if next_ifd_offset != 0 {
1288 Some(next_ifd_offset)
1289 } else {
1290 None
1291 })
1292 }
1293
1294 pub fn read_as_named_ifd(data: &[u8], ifd_name: &str) -> Vec<Tag> {
1298 let header = match parse_tiff_header(data) {
1299 Ok(h) => h,
1300 Err(_) => return Vec::new(),
1301 };
1302 let mut tags = Vec::new();
1303 let _ = Self::read_ifd(data, &header, header.ifd0_offset, ifd_name, &mut tags);
1304 tags
1305 }
1306}
1307
1308fn parse_ifd_entry(data: &[u8], offset: usize, byte_order: ByteOrderMark) -> IfdEntry {
1309 let tag = read_u16(data, offset, byte_order);
1310 let data_type = read_u16(data, offset + 2, byte_order);
1311 let count = read_u32(data, offset + 4, byte_order);
1312 let value_offset = read_u32(data, offset + 8, byte_order);
1313 let mut inline_data = [0u8; 4];
1314 inline_data.copy_from_slice(&data[offset + 8..offset + 12]);
1315
1316 IfdEntry {
1317 tag,
1318 data_type,
1319 count,
1320 value_offset,
1321 inline_data,
1322 }
1323}
1324
1325fn read_ifd_value(data: &[u8], entry: &IfdEntry, byte_order: ByteOrderMark) -> Option<Value> {
1326 let elem_size = type_size(entry.data_type)?;
1327 let total_size = elem_size * entry.count as usize;
1328
1329 let value_data = if total_size <= 4 {
1330 &entry.inline_data[..total_size]
1331 } else {
1332 let offset = entry.value_offset as usize;
1333 if offset + total_size > data.len() {
1334 return None;
1335 }
1336 &data[offset..offset + total_size]
1337 };
1338
1339 if entry.tag == 0x83BB {
1341 return Some(Value::Binary(value_data.to_vec()));
1342 }
1343
1344 if entry.tag == 0x02BC {
1346 return Some(Value::Binary(value_data.to_vec()));
1347 }
1348
1349 match entry.data_type {
1350 1 => {
1352 if entry.count == 1 {
1353 Some(Value::U8(value_data[0]))
1354 } else {
1355 Some(Value::List(
1356 value_data.iter().map(|&b| Value::U8(b)).collect(),
1357 ))
1358 }
1359 }
1360 2 => {
1362 let s = crate::encoding::decode_utf8_or_latin1(value_data);
1363 let s = s.split('\0').next().unwrap_or("").trim_end().to_string();
1366 Some(Value::String(s))
1367 }
1368 3 => {
1370 if entry.count == 1 {
1371 Some(Value::U16(read_u16(value_data, 0, byte_order)))
1372 } else {
1373 let vals: Vec<Value> = (0..entry.count as usize)
1374 .map(|i| Value::U16(read_u16(value_data, i * 2, byte_order)))
1375 .collect();
1376 Some(Value::List(vals))
1377 }
1378 }
1379 4 | 13 => {
1381 if entry.count == 1 {
1382 Some(Value::U32(read_u32(value_data, 0, byte_order)))
1383 } else {
1384 let vals: Vec<Value> = (0..entry.count as usize)
1385 .map(|i| Value::U32(read_u32(value_data, i * 4, byte_order)))
1386 .collect();
1387 Some(Value::List(vals))
1388 }
1389 }
1390 5 => {
1392 if entry.count == 1 {
1393 let n = read_u32(value_data, 0, byte_order);
1394 let d = read_u32(value_data, 4, byte_order);
1395 Some(Value::URational(n, d))
1396 } else {
1397 let vals: Vec<Value> = (0..entry.count as usize)
1398 .map(|i| {
1399 let n = read_u32(value_data, i * 8, byte_order);
1400 let d = read_u32(value_data, i * 8 + 4, byte_order);
1401 Value::URational(n, d)
1402 })
1403 .collect();
1404 Some(Value::List(vals))
1405 }
1406 }
1407 6 => {
1409 if entry.count == 1 {
1410 Some(Value::I16(value_data[0] as i8 as i16))
1411 } else {
1412 let vals: Vec<Value> = value_data
1413 .iter()
1414 .map(|&b| Value::I16(b as i8 as i16))
1415 .collect();
1416 Some(Value::List(vals))
1417 }
1418 }
1419 7 => Some(Value::Undefined(value_data.to_vec())),
1421 8 => {
1423 if entry.count == 1 {
1424 Some(Value::I16(read_i16(value_data, 0, byte_order)))
1425 } else {
1426 let vals: Vec<Value> = (0..entry.count as usize)
1427 .map(|i| Value::I16(read_i16(value_data, i * 2, byte_order)))
1428 .collect();
1429 Some(Value::List(vals))
1430 }
1431 }
1432 9 => {
1434 if entry.count == 1 {
1435 Some(Value::I32(read_i32(value_data, 0, byte_order)))
1436 } else {
1437 let vals: Vec<Value> = (0..entry.count as usize)
1438 .map(|i| Value::I32(read_i32(value_data, i * 4, byte_order)))
1439 .collect();
1440 Some(Value::List(vals))
1441 }
1442 }
1443 10 => {
1445 if entry.count == 1 {
1446 let n = read_i32(value_data, 0, byte_order);
1447 let d = read_i32(value_data, 4, byte_order);
1448 Some(Value::IRational(n, d))
1449 } else {
1450 let vals: Vec<Value> = (0..entry.count as usize)
1451 .map(|i| {
1452 let n = read_i32(value_data, i * 8, byte_order);
1453 let d = read_i32(value_data, i * 8 + 4, byte_order);
1454 Value::IRational(n, d)
1455 })
1456 .collect();
1457 Some(Value::List(vals))
1458 }
1459 }
1460 11 => {
1462 if entry.count == 1 {
1463 let bits = read_u32(value_data, 0, byte_order);
1464 Some(Value::F32(f32::from_bits(bits)))
1465 } else {
1466 let vals: Vec<Value> = (0..entry.count as usize)
1467 .map(|i| {
1468 let bits = read_u32(value_data, i * 4, byte_order);
1469 Value::F32(f32::from_bits(bits))
1470 })
1471 .collect();
1472 Some(Value::List(vals))
1473 }
1474 }
1475 12 => {
1477 if entry.count == 1 {
1478 let bits = read_u64(value_data, 0, byte_order);
1479 Some(Value::F64(f64::from_bits(bits)))
1480 } else {
1481 let vals: Vec<Value> = (0..entry.count as usize)
1482 .map(|i| {
1483 let bits = read_u64(value_data, i * 8, byte_order);
1484 Value::F64(f64::from_bits(bits))
1485 })
1486 .collect();
1487 Some(Value::List(vals))
1488 }
1489 }
1490 _ => None,
1491 }
1492}
1493
1494fn read_u16(data: &[u8], offset: usize, bo: ByteOrderMark) -> u16 {
1496 match bo {
1497 ByteOrderMark::LittleEndian => LittleEndian::read_u16(&data[offset..]),
1498 ByteOrderMark::BigEndian => BigEndian::read_u16(&data[offset..]),
1499 }
1500}
1501
1502fn read_u32(data: &[u8], offset: usize, bo: ByteOrderMark) -> u32 {
1503 match bo {
1504 ByteOrderMark::LittleEndian => LittleEndian::read_u32(&data[offset..]),
1505 ByteOrderMark::BigEndian => BigEndian::read_u32(&data[offset..]),
1506 }
1507}
1508
1509fn read_u64(data: &[u8], offset: usize, bo: ByteOrderMark) -> u64 {
1510 match bo {
1511 ByteOrderMark::LittleEndian => LittleEndian::read_u64(&data[offset..]),
1512 ByteOrderMark::BigEndian => BigEndian::read_u64(&data[offset..]),
1513 }
1514}
1515
1516fn read_i16(data: &[u8], offset: usize, bo: ByteOrderMark) -> i16 {
1517 match bo {
1518 ByteOrderMark::LittleEndian => LittleEndian::read_i16(&data[offset..]),
1519 ByteOrderMark::BigEndian => BigEndian::read_i16(&data[offset..]),
1520 }
1521}
1522
1523fn read_i32(data: &[u8], offset: usize, bo: ByteOrderMark) -> i32 {
1524 match bo {
1525 ByteOrderMark::LittleEndian => LittleEndian::read_i32(&data[offset..]),
1526 ByteOrderMark::BigEndian => BigEndian::read_i32(&data[offset..]),
1527 }
1528}
1529
1530fn process_geotiff_keys(tags: &mut Vec<Tag>) {
1533 let dir_vals: Option<Vec<u16>> =
1535 tags.iter()
1536 .find(|t| t.name == "GeoTiffDirectory")
1537 .and_then(|t| match &t.raw_value {
1538 Value::List(items) => {
1539 let vals: Vec<u16> = items
1540 .iter()
1541 .filter_map(|v| match v {
1542 Value::U16(x) => Some(*x),
1543 Value::U32(x) => Some(*x as u16),
1544 _ => None,
1545 })
1546 .collect();
1547 if vals.is_empty() {
1548 None
1549 } else {
1550 Some(vals)
1551 }
1552 }
1553 _ => None,
1554 });
1555
1556 let dir_vals = match dir_vals {
1557 Some(v) => v,
1558 None => return,
1559 };
1560
1561 if dir_vals.len() < 4 {
1562 return;
1563 }
1564
1565 let version = dir_vals[0];
1566 let revision = dir_vals[1];
1567 let minor_rev = dir_vals[2];
1568 let num_entries = dir_vals[3] as usize;
1569
1570 if dir_vals.len() < 4 + num_entries * 4 {
1571 return;
1572 }
1573
1574 let ascii_params: Option<String> = tags
1576 .iter()
1577 .find(|t| t.name == "GeoTiffAsciiParams")
1578 .map(|t| t.print_value.clone());
1579
1580 let double_params: Option<Vec<f64>> = tags
1582 .iter()
1583 .find(|t| t.name == "GeoTiffDoubleParams")
1584 .and_then(|t| match &t.raw_value {
1585 Value::List(items) => {
1586 let vals: Vec<f64> = items
1587 .iter()
1588 .filter_map(|v| match v {
1589 Value::F64(x) => Some(*x),
1590 Value::F32(x) => Some(*x as f64),
1591 _ => None,
1592 })
1593 .collect();
1594 if vals.is_empty() {
1595 None
1596 } else {
1597 Some(vals)
1598 }
1599 }
1600 _ => None,
1601 });
1602
1603 let mut new_tags = Vec::new();
1604
1605 new_tags.push(Tag {
1607 id: TagId::Text("GeoTiffVersion".to_string()),
1608 name: "GeoTiffVersion".to_string(),
1609 description: "GeoTiff Version".to_string(),
1610 group: TagGroup {
1611 family0: "EXIF".into(),
1612 family1: "IFD0".into(),
1613 family2: "Location".into(),
1614 },
1615 raw_value: Value::String(format!("{}.{}.{}", version, revision, minor_rev)),
1616 print_value: format!("{}.{}.{}", version, revision, minor_rev),
1617 priority: 0,
1618 });
1619
1620 for i in 0..num_entries {
1622 let base = 4 + i * 4;
1623 let key_id = dir_vals[base];
1624 let location = dir_vals[base + 1];
1625 let count = dir_vals[base + 2] as usize;
1626 let value_or_offset = dir_vals[base + 3];
1627
1628 let raw_val: Option<String> = match location {
1629 0 => {
1630 Some(format!("{}", value_or_offset))
1632 }
1633 34737 => {
1634 if let Some(ref ascii) = ascii_params {
1636 let off = value_or_offset as usize;
1637 let end = (off + count).min(ascii.len());
1638 if off <= end {
1639 let s = &ascii[off..end];
1640 let s = s.trim_end_matches('|').trim().to_string();
1642 Some(s)
1643 } else {
1644 None
1645 }
1646 } else {
1647 None
1648 }
1649 }
1650 34736 => {
1651 if let Some(ref doubles) = double_params {
1653 let off = value_or_offset as usize;
1654 if count == 1 && off < doubles.len() {
1655 Some(format!("{}", doubles[off]))
1656 } else if count > 1 {
1657 let vals: Vec<String> = doubles
1658 .iter()
1659 .skip(off)
1660 .take(count)
1661 .map(|v| format!("{}", v))
1662 .collect();
1663 Some(vals.join(" "))
1664 } else {
1665 None
1666 }
1667 } else {
1668 None
1669 }
1670 }
1671 _ => None,
1672 };
1673
1674 let val_str = match raw_val {
1675 Some(v) => v,
1676 None => continue,
1677 };
1678
1679 let (tag_name, print_val) = geotiff_key_to_tag(key_id, &val_str);
1681 if tag_name.is_empty() {
1682 continue;
1683 }
1684
1685 new_tags.push(Tag {
1686 id: TagId::Text(tag_name.clone()),
1687 name: tag_name.clone(),
1688 description: tag_name.clone(),
1689 group: TagGroup {
1690 family0: "EXIF".into(),
1691 family1: "IFD0".into(),
1692 family2: "Location".into(),
1693 },
1694 raw_value: Value::String(val_str),
1695 print_value: print_val,
1696 priority: 0,
1697 });
1698 }
1699
1700 if !new_tags.is_empty() {
1701 tags.retain(|t| {
1703 t.name != "GeoTiffDirectory"
1704 && t.name != "GeoTiffAsciiParams"
1705 && t.name != "GeoTiffDoubleParams"
1706 });
1707 tags.extend(new_tags);
1708 }
1709}
1710
1711fn geotiff_key_to_tag(key_id: u16, value: &str) -> (String, String) {
1713 let val_u16: Option<u16> = value.parse().ok();
1714
1715 match key_id {
1716 0x0001 => return ("GeoTiffVersion".to_string(), value.to_string()), 0x0400 => {
1719 let print = match val_u16 {
1721 Some(1) => "Projected".to_string(),
1722 Some(2) => "Geographic".to_string(),
1723 Some(3) => "Geocentric".to_string(),
1724 Some(32767) => "User Defined".to_string(),
1725 _ => value.to_string(),
1726 };
1727 return ("GTModelType".to_string(), print);
1728 }
1729 0x0401 => {
1730 let print = match val_u16 {
1732 Some(1) => "Pixel Is Area".to_string(),
1733 Some(2) => "Pixel Is Point".to_string(),
1734 Some(32767) => "User Defined".to_string(),
1735 _ => value.to_string(),
1736 };
1737 return ("GTRasterType".to_string(), print);
1738 }
1739 0x0402 => return ("GTCitation".to_string(), value.to_string()),
1740
1741 0x0800 => {
1743 return (
1744 "GeographicType".to_string(),
1745 geotiff_pcs_name(val_u16.unwrap_or(0), value),
1746 )
1747 }
1748 0x0801 => return ("GeogCitation".to_string(), value.to_string()),
1749 0x0802 => {
1750 let print = match val_u16 {
1751 Some(32767) | Some(32766) => "User Defined".to_string(),
1752 _ => value.to_string(),
1753 };
1754 return ("GeogGeodeticDatum".to_string(), print);
1755 }
1756 0x0803 => return ("GeogPrimeMeridian".to_string(), value.to_string()),
1757 0x0804 => {
1758 return (
1759 "GeogLinearUnits".to_string(),
1760 geotiff_linear_unit_name(val_u16.unwrap_or(0), value),
1761 )
1762 }
1763 0x0805 => return ("GeogLinearUnitSize".to_string(), value.to_string()),
1764 0x0806 => return ("GeogAngularUnits".to_string(), value.to_string()),
1765 0x0807 => return ("GeogAngularUnitSize".to_string(), value.to_string()),
1766 0x0808 => return ("GeogEllipsoid".to_string(), value.to_string()),
1767 0x0809 => return ("GeogSemiMajorAxis".to_string(), value.to_string()),
1768 0x080a => return ("GeogSemiMinorAxis".to_string(), value.to_string()),
1769 0x080b => return ("GeogInvFlattening".to_string(), value.to_string()),
1770 0x080c => return ("GeogAzimuthUnits".to_string(), value.to_string()),
1771 0x080d => return ("GeogPrimeMeridianLong".to_string(), value.to_string()),
1772
1773 0x0C00 => {
1775 return (
1777 "ProjectedCSType".to_string(),
1778 geotiff_pcs_name(val_u16.unwrap_or(0), value),
1779 );
1780 }
1781 0x0C01 => return ("PCSCitation".to_string(), value.to_string()),
1782 0x0C02 => {
1783 let print = match val_u16 {
1786 Some(v @ 16001..=16060) => format!("UTM zone {}N", v - 16000),
1787 Some(v @ 16101..=16160) => format!("UTM zone {}S", v - 16100),
1788 Some(32767) | Some(32766) => "User Defined".to_string(),
1789 _ => value.to_string(),
1790 };
1791 return ("Projection".to_string(), print);
1792 }
1793 0x0C03 => return ("ProjCoordTrans".to_string(), value.to_string()),
1794 0x0C04 => {
1795 return (
1796 "ProjLinearUnits".to_string(),
1797 geotiff_linear_unit_name(val_u16.unwrap_or(0), value),
1798 )
1799 }
1800 0x0C05 => return ("ProjLinearUnitSize".to_string(), value.to_string()),
1801 0x0C06 => return ("ProjStdParallel1".to_string(), value.to_string()),
1802 0x0C07 => return ("ProjStdParallel2".to_string(), value.to_string()),
1803 0x0C08 => return ("ProjNatOriginLong".to_string(), value.to_string()),
1804 0x0C09 => return ("ProjNatOriginLat".to_string(), value.to_string()),
1805 0x0c0a => return ("ProjFalseEasting".to_string(), value.to_string()),
1806 0x0c0b => return ("ProjFalseNorthing".to_string(), value.to_string()),
1807 0x0c0c => return ("ProjFalseOriginLong".to_string(), value.to_string()),
1808 0x0c0d => return ("ProjFalseOriginLat".to_string(), value.to_string()),
1809 0x0c0e => return ("ProjFalseOriginEasting".to_string(), value.to_string()),
1810 0x0c0f => return ("ProjFalseOriginNorthing".to_string(), value.to_string()),
1811 0x0C10 => return ("ProjCenterLong".to_string(), value.to_string()),
1812 0x0C11 => return ("ProjCenterLat".to_string(), value.to_string()),
1813 0x0C12 => return ("ProjCenterEasting".to_string(), value.to_string()),
1814 0x0C13 => return ("ProjCenterNorthing".to_string(), value.to_string()),
1815 0x0C14 => return ("ProjScaleAtNatOrigin".to_string(), value.to_string()),
1816 0x0C15 => return ("ProjScaleAtCenter".to_string(), value.to_string()),
1817 0x0C16 => return ("ProjAzimuthAngle".to_string(), value.to_string()),
1818 0x0C17 => return ("ProjStraightVertPoleLong".to_string(), value.to_string()),
1819
1820 0x1000 => return ("VerticalCSType".to_string(), value.to_string()),
1822 0x1001 => return ("VerticalCitation".to_string(), value.to_string()),
1823 0x1002 => return ("VerticalDatum".to_string(), value.to_string()),
1824 0x1003 => {
1825 return (
1826 "VerticalUnits".to_string(),
1827 geotiff_linear_unit_name(val_u16.unwrap_or(0), value),
1828 )
1829 }
1830
1831 _ => {}
1832 }
1833 (String::new(), String::new())
1834}
1835
1836fn geotiff_linear_unit_name(val: u16, fallback: &str) -> String {
1837 match val {
1838 9001 => "Linear Meter".to_string(),
1839 9002 => "Linear Foot".to_string(),
1840 9003 => "Linear Foot US Survey".to_string(),
1841 9004 => "Linear Foot Modified American".to_string(),
1842 9005 => "Linear Foot Clarke".to_string(),
1843 9006 => "Linear Foot Indian".to_string(),
1844 9007 => "Linear Link".to_string(),
1845 9008 => "Linear Link Benoit".to_string(),
1846 9009 => "Linear Link Sears".to_string(),
1847 9010 => "Linear Chain Benoit".to_string(),
1848 9011 => "Linear Chain Sears".to_string(),
1849 9012 => "Linear Yard Sears".to_string(),
1850 9013 => "Linear Yard Indian".to_string(),
1851 9014 => "Linear Fathom".to_string(),
1852 9015 => "Linear Mile International Nautical".to_string(),
1853 _ => fallback.to_string(),
1854 }
1855}
1856
1857fn geotiff_pcs_name(val: u16, fallback: &str) -> String {
1858 match val {
1860 26918 => "NAD83 UTM zone 18N".to_string(),
1861 26919 => "NAD83 UTM zone 19N".to_string(),
1862 32618 => "WGS84 UTM zone 18N".to_string(),
1863 32619 => "WGS84 UTM zone 19N".to_string(),
1864 4326 => "WGS 84".to_string(),
1865 4269 => "NAD83".to_string(),
1866 4267 => "NAD27".to_string(),
1867 32767 => "User Defined".to_string(),
1868 _ => fallback.to_string(),
1869 }
1870}