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 = match 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 Ok(ifd0) => ifd0.tag_code(ExifTag::GPSInfo.code()),
283 Err(e) => return Err(e),
284 };
285 Ok(gps_subifd.parse_gps_info())
286 }
287
288 pub(crate) fn add_tiff_block(
296 &mut self,
297 block_id: String,
298 data: bytes::Bytes,
299 header: Option<TiffHeader>,
300 ) {
301 self.additional_blocks.push(TiffDataBlock {
302 block_id,
303 data,
304 header,
305 });
306 }
307
308 pub(crate) fn set_has_embedded_track(&mut self, v: bool) {
311 self.has_embedded_track = v;
312 }
313
314 pub fn has_embedded_track(&self) -> bool {
330 self.has_embedded_track
331 }
332
333 #[deprecated(
335 since = "3.1.0",
336 note = "renamed to `has_embedded_track`; the original `has_embedded_media` was too vague and lumped in still-image previews"
337 )]
338 pub fn has_embedded_media(&self) -> bool {
339 self.has_embedded_track()
340 }
341}
342
343#[derive(Clone)]
354pub struct ExifIterEntry {
355 ifd: IfdIndex,
356 tag: TagOrCode,
357 res: Result<EntryValue, crate::error::EntryError>,
358}
359
360impl ExifIterEntry {
361 pub fn ifd(&self) -> IfdIndex {
363 self.ifd
364 }
365
366 pub fn tag(&self) -> TagOrCode {
368 self.tag
369 }
370
371 pub fn value(&self) -> Option<&EntryValue> {
373 self.res.as_ref().ok()
374 }
375
376 pub fn error(&self) -> Option<&crate::error::EntryError> {
378 self.res.as_ref().err()
379 }
380
381 pub fn result(&self) -> Result<&EntryValue, &crate::error::EntryError> {
383 self.res.as_ref()
384 }
385
386 pub fn into_result(self) -> Result<EntryValue, crate::error::EntryError> {
389 self.res
390 }
391
392 pub(crate) fn make_ok(ifd: usize, tag: TagOrCode, v: EntryValue) -> Self {
393 Self {
394 ifd: IfdIndex::new(ifd),
395 tag,
396 res: Ok(v),
397 }
398 }
399}
400
401impl std::fmt::Debug for ExifIterEntry {
402 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
403 let value = match &self.res {
404 Ok(v) => format!("{v}"),
405 Err(e) => format!("{e:?}"),
406 };
407 f.debug_struct("ExifIterEntry")
408 .field("ifd", &self.ifd)
409 .field("tag", &self.tag)
410 .field("value", &value)
411 .finish()
412 }
413}
414
415const MAX_IFD_DEPTH: usize = 8;
416
417impl ExifIter {
418 fn load_next_block(&mut self) -> bool {
421 let block_index = self.current_block_index;
423 if block_index >= self.additional_blocks.len() {
424 return false;
425 }
426
427 let block = &self.additional_blocks[block_index];
428 tracing::debug!(
429 block_id = block.block_id,
430 block_index,
431 "Loading additional TIFF block"
432 );
433
434 let block_data = block.data.clone();
436 let header = block.header.clone();
437
438 match input_into_iter(block_data, header) {
440 Ok(iter) => {
441 self.ifd0 = iter.ifd0;
443 self.ifds = vec![self.ifd0.clone()];
444 self.visited_offsets.clear();
445 self.current_block_index += 1;
446
447 tracing::debug!(block_index, "Successfully loaded additional TIFF block");
448 true
449 }
450 Err(e) => {
451 tracing::warn!(
452 block_index,
453 error = %e,
454 "Failed to load additional TIFF block, skipping"
455 );
456 self.current_block_index += 1;
458 self.load_next_block()
459 }
460 }
461 }
462
463 fn should_include_tag(&mut self, ifd_index: usize, tag_code: u16) -> bool {
466 let tag_key = (ifd_index, tag_code);
467 if self.encountered_tags.contains(&tag_key) {
468 tracing::debug!(ifd_index, tag_code, "Skipping duplicate tag");
469 false
470 } else {
471 self.encountered_tags.insert(tag_key);
472 true
473 }
474 }
475}
476
477impl Iterator for ExifIter {
478 type Item = ExifIterEntry;
479
480 #[tracing::instrument(skip_all)]
481 fn next(&mut self) -> Option<Self::Item> {
482 loop {
483 if self.ifds.is_empty() {
484 if !self.load_next_block() {
486 tracing::debug!(?self, "all IFDs and blocks have been parsed");
487 return None;
488 }
489 continue;
491 }
492
493 if self.ifds.len() > MAX_IFD_DEPTH {
494 let depth = self.ifds.len();
495 self.ifds.clear();
496 tracing::error!(
497 ifds_depth = depth,
498 "ifd depth is too deep, just go back to ifd0"
499 );
500 self.ifds.push(self.ifd0.clone_with_state());
501 }
502
503 let mut ifd = self.ifds.pop()?;
504 let cur_ifd_idx = ifd.ifd_idx;
505 match ifd.next() {
506 Some((tag_code, entry)) => {
507 tracing::debug!(ifd = ifd.ifd_idx, ?tag_code, "next tag entry");
508
509 match entry {
510 IfdEntry::IfdNew(new_ifd) => {
511 if new_ifd.offset > 0 {
512 if self.visited_offsets.contains(&new_ifd.offset) {
513 continue;
515 }
516 self.visited_offsets.insert(new_ifd.offset);
517 }
518
519 let is_subifd = if new_ifd.ifd_idx == ifd.ifd_idx {
520 self.ifds.push(ifd);
522 tracing::debug!(?tag_code, ?new_ifd, "got new SUB-IFD");
523 true
524 } else {
525 tracing::debug!("IFD{} parsing completed", cur_ifd_idx);
529 tracing::debug!(?new_ifd, "got new IFD");
530 false
531 };
532
533 let (ifd_idx, offset) = (new_ifd.ifd_idx, new_ifd.offset);
534 self.ifds.push(new_ifd);
535
536 if is_subifd {
537 let tc = tag_code.unwrap();
539 if !self.should_include_tag(ifd_idx, tc.code()) {
540 continue;
541 }
542 return Some(ExifIterEntry::make_ok(
544 ifd_idx,
545 tc,
546 EntryValue::U32(offset as u32),
547 ));
548 }
549 }
550 IfdEntry::Entry(v) => {
551 let tc = tag_code.unwrap();
552 if !self.should_include_tag(ifd.ifd_idx, tc.code()) {
554 self.ifds.push(ifd);
555 continue;
556 }
557 let res = Some(ExifIterEntry::make_ok(ifd.ifd_idx, tc, v));
558 self.ifds.push(ifd);
559 return res;
560 }
561 IfdEntry::Err(e) => {
562 tracing::warn!(?tag_code, ?e, "parse ifd entry error");
563 self.ifds.push(ifd);
564 continue;
565 }
566 }
567 }
568 None => continue,
569 }
570 }
571 }
572}
573
574#[derive(Clone)]
575pub(crate) struct IfdIter {
576 ifd_idx: usize,
577 tag_code: Option<TagOrCode>,
578
579 input: Bytes,
581
582 offset: usize,
584
585 header: TiffHeader,
586 entry_num: u16,
587
588 pub tz: Option<String>,
589
590 index: u16,
592 pos: usize,
593}
594
595impl Debug for IfdIter {
596 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
597 f.debug_struct("IfdIter")
598 .field("ifd_idx", &self.ifd_idx)
599 .field("tag", &self.tag_code)
600 .field("data len", &self.input.len())
601 .field("tz", &self.tz)
602 .field("header", &self.header)
603 .field("entry_num", &self.entry_num)
604 .field("index", &self.index)
605 .field("pos", &self.pos)
606 .finish()
607 }
608}
609
610impl IfdIter {
611 pub fn rewind(&mut self) {
612 self.index = 0;
613 self.pos = self.offset + 2;
615 }
616
617 pub fn clone_and_rewind(&self) -> Self {
618 let mut it = self.clone();
619 it.rewind();
620 it
621 }
622
623 pub fn tag_code_maybe(mut self, code: Option<u16>) -> Self {
624 self.tag_code = code.map(|x| x.into());
625 self
626 }
627
628 pub fn tag_code(mut self, code: u16) -> Self {
629 self.tag_code = Some(code.into());
630 self
631 }
632
633 fn is_gps_subifd(&self) -> bool {
634 matches!(
635 self.tag_code.as_ref().and_then(|t| t.tag()),
636 Some(ExifTag::GPSInfo)
637 )
638 }
639
640 #[allow(unused)]
641 pub fn tag(mut self, tag: TagOrCode) -> Self {
642 self.tag_code = Some(tag);
643 self
644 }
645
646 #[tracing::instrument(skip(input))]
647 pub fn try_new(
648 ifd_idx: usize,
649 input: Bytes,
650 header: TiffHeader,
651 offset: usize,
652 tz: Option<String>,
653 ) -> crate::Result<Self> {
654 if input.len() < 2 {
655 return Err(crate::Error::Malformed {
656 kind: crate::error::MalformedKind::TiffHeader,
657 message: "ifd data too small to decode entry num".into(),
658 });
659 }
660 assert!(offset <= input.len());
662 let ifd_data = input.slice(offset..);
663 let (_, entry_num) =
664 TiffHeader::parse_ifd_entry_num(&ifd_data, header.endian).map_err(|e| {
665 crate::error::nom_err_to_malformed(e, crate::error::MalformedKind::TiffHeader)
666 })?;
667
668 Ok(Self {
669 ifd_idx,
670 tag_code: None,
671 input,
672 offset,
673 header,
674 entry_num,
675 tz,
676 pos: offset + 2,
678 index: 0,
679 })
680 }
681
682 fn parse_tag_entry(&self, entry_data: &[u8]) -> Option<(u16, IfdEntry)> {
683 let endian = self.header.endian;
684 let (_, (tag, data_format, components_num, value_or_offset)) = (
685 complete::u16::<_, nom::error::Error<_>>(endian),
686 complete::u16(endian),
687 complete::u32(endian),
688 complete::u32(endian),
689 )
690 .parse(entry_data)
691 .ok()?;
692
693 if tag == 0 && !self.is_gps_subifd() {
698 return None;
699 }
700
701 let df: DataFormat = match DataFormat::try_from(data_format) {
702 Ok(df) => df,
703 Err(bad) => {
704 let t: TagOrCode = tag.into();
705 tracing::warn!(tag = ?t, format = bad, "invalid entry data format");
706 return Some((
707 tag,
708 IfdEntry::Err(EntryError::InvalidShape {
709 format: bad,
710 count: components_num,
711 }),
712 ));
713 }
714 };
715 let (tag, res) = self.parse_entry(tag, df, components_num, entry_data, value_or_offset);
716 Some((tag, res))
717 }
718
719 fn get_data_pos(&self, value_or_offset: u32) -> usize {
720 value_or_offset as usize
722 }
723
724 fn parse_entry(
725 &self,
726 tag: u16,
727 data_format: DataFormat,
728 components_num: u32,
729 entry_data: &[u8],
730 value_or_offset: u32,
731 ) -> (u16, IfdEntry) {
732 let component_size = data_format.component_size();
734
735 let size = components_num as usize * component_size;
737 let data = if size <= 4 {
738 &entry_data[8..8 + size] } else {
740 let start = self.get_data_pos(value_or_offset);
741 let end = start + size;
742 let Some(data) = self.input.slice_checked(start..end) else {
743 tracing::warn!(
744 "entry data overflow, tag: {:04x} start: {:08x} end: {:08x} ifd data len {:08x}",
745 tag,
746 start,
747 end,
748 self.input.len(),
749 );
750 return (
751 tag,
752 IfdEntry::Err(EntryError::Truncated {
753 needed: size,
754 available: self.input.len().saturating_sub(start),
755 }),
756 );
757 };
758
759 data
760 };
761
762 if SUBIFD_TAGS.contains(&tag) {
763 if let Some(value) = self.new_ifd_iter(self.ifd_idx, value_or_offset, Some(tag)) {
764 return (tag, value);
765 }
766 }
767
768 let entry = EntryData {
769 endian: self.header.endian,
770 tag,
771 data,
772 data_format,
773 components_num,
774 };
775 match EntryValue::parse(&entry, &self.tz) {
776 Ok(v) => (tag, IfdEntry::Entry(v)),
777 Err(e) => (tag, IfdEntry::Err(e)),
778 }
779 }
780
781 fn new_ifd_iter(
782 &self,
783 ifd_idx: usize,
784 value_or_offset: u32,
785 tag: Option<u16>,
786 ) -> Option<IfdEntry> {
787 let offset = self.get_data_pos(value_or_offset);
788 if offset < self.input.len() {
789 match IfdIter::try_new(
790 ifd_idx,
791 self.input.clone(),
792 self.header.to_owned(),
793 offset,
794 self.tz.clone(),
795 ) {
796 Ok(iter) => return Some(IfdEntry::IfdNew(iter.tag_code_maybe(tag))),
797 Err(e) => {
798 tracing::warn!(?tag, ?e, "Create next/sub IFD failed");
799 }
800 }
801 }
810 None
811 }
812
813 pub fn find_exif_iter(&self) -> Option<IfdIter> {
814 let endian = self.header.endian;
815 for i in 0..self.entry_num {
817 let pos = self.pos + i as usize * IFD_ENTRY_SIZE;
818 let (_, tag) =
819 complete::u16::<_, nom::error::Error<_>>(endian)(&self.input[pos..]).ok()?;
820 if tag == ExifTag::ExifOffset.code() {
821 let entry_data = self.input.slice_checked(pos..pos + IFD_ENTRY_SIZE)?;
822 let (_, entry) = self.parse_tag_entry(entry_data)?;
823 match entry {
824 IfdEntry::IfdNew(iter) => return Some(iter),
825 IfdEntry::Entry(_) | IfdEntry::Err(_) => return None,
826 }
827 }
828 }
829 None
830 }
831
832 pub fn find_tz_offset(&self) -> Option<String> {
833 let iter = self.find_exif_iter()?;
834 let mut offset = None;
835 for entry in iter {
836 let Some(tag) = entry.0 else {
837 continue;
838 };
839 if tag.code() == ExifTag::OffsetTimeOriginal.code()
840 || tag.code() == ExifTag::OffsetTimeDigitized.code()
841 {
842 return entry.1.as_str().map(|x| x.to_owned());
843 } else if tag.code() == ExifTag::OffsetTime.code() {
844 offset = entry.1.as_str().map(|x| x.to_owned());
845 }
846 }
847
848 offset
849 }
850
851 pub fn parse_gps_info(&mut self) -> Option<GPSInfo> {
853 use crate::exif::gps::{Altitude, LatRef, LonRef, Speed, SpeedUnit};
854
855 let mut latitude_ref = None;
856 let mut latitude = None;
857 let mut longitude_ref = None;
858 let mut longitude = None;
859 let mut altitude_ref = None;
860 let mut altitude_value = None;
861 let mut speed_unit = None;
862 let mut speed_value = None;
863 let mut has_data = false;
864
865 for (tag, entry) in self {
866 let Some(tag) = tag.and_then(|x| x.tag()) else {
867 continue;
868 };
869 has_data = true;
870 match tag {
871 ExifTag::GPSLatitudeRef => {
872 latitude_ref = entry.as_char().and_then(LatRef::from_char);
873 }
874 ExifTag::GPSLongitudeRef => {
875 longitude_ref = entry.as_char().and_then(LonRef::from_char);
876 }
877 ExifTag::GPSAltitudeRef => {
878 altitude_ref = entry.as_u8();
879 }
880 ExifTag::GPSLatitude => {
881 if let Some(v) = entry.as_urational_slice() {
882 latitude = LatLng::try_from(v).ok();
883 } else if let Some(v) = entry.as_irational_slice() {
884 latitude = LatLng::try_from(v).ok();
885 }
886 }
887 ExifTag::GPSLongitude => {
888 if let Some(v) = entry.as_urational_slice() {
889 longitude = LatLng::try_from(v).ok();
890 } else if let Some(v) = entry.as_irational_slice() {
891 longitude = LatLng::try_from(v).ok();
892 }
893 }
894 ExifTag::GPSAltitude => {
895 if let Some(v) = entry.as_urational() {
896 altitude_value = Some(*v);
897 } else if let Some(v) = entry.as_irational() {
898 if let Ok(u) = URational::try_from(*v) {
899 altitude_value = Some(u);
900 }
901 }
902 }
903 ExifTag::GPSSpeedRef => {
904 speed_unit = entry.as_char().and_then(SpeedUnit::from_char);
905 }
906 ExifTag::GPSSpeed => {
907 if let Some(v) = entry.as_urational() {
908 speed_value = Some(*v);
909 } else if let Some(v) = entry.as_irational() {
910 if let Ok(u) = URational::try_from(*v) {
911 speed_value = Some(u);
912 }
913 }
914 }
915 _ => (),
916 }
917 }
918
919 if !has_data {
920 tracing::warn!("GPSInfo data not found");
921 return None;
922 }
923
924 let altitude = match (altitude_ref, altitude_value) {
925 (Some(0), Some(v)) => Altitude::AboveSeaLevel(v),
926 (Some(1), Some(v)) => Altitude::BelowSeaLevel(v),
927 _ => Altitude::Unknown,
928 };
929
930 let speed = match (speed_unit, speed_value) {
931 (Some(unit), Some(value)) => Some(Speed { unit, value }),
932 _ => None,
933 };
934
935 Some(GPSInfo {
936 latitude_ref: latitude_ref.unwrap_or(LatRef::North),
937 latitude: latitude.unwrap_or_default(),
938 longitude_ref: longitude_ref.unwrap_or(LonRef::East),
939 longitude: longitude.unwrap_or_default(),
940 altitude,
941 speed,
942 })
943 }
944
945 fn clone_with_state(&self) -> IfdIter {
946 let mut it = self.clone();
947 it.index = self.index;
948 it.pos = self.pos;
949 it
950 }
951}
952
953#[derive(Debug)]
954pub(crate) enum IfdEntry {
955 IfdNew(IfdIter), Entry(EntryValue),
957 Err(EntryError),
958}
959
960impl IfdEntry {
961 pub fn as_u8(&self) -> Option<u8> {
962 if let IfdEntry::Entry(EntryValue::U8(v)) = self {
963 Some(*v)
964 } else {
965 None
966 }
967 }
968
969 pub fn as_char(&self) -> Option<char> {
970 if let IfdEntry::Entry(EntryValue::Text(s)) = self {
971 s.chars().next()
972 } else {
973 None
974 }
975 }
976
977 fn as_irational(&self) -> Option<&IRational> {
978 if let IfdEntry::Entry(EntryValue::IRational(v)) = self {
979 Some(v)
980 } else {
981 None
982 }
983 }
984
985 fn as_irational_slice(&self) -> Option<&Vec<IRational>> {
986 if let IfdEntry::Entry(EntryValue::IRationalArray(v)) = self {
987 Some(v)
988 } else {
989 None
990 }
991 }
992
993 fn as_urational(&self) -> Option<&URational> {
994 if let IfdEntry::Entry(EntryValue::URational(v)) = self {
995 Some(v)
996 } else {
997 None
998 }
999 }
1000
1001 fn as_urational_slice(&self) -> Option<&Vec<URational>> {
1002 if let IfdEntry::Entry(EntryValue::URationalArray(v)) = self {
1003 Some(v)
1004 } else {
1005 None
1006 }
1007 }
1008
1009 fn as_str(&self) -> Option<&str> {
1010 if let IfdEntry::Entry(e) = self {
1011 e.as_str()
1012 } else {
1013 None
1014 }
1015 }
1016}
1017
1018pub(crate) const SUBIFD_TAGS: &[u16] = &[ExifTag::ExifOffset.code(), ExifTag::GPSInfo.code()];
1019
1020impl Iterator for IfdIter {
1021 type Item = (Option<TagOrCode>, IfdEntry);
1022
1023 #[tracing::instrument(skip(self))]
1024 fn next(&mut self) -> Option<Self::Item> {
1025 tracing::debug!(
1026 ifd = self.ifd_idx,
1027 index = self.index,
1028 entry_num = self.entry_num,
1029 offset = format!("{:08x}", self.offset),
1030 pos = format!("{:08x}", self.pos),
1031 "next IFD entry"
1032 );
1033 if self.input.len() < self.pos + IFD_ENTRY_SIZE {
1034 return None;
1035 }
1036
1037 let endian = self.header.endian;
1038 if self.index > self.entry_num {
1039 return None;
1040 }
1041 if self.index == self.entry_num {
1042 tracing::debug!(
1043 self.ifd_idx,
1044 self.index,
1045 pos = self.pos,
1046 "try to get next ifd"
1047 );
1048 self.index += 1;
1049
1050 let (_, offset) =
1052 complete::u32::<_, nom::error::Error<_>>(endian)(&self.input[self.pos..]).ok()?;
1053
1054 if offset == 0 {
1055 tracing::debug!(?self, "IFD parsing completed");
1057 return None;
1058 }
1059
1060 return self
1061 .new_ifd_iter(self.ifd_idx + 1, offset, None)
1062 .map(|x| (None, x));
1063 }
1064
1065 let entry_data = self
1066 .input
1067 .slice_checked(self.pos..self.pos + IFD_ENTRY_SIZE)?;
1068 self.index += 1;
1069 self.pos += IFD_ENTRY_SIZE;
1070
1071 let (tag, res) = self.parse_tag_entry(entry_data)?;
1072
1073 Some((Some(tag.into()), res)) }
1075}
1076
1077#[cfg(test)]
1078mod tests {
1079
1080 use crate::exif::extract_exif_with_mime;
1081 use crate::exif::input_into_iter;
1082 use crate::file::MediaMimeImage;
1083 use crate::slice::SubsliceRange;
1084 use crate::testkit::read_sample;
1085 use crate::Exif;
1086 use test_case::test_case;
1087
1088 #[test_case(
1089 "exif.jpg",
1090 "+08:00",
1091 "2023-07-09T20:36:33+08:00",
1092 MediaMimeImage::Jpeg
1093 )]
1094 #[test_case("exif-no-tz.jpg", "", "2023-07-09 20:36:33", MediaMimeImage::Jpeg)]
1095 #[test_case("broken.jpg", "-", "2014-09-21 15:51:22", MediaMimeImage::Jpeg)]
1096 #[test_case(
1097 "exif.heic",
1098 "+08:00",
1099 "2022-07-22T21:26:32+08:00",
1100 MediaMimeImage::Heic
1101 )]
1102 #[test_case(
1103 "exif.avif",
1104 "+08:00",
1105 "2022-07-22T21:26:32+08:00",
1106 MediaMimeImage::Avif
1107 )]
1108 #[test_case("tif.tif", "-", "-", MediaMimeImage::Tiff)]
1109 #[test_case(
1110 "fujifilm_x_t1_01.raf.meta",
1111 "-",
1112 "2014-01-30 12:49:13",
1113 MediaMimeImage::Raf
1114 )]
1115 fn exif_iter_tz(path: &str, tz: &str, time: &str, img_type: MediaMimeImage) {
1116 let buf = read_sample(path).unwrap();
1117 let (data, _) = extract_exif_with_mime(img_type, &buf, None).unwrap();
1118 let range = data.and_then(|x| buf.subslice_in_range(x)).unwrap();
1119 let iter = input_into_iter(bytes::Bytes::from(buf).slice(range), None).unwrap();
1120 let expect = if tz == "-" {
1121 None
1122 } else {
1123 Some(tz.to_string())
1124 };
1125 assert_eq!(iter.tz, expect);
1126 let exif: Exif = iter.into();
1127 let value = exif.get(crate::ExifTag::DateTimeOriginal);
1128 if time == "-" {
1129 assert!(value.is_none());
1130 } else {
1131 let value = value.unwrap();
1132 assert_eq!(value.to_string(), time);
1133 }
1134 }
1135
1136 #[test]
1137 fn ifd_index_constants() {
1138 use crate::IfdIndex;
1139 assert_eq!(IfdIndex::MAIN.as_usize(), 0);
1140 assert_eq!(IfdIndex::THUMBNAIL.as_usize(), 1);
1141 }
1142
1143 #[test]
1144 fn ifd_index_roundtrip_via_new_and_as_usize() {
1145 use crate::IfdIndex;
1146 for raw in [0, 1, 2, 3, 7, 99] {
1147 assert_eq!(IfdIndex::new(raw).as_usize(), raw);
1148 }
1149 }
1150
1151 #[test]
1152 fn ifd_index_equality_and_hash() {
1153 use crate::IfdIndex;
1154 use std::collections::HashSet;
1155 let mut set: HashSet<IfdIndex> = HashSet::new();
1156 set.insert(IfdIndex::MAIN);
1157 set.insert(IfdIndex::new(0)); set.insert(IfdIndex::THUMBNAIL);
1159 assert_eq!(set.len(), 2);
1160 }
1161
1162 #[test]
1163 fn ifd_index_display_format() {
1164 use crate::IfdIndex;
1165 assert_eq!(format!("{}", IfdIndex::MAIN), "ifd0");
1166 assert_eq!(format!("{}", IfdIndex::new(7)), "ifd7");
1167 }
1168
1169 #[test]
1170 fn tag_or_code_for_known_tag_resolves_to_tag_variant() {
1171 use crate::{ExifTag, TagOrCode};
1172 let t: TagOrCode = ExifTag::Make.code().into();
1173 assert_eq!(t, TagOrCode::Tag(ExifTag::Make));
1174 assert_eq!(t.code(), ExifTag::Make.code());
1175 }
1176
1177 #[test]
1178 fn tag_or_code_for_unknown_tag_resolves_to_unknown_variant() {
1179 use crate::TagOrCode;
1180 let t: TagOrCode = 0xffff_u16.into();
1181 assert_eq!(t, TagOrCode::Unknown(0xffff));
1182 assert_eq!(t.code(), 0xffff);
1183 }
1184
1185 #[test]
1186 fn exif_entry_pub_fields_construct_and_destructure() {
1187 use crate::{EntryValue, ExifEntry, ExifTag, IfdIndex, TagOrCode};
1188 let val = EntryValue::Text("vivo X90 Pro+".into());
1189 let e = ExifEntry {
1190 ifd: IfdIndex::MAIN,
1191 tag: TagOrCode::Tag(ExifTag::Model),
1192 value: &val,
1193 };
1194 let ExifEntry { ifd, tag, value } = e;
1196 assert_eq!(ifd, IfdIndex::MAIN);
1197 assert_eq!(tag.code(), ExifTag::Model.code());
1198 assert!(matches!(value, EntryValue::Text(_)));
1199 let _e2 = e;
1201 let _e3 = e;
1202 }
1203
1204 #[test]
1205 fn exif_iter_entry_value_xor_error_invariant() {
1206 use crate::{MediaParser, MediaSource};
1207 let mut parser = MediaParser::new();
1208 let ms = MediaSource::open("testdata/exif.jpg").unwrap();
1209 for entry in parser.parse_exif(ms).unwrap() {
1210 let has_v = entry.value().is_some();
1212 let has_e = entry.error().is_some();
1213 assert!(has_v ^ has_e, "entry must be value xor error");
1214 match entry.result() {
1216 Ok(v) => assert_eq!(Some(v), entry.value()),
1217 Err(e) => assert_eq!(Some(e), entry.error()),
1218 }
1219 }
1220 }
1221
1222 #[test]
1223 fn exif_iter_entry_into_result_consumes_self() {
1224 use crate::{MediaParser, MediaSource};
1225 let mut parser = MediaParser::new();
1226 let ms = MediaSource::open("testdata/exif.jpg").unwrap();
1227 let mut count_ok = 0usize;
1228 for entry in parser.parse_exif(ms).unwrap() {
1229 if entry.into_result().is_ok() {
1233 count_ok += 1;
1234 }
1235 }
1236 assert!(count_ok > 0);
1237 }
1238
1239 #[test]
1240 fn exif_iter_entry_tag_returns_tag_or_code() {
1241 use crate::{ExifTag, MediaParser, MediaSource, TagOrCode};
1242 let mut parser = MediaParser::new();
1243 let ms = MediaSource::open("testdata/exif.jpg").unwrap();
1244 let make_present = parser
1245 .parse_exif(ms)
1246 .unwrap()
1247 .any(|e| matches!(e.tag(), TagOrCode::Tag(ExifTag::Make)));
1248 assert!(make_present);
1249 }
1250
1251 #[test]
1252 fn exif_iter_rewind_resets_iteration_state() {
1253 use crate::{MediaParser, MediaSource};
1254 let mut parser = MediaParser::new();
1255 let ms = MediaSource::open("testdata/exif.jpg").unwrap();
1256 let mut iter = parser.parse_exif(ms).unwrap();
1257 let first_count = iter.by_ref().count();
1258 assert!(first_count > 0);
1259 assert_eq!(iter.by_ref().count(), 0);
1261 iter.rewind();
1262 let after_rewind = iter.count();
1263 assert_eq!(first_count, after_rewind);
1264 }
1265
1266 #[test]
1267 fn exif_iter_clone_rewound_yields_independent_full_iter() {
1268 use crate::{MediaParser, MediaSource};
1269 let mut parser = MediaParser::new();
1270 let ms = MediaSource::open("testdata/exif.jpg").unwrap();
1271 let mut iter = parser.parse_exif(ms).unwrap();
1272 let _consumed = iter.by_ref().take(2).count();
1273 let cloned = iter.clone_rewound();
1274 let cloned_total = cloned.count();
1276 let remaining = iter.count();
1277 assert!(cloned_total > remaining);
1278 }
1279
1280 #[test]
1281 fn exif_iter_parse_gps_returns_option_no_iteration_advance() {
1282 use crate::{MediaParser, MediaSource};
1283 let mut parser = MediaParser::new();
1284 let ms = MediaSource::open("testdata/exif.jpg").unwrap();
1285 let iter = parser.parse_exif(ms).unwrap();
1286 let gps = iter.parse_gps().unwrap();
1287 assert!(gps.is_some());
1288 let count = iter.count();
1290 assert!(count > 0);
1291 }
1292
1293 #[test]
1300 fn gps_subifd_first_entry_is_gpsversion_id_issue_50() {
1301 use crate::exif::exif_iter::input_into_iter;
1302 #[rustfmt::skip]
1303 let tiff: &[u8] = &[
1304 b'I', b'I', 0x2a, 0x00,
1306 0x08, 0x00, 0x00, 0x00,
1307
1308 0x01, 0x00,
1310 0x25, 0x88, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00,
1311 0x1a, 0x00, 0x00, 0x00,
1312 0x00, 0x00, 0x00, 0x00, 0x05, 0x00,
1316 0x00, 0x00, 0x01, 0x00, 0x04, 0x00, 0x00, 0x00,
1318 0x02, 0x03, 0x00, 0x00,
1319 0x01, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00,
1321 b'N', 0x00, 0x00, 0x00,
1322 0x02, 0x00, 0x05, 0x00, 0x03, 0x00, 0x00, 0x00,
1324 0x5c, 0x00, 0x00, 0x00,
1325 0x03, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00,
1327 b'E', 0x00, 0x00, 0x00,
1328 0x04, 0x00, 0x05, 0x00, 0x03, 0x00, 0x00, 0x00,
1330 0x74, 0x00, 0x00, 0x00,
1331 0x00, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
1335 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
1336 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
1337
1338 0x78, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
1340 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
1341 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
1342 ];
1343
1344 let iter = input_into_iter(tiff.to_vec(), None).unwrap();
1345
1346 let gps = iter
1348 .parse_gps()
1349 .expect("parse_gps must succeed")
1350 .expect("GPS sub-IFD with GPSVersionID first must yield GPSInfo");
1351 assert_eq!(gps.latitude_decimal(), Some(36.0));
1352 assert_eq!(gps.longitude_decimal(), Some(120.0));
1353
1354 let tags: Vec<u16> = iter.map(|e| e.tag().code()).collect();
1357 assert!(
1358 tags.contains(&crate::ExifTag::GPSVersionID.code()),
1359 "GPSVersionID (tag 0) should be visible to iterators; got {tags:?}"
1360 );
1361 }
1362}