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[..])?;
102
103 tracing::debug!(
104 ?header,
105 data_len = format!("{:#x}", input.len()),
106 "TIFF header parsed"
107 );
108 header
109 }
110 };
111
112 let start = header.ifd0_offset as usize;
113 if start > input.len() {
114 return Err(crate::Error::UnexpectedEof {
115 context: "exif iter init",
116 });
117 }
118 tracing::debug!(?header, offset = start);
119
120 let mut ifd0 = IfdIter::try_new(0, input.clone(), header.to_owned(), start, None)?;
121
122 let tz = ifd0.find_tz_offset();
123 ifd0.tz = tz.clone();
124 let iter: ExifIter = ExifIter::new(input, header, tz, ifd0);
125
126 tracing::debug!(?iter, "got IFD0");
127
128 Ok(iter)
129}
130
131pub struct ExifIter {
144 input: Bytes,
145 tiff_header: TiffHeader,
146 tz: Option<String>,
147 ifd0: IfdIter,
148
149 ifds: Vec<IfdIter>,
151 visited_offsets: HashSet<usize>,
152
153 additional_blocks: Vec<TiffDataBlock>,
156 current_block_index: usize,
158 encountered_tags: HashSet<(usize, u16)>,
160 has_embedded_track: bool,
161}
162
163impl Debug for ExifIter {
164 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
165 f.debug_struct("ExifIter")
166 .field("data len", &self.input.len())
167 .field("tiff_header", &self.tiff_header)
168 .field("ifd0", &self.ifd0)
169 .field("state", &self.ifds.first().map(|x| (x.index, x.pos)))
170 .field("ifds num", &self.ifds.len())
171 .field("additional_blocks", &self.additional_blocks.len())
172 .field("current_block_index", &self.current_block_index)
173 .finish_non_exhaustive()
174 }
175}
176
177impl Clone for ExifIter {
178 fn clone(&self) -> Self {
179 self.clone_rewound()
180 }
181}
182
183impl ExifIter {
184 pub(crate) fn new(
185 input: bytes::Bytes,
186 tiff_header: TiffHeader,
187 tz: Option<String>,
188 ifd0: IfdIter,
189 ) -> ExifIter {
190 let ifds = vec![ifd0.clone()];
191 ExifIter {
192 input,
193 tiff_header,
194 tz,
195 ifd0,
196 ifds,
197 visited_offsets: HashSet::new(),
198 additional_blocks: Vec::new(),
199 current_block_index: 0,
200 encountered_tags: HashSet::new(),
201 has_embedded_track: false,
202 }
203 }
204
205 pub fn clone_rewound(&self) -> Self {
209 let ifd0 = self.ifd0.clone_and_rewind();
210 let ifds = vec![ifd0.clone()];
211 Self {
212 input: self.input.clone(),
213 tiff_header: self.tiff_header.clone(),
214 tz: self.tz.clone(),
215 ifd0,
216 ifds,
217 visited_offsets: HashSet::new(),
218 additional_blocks: self.additional_blocks.clone(),
219 current_block_index: 0,
220 encountered_tags: HashSet::new(),
221 has_embedded_track: self.has_embedded_track,
222 }
223 }
224
225 pub fn rewind(&mut self) {
228 let ifd0 = self.ifd0.clone_and_rewind();
229 self.ifds = vec![ifd0.clone()];
230 self.ifd0 = ifd0;
231 self.visited_offsets.clear();
232 self.current_block_index = 0;
233 self.encountered_tags.clear();
234 }
235
236 #[tracing::instrument(skip_all)]
246 pub fn parse_gps(&self) -> crate::Result<Option<GPSInfo>> {
247 let mut iter = self.clone_rewound();
248 let Some(gps) = iter.find(|x| {
249 tracing::info!(?x, "find");
250 x.tag().tag().is_some_and(|t| t == ExifTag::GPSInfo)
251 }) else {
252 tracing::warn!(ifd0 = ?iter.ifds.first(), "GPSInfo not found");
253 return Ok(None);
254 };
255
256 let offset = match gps.result() {
257 Ok(v) => {
258 if let Some(offset) = v.as_u32() {
259 offset
260 } else {
261 return Err(EntryError::InvalidValue("invalid gps offset").into());
262 }
263 }
264 Err(e) => return Err(e.clone().into()),
265 };
266 if offset as usize >= iter.input.len() {
267 return Err(crate::Error::Malformed {
268 kind: crate::error::MalformedKind::IfdEntry,
269 message: "GPSInfo offset out of range".into(),
270 });
271 }
272
273 let mut gps_subifd = match IfdIter::try_new(
274 gps.ifd().as_usize(),
275 iter.input.clone(),
276 iter.tiff_header,
277 offset as usize,
278 iter.tz.clone(),
279 ) {
280 Ok(ifd0) => ifd0.tag_code(ExifTag::GPSInfo.code()),
281 Err(e) => return Err(e),
282 };
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) = TiffHeader::parse_ifd_entry_num(&ifd_data, header.endian)?;
662
663 Ok(Self {
664 ifd_idx,
665 tag_code: None,
666 input,
667 offset,
668 header,
669 entry_num,
670 tz,
671 pos: offset + 2,
673 index: 0,
674 })
675 }
676
677 fn parse_tag_entry(&self, entry_data: &[u8]) -> Option<(u16, IfdEntry)> {
678 let endian = self.header.endian;
679 let (_, (tag, data_format, components_num, value_or_offset)) = (
680 complete::u16::<_, nom::error::Error<_>>(endian),
681 complete::u16(endian),
682 complete::u32(endian),
683 complete::u32(endian),
684 )
685 .parse(entry_data)
686 .ok()?;
687
688 if tag == 0 && !self.is_gps_subifd() {
693 return None;
694 }
695
696 let df: DataFormat = match DataFormat::try_from(data_format) {
697 Ok(df) => df,
698 Err(bad) => {
699 let t: TagOrCode = tag.into();
700 tracing::warn!(tag = ?t, format = bad, "invalid entry data format");
701 return Some((
702 tag,
703 IfdEntry::Err(EntryError::InvalidShape {
704 format: bad,
705 count: components_num,
706 }),
707 ));
708 }
709 };
710 let (tag, res) = self.parse_entry(tag, df, components_num, entry_data, value_or_offset);
711 Some((tag, res))
712 }
713
714 fn get_data_pos(&self, value_or_offset: u32) -> usize {
715 value_or_offset as usize
717 }
718
719 fn parse_entry(
720 &self,
721 tag: u16,
722 data_format: DataFormat,
723 components_num: u32,
724 entry_data: &[u8],
725 value_or_offset: u32,
726 ) -> (u16, IfdEntry) {
727 let component_size = data_format.component_size();
729
730 let size = components_num as usize * component_size;
732 let data = if size <= 4 {
733 &entry_data[8..8 + size] } else {
735 let start = self.get_data_pos(value_or_offset);
736 let end = start + size;
737 let Some(data) = self.input.slice_checked(start..end) else {
738 tracing::warn!(
739 "entry data overflow, tag: {:04x} start: {:08x} end: {:08x} ifd data len {:08x}",
740 tag,
741 start,
742 end,
743 self.input.len(),
744 );
745 return (
746 tag,
747 IfdEntry::Err(EntryError::Truncated {
748 needed: size,
749 available: self.input.len().saturating_sub(start),
750 }),
751 );
752 };
753
754 data
755 };
756
757 if SUBIFD_TAGS.contains(&tag) {
758 if let Some(value) = self.new_ifd_iter(self.ifd_idx, value_or_offset, Some(tag)) {
759 return (tag, value);
760 }
761 }
762
763 let entry = EntryData {
764 endian: self.header.endian,
765 tag,
766 data,
767 data_format,
768 components_num,
769 };
770 match EntryValue::parse(&entry, &self.tz) {
771 Ok(v) => (tag, IfdEntry::Entry(v)),
772 Err(e) => (tag, IfdEntry::Err(e)),
773 }
774 }
775
776 fn new_ifd_iter(
777 &self,
778 ifd_idx: usize,
779 value_or_offset: u32,
780 tag: Option<u16>,
781 ) -> Option<IfdEntry> {
782 let offset = self.get_data_pos(value_or_offset);
783 if offset < self.input.len() {
784 match IfdIter::try_new(
785 ifd_idx,
786 self.input.clone(),
787 self.header.to_owned(),
788 offset,
789 self.tz.clone(),
790 ) {
791 Ok(iter) => return Some(IfdEntry::IfdNew(iter.tag_code_maybe(tag))),
792 Err(e) => {
793 tracing::warn!(?tag, ?e, "Create next/sub IFD failed");
794 }
795 }
796 }
805 None
806 }
807
808 pub fn find_exif_iter(&self) -> Option<IfdIter> {
809 let endian = self.header.endian;
810 for i in 0..self.entry_num {
812 let pos = self.pos + i as usize * IFD_ENTRY_SIZE;
813 let (_, tag) =
814 complete::u16::<_, nom::error::Error<_>>(endian)(&self.input[pos..]).ok()?;
815 if tag == ExifTag::ExifOffset.code() {
816 let entry_data = self.input.slice_checked(pos..pos + IFD_ENTRY_SIZE)?;
817 let (_, entry) = self.parse_tag_entry(entry_data)?;
818 match entry {
819 IfdEntry::IfdNew(iter) => return Some(iter),
820 IfdEntry::Entry(_) | IfdEntry::Err(_) => return None,
821 }
822 }
823 }
824 None
825 }
826
827 pub fn find_tz_offset(&self) -> Option<String> {
828 let iter = self.find_exif_iter()?;
829 let mut offset = None;
830 for entry in iter {
831 let Some(tag) = entry.0 else {
832 continue;
833 };
834 if tag.code() == ExifTag::OffsetTimeOriginal.code()
835 || tag.code() == ExifTag::OffsetTimeDigitized.code()
836 {
837 return entry.1.as_str().map(|x| x.to_owned());
838 } else if tag.code() == ExifTag::OffsetTime.code() {
839 offset = entry.1.as_str().map(|x| x.to_owned());
840 }
841 }
842
843 offset
844 }
845
846 pub fn parse_gps_info(&mut self) -> Option<GPSInfo> {
848 use crate::exif::gps::{Altitude, LatRef, LonRef, Speed, SpeedUnit};
849
850 let mut latitude_ref = None;
851 let mut latitude = None;
852 let mut longitude_ref = None;
853 let mut longitude = None;
854 let mut altitude_ref = None;
855 let mut altitude_value = None;
856 let mut speed_unit = None;
857 let mut speed_value = None;
858 let mut has_data = false;
859
860 for (tag, entry) in self {
861 let Some(tag) = tag.and_then(|x| x.tag()) else {
862 continue;
863 };
864 has_data = true;
865 match tag {
866 ExifTag::GPSLatitudeRef => {
867 latitude_ref = entry.as_char().and_then(LatRef::from_char);
868 }
869 ExifTag::GPSLongitudeRef => {
870 longitude_ref = entry.as_char().and_then(LonRef::from_char);
871 }
872 ExifTag::GPSAltitudeRef => {
873 altitude_ref = entry.as_u8();
874 }
875 ExifTag::GPSLatitude => {
876 if let Some(v) = entry.as_urational_slice() {
877 latitude = LatLng::try_from(v).ok();
878 } else if let Some(v) = entry.as_irational_slice() {
879 latitude = LatLng::try_from(v).ok();
880 }
881 }
882 ExifTag::GPSLongitude => {
883 if let Some(v) = entry.as_urational_slice() {
884 longitude = LatLng::try_from(v).ok();
885 } else if let Some(v) = entry.as_irational_slice() {
886 longitude = LatLng::try_from(v).ok();
887 }
888 }
889 ExifTag::GPSAltitude => {
890 if let Some(v) = entry.as_urational() {
891 altitude_value = Some(*v);
892 } else if let Some(v) = entry.as_irational() {
893 if let Ok(u) = URational::try_from(*v) {
894 altitude_value = Some(u);
895 }
896 }
897 }
898 ExifTag::GPSSpeedRef => {
899 speed_unit = entry.as_char().and_then(SpeedUnit::from_char);
900 }
901 ExifTag::GPSSpeed => {
902 if let Some(v) = entry.as_urational() {
903 speed_value = Some(*v);
904 } else if let Some(v) = entry.as_irational() {
905 if let Ok(u) = URational::try_from(*v) {
906 speed_value = Some(u);
907 }
908 }
909 }
910 _ => (),
911 }
912 }
913
914 if !has_data {
915 tracing::warn!("GPSInfo data not found");
916 return None;
917 }
918
919 let altitude = match (altitude_ref, altitude_value) {
920 (Some(0), Some(v)) => Altitude::AboveSeaLevel(v),
921 (Some(1), Some(v)) => Altitude::BelowSeaLevel(v),
922 _ => Altitude::Unknown,
923 };
924
925 let speed = match (speed_unit, speed_value) {
926 (Some(unit), Some(value)) => Some(Speed { unit, value }),
927 _ => None,
928 };
929
930 Some(GPSInfo {
931 latitude_ref: latitude_ref.unwrap_or(LatRef::North),
932 latitude: latitude.unwrap_or_default(),
933 longitude_ref: longitude_ref.unwrap_or(LonRef::East),
934 longitude: longitude.unwrap_or_default(),
935 altitude,
936 speed,
937 })
938 }
939
940 fn clone_with_state(&self) -> IfdIter {
941 let mut it = self.clone();
942 it.index = self.index;
943 it.pos = self.pos;
944 it
945 }
946}
947
948#[derive(Debug)]
949pub(crate) enum IfdEntry {
950 IfdNew(IfdIter), Entry(EntryValue),
952 Err(EntryError),
953}
954
955impl IfdEntry {
956 pub fn as_u8(&self) -> Option<u8> {
957 if let IfdEntry::Entry(EntryValue::U8(v)) = self {
958 Some(*v)
959 } else {
960 None
961 }
962 }
963
964 pub fn as_char(&self) -> Option<char> {
965 if let IfdEntry::Entry(EntryValue::Text(s)) = self {
966 s.chars().next()
967 } else {
968 None
969 }
970 }
971
972 fn as_irational(&self) -> Option<&IRational> {
973 if let IfdEntry::Entry(EntryValue::IRational(v)) = self {
974 Some(v)
975 } else {
976 None
977 }
978 }
979
980 fn as_irational_slice(&self) -> Option<&Vec<IRational>> {
981 if let IfdEntry::Entry(EntryValue::IRationalArray(v)) = self {
982 Some(v)
983 } else {
984 None
985 }
986 }
987
988 fn as_urational(&self) -> Option<&URational> {
989 if let IfdEntry::Entry(EntryValue::URational(v)) = self {
990 Some(v)
991 } else {
992 None
993 }
994 }
995
996 fn as_urational_slice(&self) -> Option<&Vec<URational>> {
997 if let IfdEntry::Entry(EntryValue::URationalArray(v)) = self {
998 Some(v)
999 } else {
1000 None
1001 }
1002 }
1003
1004 fn as_str(&self) -> Option<&str> {
1005 if let IfdEntry::Entry(e) = self {
1006 e.as_str()
1007 } else {
1008 None
1009 }
1010 }
1011}
1012
1013pub(crate) const SUBIFD_TAGS: &[u16] = &[ExifTag::ExifOffset.code(), ExifTag::GPSInfo.code()];
1014
1015impl Iterator for IfdIter {
1016 type Item = (Option<TagOrCode>, IfdEntry);
1017
1018 #[tracing::instrument(skip(self))]
1019 fn next(&mut self) -> Option<Self::Item> {
1020 tracing::debug!(
1021 ifd = self.ifd_idx,
1022 index = self.index,
1023 entry_num = self.entry_num,
1024 offset = format!("{:08x}", self.offset),
1025 pos = format!("{:08x}", self.pos),
1026 "next IFD entry"
1027 );
1028 if self.input.len() < self.pos + IFD_ENTRY_SIZE {
1029 return None;
1030 }
1031
1032 let endian = self.header.endian;
1033 if self.index > self.entry_num {
1034 return None;
1035 }
1036 if self.index == self.entry_num {
1037 tracing::debug!(
1038 self.ifd_idx,
1039 self.index,
1040 pos = self.pos,
1041 "try to get next ifd"
1042 );
1043 self.index += 1;
1044
1045 let (_, offset) =
1047 complete::u32::<_, nom::error::Error<_>>(endian)(&self.input[self.pos..]).ok()?;
1048
1049 if offset == 0 {
1050 tracing::debug!(?self, "IFD parsing completed");
1052 return None;
1053 }
1054
1055 return self
1056 .new_ifd_iter(self.ifd_idx + 1, offset, None)
1057 .map(|x| (None, x));
1058 }
1059
1060 let entry_data = self
1061 .input
1062 .slice_checked(self.pos..self.pos + IFD_ENTRY_SIZE)?;
1063 self.index += 1;
1064 self.pos += IFD_ENTRY_SIZE;
1065
1066 let (tag, res) = self.parse_tag_entry(entry_data)?;
1067
1068 Some((Some(tag.into()), res)) }
1070}
1071
1072#[cfg(test)]
1073mod tests {
1074
1075 use crate::exif::extract_exif_with_mime;
1076 use crate::exif::input_into_iter;
1077 use crate::file::MediaMimeImage;
1078 use crate::slice::SubsliceRange;
1079 use crate::testkit::read_sample;
1080 use crate::Exif;
1081 use test_case::test_case;
1082
1083 #[test_case(
1084 "exif.jpg",
1085 "+08:00",
1086 "2023-07-09T20:36:33+08:00",
1087 MediaMimeImage::Jpeg
1088 )]
1089 #[test_case("exif-no-tz.jpg", "", "2023-07-09 20:36:33", MediaMimeImage::Jpeg)]
1090 #[test_case("broken.jpg", "-", "2014-09-21 15:51:22", MediaMimeImage::Jpeg)]
1091 #[test_case(
1092 "exif.heic",
1093 "+08:00",
1094 "2022-07-22T21:26:32+08:00",
1095 MediaMimeImage::Heic
1096 )]
1097 #[test_case(
1098 "exif.avif",
1099 "+08:00",
1100 "2022-07-22T21:26:32+08:00",
1101 MediaMimeImage::Avif
1102 )]
1103 #[test_case("tif.tif", "-", "-", MediaMimeImage::Tiff)]
1104 #[test_case(
1105 "fujifilm_x_t1_01.raf.meta",
1106 "-",
1107 "2014-01-30 12:49:13",
1108 MediaMimeImage::Raf
1109 )]
1110 fn exif_iter_tz(path: &str, tz: &str, time: &str, img_type: MediaMimeImage) {
1111 let buf = read_sample(path).unwrap();
1112 let (data, _) = extract_exif_with_mime(img_type, &buf, None).unwrap();
1113 let range = data.and_then(|x| buf.subslice_in_range(x)).unwrap();
1114 let iter = input_into_iter(bytes::Bytes::from(buf).slice(range), None).unwrap();
1115 let expect = if tz == "-" {
1116 None
1117 } else {
1118 Some(tz.to_string())
1119 };
1120 assert_eq!(iter.tz, expect);
1121 let exif: Exif = iter.into();
1122 let value = exif.get(crate::ExifTag::DateTimeOriginal);
1123 if time == "-" {
1124 assert!(value.is_none());
1125 } else {
1126 let value = value.unwrap();
1127 assert_eq!(value.to_string(), time);
1128 }
1129 }
1130
1131 #[test]
1132 fn ifd_index_constants() {
1133 use crate::IfdIndex;
1134 assert_eq!(IfdIndex::MAIN.as_usize(), 0);
1135 assert_eq!(IfdIndex::THUMBNAIL.as_usize(), 1);
1136 }
1137
1138 #[test]
1139 fn ifd_index_roundtrip_via_new_and_as_usize() {
1140 use crate::IfdIndex;
1141 for raw in [0, 1, 2, 3, 7, 99] {
1142 assert_eq!(IfdIndex::new(raw).as_usize(), raw);
1143 }
1144 }
1145
1146 #[test]
1147 fn ifd_index_equality_and_hash() {
1148 use crate::IfdIndex;
1149 use std::collections::HashSet;
1150 let mut set: HashSet<IfdIndex> = HashSet::new();
1151 set.insert(IfdIndex::MAIN);
1152 set.insert(IfdIndex::new(0)); set.insert(IfdIndex::THUMBNAIL);
1154 assert_eq!(set.len(), 2);
1155 }
1156
1157 #[test]
1158 fn ifd_index_display_format() {
1159 use crate::IfdIndex;
1160 assert_eq!(format!("{}", IfdIndex::MAIN), "ifd0");
1161 assert_eq!(format!("{}", IfdIndex::new(7)), "ifd7");
1162 }
1163
1164 #[test]
1165 fn tag_or_code_for_known_tag_resolves_to_tag_variant() {
1166 use crate::{ExifTag, TagOrCode};
1167 let t: TagOrCode = ExifTag::Make.code().into();
1168 assert_eq!(t, TagOrCode::Tag(ExifTag::Make));
1169 assert_eq!(t.code(), ExifTag::Make.code());
1170 }
1171
1172 #[test]
1173 fn tag_or_code_for_unknown_tag_resolves_to_unknown_variant() {
1174 use crate::TagOrCode;
1175 let t: TagOrCode = 0xffff_u16.into();
1176 assert_eq!(t, TagOrCode::Unknown(0xffff));
1177 assert_eq!(t.code(), 0xffff);
1178 }
1179
1180 #[test]
1181 fn exif_entry_pub_fields_construct_and_destructure() {
1182 use crate::{EntryValue, ExifEntry, ExifTag, IfdIndex, TagOrCode};
1183 let val = EntryValue::Text("vivo X90 Pro+".into());
1184 let e = ExifEntry {
1185 ifd: IfdIndex::MAIN,
1186 tag: TagOrCode::Tag(ExifTag::Model),
1187 value: &val,
1188 };
1189 let ExifEntry { ifd, tag, value } = e;
1191 assert_eq!(ifd, IfdIndex::MAIN);
1192 assert_eq!(tag.code(), ExifTag::Model.code());
1193 assert!(matches!(value, EntryValue::Text(_)));
1194 let _e2 = e;
1196 let _e3 = e;
1197 }
1198
1199 #[test]
1200 fn exif_iter_entry_value_xor_error_invariant() {
1201 use crate::{MediaParser, MediaSource};
1202 let mut parser = MediaParser::new();
1203 let ms = MediaSource::open("testdata/exif.jpg").unwrap();
1204 for entry in parser.parse_exif(ms).unwrap() {
1205 let has_v = entry.value().is_some();
1207 let has_e = entry.error().is_some();
1208 assert!(has_v ^ has_e, "entry must be value xor error");
1209 match entry.result() {
1211 Ok(v) => assert_eq!(Some(v), entry.value()),
1212 Err(e) => assert_eq!(Some(e), entry.error()),
1213 }
1214 }
1215 }
1216
1217 #[test]
1218 fn exif_iter_entry_into_result_consumes_self() {
1219 use crate::{MediaParser, MediaSource};
1220 let mut parser = MediaParser::new();
1221 let ms = MediaSource::open("testdata/exif.jpg").unwrap();
1222 let mut count_ok = 0usize;
1223 for entry in parser.parse_exif(ms).unwrap() {
1224 if entry.into_result().is_ok() {
1228 count_ok += 1;
1229 }
1230 }
1231 assert!(count_ok > 0);
1232 }
1233
1234 #[test]
1235 fn exif_iter_entry_tag_returns_tag_or_code() {
1236 use crate::{ExifTag, MediaParser, MediaSource, TagOrCode};
1237 let mut parser = MediaParser::new();
1238 let ms = MediaSource::open("testdata/exif.jpg").unwrap();
1239 let make_present = parser
1240 .parse_exif(ms)
1241 .unwrap()
1242 .any(|e| matches!(e.tag(), TagOrCode::Tag(ExifTag::Make)));
1243 assert!(make_present);
1244 }
1245
1246 #[test]
1247 fn exif_iter_rewind_resets_iteration_state() {
1248 use crate::{MediaParser, MediaSource};
1249 let mut parser = MediaParser::new();
1250 let ms = MediaSource::open("testdata/exif.jpg").unwrap();
1251 let mut iter = parser.parse_exif(ms).unwrap();
1252 let first_count = iter.by_ref().count();
1253 assert!(first_count > 0);
1254 assert_eq!(iter.by_ref().count(), 0);
1256 iter.rewind();
1257 let after_rewind = iter.count();
1258 assert_eq!(first_count, after_rewind);
1259 }
1260
1261 #[test]
1262 fn exif_iter_clone_rewound_yields_independent_full_iter() {
1263 use crate::{MediaParser, MediaSource};
1264 let mut parser = MediaParser::new();
1265 let ms = MediaSource::open("testdata/exif.jpg").unwrap();
1266 let mut iter = parser.parse_exif(ms).unwrap();
1267 let _consumed = iter.by_ref().take(2).count();
1268 let cloned = iter.clone_rewound();
1269 let cloned_total = cloned.count();
1271 let remaining = iter.count();
1272 assert!(cloned_total > remaining);
1273 }
1274
1275 #[test]
1276 fn exif_iter_parse_gps_returns_option_no_iteration_advance() {
1277 use crate::{MediaParser, MediaSource};
1278 let mut parser = MediaParser::new();
1279 let ms = MediaSource::open("testdata/exif.jpg").unwrap();
1280 let iter = parser.parse_exif(ms).unwrap();
1281 let gps = iter.parse_gps().unwrap();
1282 assert!(gps.is_some());
1283 let count = iter.count();
1285 assert!(count > 0);
1286 }
1287
1288 #[test]
1295 fn gps_subifd_first_entry_is_gpsversion_id_issue_50() {
1296 use crate::exif::exif_iter::input_into_iter;
1297 #[rustfmt::skip]
1298 let tiff: &[u8] = &[
1299 b'I', b'I', 0x2a, 0x00,
1301 0x08, 0x00, 0x00, 0x00,
1302
1303 0x01, 0x00,
1305 0x25, 0x88, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00,
1306 0x1a, 0x00, 0x00, 0x00,
1307 0x00, 0x00, 0x00, 0x00, 0x05, 0x00,
1311 0x00, 0x00, 0x01, 0x00, 0x04, 0x00, 0x00, 0x00,
1313 0x02, 0x03, 0x00, 0x00,
1314 0x01, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00,
1316 b'N', 0x00, 0x00, 0x00,
1317 0x02, 0x00, 0x05, 0x00, 0x03, 0x00, 0x00, 0x00,
1319 0x5c, 0x00, 0x00, 0x00,
1320 0x03, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00,
1322 b'E', 0x00, 0x00, 0x00,
1323 0x04, 0x00, 0x05, 0x00, 0x03, 0x00, 0x00, 0x00,
1325 0x74, 0x00, 0x00, 0x00,
1326 0x00, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
1330 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
1331 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
1332
1333 0x78, 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
1339 let iter = input_into_iter(tiff.to_vec(), None).unwrap();
1340
1341 let gps = iter
1343 .parse_gps()
1344 .expect("parse_gps must succeed")
1345 .expect("GPS sub-IFD with GPSVersionID first must yield GPSInfo");
1346 assert_eq!(gps.latitude_decimal(), Some(36.0));
1347 assert_eq!(gps.longitude_decimal(), Some(120.0));
1348
1349 let tags: Vec<u16> = iter.map(|e| e.tag().code()).collect();
1352 assert!(
1353 tags.contains(&crate::ExifTag::GPSVersionID.code()),
1354 "GPSVersionID (tag 0) should be visible to iterators; got {tags:?}"
1355 );
1356 }
1357}