1use std::{collections::HashSet, fmt::Debug};
2
3use bytes::Bytes;
4use nom::{number::complete, Parser};
5
6use crate::{
7 error::EntryError,
8 slice::SliceChecked,
9 values::{DataFormat, EntryData, IRational, URational},
10 EntryValue, ExifTag,
11};
12
13use super::{exif_exif::IFD_ENTRY_SIZE, GPSInfo, LatLng, TiffHeader};
14use crate::TagOrCode;
15
16#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
22#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
23pub struct IfdIndex(usize);
24
25impl IfdIndex {
26 pub const MAIN: Self = IfdIndex(0);
28
29 pub const THUMBNAIL: Self = IfdIndex(1);
31
32 pub const fn new(index: usize) -> Self {
35 IfdIndex(index)
36 }
37
38 pub const fn as_usize(self) -> usize {
40 self.0
41 }
42}
43
44impl std::fmt::Display for IfdIndex {
45 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
46 write!(f, "ifd{}", self.0)
47 }
48}
49
50#[derive(Clone, Copy, Debug)]
62pub struct ExifEntry<'a> {
63 pub ifd: IfdIndex,
64 pub tag: TagOrCode,
65 pub value: &'a crate::EntryValue,
66}
67
68#[derive(Clone)]
71pub(crate) struct TiffDataBlock {
72 #[allow(dead_code)]
74 pub block_id: String,
75 pub data: Bytes,
77 pub header: Option<TiffHeader>,
79}
80
81#[tracing::instrument]
90pub(crate) fn input_into_iter(
91 input: impl Into<bytes::Bytes> + Debug,
92 state: Option<TiffHeader>,
93) -> crate::Result<ExifIter> {
94 let input: bytes::Bytes = input.into();
95 let header = match state {
96 Some(header) => header,
99 _ => {
100 let (_, header) = TiffHeader::parse(&input[..]).map_err(|e| {
102 crate::error::nom_err_to_malformed(e, crate::error::MalformedKind::TiffHeader)
103 })?;
104
105 tracing::debug!(
106 ?header,
107 data_len = format!("{:#x}", input.len()),
108 "TIFF header parsed"
109 );
110 header
111 }
112 };
113
114 let start = header.ifd0_offset as usize;
115 if start > input.len() {
116 return Err(crate::Error::UnexpectedEof {
117 context: "exif iter init",
118 });
119 }
120 tracing::debug!(?header, offset = start);
121
122 let mut ifd0 = IfdIter::try_new(0, input.clone(), header.to_owned(), start, None)?;
123
124 let tz = ifd0.find_tz_offset();
125 ifd0.tz = tz.clone();
126 let iter: ExifIter = ExifIter::new(input, header, tz, ifd0);
127
128 tracing::debug!(?iter, "got IFD0");
129
130 Ok(iter)
131}
132
133pub struct ExifIter {
146 input: Bytes,
147 tiff_header: TiffHeader,
148 tz: Option<String>,
149 ifd0: IfdIter,
150
151 ifds: Vec<IfdIter>,
153 visited_offsets: HashSet<usize>,
154
155 additional_blocks: Vec<TiffDataBlock>,
158 current_block_index: usize,
160 encountered_tags: HashSet<(usize, u16)>,
162 has_embedded_track: bool,
163}
164
165impl Debug for ExifIter {
166 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
167 f.debug_struct("ExifIter")
168 .field("data len", &self.input.len())
169 .field("tiff_header", &self.tiff_header)
170 .field("ifd0", &self.ifd0)
171 .field("state", &self.ifds.first().map(|x| (x.index, x.pos)))
172 .field("ifds num", &self.ifds.len())
173 .field("additional_blocks", &self.additional_blocks.len())
174 .field("current_block_index", &self.current_block_index)
175 .finish_non_exhaustive()
176 }
177}
178
179impl Clone for ExifIter {
180 fn clone(&self) -> Self {
181 self.clone_rewound()
182 }
183}
184
185impl ExifIter {
186 pub(crate) fn new(
187 input: bytes::Bytes,
188 tiff_header: TiffHeader,
189 tz: Option<String>,
190 ifd0: IfdIter,
191 ) -> ExifIter {
192 let ifds = vec![ifd0.clone()];
193 ExifIter {
194 input,
195 tiff_header,
196 tz,
197 ifd0,
198 ifds,
199 visited_offsets: HashSet::new(),
200 additional_blocks: Vec::new(),
201 current_block_index: 0,
202 encountered_tags: HashSet::new(),
203 has_embedded_track: false,
204 }
205 }
206
207 pub fn clone_rewound(&self) -> Self {
211 let ifd0 = self.ifd0.clone_and_rewind();
212 let ifds = vec![ifd0.clone()];
213 Self {
214 input: self.input.clone(),
215 tiff_header: self.tiff_header.clone(),
216 tz: self.tz.clone(),
217 ifd0,
218 ifds,
219 visited_offsets: HashSet::new(),
220 additional_blocks: self.additional_blocks.clone(),
221 current_block_index: 0,
222 encountered_tags: HashSet::new(),
223 has_embedded_track: self.has_embedded_track,
224 }
225 }
226
227 pub fn rewind(&mut self) {
230 let ifd0 = self.ifd0.clone_and_rewind();
231 self.ifds = vec![ifd0.clone()];
232 self.ifd0 = ifd0;
233 self.visited_offsets.clear();
234 self.current_block_index = 0;
235 self.encountered_tags.clear();
236 }
237
238 #[tracing::instrument(skip_all)]
248 pub fn parse_gps(&self) -> crate::Result<Option<GPSInfo>> {
249 let mut iter = self.clone_rewound();
250 let Some(gps) = iter.find(|x| {
251 tracing::info!(?x, "find");
252 x.tag().tag().is_some_and(|t| t == ExifTag::GPSInfo)
253 }) else {
254 tracing::warn!(ifd0 = ?iter.ifds.first(), "GPSInfo not found");
255 return Ok(None);
256 };
257
258 let offset = match gps.result() {
259 Ok(v) => {
260 if let Some(offset) = v.as_u32() {
261 offset
262 } else {
263 return Err(EntryError::InvalidValue("invalid gps offset").into());
264 }
265 }
266 Err(e) => return Err(e.clone().into()),
267 };
268 if offset as usize >= iter.input.len() {
269 return Err(crate::Error::Malformed {
270 kind: crate::error::MalformedKind::IfdEntry,
271 message: "GPSInfo offset out of range".into(),
272 });
273 }
274
275 let mut gps_subifd = IfdIter::try_new(
276 gps.ifd().as_usize(),
277 iter.input.clone(),
278 iter.tiff_header,
279 offset as usize,
280 iter.tz.clone(),
281 )?
282 .tag_code(ExifTag::GPSInfo.code());
283 Ok(gps_subifd.parse_gps_info())
284 }
285
286 pub(crate) fn add_tiff_block(
294 &mut self,
295 block_id: String,
296 data: bytes::Bytes,
297 header: Option<TiffHeader>,
298 ) {
299 self.additional_blocks.push(TiffDataBlock {
300 block_id,
301 data,
302 header,
303 });
304 }
305
306 pub(crate) fn set_has_embedded_track(&mut self, v: bool) {
309 self.has_embedded_track = v;
310 }
311
312 pub fn has_embedded_track(&self) -> bool {
328 self.has_embedded_track
329 }
330
331 #[deprecated(
333 since = "3.1.0",
334 note = "renamed to `has_embedded_track`; the original `has_embedded_media` was too vague and lumped in still-image previews"
335 )]
336 pub fn has_embedded_media(&self) -> bool {
337 self.has_embedded_track()
338 }
339}
340
341#[derive(Clone)]
352pub struct ExifIterEntry {
353 ifd: IfdIndex,
354 tag: TagOrCode,
355 res: Result<EntryValue, crate::error::EntryError>,
356}
357
358impl ExifIterEntry {
359 pub fn ifd(&self) -> IfdIndex {
361 self.ifd
362 }
363
364 pub fn tag(&self) -> TagOrCode {
366 self.tag
367 }
368
369 pub fn value(&self) -> Option<&EntryValue> {
371 self.res.as_ref().ok()
372 }
373
374 pub fn error(&self) -> Option<&crate::error::EntryError> {
376 self.res.as_ref().err()
377 }
378
379 pub fn result(&self) -> Result<&EntryValue, &crate::error::EntryError> {
381 self.res.as_ref()
382 }
383
384 pub fn into_result(self) -> Result<EntryValue, crate::error::EntryError> {
387 self.res
388 }
389
390 pub(crate) fn make_ok(ifd: usize, tag: TagOrCode, v: EntryValue) -> Self {
391 Self {
392 ifd: IfdIndex::new(ifd),
393 tag,
394 res: Ok(v),
395 }
396 }
397}
398
399impl std::fmt::Debug for ExifIterEntry {
400 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
401 let value = match &self.res {
402 Ok(v) => format!("{v}"),
403 Err(e) => format!("{e:?}"),
404 };
405 f.debug_struct("ExifIterEntry")
406 .field("ifd", &self.ifd)
407 .field("tag", &self.tag)
408 .field("value", &value)
409 .finish()
410 }
411}
412
413const MAX_IFD_DEPTH: usize = 8;
414
415impl ExifIter {
416 fn load_next_block(&mut self) -> bool {
419 let block_index = self.current_block_index;
421 if block_index >= self.additional_blocks.len() {
422 return false;
423 }
424
425 let block = &self.additional_blocks[block_index];
426 tracing::debug!(
427 block_id = block.block_id,
428 block_index,
429 "Loading additional TIFF block"
430 );
431
432 let block_data = block.data.clone();
434 let header = block.header.clone();
435
436 match input_into_iter(block_data, header) {
438 Ok(iter) => {
439 self.ifd0 = iter.ifd0;
441 self.ifds = vec![self.ifd0.clone()];
442 self.visited_offsets.clear();
443 self.current_block_index += 1;
444
445 tracing::debug!(block_index, "Successfully loaded additional TIFF block");
446 true
447 }
448 Err(e) => {
449 tracing::warn!(
450 block_index,
451 error = %e,
452 "Failed to load additional TIFF block, skipping"
453 );
454 self.current_block_index += 1;
456 self.load_next_block()
457 }
458 }
459 }
460
461 fn should_include_tag(&mut self, ifd_index: usize, tag_code: u16) -> bool {
464 let tag_key = (ifd_index, tag_code);
465 if self.encountered_tags.contains(&tag_key) {
466 tracing::debug!(ifd_index, tag_code, "Skipping duplicate tag");
467 false
468 } else {
469 self.encountered_tags.insert(tag_key);
470 true
471 }
472 }
473}
474
475impl Iterator for ExifIter {
476 type Item = ExifIterEntry;
477
478 #[tracing::instrument(skip_all)]
479 fn next(&mut self) -> Option<Self::Item> {
480 loop {
481 if self.ifds.is_empty() {
482 if !self.load_next_block() {
484 tracing::debug!(?self, "all IFDs and blocks have been parsed");
485 return None;
486 }
487 continue;
489 }
490
491 if self.ifds.len() > MAX_IFD_DEPTH {
492 let depth = self.ifds.len();
493 self.ifds.clear();
494 tracing::error!(
495 ifds_depth = depth,
496 "ifd depth is too deep, just go back to ifd0"
497 );
498 self.ifds.push(self.ifd0.clone_with_state());
499 }
500
501 let mut ifd = self.ifds.pop()?;
502 let cur_ifd_idx = ifd.ifd_idx;
503 match ifd.next() {
504 Some((tag_code, entry)) => {
505 tracing::debug!(ifd = ifd.ifd_idx, ?tag_code, "next tag entry");
506
507 match entry {
508 IfdEntry::IfdNew(new_ifd) => {
509 if new_ifd.offset > 0 {
510 if self.visited_offsets.contains(&new_ifd.offset) {
511 continue;
513 }
514 self.visited_offsets.insert(new_ifd.offset);
515 }
516
517 let is_subifd = if new_ifd.ifd_idx == ifd.ifd_idx {
518 self.ifds.push(ifd);
520 tracing::debug!(?tag_code, ?new_ifd, "got new SUB-IFD");
521 true
522 } else {
523 tracing::debug!("IFD{} parsing completed", cur_ifd_idx);
527 tracing::debug!(?new_ifd, "got new IFD");
528 false
529 };
530
531 let (ifd_idx, offset) = (new_ifd.ifd_idx, new_ifd.offset);
532 self.ifds.push(new_ifd);
533
534 if is_subifd {
535 let tc = tag_code.unwrap();
537 if !self.should_include_tag(ifd_idx, tc.code()) {
538 continue;
539 }
540 return Some(ExifIterEntry::make_ok(
542 ifd_idx,
543 tc,
544 EntryValue::U32(offset as u32),
545 ));
546 }
547 }
548 IfdEntry::Entry(v) => {
549 let tc = tag_code.unwrap();
550 if !self.should_include_tag(ifd.ifd_idx, tc.code()) {
552 self.ifds.push(ifd);
553 continue;
554 }
555 let res = Some(ExifIterEntry::make_ok(ifd.ifd_idx, tc, v));
556 self.ifds.push(ifd);
557 return res;
558 }
559 IfdEntry::Err(e) => {
560 tracing::warn!(?tag_code, ?e, "parse ifd entry error");
561 self.ifds.push(ifd);
562 continue;
563 }
564 }
565 }
566 None => continue,
567 }
568 }
569 }
570}
571
572#[derive(Clone)]
573pub(crate) struct IfdIter {
574 ifd_idx: usize,
575 tag_code: Option<TagOrCode>,
576
577 input: Bytes,
579
580 offset: usize,
582
583 header: TiffHeader,
584 entry_num: u16,
585
586 pub tz: Option<String>,
587
588 index: u16,
590 pos: usize,
591}
592
593impl Debug for IfdIter {
594 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
595 f.debug_struct("IfdIter")
596 .field("ifd_idx", &self.ifd_idx)
597 .field("tag", &self.tag_code)
598 .field("data len", &self.input.len())
599 .field("tz", &self.tz)
600 .field("header", &self.header)
601 .field("entry_num", &self.entry_num)
602 .field("index", &self.index)
603 .field("pos", &self.pos)
604 .finish()
605 }
606}
607
608impl IfdIter {
609 pub fn rewind(&mut self) {
610 self.index = 0;
611 self.pos = self.offset + 2;
613 }
614
615 pub fn clone_and_rewind(&self) -> Self {
616 let mut it = self.clone();
617 it.rewind();
618 it
619 }
620
621 pub fn tag_code_maybe(mut self, code: Option<u16>) -> Self {
622 self.tag_code = code.map(|x| x.into());
623 self
624 }
625
626 pub fn tag_code(mut self, code: u16) -> Self {
627 self.tag_code = Some(code.into());
628 self
629 }
630
631 fn is_gps_subifd(&self) -> bool {
632 matches!(
633 self.tag_code.as_ref().and_then(|t| t.tag()),
634 Some(ExifTag::GPSInfo)
635 )
636 }
637
638 #[allow(unused)]
639 pub fn tag(mut self, tag: TagOrCode) -> Self {
640 self.tag_code = Some(tag);
641 self
642 }
643
644 #[tracing::instrument(skip(input))]
645 pub fn try_new(
646 ifd_idx: usize,
647 input: Bytes,
648 header: TiffHeader,
649 offset: usize,
650 tz: Option<String>,
651 ) -> crate::Result<Self> {
652 if input.len() < 2 {
653 return Err(crate::Error::Malformed {
654 kind: crate::error::MalformedKind::TiffHeader,
655 message: "ifd data too small to decode entry num".into(),
656 });
657 }
658 assert!(offset <= input.len());
660 let ifd_data = input.slice(offset..);
661 let (_, entry_num) =
662 TiffHeader::parse_ifd_entry_num(&ifd_data, header.endian).map_err(|e| {
663 crate::error::nom_err_to_malformed(e, crate::error::MalformedKind::TiffHeader)
664 })?;
665
666 Ok(Self {
667 ifd_idx,
668 tag_code: None,
669 input,
670 offset,
671 header,
672 entry_num,
673 tz,
674 pos: offset + 2,
676 index: 0,
677 })
678 }
679
680 fn parse_tag_entry(&self, entry_data: &[u8]) -> Option<(u16, IfdEntry)> {
681 let endian = self.header.endian;
682 let (_, (tag, data_format, components_num, value_or_offset)) = (
683 complete::u16::<_, nom::error::Error<_>>(endian),
684 complete::u16(endian),
685 complete::u32(endian),
686 complete::u32(endian),
687 )
688 .parse(entry_data)
689 .ok()?;
690
691 if tag == 0 && !self.is_gps_subifd() {
696 return None;
697 }
698
699 let df: DataFormat = match DataFormat::try_from(data_format) {
700 Ok(df) => df,
701 Err(bad) => {
702 let t: TagOrCode = tag.into();
703 tracing::warn!(tag = ?t, format = bad, "invalid entry data format");
704 return Some((
705 tag,
706 IfdEntry::Err(EntryError::InvalidShape {
707 format: bad,
708 count: components_num,
709 }),
710 ));
711 }
712 };
713 let (tag, res) = self.parse_entry(tag, df, components_num, entry_data, value_or_offset);
714 Some((tag, res))
715 }
716
717 fn get_data_pos(&self, value_or_offset: u32) -> usize {
718 value_or_offset as usize
720 }
721
722 fn parse_entry(
723 &self,
724 tag: u16,
725 data_format: DataFormat,
726 components_num: u32,
727 entry_data: &[u8],
728 value_or_offset: u32,
729 ) -> (u16, IfdEntry) {
730 let component_size = data_format.component_size();
732
733 let size = components_num as usize * component_size;
735 let data = if size <= 4 {
736 &entry_data[8..8 + size] } else {
738 let start = self.get_data_pos(value_or_offset);
739 let end = start + size;
740 let Some(data) = self.input.slice_checked(start..end) else {
741 tracing::warn!(
742 "entry data overflow, tag: {:04x} start: {:08x} end: {:08x} ifd data len {:08x}",
743 tag,
744 start,
745 end,
746 self.input.len(),
747 );
748 return (
749 tag,
750 IfdEntry::Err(EntryError::Truncated {
751 needed: size,
752 available: self.input.len().saturating_sub(start),
753 }),
754 );
755 };
756
757 data
758 };
759
760 if SUBIFD_TAGS.contains(&tag) {
761 if let Some(value) = self.new_ifd_iter(self.ifd_idx, value_or_offset, Some(tag)) {
762 return (tag, value);
763 }
764 }
765
766 let entry = EntryData {
767 endian: self.header.endian,
768 tag,
769 data,
770 data_format,
771 components_num,
772 };
773 match EntryValue::parse(&entry, &self.tz) {
774 Ok(v) => (tag, IfdEntry::Entry(v)),
775 Err(e) => (tag, IfdEntry::Err(e)),
776 }
777 }
778
779 fn new_ifd_iter(
780 &self,
781 ifd_idx: usize,
782 value_or_offset: u32,
783 tag: Option<u16>,
784 ) -> Option<IfdEntry> {
785 let offset = self.get_data_pos(value_or_offset);
786 if offset < self.input.len() {
787 match IfdIter::try_new(
788 ifd_idx,
789 self.input.clone(),
790 self.header.to_owned(),
791 offset,
792 self.tz.clone(),
793 ) {
794 Ok(iter) => return Some(IfdEntry::IfdNew(iter.tag_code_maybe(tag))),
795 Err(e) => {
796 tracing::warn!(?tag, ?e, "Create next/sub IFD failed");
797 }
798 }
799 }
808 None
809 }
810
811 pub fn find_exif_iter(&self) -> Option<IfdIter> {
812 let endian = self.header.endian;
813 for i in 0..self.entry_num {
815 let pos = self.pos + i as usize * IFD_ENTRY_SIZE;
816 let (_, tag) =
817 complete::u16::<_, nom::error::Error<_>>(endian)(&self.input[pos..]).ok()?;
818 if tag == ExifTag::ExifOffset.code() {
819 let entry_data = self.input.slice_checked(pos..pos + IFD_ENTRY_SIZE)?;
820 let (_, entry) = self.parse_tag_entry(entry_data)?;
821 match entry {
822 IfdEntry::IfdNew(iter) => return Some(iter),
823 IfdEntry::Entry(_) | IfdEntry::Err(_) => return None,
824 }
825 }
826 }
827 None
828 }
829
830 pub fn find_tz_offset(&self) -> Option<String> {
831 let iter = self.find_exif_iter()?;
832 let mut offset = None;
833 for entry in iter {
834 let Some(tag) = entry.0 else {
835 continue;
836 };
837 if tag.code() == ExifTag::OffsetTimeOriginal.code()
838 || tag.code() == ExifTag::OffsetTimeDigitized.code()
839 {
840 return entry.1.as_str().map(|x| x.to_owned());
841 } else if tag.code() == ExifTag::OffsetTime.code() {
842 offset = entry.1.as_str().map(|x| x.to_owned());
843 }
844 }
845
846 offset
847 }
848
849 pub fn parse_gps_info(&mut self) -> Option<GPSInfo> {
851 use crate::exif::gps::{Altitude, LatRef, LonRef, Speed, SpeedUnit};
852
853 let mut latitude_ref = None;
854 let mut latitude = None;
855 let mut longitude_ref = None;
856 let mut longitude = None;
857 let mut altitude_ref = None;
858 let mut altitude_value = None;
859 let mut speed_unit = None;
860 let mut speed_value = None;
861 let mut has_data = false;
862
863 for (tag, entry) in self {
864 let Some(tag) = tag.and_then(|x| x.tag()) else {
865 continue;
866 };
867 has_data = true;
868 match tag {
869 ExifTag::GPSLatitudeRef => {
870 latitude_ref = entry.as_char().and_then(LatRef::from_char);
871 }
872 ExifTag::GPSLongitudeRef => {
873 longitude_ref = entry.as_char().and_then(LonRef::from_char);
874 }
875 ExifTag::GPSAltitudeRef => {
876 altitude_ref = entry.as_u8();
877 }
878 ExifTag::GPSLatitude => {
879 if let Some(v) = entry.as_urational_slice() {
880 latitude = LatLng::try_from(v).ok();
881 } else if let Some(v) = entry.as_irational_slice() {
882 latitude = LatLng::try_from(v).ok();
883 }
884 }
885 ExifTag::GPSLongitude => {
886 if let Some(v) = entry.as_urational_slice() {
887 longitude = LatLng::try_from(v).ok();
888 } else if let Some(v) = entry.as_irational_slice() {
889 longitude = LatLng::try_from(v).ok();
890 }
891 }
892 ExifTag::GPSAltitude => {
893 if let Some(v) = entry.as_urational() {
894 altitude_value = Some(*v);
895 } else if let Some(v) = entry.as_irational() {
896 if let Ok(u) = URational::try_from(*v) {
897 altitude_value = Some(u);
898 }
899 }
900 }
901 ExifTag::GPSSpeedRef => {
902 speed_unit = entry.as_char().and_then(SpeedUnit::from_char);
903 }
904 ExifTag::GPSSpeed => {
905 if let Some(v) = entry.as_urational() {
906 speed_value = Some(*v);
907 } else if let Some(v) = entry.as_irational() {
908 if let Ok(u) = URational::try_from(*v) {
909 speed_value = Some(u);
910 }
911 }
912 }
913 _ => (),
914 }
915 }
916
917 if !has_data {
918 tracing::warn!("GPSInfo data not found");
919 return None;
920 }
921
922 let altitude = match (altitude_ref, altitude_value) {
923 (Some(0), Some(v)) => Altitude::AboveSeaLevel(v),
924 (Some(1), Some(v)) => Altitude::BelowSeaLevel(v),
925 _ => Altitude::Unknown,
926 };
927
928 let speed = match (speed_unit, speed_value) {
929 (Some(unit), Some(value)) => Some(Speed { unit, value }),
930 _ => None,
931 };
932
933 Some(GPSInfo {
934 latitude_ref: latitude_ref.unwrap_or(LatRef::North),
935 latitude: latitude.unwrap_or_default(),
936 longitude_ref: longitude_ref.unwrap_or(LonRef::East),
937 longitude: longitude.unwrap_or_default(),
938 altitude,
939 speed,
940 })
941 }
942
943 fn clone_with_state(&self) -> IfdIter {
944 let mut it = self.clone();
945 it.index = self.index;
946 it.pos = self.pos;
947 it
948 }
949}
950
951#[derive(Debug)]
952pub(crate) enum IfdEntry {
953 IfdNew(IfdIter), Entry(EntryValue),
955 Err(EntryError),
956}
957
958impl IfdEntry {
959 pub fn as_u8(&self) -> Option<u8> {
960 if let IfdEntry::Entry(EntryValue::U8(v)) = self {
961 Some(*v)
962 } else {
963 None
964 }
965 }
966
967 pub fn as_char(&self) -> Option<char> {
968 if let IfdEntry::Entry(EntryValue::Text(s)) = self {
969 s.chars().next()
970 } else {
971 None
972 }
973 }
974
975 fn as_irational(&self) -> Option<&IRational> {
976 if let IfdEntry::Entry(EntryValue::IRational(v)) = self {
977 Some(v)
978 } else {
979 None
980 }
981 }
982
983 fn as_irational_slice(&self) -> Option<&Vec<IRational>> {
984 if let IfdEntry::Entry(EntryValue::IRationalArray(v)) = self {
985 Some(v)
986 } else {
987 None
988 }
989 }
990
991 fn as_urational(&self) -> Option<&URational> {
992 if let IfdEntry::Entry(EntryValue::URational(v)) = self {
993 Some(v)
994 } else {
995 None
996 }
997 }
998
999 fn as_urational_slice(&self) -> Option<&Vec<URational>> {
1000 if let IfdEntry::Entry(EntryValue::URationalArray(v)) = self {
1001 Some(v)
1002 } else {
1003 None
1004 }
1005 }
1006
1007 fn as_str(&self) -> Option<&str> {
1008 if let IfdEntry::Entry(e) = self {
1009 e.as_str()
1010 } else {
1011 None
1012 }
1013 }
1014}
1015
1016pub(crate) const SUBIFD_TAGS: &[u16] = &[ExifTag::ExifOffset.code(), ExifTag::GPSInfo.code()];
1017
1018impl Iterator for IfdIter {
1019 type Item = (Option<TagOrCode>, IfdEntry);
1020
1021 #[tracing::instrument(skip(self))]
1022 fn next(&mut self) -> Option<Self::Item> {
1023 tracing::debug!(
1024 ifd = self.ifd_idx,
1025 index = self.index,
1026 entry_num = self.entry_num,
1027 offset = format!("{:08x}", self.offset),
1028 pos = format!("{:08x}", self.pos),
1029 "next IFD entry"
1030 );
1031 if self.input.len() < self.pos + IFD_ENTRY_SIZE {
1032 return None;
1033 }
1034
1035 let endian = self.header.endian;
1036 if self.index > self.entry_num {
1037 return None;
1038 }
1039 if self.index == self.entry_num {
1040 tracing::debug!(
1041 self.ifd_idx,
1042 self.index,
1043 pos = self.pos,
1044 "try to get next ifd"
1045 );
1046 self.index += 1;
1047
1048 let (_, offset) =
1050 complete::u32::<_, nom::error::Error<_>>(endian)(&self.input[self.pos..]).ok()?;
1051
1052 if offset == 0 {
1053 tracing::debug!(?self, "IFD parsing completed");
1055 return None;
1056 }
1057
1058 return self
1059 .new_ifd_iter(self.ifd_idx + 1, offset, None)
1060 .map(|x| (None, x));
1061 }
1062
1063 let entry_data = self
1064 .input
1065 .slice_checked(self.pos..self.pos + IFD_ENTRY_SIZE)?;
1066 self.index += 1;
1067 self.pos += IFD_ENTRY_SIZE;
1068
1069 let (tag, res) = self.parse_tag_entry(entry_data)?;
1070
1071 Some((Some(tag.into()), res)) }
1073}
1074
1075#[cfg(test)]
1076mod tests {
1077
1078 use crate::exif::extract_exif_with_mime;
1079 use crate::exif::input_into_iter;
1080 use crate::file::MediaMimeImage;
1081 use crate::slice::SubsliceRange;
1082 use crate::testkit::read_sample;
1083 use crate::Exif;
1084 use test_case::test_case;
1085
1086 #[test_case(
1087 "exif.jpg",
1088 "+08:00",
1089 "2023-07-09T20:36:33+08:00",
1090 MediaMimeImage::Jpeg
1091 )]
1092 #[test_case("exif-no-tz.jpg", "", "2023-07-09 20:36:33", MediaMimeImage::Jpeg)]
1093 #[test_case("broken.jpg", "-", "2014-09-21 15:51:22", MediaMimeImage::Jpeg)]
1094 #[test_case(
1095 "exif.heic",
1096 "+08:00",
1097 "2022-07-22T21:26:32+08:00",
1098 MediaMimeImage::Heic
1099 )]
1100 #[test_case(
1101 "exif.avif",
1102 "+08:00",
1103 "2022-07-22T21:26:32+08:00",
1104 MediaMimeImage::Avif
1105 )]
1106 #[test_case("tif.tif", "-", "-", MediaMimeImage::Tiff)]
1107 #[test_case(
1108 "fujifilm_x_t1_01.raf.meta",
1109 "-",
1110 "2014-01-30 12:49:13",
1111 MediaMimeImage::Raf
1112 )]
1113 fn exif_iter_tz(path: &str, tz: &str, time: &str, img_type: MediaMimeImage) {
1114 let buf = read_sample(path).unwrap();
1115 let (data, _) = extract_exif_with_mime(img_type, &buf, None).unwrap();
1116 let range = data.and_then(|x| buf.subslice_in_range(x)).unwrap();
1117 let iter = input_into_iter(bytes::Bytes::from(buf).slice(range), None).unwrap();
1118 let expect = if tz == "-" {
1119 None
1120 } else {
1121 Some(tz.to_string())
1122 };
1123 assert_eq!(iter.tz, expect);
1124 let exif: Exif = iter.into();
1125 let value = exif.get(crate::ExifTag::DateTimeOriginal);
1126 if time == "-" {
1127 assert!(value.is_none());
1128 } else {
1129 let value = value.unwrap();
1130 assert_eq!(value.to_string(), time);
1131 }
1132 }
1133
1134 #[test]
1135 fn ifd_index_constants() {
1136 use crate::IfdIndex;
1137 assert_eq!(IfdIndex::MAIN.as_usize(), 0);
1138 assert_eq!(IfdIndex::THUMBNAIL.as_usize(), 1);
1139 }
1140
1141 #[test]
1142 fn ifd_index_roundtrip_via_new_and_as_usize() {
1143 use crate::IfdIndex;
1144 for raw in [0, 1, 2, 3, 7, 99] {
1145 assert_eq!(IfdIndex::new(raw).as_usize(), raw);
1146 }
1147 }
1148
1149 #[test]
1150 fn ifd_index_equality_and_hash() {
1151 use crate::IfdIndex;
1152 use std::collections::HashSet;
1153 let mut set: HashSet<IfdIndex> = HashSet::new();
1154 set.insert(IfdIndex::MAIN);
1155 set.insert(IfdIndex::new(0)); set.insert(IfdIndex::THUMBNAIL);
1157 assert_eq!(set.len(), 2);
1158 }
1159
1160 #[test]
1161 fn ifd_index_display_format() {
1162 use crate::IfdIndex;
1163 assert_eq!(format!("{}", IfdIndex::MAIN), "ifd0");
1164 assert_eq!(format!("{}", IfdIndex::new(7)), "ifd7");
1165 }
1166
1167 #[test]
1168 fn tag_or_code_for_known_tag_resolves_to_tag_variant() {
1169 use crate::{ExifTag, TagOrCode};
1170 let t: TagOrCode = ExifTag::Make.code().into();
1171 assert_eq!(t, TagOrCode::Tag(ExifTag::Make));
1172 assert_eq!(t.code(), ExifTag::Make.code());
1173 }
1174
1175 #[test]
1176 fn tag_or_code_for_unknown_tag_resolves_to_unknown_variant() {
1177 use crate::TagOrCode;
1178 let t: TagOrCode = 0xffff_u16.into();
1179 assert_eq!(t, TagOrCode::Unknown(0xffff));
1180 assert_eq!(t.code(), 0xffff);
1181 }
1182
1183 #[test]
1184 fn exif_entry_pub_fields_construct_and_destructure() {
1185 use crate::{EntryValue, ExifEntry, ExifTag, IfdIndex, TagOrCode};
1186 let val = EntryValue::Text("vivo X90 Pro+".into());
1187 let e = ExifEntry {
1188 ifd: IfdIndex::MAIN,
1189 tag: TagOrCode::Tag(ExifTag::Model),
1190 value: &val,
1191 };
1192 let ExifEntry { ifd, tag, value } = e;
1194 assert_eq!(ifd, IfdIndex::MAIN);
1195 assert_eq!(tag.code(), ExifTag::Model.code());
1196 assert!(matches!(value, EntryValue::Text(_)));
1197 let _e2 = e;
1199 let _e3 = e;
1200 }
1201
1202 #[test]
1203 fn exif_iter_entry_value_xor_error_invariant() {
1204 use crate::{MediaParser, MediaSource};
1205 let mut parser = MediaParser::new();
1206 let ms = MediaSource::open("testdata/exif.jpg").unwrap();
1207 for entry in parser.parse_exif(ms).unwrap() {
1208 let has_v = entry.value().is_some();
1210 let has_e = entry.error().is_some();
1211 assert!(has_v ^ has_e, "entry must be value xor error");
1212 match entry.result() {
1214 Ok(v) => assert_eq!(Some(v), entry.value()),
1215 Err(e) => assert_eq!(Some(e), entry.error()),
1216 }
1217 }
1218 }
1219
1220 #[test]
1221 fn exif_iter_entry_into_result_consumes_self() {
1222 use crate::{MediaParser, MediaSource};
1223 let mut parser = MediaParser::new();
1224 let ms = MediaSource::open("testdata/exif.jpg").unwrap();
1225 let mut count_ok = 0usize;
1226 for entry in parser.parse_exif(ms).unwrap() {
1227 if entry.into_result().is_ok() {
1231 count_ok += 1;
1232 }
1233 }
1234 assert!(count_ok > 0);
1235 }
1236
1237 #[test]
1238 fn exif_iter_entry_tag_returns_tag_or_code() {
1239 use crate::{ExifTag, MediaParser, MediaSource, TagOrCode};
1240 let mut parser = MediaParser::new();
1241 let ms = MediaSource::open("testdata/exif.jpg").unwrap();
1242 let make_present = parser
1243 .parse_exif(ms)
1244 .unwrap()
1245 .any(|e| matches!(e.tag(), TagOrCode::Tag(ExifTag::Make)));
1246 assert!(make_present);
1247 }
1248
1249 #[test]
1250 fn exif_iter_rewind_resets_iteration_state() {
1251 use crate::{MediaParser, MediaSource};
1252 let mut parser = MediaParser::new();
1253 let ms = MediaSource::open("testdata/exif.jpg").unwrap();
1254 let mut iter = parser.parse_exif(ms).unwrap();
1255 let first_count = iter.by_ref().count();
1256 assert!(first_count > 0);
1257 assert_eq!(iter.by_ref().count(), 0);
1259 iter.rewind();
1260 let after_rewind = iter.count();
1261 assert_eq!(first_count, after_rewind);
1262 }
1263
1264 #[test]
1265 fn exif_iter_clone_rewound_yields_independent_full_iter() {
1266 use crate::{MediaParser, MediaSource};
1267 let mut parser = MediaParser::new();
1268 let ms = MediaSource::open("testdata/exif.jpg").unwrap();
1269 let mut iter = parser.parse_exif(ms).unwrap();
1270 let _consumed = iter.by_ref().take(2).count();
1271 let cloned = iter.clone_rewound();
1272 let cloned_total = cloned.count();
1274 let remaining = iter.count();
1275 assert!(cloned_total > remaining);
1276 }
1277
1278 #[test]
1279 fn exif_iter_parse_gps_returns_option_no_iteration_advance() {
1280 use crate::{MediaParser, MediaSource};
1281 let mut parser = MediaParser::new();
1282 let ms = MediaSource::open("testdata/exif.jpg").unwrap();
1283 let iter = parser.parse_exif(ms).unwrap();
1284 let gps = iter.parse_gps().unwrap();
1285 assert!(gps.is_some());
1286 let count = iter.count();
1288 assert!(count > 0);
1289 }
1290
1291 #[test]
1298 fn gps_subifd_first_entry_is_gpsversion_id_issue_50() {
1299 use crate::exif::exif_iter::input_into_iter;
1300 #[rustfmt::skip]
1301 let tiff: &[u8] = &[
1302 b'I', b'I', 0x2a, 0x00,
1304 0x08, 0x00, 0x00, 0x00,
1305
1306 0x01, 0x00,
1308 0x25, 0x88, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00,
1309 0x1a, 0x00, 0x00, 0x00,
1310 0x00, 0x00, 0x00, 0x00, 0x05, 0x00,
1314 0x00, 0x00, 0x01, 0x00, 0x04, 0x00, 0x00, 0x00,
1316 0x02, 0x03, 0x00, 0x00,
1317 0x01, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00,
1319 b'N', 0x00, 0x00, 0x00,
1320 0x02, 0x00, 0x05, 0x00, 0x03, 0x00, 0x00, 0x00,
1322 0x5c, 0x00, 0x00, 0x00,
1323 0x03, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00,
1325 b'E', 0x00, 0x00, 0x00,
1326 0x04, 0x00, 0x05, 0x00, 0x03, 0x00, 0x00, 0x00,
1328 0x74, 0x00, 0x00, 0x00,
1329 0x00, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
1333 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
1334 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
1335
1336 0x78, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
1338 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
1339 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
1340 ];
1341
1342 let iter = input_into_iter(tiff.to_vec(), None).unwrap();
1343
1344 let gps = iter
1346 .parse_gps()
1347 .expect("parse_gps must succeed")
1348 .expect("GPS sub-IFD with GPSVersionID first must yield GPSInfo");
1349 assert_eq!(gps.latitude_decimal(), Some(36.0));
1350 assert_eq!(gps.longitude_decimal(), Some(120.0));
1351
1352 let tags: Vec<u16> = iter.map(|e| e.tag().code()).collect();
1355 assert!(
1356 tags.contains(&crate::ExifTag::GPSVersionID.code()),
1357 "GPSVersionID (tag 0) should be visible to iterators; got {tags:?}"
1358 );
1359 }
1360}