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 struct ExifReader;
108
109impl ExifReader {
110 pub fn read(data: &[u8]) -> Result<Vec<Tag>> {
112 let header = parse_tiff_header(data)?;
113 let mut tags = Vec::new();
114
115 let bo_str = match header.byte_order {
117 ByteOrderMark::LittleEndian => "Little-endian (Intel, II)",
118 ByteOrderMark::BigEndian => "Big-endian (Motorola, MM)",
119 };
120 tags.push(Tag {
121 id: TagId::Text("ExifByteOrder".to_string()),
122 name: "ExifByteOrder".to_string(),
123 description: "Exif Byte Order".to_string(),
124 group: TagGroup {
125 family0: "EXIF".to_string(),
126 family1: "IFD0".to_string(),
127 family2: "ExifTool".to_string(),
128 },
129 raw_value: Value::String(bo_str.to_string()),
130 print_value: bo_str.to_string(),
131 priority: 0,
132 });
133
134 let is_cr2 = data.len() > 10 && &data[8..10] == b"CR";
136
137 Self::read_ifd(data, &header, header.ifd0_offset, "IFD0", &mut tags)?;
139
140 if is_cr2 {
143 for tag in tags.iter_mut() {
145 if tag.group.family1 == "IFD0" {
146 if tag.name == "StripOffsets" {
147 tag.name = "PreviewImageStart".to_string();
148 tag.description = "Preview Image Start".to_string();
149 tag.id = TagId::Text("PreviewImageStart".to_string());
150 } else if tag.name == "StripByteCounts" {
151 tag.name = "PreviewImageLength".to_string();
152 tag.description = "Preview Image Length".to_string();
153 tag.id = TagId::Text("PreviewImageLength".to_string());
154 }
155 }
156 }
157 let preview_start = tags
159 .iter()
160 .find(|t| t.name == "PreviewImageStart" && t.group.family1 == "IFD0")
161 .and_then(|t| t.raw_value.as_u64())
162 .map(|v| v as usize);
163 let preview_len = tags
164 .iter()
165 .find(|t| t.name == "PreviewImageLength" && t.group.family1 == "IFD0")
166 .and_then(|t| t.raw_value.as_u64())
167 .map(|v| v as usize);
168 if let (Some(start), Some(len)) = (preview_start, preview_len) {
169 if len > 0 && start + len <= data.len() {
170 let img_data = data[start..start + len].to_vec();
171 let pv = format!("(Binary data {} bytes, use -b option to extract)", len);
172 tags.push(Tag {
173 id: TagId::Text("PreviewImage".to_string()),
174 name: "PreviewImage".to_string(),
175 description: "Preview Image".to_string(),
176 group: TagGroup {
177 family0: "EXIF".to_string(),
178 family1: "IFD0".to_string(),
179 family2: "Preview".to_string(),
180 },
181 raw_value: Value::Binary(img_data),
182 print_value: pv,
183 priority: 0,
184 });
185 }
186 }
187 }
188
189 let make = tags
191 .iter()
192 .find(|t| t.name == "Make")
193 .map(|t| t.print_value.clone())
194 .unwrap_or_default();
195
196 let model = tags
197 .iter()
198 .find(|t| t.name == "Model")
199 .map(|t| t.print_value.clone())
200 .unwrap_or_default();
201
202 let make_and_model = if model.is_empty() {
204 make.clone()
205 } else {
206 model
207 };
208
209 let mn_info: Option<(usize, usize)> = {
212 let mut result = None;
214 Self::find_makernote(data, &header, &mut result);
215 result
216 };
217
218 if let Some((mn_offset, mn_size)) = mn_info {
219 let mn_tags = crate::metadata::makernotes::parse_makernotes(
220 data,
221 mn_offset,
222 mn_size,
223 &make,
224 &make_and_model,
225 header.byte_order,
226 );
227 tags.retain(|t| t.name != "MakerNote");
229 {
233 let exif_primary: &[&str] = &[
235 "ThumbnailOffset",
236 "ThumbnailLength",
237 "ThumbnailImage",
238 "StripOffsets",
239 "StripByteCounts",
240 "PreviewImageStart",
241 "PreviewImageLength",
242 "PreviewImage",
243 "ImageWidth",
244 "ImageHeight",
245 "BitsPerSample",
246 "Compression",
247 "PhotometricInterpretation",
248 "SamplesPerPixel",
249 "RowsPerStrip",
250 "PlanarConfiguration",
251 "XResolution",
252 "YResolution",
253 "ResolutionUnit",
254 "Orientation",
255 "Make",
256 "Model",
257 "Software",
258 "ExifByteOrder",
259 "CR2CFAPattern",
260 "RawImageSegmentation",
261 "ColorSpace",
262 "ExifVersion",
263 "FlashpixVersion",
264 "ExifImageWidth",
265 "ExifImageHeight",
266 "InteropIndex",
267 "InteropVersion",
268 "DateTimeOriginal",
269 "CreateDate",
270 "ModifyDate",
271 "DateTime",
272 "FocalPlaneXResolution",
273 "FocalPlaneYResolution",
274 "FocalPlaneResolutionUnit",
275 "CustomRendered",
276 "ExposureMode",
277 "SceneCaptureType",
278 "Flash",
279 "FocalLength",
280 "ISO",
281 "ExposureTime",
282 "ExposureProgram",
283 "FNumber",
284 "ShutterSpeedValue",
285 "ApertureValue",
286 "ComponentsConfiguration",
287 "UserComment",
288 ];
289 let mn_name_set: std::collections::HashSet<String> =
290 mn_tags.iter().map(|t| t.name.clone()).collect();
291 let exif_has: std::collections::HashSet<String> =
292 tags.iter().map(|t| t.name.clone()).collect();
293 tags.retain(|t| {
295 !mn_name_set.contains(&t.name) || exif_primary.contains(&t.name.as_str())
296 });
297 for mn_tag in mn_tags {
299 if exif_primary.contains(&mn_tag.name.as_str())
300 && exif_has.contains(&mn_tag.name)
301 {
302 continue;
304 }
305 tags.push(mn_tag);
306 }
307 }
308 }
309
310 if mn_info.is_none() {
312 Self::parse_dng_private_data(data, &header, &make, &make_and_model, &mut tags);
315 }
316
317 {
320 let iptc_data: Option<Vec<u8>> =
321 tags.iter().find(|t| t.name == "IPTC-NAA").and_then(|t| {
322 match &t.raw_value {
323 Value::Undefined(bytes) => Some(bytes.clone()),
324 Value::Binary(bytes) => Some(bytes.clone()),
325 Value::List(items) => {
326 let mut bytes = Vec::with_capacity(items.len() * 4);
328 for item in items {
329 if let Value::U32(v) = item {
330 bytes.extend_from_slice(&v.to_be_bytes())
331 }
332 }
333 if bytes.is_empty() {
334 None
335 } else {
336 Some(bytes)
337 }
338 }
339 _ => None,
340 }
341 });
342
343 if let Some(iptc_bytes) = iptc_data {
344 let md5_hex = crate::md5::md5_hex(&iptc_bytes);
346
347 if let Ok(iptc_tags) = crate::metadata::IptcReader::read(&iptc_bytes) {
348 tags.retain(|t| t.name != "IPTC-NAA");
350 tags.extend(iptc_tags);
351 }
352
353 tags.push(crate::tag::Tag {
355 id: crate::tag::TagId::Text("CurrentIPTCDigest".into()),
356 name: "CurrentIPTCDigest".into(),
357 description: "Current IPTC Digest".into(),
358 group: crate::tag::TagGroup {
359 family0: "IPTC".into(),
360 family1: "IPTC".into(),
361 family2: "Other".into(),
362 },
363 raw_value: Value::String(md5_hex.clone()),
364 print_value: md5_hex,
365 priority: 0,
366 });
367 }
368 }
369
370 {
372 let icc_data: Option<Vec<u8>> =
373 tags.iter()
374 .find(|t| t.name == "ICC_Profile")
375 .and_then(|t| match &t.raw_value {
376 Value::Undefined(bytes) => Some(bytes.clone()),
377 Value::Binary(bytes) => Some(bytes.clone()),
378 _ => None,
379 });
380
381 if let Some(icc_bytes) = icc_data {
382 if let Ok(icc_tags) = crate::formats::icc::read_icc(&icc_bytes) {
383 tags.retain(|t| t.name != "ICC_Profile");
385 tags.extend(icc_tags);
386 }
387 }
388 }
389
390 process_geotiff_keys(&mut tags);
392
393 {
398 let mn_tags_start = tags
400 .iter()
401 .position(|t| t.group.family0 == "MakerNotes")
402 .unwrap_or(tags.len());
403 if mn_tags_start < tags.len() {
404 let mut last_idx: std::collections::HashMap<&str, usize> =
406 std::collections::HashMap::new();
407 for (i, t) in tags[mn_tags_start..].iter().enumerate() {
408 last_idx.insert(t.name.as_str(), i + mn_tags_start);
409 }
410 let mut seen: std::collections::HashSet<&str> = std::collections::HashSet::new();
412 let mut keep = vec![false; tags.len()];
414 for (i, t) in tags.iter().enumerate().rev() {
415 if t.group.family0 != "MakerNotes" {
416 keep[i] = true;
417 continue;
418 }
419 if seen.insert(t.name.as_str()) {
420 keep[i] = true; }
422 }
423 let mut iter = keep.iter();
424 tags.retain(|_| *iter.next().unwrap_or(&true));
425 }
426 }
427
428 Ok(tags)
429 }
430
431 fn parse_dng_private_data(
434 data: &[u8],
435 header: &TiffHeader,
436 make: &str,
437 model: &str,
438 tags: &mut Vec<Tag>,
439 ) {
440 let ifd0_offset = header.ifd0_offset as usize;
442 if ifd0_offset + 2 > data.len() {
443 return;
444 }
445 let entry_count = read_u16(data, ifd0_offset, header.byte_order) as usize;
446 let entries_start = ifd0_offset + 2;
447 for i in 0..entry_count {
448 let eoff = entries_start + i * 12;
449 if eoff + 12 > data.len() {
450 break;
451 }
452 let tag = read_u16(data, eoff, header.byte_order);
453 if tag == 0xC634 {
454 let dtype = read_u16(data, eoff + 2, header.byte_order);
455 let count = read_u32(data, eoff + 4, header.byte_order) as usize;
456 let elem_size = match dtype {
457 1 | 7 => 1,
458 _ => 0,
459 };
460 let total = elem_size * count;
461 if total < 14 {
462 continue;
463 }
464 let off = read_u32(data, eoff + 8, header.byte_order) as usize;
465 if off + total > data.len() {
466 continue;
467 }
468 let pdata = &data[off..off + total];
469 if !pdata.starts_with(b"Adobe\0") {
471 continue;
472 }
473 let mut bpos = 6;
474 while bpos + 8 <= pdata.len() {
475 let btag = &pdata[bpos..bpos + 4];
476 let bsize = u32::from_be_bytes([
477 pdata[bpos + 4],
478 pdata[bpos + 5],
479 pdata[bpos + 6],
480 pdata[bpos + 7],
481 ]) as usize;
482 bpos += 8;
483 if bpos + bsize > pdata.len() {
484 break;
485 }
486 if btag == b"MakN" && bsize > 6 {
487 let mn_block = &pdata[bpos..bpos + bsize];
488 let mn_bo = if &mn_block[0..2] == b"II" {
489 ByteOrderMark::LittleEndian
490 } else {
491 ByteOrderMark::BigEndian
492 };
493 let mut mn_start = 6; if bsize >= 18 && &mn_block[6..10] == b"\0\0\0\x01" {
496 mn_start += 12;
497 }
498 if mn_start < bsize {
499 let mn_bo_str = if mn_bo == ByteOrderMark::LittleEndian {
501 "Little-endian (Intel, II)"
502 } else {
503 "Big-endian (Motorola, MM)"
504 };
505 tags.push(Tag {
506 id: TagId::Text("MakerNoteByteOrder".into()),
507 name: "MakerNoteByteOrder".into(),
508 description: "Maker Note Byte Order".into(),
509 group: TagGroup {
510 family0: "File".into(),
511 family1: "File".into(),
512 family2: "Image".into(),
513 },
514 raw_value: Value::String(mn_bo_str.into()),
515 print_value: mn_bo_str.into(),
516 priority: 0,
517 });
518 let mn_data_in_block = &mn_block[mn_start..];
522 let mn_abs_offset = off + (bpos - 8 + 8) + mn_start; let fix_base = if mn_data_in_block.len() > 8 {
525 let footer = &mn_data_in_block[mn_data_in_block.len() - 8..];
526 if (footer[0..2] == *b"II" || footer[0..2] == *b"MM")
527 && (footer[2..4] == *b"\x2a\x00"
528 || footer[2..4] == *b"\x00\x2a")
529 {
530 let old_off = if footer[0] == b'I' {
531 u32::from_le_bytes([
532 footer[4], footer[5], footer[6], footer[7],
533 ])
534 } else {
535 u32::from_be_bytes([
536 footer[4], footer[5], footer[6], footer[7],
537 ])
538 } as usize;
539 if old_off > 0 && mn_abs_offset > old_off {
540 mn_abs_offset as isize - old_off as isize
541 } else {
542 0
543 }
544 } else {
545 0
546 }
547 } else {
548 0
549 };
550
551 let mn_tags = if fix_base != 0 {
552 crate::metadata::makernotes::parse_makernotes_with_base(
554 data,
555 mn_abs_offset,
556 mn_data_in_block.len(),
557 make,
558 model,
559 mn_bo,
560 fix_base,
561 )
562 } else {
563 crate::metadata::makernotes::parse_makernotes(
564 mn_data_in_block,
565 0,
566 mn_data_in_block.len(),
567 make,
568 model,
569 mn_bo,
570 )
571 };
572 let dng_suppress = [
574 "AESetting",
575 "CameraISO",
576 "ImageStabilization",
577 "SpotMeteringMode",
578 "RawJpgSize",
579 "Warning",
580 ];
581 for mn_tag in mn_tags {
582 if tags.iter().any(|t| t.name == mn_tag.name) {
584 continue;
585 }
586 if dng_suppress.contains(&mn_tag.name.as_str()) {
587 continue;
588 }
589 tags.push(mn_tag);
590 }
591 }
592 }
593 bpos += bsize;
594 }
595 break;
596 }
597 }
598 }
599
600 fn find_makernote(data: &[u8], header: &TiffHeader, result: &mut Option<(usize, usize)>) {
601 let ifd0_offset = header.ifd0_offset as usize;
603 if ifd0_offset + 2 > data.len() {
604 return;
605 }
606 let entry_count = read_u16(data, ifd0_offset, header.byte_order) as usize;
607 let entries_start = ifd0_offset + 2;
608
609 for i in 0..entry_count {
610 let eoff = entries_start + i * 12;
611 if eoff + 12 > data.len() {
612 break;
613 }
614 let tag = read_u16(data, eoff, header.byte_order);
615 if tag == 0x8769 {
616 let exif_offset = read_u32(data, eoff + 8, header.byte_order) as usize;
618 Self::find_makernote_in_ifd(data, header, exif_offset, result);
619 break;
620 }
621 }
622 }
623
624 fn find_makernote_in_ifd(
625 data: &[u8],
626 header: &TiffHeader,
627 ifd_offset: usize,
628 result: &mut Option<(usize, usize)>,
629 ) {
630 if ifd_offset + 2 > data.len() {
631 return;
632 }
633 let entry_count = read_u16(data, ifd_offset, header.byte_order) as usize;
634 let entries_start = ifd_offset + 2;
635
636 for i in 0..entry_count {
637 let eoff = entries_start + i * 12;
638 if eoff + 12 > data.len() {
639 break;
640 }
641 let tag = read_u16(data, eoff, header.byte_order);
642 if tag == 0x927C {
643 let data_type = read_u16(data, eoff + 2, header.byte_order);
644 let count = read_u32(data, eoff + 4, header.byte_order) as usize;
645 let type_size = match data_type {
646 1 | 2 | 6 | 7 => 1,
647 3 | 8 => 2,
648 4 | 9 | 11 | 13 => 4,
649 5 | 10 | 12 => 8,
650 _ => 1,
651 };
652 let total_size = type_size * count;
653
654 if total_size <= 4 {
655 break;
657 }
658 let offset = read_u32(data, eoff + 8, header.byte_order) as usize;
659 if offset + total_size <= data.len() {
660 *result = Some((offset, total_size));
661 }
662 break;
663 }
664 }
665 }
666
667 fn read_ifd(
669 data: &[u8],
670 header: &TiffHeader,
671 offset: u32,
672 ifd_name: &str,
673 tags: &mut Vec<Tag>,
674 ) -> Result<Option<u32>> {
675 let offset = offset as usize;
676 if offset + 2 > data.len() {
677 return Err(Error::InvalidExif(format!(
678 "{} offset {} beyond data length {}",
679 ifd_name,
680 offset,
681 data.len()
682 )));
683 }
684
685 let entry_count = read_u16(data, offset, header.byte_order) as usize;
686 let entries_start = offset + 2;
687 let _entries_end = entries_start + entry_count * 12;
688
689 if entries_start + 12 > data.len() && entry_count > 0 {
691 return Err(Error::InvalidExif(format!(
692 "{} entries extend beyond data (need {}, have {})",
693 ifd_name,
694 entries_start + 12,
695 data.len()
696 )));
697 }
698 let entry_count = entry_count.min((data.len().saturating_sub(entries_start)) / 12);
700 let entries_end = entries_start + entry_count * 12;
701
702 for i in 0..entry_count {
703 let entry_offset = entries_start + i * 12;
704 let entry = parse_ifd_entry(data, entry_offset, header.byte_order);
705
706 match entry.tag {
708 0x8769 => {
709 let sub_offset = entry.value_offset;
711 if (sub_offset as usize) < data.len() {
712 let _ = Self::read_ifd(data, header, sub_offset, "ExifIFD", tags);
713 }
714 continue;
715 }
716 0x8825 => {
717 let sub_offset = entry.value_offset;
719 if (sub_offset as usize) < data.len() {
720 let _ = Self::read_ifd(data, header, sub_offset, "GPS", tags);
721 }
722 continue;
723 }
724 0xA005 => {
725 let sub_offset = entry.value_offset;
727 if (sub_offset as usize) < data.len() {
728 let _ = Self::read_ifd(data, header, sub_offset, "InteropIFD", tags);
729 }
730 continue;
731 }
732 0xC4A5 => {
734 let total_size = match entry.data_type {
735 1 | 2 | 6 | 7 => entry.count as usize,
736 _ => 0,
737 };
738 if total_size > 11 {
739 let off = entry.value_offset as usize;
740 if off + 11 <= data.len() && &data[off..off + 7] == b"PrintIM" {
741 let ver =
742 crate::encoding::decode_utf8_or_latin1(&data[off + 7..off + 11])
743 .to_string();
744 tags.push(Tag {
745 id: TagId::Text("PrintIMVersion".into()),
746 name: "PrintIMVersion".into(),
747 description: "PrintIM Version".into(),
748 group: TagGroup {
749 family0: "PrintIM".into(),
750 family1: "PrintIM".into(),
751 family2: "Printing".into(),
752 },
753 raw_value: Value::String(ver.clone()),
754 print_value: ver,
755 priority: 0,
756 });
757 }
758 }
759 continue; }
761 0x0006 if ifd_name == "GPS" => {
763 if let Some(Value::URational(0, 0)) =
764 read_ifd_value(data, &entry, header.byte_order)
765 {
766 continue;
767 }
768 }
769 0x0201 if ifd_name.starts_with("SubIFD") => {
771 if let Some(val) = read_ifd_value(data, &entry, header.byte_order) {
772 let pv = val.to_display_string();
773 tags.push(Tag {
774 id: TagId::Numeric(entry.tag),
775 name: "JpgFromRawStart".into(),
776 description: "Jpg From Raw Start".into(),
777 group: TagGroup {
778 family0: "EXIF".into(),
779 family1: ifd_name.to_string(),
780 family2: "Image".into(),
781 },
782 raw_value: val,
783 print_value: pv,
784 priority: 0,
785 });
786 }
787 continue;
788 }
789 0x0202 if ifd_name.starts_with("SubIFD") => {
791 if let Some(val) = read_ifd_value(data, &entry, header.byte_order) {
792 let pv = val.to_display_string();
793 tags.push(Tag {
794 id: TagId::Numeric(entry.tag),
795 name: "JpgFromRawLength".into(),
796 description: "Jpg From Raw Length".into(),
797 group: TagGroup {
798 family0: "EXIF".into(),
799 family1: ifd_name.to_string(),
800 family2: "Image".into(),
801 },
802 raw_value: val,
803 print_value: pv,
804 priority: 0,
805 });
806 }
807 continue;
808 }
809 0x014A if ifd_name == "IFD0" => {
811 if let Some(val) = read_ifd_value(data, &entry, header.byte_order) {
813 let offsets: Vec<u32> = match &val {
814 Value::U32(v) => vec![*v],
815 Value::List(items) => items
816 .iter()
817 .filter_map(|v| {
818 if let Value::U32(o) = v {
819 Some(*o)
820 } else {
821 None
822 }
823 })
824 .collect(),
825 _ => vec![],
826 };
827 for (idx, &off) in offsets.iter().enumerate() {
828 if (off as usize) < data.len() {
829 let sub_name = format!("SubIFD{}", idx);
830 let before_idx = tags.len();
831 let _ = Self::read_ifd(data, header, off, &sub_name, tags);
832
833 let is_jpeg = tags[before_idx..].iter().any(|t| {
835 t.name == "Compression"
836 && (t.print_value.contains("JPEG")
837 || t.raw_value.as_u64() == Some(6))
838 });
839
840 if is_jpeg {
841 let (start_name, len_name, img_name) = if idx == 2 {
844 ("JpgFromRawStart", "JpgFromRawLength", "JpgFromRaw")
845 } else {
846 ("PreviewImageStart", "PreviewImageLength", "PreviewImage")
847 };
848 let strip_off = tags[before_idx..]
850 .iter()
851 .find(|t| t.name == "StripOffsets")
852 .and_then(|t| t.raw_value.as_u64());
853 let strip_len = tags[before_idx..]
854 .iter()
855 .find(|t| t.name == "StripByteCounts")
856 .and_then(|t| t.raw_value.as_u64());
857 if let (Some(s), Some(l)) = (strip_off, strip_len) {
858 tags.push(Tag {
859 id: TagId::Text(start_name.into()),
860 name: start_name.into(),
861 description: start_name.into(),
862 group: TagGroup {
863 family0: "EXIF".into(),
864 family1: sub_name.clone(),
865 family2: "Preview".into(),
866 },
867 raw_value: Value::U32(s as u32),
868 print_value: s.to_string(),
869 priority: 0,
870 });
871 tags.push(Tag {
872 id: TagId::Text(len_name.into()),
873 name: len_name.into(),
874 description: len_name.into(),
875 group: TagGroup {
876 family0: "EXIF".into(),
877 family1: sub_name.clone(),
878 family2: "Preview".into(),
879 },
880 raw_value: Value::U32(l as u32),
881 print_value: l.to_string(),
882 priority: 0,
883 });
884 let s = s as usize;
886 let l = l as usize;
887 if l > 0 && s + l <= data.len() {
888 let pv = format!(
889 "(Binary data {} bytes, use -b option to extract)",
890 l
891 );
892 tags.push(Tag {
893 id: TagId::Text(img_name.into()),
894 name: img_name.into(),
895 description: img_name.into(),
896 group: TagGroup {
897 family0: "EXIF".into(),
898 family1: sub_name.clone(),
899 family2: "Preview".into(),
900 },
901 raw_value: Value::Binary(data[s..s + l].to_vec()),
902 print_value: pv,
903 priority: 0,
904 });
905 }
906 }
907 }
908
909 let jpg_start = tags[before_idx..]
911 .iter()
912 .find(|t| t.name == "JpgFromRawStart")
913 .and_then(|t| t.raw_value.as_u64());
914 let jpg_len = tags[before_idx..]
915 .iter()
916 .find(|t| t.name == "JpgFromRawLength")
917 .and_then(|t| t.raw_value.as_u64());
918 if let (Some(start), Some(len)) = (jpg_start, jpg_len) {
919 let start = start as usize;
920 let len = len as usize;
921 if len > 0 && start + len <= data.len() {
922 let pv = format!(
923 "(Binary data {} bytes, use -b option to extract)",
924 len
925 );
926 tags.push(Tag {
927 id: TagId::Text("JpgFromRaw".into()),
928 name: "JpgFromRaw".into(),
929 description: "Jpg From Raw".into(),
930 group: TagGroup {
931 family0: "EXIF".into(),
932 family1: sub_name,
933 family2: "Preview".into(),
934 },
935 raw_value: Value::Binary(
936 data[start..start + len].to_vec(),
937 ),
938 print_value: pv,
939 priority: 0,
940 });
941 }
942 }
943 }
944 }
945 }
946 continue;
947 }
948 0x0100 | 0x0101 | 0x0102 | 0x0103 | 0x0111 | 0x0117 if ifd_name == "IFD2" => {
953 continue;
954 }
955 0x0103 if ifd_name == "IFD3" => {
957 continue;
958 }
959 _ => {}
960 }
961
962 if let Some(mut value) = read_ifd_value(data, &entry, header.byte_order) {
963 if ifd_name == "GPS" && entry.tag == 0x0007 {
966 if let Value::List(ref mut items) = value {
967 for item in items.iter_mut() {
968 if matches!(item, Value::URational(0, 0)) {
969 *item = Value::URational(0, 1);
970 }
971 }
972 }
973 }
974 let tag_info = exif_tags::lookup(ifd_name, entry.tag);
975 let (name, description, family2) = match tag_info {
976 Some(info) => (
977 info.name.to_string(),
978 info.description.to_string(),
979 info.family2.to_string(),
980 ),
981 None => {
982 if matches!(
984 entry.tag,
985 0xC634 ) {
989 continue;
990 }
991 match exif_tags::lookup_generated(entry.tag) {
993 Some((n, d)) => (n.to_string(), d.to_string(), "Other".to_string()),
994 None => {
995 continue;
997 }
998 }
999 }
1000 };
1001
1002 if name == "ApplicationNotes" {
1004 if let Value::Binary(ref xmp_bytes) = value {
1005 if let Ok(xmp_tags) = crate::metadata::XmpReader::read(xmp_bytes) {
1006 tags.extend(xmp_tags);
1007 }
1008 }
1009 continue;
1010 }
1011 if matches!(
1013 name.as_str(),
1014 "MinSampleValue" | "MaxSampleValue" | "ProcessingSoftware" | "PanasonicTitle" | "PanasonicTitle2" ) {
1018 continue;
1019 }
1020
1021 let print_value = if name.starts_with("Tag0x") && get_show_unknown() >= 2 {
1022 match &value {
1024 Value::Binary(bytes) | Value::Undefined(bytes) => bytes
1025 .iter()
1026 .map(|b| format!("{:02x}", b))
1027 .collect::<Vec<_>>()
1028 .join(" "),
1029 _ => value.to_display_string(),
1030 }
1031 } else if name.starts_with("Tag0x") {
1032 value.to_display_string()
1034 } else {
1035 exif_tags::print_conv(ifd_name, entry.tag, &value)
1036 .or_else(|| {
1037 value
1039 .as_u64()
1040 .and_then(|v| {
1041 crate::tags::print_conv_generated::print_conv_by_name(
1042 &name, v as i64,
1043 )
1044 })
1045 .map(|s| s.to_string())
1046 })
1047 .unwrap_or_else(|| value.to_display_string())
1048 };
1049
1050 tags.push(Tag {
1051 id: TagId::Numeric(entry.tag),
1052 name,
1053 description,
1054 group: TagGroup {
1055 family0: "EXIF".to_string(),
1056 family1: ifd_name.to_string(),
1057 family2,
1058 },
1059 raw_value: value,
1060 print_value,
1061 priority: 0,
1062 });
1063 }
1064 }
1065
1066 let next_ifd_offset = if entries_end + 4 <= data.len() {
1068 read_u32(data, entries_end, header.byte_order)
1069 } else {
1070 0
1071 };
1072 if next_ifd_offset != 0 && ifd_name == "IFD0" {
1073 let ifd1_start_idx = tags.len();
1075 let ifd1_next = Self::read_ifd(data, header, next_ifd_offset, "IFD1", tags)
1076 .ok()
1077 .flatten();
1078 {
1081 let ifd0_names: std::collections::HashSet<String> = tags[..ifd1_start_idx]
1082 .iter()
1083 .map(|t| t.name.clone())
1084 .collect();
1085 let thumbnail_tags = [
1086 "ThumbnailOffset",
1087 "ThumbnailLength",
1088 "ThumbnailImage",
1089 "Compression",
1090 "PhotometricInterpretation",
1091 "JPEGInterchangeFormat",
1092 "JPEGInterchangeFormatLength",
1093 "SubfileType",
1094 "StripOffsets",
1095 "StripByteCounts",
1096 ];
1097 tags.retain(|t| {
1098 if t.group.family1 != "IFD1" {
1099 return true;
1100 }
1101 if thumbnail_tags.contains(&t.name.as_str()) {
1103 return true;
1104 }
1105 !ifd0_names.contains(&t.name)
1107 });
1108 }
1109
1110 let thumb_offset = tags
1112 .iter()
1113 .find(|t| t.name == "ThumbnailOffset" && t.group.family1 == "IFD1")
1114 .and_then(|t| t.raw_value.as_u64());
1115 let thumb_length = tags
1116 .iter()
1117 .find(|t| t.name == "ThumbnailLength" && t.group.family1 == "IFD1")
1118 .and_then(|t| t.raw_value.as_u64());
1119
1120 if let (Some(off), Some(len)) = (thumb_offset, thumb_length) {
1121 let off = off as usize;
1122 let len = len as usize;
1123 if off + len <= data.len() && len > 0 {
1124 tags.push(Tag {
1125 id: TagId::Text("ThumbnailImage".into()),
1126 name: "ThumbnailImage".into(),
1127 description: "Thumbnail Image".into(),
1128 group: TagGroup {
1129 family0: "EXIF".into(),
1130 family1: "IFD1".into(),
1131 family2: "Image".into(),
1132 },
1133 raw_value: Value::Binary(data[off..off + len].to_vec()),
1134 print_value: format!("(Binary data {} bytes)", len),
1135 priority: 0,
1136 });
1137 }
1138 }
1139
1140 let is_cr2 = data.len() > 10 && &data[8..10] == b"CR";
1143 if is_cr2 {
1144 if let Some(ifd2_offset) = ifd1_next {
1145 let ifd2_next = Self::read_ifd(data, header, ifd2_offset, "IFD2", tags)
1147 .ok()
1148 .flatten();
1149 if let Some(ifd3_offset) = ifd2_next {
1151 let _ = Self::read_ifd(data, header, ifd3_offset, "IFD3", tags);
1152 }
1153 }
1154 }
1155 }
1156
1157 Ok(if next_ifd_offset != 0 {
1158 Some(next_ifd_offset)
1159 } else {
1160 None
1161 })
1162 }
1163
1164 pub fn read_as_named_ifd(data: &[u8], ifd_name: &str) -> Vec<Tag> {
1168 let header = match parse_tiff_header(data) {
1169 Ok(h) => h,
1170 Err(_) => return Vec::new(),
1171 };
1172 let mut tags = Vec::new();
1173 let _ = Self::read_ifd(data, &header, header.ifd0_offset, ifd_name, &mut tags);
1174 tags
1175 }
1176}
1177
1178fn parse_ifd_entry(data: &[u8], offset: usize, byte_order: ByteOrderMark) -> IfdEntry {
1179 let tag = read_u16(data, offset, byte_order);
1180 let data_type = read_u16(data, offset + 2, byte_order);
1181 let count = read_u32(data, offset + 4, byte_order);
1182 let value_offset = read_u32(data, offset + 8, byte_order);
1183 let mut inline_data = [0u8; 4];
1184 inline_data.copy_from_slice(&data[offset + 8..offset + 12]);
1185
1186 IfdEntry {
1187 tag,
1188 data_type,
1189 count,
1190 value_offset,
1191 inline_data,
1192 }
1193}
1194
1195fn read_ifd_value(data: &[u8], entry: &IfdEntry, byte_order: ByteOrderMark) -> Option<Value> {
1196 let elem_size = type_size(entry.data_type)?;
1197 let total_size = elem_size * entry.count as usize;
1198
1199 let value_data = if total_size <= 4 {
1200 &entry.inline_data[..total_size]
1201 } else {
1202 let offset = entry.value_offset as usize;
1203 if offset + total_size > data.len() {
1204 return None;
1205 }
1206 &data[offset..offset + total_size]
1207 };
1208
1209 if entry.tag == 0x83BB {
1211 return Some(Value::Binary(value_data.to_vec()));
1212 }
1213
1214 if entry.tag == 0x02BC {
1216 return Some(Value::Binary(value_data.to_vec()));
1217 }
1218
1219 match entry.data_type {
1220 1 => {
1222 if entry.count == 1 {
1223 Some(Value::U8(value_data[0]))
1224 } else {
1225 Some(Value::List(
1226 value_data.iter().map(|&b| Value::U8(b)).collect(),
1227 ))
1228 }
1229 }
1230 2 => {
1232 let s = crate::encoding::decode_utf8_or_latin1(value_data);
1233 Some(Value::String(s.trim_end_matches('\0').to_string()))
1234 }
1235 3 => {
1237 if entry.count == 1 {
1238 Some(Value::U16(read_u16(value_data, 0, byte_order)))
1239 } else {
1240 let vals: Vec<Value> = (0..entry.count as usize)
1241 .map(|i| Value::U16(read_u16(value_data, i * 2, byte_order)))
1242 .collect();
1243 Some(Value::List(vals))
1244 }
1245 }
1246 4 | 13 => {
1248 if entry.count == 1 {
1249 Some(Value::U32(read_u32(value_data, 0, byte_order)))
1250 } else {
1251 let vals: Vec<Value> = (0..entry.count as usize)
1252 .map(|i| Value::U32(read_u32(value_data, i * 4, byte_order)))
1253 .collect();
1254 Some(Value::List(vals))
1255 }
1256 }
1257 5 => {
1259 if entry.count == 1 {
1260 let n = read_u32(value_data, 0, byte_order);
1261 let d = read_u32(value_data, 4, byte_order);
1262 Some(Value::URational(n, d))
1263 } else {
1264 let vals: Vec<Value> = (0..entry.count as usize)
1265 .map(|i| {
1266 let n = read_u32(value_data, i * 8, byte_order);
1267 let d = read_u32(value_data, i * 8 + 4, byte_order);
1268 Value::URational(n, d)
1269 })
1270 .collect();
1271 Some(Value::List(vals))
1272 }
1273 }
1274 6 => {
1276 if entry.count == 1 {
1277 Some(Value::I16(value_data[0] as i8 as i16))
1278 } else {
1279 let vals: Vec<Value> = value_data
1280 .iter()
1281 .map(|&b| Value::I16(b as i8 as i16))
1282 .collect();
1283 Some(Value::List(vals))
1284 }
1285 }
1286 7 => Some(Value::Undefined(value_data.to_vec())),
1288 8 => {
1290 if entry.count == 1 {
1291 Some(Value::I16(read_i16(value_data, 0, byte_order)))
1292 } else {
1293 let vals: Vec<Value> = (0..entry.count as usize)
1294 .map(|i| Value::I16(read_i16(value_data, i * 2, byte_order)))
1295 .collect();
1296 Some(Value::List(vals))
1297 }
1298 }
1299 9 => {
1301 if entry.count == 1 {
1302 Some(Value::I32(read_i32(value_data, 0, byte_order)))
1303 } else {
1304 let vals: Vec<Value> = (0..entry.count as usize)
1305 .map(|i| Value::I32(read_i32(value_data, i * 4, byte_order)))
1306 .collect();
1307 Some(Value::List(vals))
1308 }
1309 }
1310 10 => {
1312 if entry.count == 1 {
1313 let n = read_i32(value_data, 0, byte_order);
1314 let d = read_i32(value_data, 4, byte_order);
1315 Some(Value::IRational(n, d))
1316 } else {
1317 let vals: Vec<Value> = (0..entry.count as usize)
1318 .map(|i| {
1319 let n = read_i32(value_data, i * 8, byte_order);
1320 let d = read_i32(value_data, i * 8 + 4, byte_order);
1321 Value::IRational(n, d)
1322 })
1323 .collect();
1324 Some(Value::List(vals))
1325 }
1326 }
1327 11 => {
1329 if entry.count == 1 {
1330 let bits = read_u32(value_data, 0, byte_order);
1331 Some(Value::F32(f32::from_bits(bits)))
1332 } else {
1333 let vals: Vec<Value> = (0..entry.count as usize)
1334 .map(|i| {
1335 let bits = read_u32(value_data, i * 4, byte_order);
1336 Value::F32(f32::from_bits(bits))
1337 })
1338 .collect();
1339 Some(Value::List(vals))
1340 }
1341 }
1342 12 => {
1344 if entry.count == 1 {
1345 let bits = read_u64(value_data, 0, byte_order);
1346 Some(Value::F64(f64::from_bits(bits)))
1347 } else {
1348 let vals: Vec<Value> = (0..entry.count as usize)
1349 .map(|i| {
1350 let bits = read_u64(value_data, i * 8, byte_order);
1351 Value::F64(f64::from_bits(bits))
1352 })
1353 .collect();
1354 Some(Value::List(vals))
1355 }
1356 }
1357 _ => None,
1358 }
1359}
1360
1361fn read_u16(data: &[u8], offset: usize, bo: ByteOrderMark) -> u16 {
1363 match bo {
1364 ByteOrderMark::LittleEndian => LittleEndian::read_u16(&data[offset..]),
1365 ByteOrderMark::BigEndian => BigEndian::read_u16(&data[offset..]),
1366 }
1367}
1368
1369fn read_u32(data: &[u8], offset: usize, bo: ByteOrderMark) -> u32 {
1370 match bo {
1371 ByteOrderMark::LittleEndian => LittleEndian::read_u32(&data[offset..]),
1372 ByteOrderMark::BigEndian => BigEndian::read_u32(&data[offset..]),
1373 }
1374}
1375
1376fn read_u64(data: &[u8], offset: usize, bo: ByteOrderMark) -> u64 {
1377 match bo {
1378 ByteOrderMark::LittleEndian => LittleEndian::read_u64(&data[offset..]),
1379 ByteOrderMark::BigEndian => BigEndian::read_u64(&data[offset..]),
1380 }
1381}
1382
1383fn read_i16(data: &[u8], offset: usize, bo: ByteOrderMark) -> i16 {
1384 match bo {
1385 ByteOrderMark::LittleEndian => LittleEndian::read_i16(&data[offset..]),
1386 ByteOrderMark::BigEndian => BigEndian::read_i16(&data[offset..]),
1387 }
1388}
1389
1390fn read_i32(data: &[u8], offset: usize, bo: ByteOrderMark) -> i32 {
1391 match bo {
1392 ByteOrderMark::LittleEndian => LittleEndian::read_i32(&data[offset..]),
1393 ByteOrderMark::BigEndian => BigEndian::read_i32(&data[offset..]),
1394 }
1395}
1396
1397fn process_geotiff_keys(tags: &mut Vec<Tag>) {
1400 let dir_vals: Option<Vec<u16>> =
1402 tags.iter()
1403 .find(|t| t.name == "GeoTiffDirectory")
1404 .and_then(|t| match &t.raw_value {
1405 Value::List(items) => {
1406 let vals: Vec<u16> = items
1407 .iter()
1408 .filter_map(|v| match v {
1409 Value::U16(x) => Some(*x),
1410 Value::U32(x) => Some(*x as u16),
1411 _ => None,
1412 })
1413 .collect();
1414 if vals.is_empty() {
1415 None
1416 } else {
1417 Some(vals)
1418 }
1419 }
1420 _ => None,
1421 });
1422
1423 let dir_vals = match dir_vals {
1424 Some(v) => v,
1425 None => return,
1426 };
1427
1428 if dir_vals.len() < 4 {
1429 return;
1430 }
1431
1432 let version = dir_vals[0];
1433 let revision = dir_vals[1];
1434 let minor_rev = dir_vals[2];
1435 let num_entries = dir_vals[3] as usize;
1436
1437 if dir_vals.len() < 4 + num_entries * 4 {
1438 return;
1439 }
1440
1441 let ascii_params: Option<String> = tags
1443 .iter()
1444 .find(|t| t.name == "GeoTiffAsciiParams")
1445 .map(|t| t.print_value.clone());
1446
1447 let double_params: Option<Vec<f64>> = tags
1449 .iter()
1450 .find(|t| t.name == "GeoTiffDoubleParams")
1451 .and_then(|t| match &t.raw_value {
1452 Value::List(items) => {
1453 let vals: Vec<f64> = items
1454 .iter()
1455 .filter_map(|v| match v {
1456 Value::F64(x) => Some(*x),
1457 Value::F32(x) => Some(*x as f64),
1458 _ => None,
1459 })
1460 .collect();
1461 if vals.is_empty() {
1462 None
1463 } else {
1464 Some(vals)
1465 }
1466 }
1467 _ => None,
1468 });
1469
1470 let mut new_tags = Vec::new();
1471
1472 new_tags.push(Tag {
1474 id: TagId::Text("GeoTiffVersion".to_string()),
1475 name: "GeoTiffVersion".to_string(),
1476 description: "GeoTiff Version".to_string(),
1477 group: TagGroup {
1478 family0: "EXIF".into(),
1479 family1: "IFD0".into(),
1480 family2: "Location".into(),
1481 },
1482 raw_value: Value::String(format!("{}.{}.{}", version, revision, minor_rev)),
1483 print_value: format!("{}.{}.{}", version, revision, minor_rev),
1484 priority: 0,
1485 });
1486
1487 for i in 0..num_entries {
1489 let base = 4 + i * 4;
1490 let key_id = dir_vals[base];
1491 let location = dir_vals[base + 1];
1492 let count = dir_vals[base + 2] as usize;
1493 let value_or_offset = dir_vals[base + 3];
1494
1495 let raw_val: Option<String> = match location {
1496 0 => {
1497 Some(format!("{}", value_or_offset))
1499 }
1500 34737 => {
1501 if let Some(ref ascii) = ascii_params {
1503 let off = value_or_offset as usize;
1504 let end = (off + count).min(ascii.len());
1505 if off <= end {
1506 let s = &ascii[off..end];
1507 let s = s.trim_end_matches('|').trim().to_string();
1509 Some(s)
1510 } else {
1511 None
1512 }
1513 } else {
1514 None
1515 }
1516 }
1517 34736 => {
1518 if let Some(ref doubles) = double_params {
1520 let off = value_or_offset as usize;
1521 if count == 1 && off < doubles.len() {
1522 Some(format!("{}", doubles[off]))
1523 } else if count > 1 {
1524 let vals: Vec<String> = doubles
1525 .iter()
1526 .skip(off)
1527 .take(count)
1528 .map(|v| format!("{}", v))
1529 .collect();
1530 Some(vals.join(" "))
1531 } else {
1532 None
1533 }
1534 } else {
1535 None
1536 }
1537 }
1538 _ => None,
1539 };
1540
1541 let val_str = match raw_val {
1542 Some(v) => v,
1543 None => continue,
1544 };
1545
1546 let (tag_name, print_val) = geotiff_key_to_tag(key_id, &val_str);
1548 if tag_name.is_empty() {
1549 continue;
1550 }
1551
1552 new_tags.push(Tag {
1553 id: TagId::Text(tag_name.clone()),
1554 name: tag_name.clone(),
1555 description: tag_name.clone(),
1556 group: TagGroup {
1557 family0: "EXIF".into(),
1558 family1: "IFD0".into(),
1559 family2: "Location".into(),
1560 },
1561 raw_value: Value::String(val_str),
1562 print_value: print_val,
1563 priority: 0,
1564 });
1565 }
1566
1567 if !new_tags.is_empty() {
1568 tags.retain(|t| {
1570 t.name != "GeoTiffDirectory"
1571 && t.name != "GeoTiffAsciiParams"
1572 && t.name != "GeoTiffDoubleParams"
1573 });
1574 tags.extend(new_tags);
1575 }
1576}
1577
1578fn geotiff_key_to_tag(key_id: u16, value: &str) -> (String, String) {
1580 let val_u16: Option<u16> = value.parse().ok();
1581
1582 match key_id {
1583 0x0001 => return ("GeoTiffVersion".to_string(), value.to_string()), 0x0400 => {
1586 let print = match val_u16 {
1588 Some(1) => "Projected".to_string(),
1589 Some(2) => "Geographic".to_string(),
1590 Some(3) => "Geocentric".to_string(),
1591 Some(32767) => "User Defined".to_string(),
1592 _ => value.to_string(),
1593 };
1594 return ("GTModelType".to_string(), print);
1595 }
1596 0x0401 => {
1597 let print = match val_u16 {
1599 Some(1) => "Pixel Is Area".to_string(),
1600 Some(2) => "Pixel Is Point".to_string(),
1601 Some(32767) => "User Defined".to_string(),
1602 _ => value.to_string(),
1603 };
1604 return ("GTRasterType".to_string(), print);
1605 }
1606 0x0402 => return ("GTCitation".to_string(), value.to_string()),
1607
1608 0x0800 => {
1610 return (
1611 "GeographicType".to_string(),
1612 geotiff_pcs_name(val_u16.unwrap_or(0), value),
1613 )
1614 }
1615 0x0801 => return ("GeogCitation".to_string(), value.to_string()),
1616 0x0802 => return ("GeogGeodeticDatum".to_string(), value.to_string()),
1617 0x0803 => return ("GeogPrimeMeridian".to_string(), value.to_string()),
1618 0x0804 => {
1619 return (
1620 "GeogLinearUnits".to_string(),
1621 geotiff_linear_unit_name(val_u16.unwrap_or(0), value),
1622 )
1623 }
1624 0x0805 => return ("GeogLinearUnitSize".to_string(), value.to_string()),
1625 0x0806 => return ("GeogAngularUnits".to_string(), value.to_string()),
1626 0x0807 => return ("GeogAngularUnitSize".to_string(), value.to_string()),
1627 0x0808 => return ("GeogEllipsoid".to_string(), value.to_string()),
1628 0x0809 => return ("GeogSemiMajorAxis".to_string(), value.to_string()),
1629 0x080a => return ("GeogSemiMinorAxis".to_string(), value.to_string()),
1630 0x080b => return ("GeogInvFlattening".to_string(), value.to_string()),
1631 0x080c => return ("GeogAzimuthUnits".to_string(), value.to_string()),
1632 0x080d => return ("GeogPrimeMeridianLong".to_string(), value.to_string()),
1633
1634 0x0C00 => {
1636 return (
1638 "ProjectedCSType".to_string(),
1639 geotiff_pcs_name(val_u16.unwrap_or(0), value),
1640 );
1641 }
1642 0x0C01 => return ("PCSCitation".to_string(), value.to_string()),
1643 0x0C02 => return ("Projection".to_string(), value.to_string()),
1644 0x0C03 => return ("ProjCoordTrans".to_string(), value.to_string()),
1645 0x0C04 => {
1646 return (
1647 "ProjLinearUnits".to_string(),
1648 geotiff_linear_unit_name(val_u16.unwrap_or(0), value),
1649 )
1650 }
1651 0x0C05 => return ("ProjLinearUnitSize".to_string(), value.to_string()),
1652 0x0C06 => return ("ProjStdParallel1".to_string(), value.to_string()),
1653 0x0C07 => return ("ProjStdParallel2".to_string(), value.to_string()),
1654 0x0C08 => return ("ProjNatOriginLong".to_string(), value.to_string()),
1655 0x0C09 => return ("ProjNatOriginLat".to_string(), value.to_string()),
1656 0x0c0a => return ("ProjFalseEasting".to_string(), value.to_string()),
1657 0x0c0b => return ("ProjFalseNorthing".to_string(), value.to_string()),
1658 0x0c0c => return ("ProjFalseOriginLong".to_string(), value.to_string()),
1659 0x0c0d => return ("ProjFalseOriginLat".to_string(), value.to_string()),
1660 0x0c0e => return ("ProjFalseOriginEasting".to_string(), value.to_string()),
1661 0x0c0f => return ("ProjFalseOriginNorthing".to_string(), value.to_string()),
1662 0x0C10 => return ("ProjCenterLong".to_string(), value.to_string()),
1663 0x0C11 => return ("ProjCenterLat".to_string(), value.to_string()),
1664 0x0C12 => return ("ProjCenterEasting".to_string(), value.to_string()),
1665 0x0C13 => return ("ProjCenterNorthing".to_string(), value.to_string()),
1666 0x0C14 => return ("ProjScaleAtNatOrigin".to_string(), value.to_string()),
1667 0x0C15 => return ("ProjScaleAtCenter".to_string(), value.to_string()),
1668 0x0C16 => return ("ProjAzimuthAngle".to_string(), value.to_string()),
1669 0x0C17 => return ("ProjStraightVertPoleLong".to_string(), value.to_string()),
1670
1671 0x1000 => return ("VerticalCSType".to_string(), value.to_string()),
1673 0x1001 => return ("VerticalCitation".to_string(), value.to_string()),
1674 0x1002 => return ("VerticalDatum".to_string(), value.to_string()),
1675 0x1003 => {
1676 return (
1677 "VerticalUnits".to_string(),
1678 geotiff_linear_unit_name(val_u16.unwrap_or(0), value),
1679 )
1680 }
1681
1682 _ => {}
1683 }
1684 (String::new(), String::new())
1685}
1686
1687fn geotiff_linear_unit_name(val: u16, fallback: &str) -> String {
1688 match val {
1689 9001 => "Linear Meter".to_string(),
1690 9002 => "Linear Foot".to_string(),
1691 9003 => "Linear Foot US Survey".to_string(),
1692 9004 => "Linear Foot Modified American".to_string(),
1693 9005 => "Linear Foot Clarke".to_string(),
1694 9006 => "Linear Foot Indian".to_string(),
1695 9007 => "Linear Link".to_string(),
1696 9008 => "Linear Link Benoit".to_string(),
1697 9009 => "Linear Link Sears".to_string(),
1698 9010 => "Linear Chain Benoit".to_string(),
1699 9011 => "Linear Chain Sears".to_string(),
1700 9012 => "Linear Yard Sears".to_string(),
1701 9013 => "Linear Yard Indian".to_string(),
1702 9014 => "Linear Fathom".to_string(),
1703 9015 => "Linear Mile International Nautical".to_string(),
1704 _ => fallback.to_string(),
1705 }
1706}
1707
1708fn geotiff_pcs_name(val: u16, fallback: &str) -> String {
1709 match val {
1711 26918 => "NAD83 UTM zone 18N".to_string(),
1712 26919 => "NAD83 UTM zone 19N".to_string(),
1713 32618 => "WGS84 UTM zone 18N".to_string(),
1714 32619 => "WGS84 UTM zone 19N".to_string(),
1715 4326 => "WGS 84".to_string(),
1716 4269 => "NAD83".to_string(),
1717 4267 => "NAD27".to_string(),
1718 32767 => "User Defined".to_string(),
1719 _ => fallback.to_string(),
1720 }
1721}