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#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
64#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
65#[non_exhaustive]
66pub enum IfdKind {
67 Tiff,
69
70 Exif,
72
73 Gps,
75
76 Interop,
78}
79
80impl std::fmt::Display for IfdKind {
81 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
82 let s = match self {
83 IfdKind::Tiff => "tiff",
84 IfdKind::Exif => "exif",
85 IfdKind::Gps => "gps",
86 IfdKind::Interop => "interop",
87 };
88 f.write_str(s)
89 }
90}
91
92#[derive(Clone, Copy, Debug)]
104pub struct ExifEntry<'a> {
105 pub ifd: IfdIndex,
106 pub tag: TagOrCode,
107 pub value: &'a crate::EntryValue,
108}
109
110#[derive(Clone, Copy, Debug)]
118pub struct ExifEntryRef<'a> {
119 ifd: IfdIndex,
120 kind: IfdKind,
121 tag: TagOrCode,
122 value: &'a crate::EntryValue,
123}
124
125impl<'a> ExifEntryRef<'a> {
126 pub(crate) fn new(
127 ifd: IfdIndex,
128 kind: IfdKind,
129 tag: TagOrCode,
130 value: &'a crate::EntryValue,
131 ) -> Self {
132 Self {
133 ifd,
134 kind,
135 tag,
136 value,
137 }
138 }
139
140 pub fn ifd(&self) -> IfdIndex {
142 self.ifd
143 }
144
145 pub fn ifd_kind(&self) -> IfdKind {
148 self.kind
149 }
150
151 pub fn tag(&self) -> TagOrCode {
153 self.tag
154 }
155
156 pub fn value(&self) -> &'a crate::EntryValue {
158 self.value
159 }
160}
161
162#[derive(Clone)]
165pub(crate) struct TiffDataBlock {
166 #[allow(dead_code)]
168 pub block_id: String,
169 pub data: Bytes,
171 pub header: Option<TiffHeader>,
173}
174
175#[tracing::instrument]
184pub(crate) fn input_into_iter(
185 input: impl Into<bytes::Bytes> + Debug,
186 state: Option<TiffHeader>,
187) -> crate::Result<ExifIter> {
188 let input: bytes::Bytes = input.into();
189 let header = match state {
190 Some(header) => header,
193 _ => {
194 let (_, header) = TiffHeader::parse(&input[..]).map_err(|e| {
196 crate::error::nom_err_to_malformed(e, crate::error::MalformedKind::TiffHeader)
197 })?;
198
199 tracing::debug!(
200 ?header,
201 data_len = format!("{:#x}", input.len()),
202 "TIFF header parsed"
203 );
204 header
205 }
206 };
207
208 let start = header.ifd0_offset as usize;
209 if start > input.len() {
210 return Err(crate::Error::UnexpectedEof {
211 context: "exif iter init",
212 });
213 }
214 tracing::debug!(?header, offset = start);
215
216 let mut ifd0 = IfdIter::try_new(0, input.clone(), header.to_owned(), start, None)?;
217
218 let tz = ifd0.find_tz_offset();
219 ifd0.tz = tz.clone();
220 let iter: ExifIter = ExifIter::new(input, header, tz, ifd0);
221
222 tracing::debug!(?iter, "got IFD0");
223
224 Ok(iter)
225}
226
227pub struct ExifIter {
240 input: Bytes,
241 tiff_header: TiffHeader,
242 tz: Option<String>,
243 ifd0: IfdIter,
244
245 ifds: Vec<IfdIter>,
247 visited_offsets: HashSet<usize>,
248
249 additional_blocks: Vec<TiffDataBlock>,
252 current_block_index: usize,
254 encountered_tags: HashSet<(usize, IfdKind, u16)>,
259 has_embedded_track: bool,
260}
261
262impl Debug for ExifIter {
263 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
264 f.debug_struct("ExifIter")
265 .field("data len", &self.input.len())
266 .field("tiff_header", &self.tiff_header)
267 .field("ifd0", &self.ifd0)
268 .field("state", &self.ifds.first().map(|x| (x.index, x.pos)))
269 .field("ifds num", &self.ifds.len())
270 .field("additional_blocks", &self.additional_blocks.len())
271 .field("current_block_index", &self.current_block_index)
272 .finish_non_exhaustive()
273 }
274}
275
276impl Clone for ExifIter {
277 fn clone(&self) -> Self {
278 self.clone_rewound()
279 }
280}
281
282impl ExifIter {
283 pub(crate) fn new(
284 input: bytes::Bytes,
285 tiff_header: TiffHeader,
286 tz: Option<String>,
287 ifd0: IfdIter,
288 ) -> ExifIter {
289 let ifds = vec![ifd0.clone()];
290 ExifIter {
291 input,
292 tiff_header,
293 tz,
294 ifd0,
295 ifds,
296 visited_offsets: HashSet::new(),
297 additional_blocks: Vec::new(),
298 current_block_index: 0,
299 encountered_tags: HashSet::new(),
300 has_embedded_track: false,
301 }
302 }
303
304 pub fn clone_rewound(&self) -> Self {
308 let ifd0 = self.ifd0.clone_and_rewind();
309 let ifds = vec![ifd0.clone()];
310 Self {
311 input: self.input.clone(),
312 tiff_header: self.tiff_header.clone(),
313 tz: self.tz.clone(),
314 ifd0,
315 ifds,
316 visited_offsets: HashSet::new(),
317 additional_blocks: self.additional_blocks.clone(),
318 current_block_index: 0,
319 encountered_tags: HashSet::new(),
320 has_embedded_track: self.has_embedded_track,
321 }
322 }
323
324 pub fn rewind(&mut self) {
327 let ifd0 = self.ifd0.clone_and_rewind();
328 self.ifds = vec![ifd0.clone()];
329 self.ifd0 = ifd0;
330 self.visited_offsets.clear();
331 self.current_block_index = 0;
332 self.encountered_tags.clear();
333 }
334
335 #[tracing::instrument(skip_all)]
345 pub fn parse_gps(&self) -> crate::Result<Option<GPSInfo>> {
346 let mut iter = self.clone_rewound();
347 let Some(gps) = iter.find(|x| {
348 tracing::info!(?x, "find");
349 x.tag().tag().is_some_and(|t| t == ExifTag::GPSInfo)
350 }) else {
351 tracing::warn!(ifd0 = ?iter.ifds.first(), "GPSInfo not found");
352 return Ok(None);
353 };
354
355 let offset = match gps.result() {
356 Ok(v) => {
357 if let Some(offset) = v.as_u32() {
358 offset
359 } else {
360 return Err(EntryError::InvalidValue("invalid gps offset").into());
361 }
362 }
363 Err(e) => return Err(e.clone().into()),
364 };
365 if offset as usize >= iter.input.len() {
366 return Err(crate::Error::Malformed {
367 kind: crate::error::MalformedKind::IfdEntry,
368 message: "GPSInfo offset out of range".into(),
369 });
370 }
371
372 let mut gps_subifd = IfdIter::try_new(
373 gps.ifd().as_usize(),
374 iter.input.clone(),
375 iter.tiff_header,
376 offset as usize,
377 iter.tz.clone(),
378 )?
379 .tag_code(ExifTag::GPSInfo.code());
380 Ok(gps_subifd.parse_gps_info())
381 }
382
383 pub(crate) fn add_tiff_block(
391 &mut self,
392 block_id: String,
393 data: bytes::Bytes,
394 header: Option<TiffHeader>,
395 ) {
396 self.additional_blocks.push(TiffDataBlock {
397 block_id,
398 data,
399 header,
400 });
401 }
402
403 pub(crate) fn set_has_embedded_track(&mut self, v: bool) {
406 self.has_embedded_track = v;
407 }
408
409 pub fn has_embedded_track(&self) -> bool {
425 self.has_embedded_track
426 }
427
428 #[deprecated(
430 since = "3.1.0",
431 note = "renamed to `has_embedded_track`; the original `has_embedded_media` was too vague and lumped in still-image previews"
432 )]
433 pub fn has_embedded_media(&self) -> bool {
434 self.has_embedded_track()
435 }
436}
437
438#[derive(Clone)]
449pub struct ExifIterEntry {
450 ifd: IfdIndex,
451 kind: IfdKind,
452 tag: TagOrCode,
453 res: Result<EntryValue, crate::error::EntryError>,
454}
455
456impl ExifIterEntry {
457 pub fn ifd(&self) -> IfdIndex {
459 self.ifd
460 }
461
462 pub fn ifd_kind(&self) -> IfdKind {
466 self.kind
467 }
468
469 pub fn tag(&self) -> TagOrCode {
471 self.tag
472 }
473
474 pub fn value(&self) -> Option<&EntryValue> {
476 self.res.as_ref().ok()
477 }
478
479 pub fn error(&self) -> Option<&crate::error::EntryError> {
481 self.res.as_ref().err()
482 }
483
484 pub fn result(&self) -> Result<&EntryValue, &crate::error::EntryError> {
486 self.res.as_ref()
487 }
488
489 pub fn into_result(self) -> Result<EntryValue, crate::error::EntryError> {
492 self.res
493 }
494
495 pub(crate) fn make_ok(ifd: usize, kind: IfdKind, tag: TagOrCode, v: EntryValue) -> Self {
496 Self {
497 ifd: IfdIndex::new(ifd),
498 kind,
499 tag,
500 res: Ok(v),
501 }
502 }
503}
504
505impl std::fmt::Debug for ExifIterEntry {
506 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
507 let value = match &self.res {
508 Ok(v) => format!("{v}"),
509 Err(e) => format!("{e:?}"),
510 };
511 f.debug_struct("ExifIterEntry")
512 .field("ifd", &self.ifd)
513 .field("kind", &self.kind)
514 .field("tag", &self.tag)
515 .field("value", &value)
516 .finish()
517 }
518}
519
520const MAX_IFD_DEPTH: usize = 8;
521
522impl ExifIter {
523 fn load_next_block(&mut self) -> bool {
526 let block_index = self.current_block_index;
528 if block_index >= self.additional_blocks.len() {
529 return false;
530 }
531
532 let block = &self.additional_blocks[block_index];
533 tracing::debug!(
534 block_id = block.block_id,
535 block_index,
536 "Loading additional TIFF block"
537 );
538
539 let block_data = block.data.clone();
541 let header = block.header.clone();
542
543 match input_into_iter(block_data, header) {
545 Ok(iter) => {
546 self.ifd0 = iter.ifd0;
548 self.ifds = vec![self.ifd0.clone()];
549 self.visited_offsets.clear();
550 self.current_block_index += 1;
551
552 tracing::debug!(block_index, "Successfully loaded additional TIFF block");
553 true
554 }
555 Err(e) => {
556 tracing::warn!(
557 block_index,
558 error = %e,
559 "Failed to load additional TIFF block, skipping"
560 );
561 self.current_block_index += 1;
563 self.load_next_block()
564 }
565 }
566 }
567
568 fn should_include_tag(&mut self, ifd_index: usize, kind: IfdKind, tag_code: u16) -> bool {
571 let tag_key = (ifd_index, kind, tag_code);
572 if self.encountered_tags.contains(&tag_key) {
573 tracing::debug!(ifd_index, %kind, tag_code, "Skipping duplicate tag");
574 false
575 } else {
576 self.encountered_tags.insert(tag_key);
577 true
578 }
579 }
580}
581
582impl Iterator for ExifIter {
583 type Item = ExifIterEntry;
584
585 #[tracing::instrument(skip_all)]
586 fn next(&mut self) -> Option<Self::Item> {
587 loop {
588 if self.ifds.is_empty() {
589 if !self.load_next_block() {
591 tracing::debug!(?self, "all IFDs and blocks have been parsed");
592 return None;
593 }
594 continue;
596 }
597
598 if self.ifds.len() > MAX_IFD_DEPTH {
599 let depth = self.ifds.len();
600 self.ifds.clear();
601 tracing::error!(
602 ifds_depth = depth,
603 "ifd depth is too deep, just go back to ifd0"
604 );
605 self.ifds.push(self.ifd0.clone_with_state());
606 }
607
608 let mut ifd = self.ifds.pop()?;
609 let cur_ifd_idx = ifd.ifd_idx;
610 let parent_kind = ifd.kind;
613 match ifd.next() {
614 Some((tag_code, entry)) => {
615 tracing::debug!(ifd = ifd.ifd_idx, ?tag_code, "next tag entry");
616
617 match entry {
618 IfdEntry::IfdNew(new_ifd) => {
619 if new_ifd.offset > 0 {
620 if self.visited_offsets.contains(&new_ifd.offset) {
621 continue;
623 }
624 self.visited_offsets.insert(new_ifd.offset);
625 }
626
627 let is_subifd = if new_ifd.ifd_idx == ifd.ifd_idx {
628 self.ifds.push(ifd);
630 tracing::debug!(?tag_code, ?new_ifd, "got new SUB-IFD");
631 true
632 } else {
633 tracing::debug!("IFD{} parsing completed", cur_ifd_idx);
637 tracing::debug!(?new_ifd, "got new IFD");
638 false
639 };
640
641 let (ifd_idx, offset) = (new_ifd.ifd_idx, new_ifd.offset);
642 self.ifds.push(new_ifd);
643
644 if is_subifd {
645 let tc = tag_code.unwrap();
647 if !self.should_include_tag(ifd_idx, parent_kind, tc.code()) {
648 continue;
649 }
650 return Some(ExifIterEntry::make_ok(
652 ifd_idx,
653 parent_kind,
654 tc,
655 EntryValue::U32(offset as u32),
656 ));
657 }
658 }
659 IfdEntry::Entry(v) => {
660 let tc = tag_code.unwrap();
661 if !self.should_include_tag(ifd.ifd_idx, ifd.kind, tc.code()) {
663 self.ifds.push(ifd);
664 continue;
665 }
666 let res = Some(ExifIterEntry::make_ok(ifd.ifd_idx, ifd.kind, tc, v));
667 self.ifds.push(ifd);
668 return res;
669 }
670 IfdEntry::Err(e) => {
671 tracing::warn!(?tag_code, ?e, "parse ifd entry error");
672 self.ifds.push(ifd);
673 continue;
674 }
675 }
676 }
677 None => continue,
678 }
679 }
680 }
681}
682
683#[derive(Clone)]
684pub(crate) struct IfdIter {
685 ifd_idx: usize,
686 tag_code: Option<TagOrCode>,
687 kind: IfdKind,
691
692 input: Bytes,
694
695 offset: usize,
697
698 header: TiffHeader,
699 entry_num: u16,
700
701 pub tz: Option<String>,
702
703 index: u16,
705 pos: usize,
706}
707
708impl Debug for IfdIter {
709 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
710 f.debug_struct("IfdIter")
711 .field("ifd_idx", &self.ifd_idx)
712 .field("kind", &self.kind)
713 .field("tag", &self.tag_code)
714 .field("data len", &self.input.len())
715 .field("tz", &self.tz)
716 .field("header", &self.header)
717 .field("entry_num", &self.entry_num)
718 .field("index", &self.index)
719 .field("pos", &self.pos)
720 .finish()
721 }
722}
723
724impl IfdIter {
725 pub fn rewind(&mut self) {
726 self.index = 0;
727 self.pos = self.offset + 2;
729 }
730
731 pub fn clone_and_rewind(&self) -> Self {
732 let mut it = self.clone();
733 it.rewind();
734 it
735 }
736
737 pub fn tag_code_maybe(mut self, code: Option<u16>) -> Self {
738 self.tag_code = code.map(|x| x.into());
739 self.kind = Self::kind_for(code);
740 self
741 }
742
743 pub fn tag_code(self, code: u16) -> Self {
744 self.tag_code_maybe(Some(code))
745 }
746
747 fn kind_for(code: Option<u16>) -> IfdKind {
750 match code {
751 Some(c) if c == ExifTag::ExifOffset.code() => IfdKind::Exif,
752 Some(c) if c == ExifTag::GPSInfo.code() => IfdKind::Gps,
753 Some(c) if c == ExifTag::InteropOffset.code() => IfdKind::Interop,
754 _ => IfdKind::Tiff,
755 }
756 }
757
758 fn is_gps_subifd(&self) -> bool {
759 matches!(
760 self.tag_code.as_ref().and_then(|t| t.tag()),
761 Some(ExifTag::GPSInfo)
762 )
763 }
764
765 #[allow(unused)]
766 pub fn tag(mut self, tag: TagOrCode) -> Self {
767 self.tag_code = Some(tag);
768 self
769 }
770
771 #[tracing::instrument(skip(input))]
772 pub fn try_new(
773 ifd_idx: usize,
774 input: Bytes,
775 header: TiffHeader,
776 offset: usize,
777 tz: Option<String>,
778 ) -> crate::Result<Self> {
779 if input.len() < 2 {
780 return Err(crate::Error::Malformed {
781 kind: crate::error::MalformedKind::TiffHeader,
782 message: "ifd data too small to decode entry num".into(),
783 });
784 }
785 assert!(offset <= input.len());
787 let ifd_data = input.slice(offset..);
788 let (_, entry_num) =
789 TiffHeader::parse_ifd_entry_num(&ifd_data, header.endian).map_err(|e| {
790 crate::error::nom_err_to_malformed(e, crate::error::MalformedKind::TiffHeader)
791 })?;
792
793 Ok(Self {
794 ifd_idx,
795 tag_code: None,
796 kind: IfdKind::Tiff,
797 input,
798 offset,
799 header,
800 entry_num,
801 tz,
802 pos: offset + 2,
804 index: 0,
805 })
806 }
807
808 fn parse_tag_entry(&self, entry_data: &[u8]) -> Option<(u16, IfdEntry)> {
809 let endian = self.header.endian;
810 let (_, (tag, data_format, components_num, value_or_offset)) = (
811 complete::u16::<_, nom::error::Error<_>>(endian),
812 complete::u16(endian),
813 complete::u32(endian),
814 complete::u32(endian),
815 )
816 .parse(entry_data)
817 .ok()?;
818
819 if tag == 0 && !self.is_gps_subifd() {
824 return None;
825 }
826
827 let df: DataFormat = match DataFormat::try_from(data_format) {
828 Ok(df) => df,
829 Err(bad) => {
830 let t: TagOrCode = tag.into();
831 tracing::warn!(tag = ?t, format = bad, "invalid entry data format");
832 return Some((
833 tag,
834 IfdEntry::Err(EntryError::InvalidShape {
835 format: bad,
836 count: components_num,
837 }),
838 ));
839 }
840 };
841 let (tag, res) = self.parse_entry(tag, df, components_num, entry_data, value_or_offset);
842 Some((tag, res))
843 }
844
845 fn get_data_pos(&self, value_or_offset: u32) -> usize {
846 value_or_offset as usize
848 }
849
850 fn parse_entry(
851 &self,
852 tag: u16,
853 data_format: DataFormat,
854 components_num: u32,
855 entry_data: &[u8],
856 value_or_offset: u32,
857 ) -> (u16, IfdEntry) {
858 let component_size = data_format.component_size();
860
861 let size = components_num as usize * component_size;
863 let data = if size <= 4 {
864 &entry_data[8..8 + size] } else {
866 let start = self.get_data_pos(value_or_offset);
867 let end = start + size;
868 let Some(data) = self.input.slice_checked(start..end) else {
869 tracing::warn!(
870 "entry data overflow, tag: {:04x} start: {:08x} end: {:08x} ifd data len {:08x}",
871 tag,
872 start,
873 end,
874 self.input.len(),
875 );
876 return (
877 tag,
878 IfdEntry::Err(EntryError::Truncated {
879 needed: size,
880 available: self.input.len().saturating_sub(start),
881 }),
882 );
883 };
884
885 data
886 };
887
888 if SUBIFD_TAGS.contains(&tag) {
889 if let Some(value) = self.new_ifd_iter(self.ifd_idx, value_or_offset, Some(tag)) {
890 return (tag, value);
891 }
892 }
893
894 let entry = EntryData {
895 endian: self.header.endian,
896 tag,
897 data,
898 data_format,
899 components_num,
900 };
901 match EntryValue::parse(&entry, &self.tz) {
902 Ok(v) => (tag, IfdEntry::Entry(v)),
903 Err(e) => (tag, IfdEntry::Err(e)),
904 }
905 }
906
907 fn new_ifd_iter(
908 &self,
909 ifd_idx: usize,
910 value_or_offset: u32,
911 tag: Option<u16>,
912 ) -> Option<IfdEntry> {
913 let offset = self.get_data_pos(value_or_offset);
914 if offset < self.input.len() {
915 match IfdIter::try_new(
916 ifd_idx,
917 self.input.clone(),
918 self.header.to_owned(),
919 offset,
920 self.tz.clone(),
921 ) {
922 Ok(iter) => return Some(IfdEntry::IfdNew(iter.tag_code_maybe(tag))),
923 Err(e) => {
924 tracing::warn!(?tag, ?e, "Create next/sub IFD failed");
925 }
926 }
927 }
936 None
937 }
938
939 pub fn find_exif_iter(&self) -> Option<IfdIter> {
940 let endian = self.header.endian;
941 for i in 0..self.entry_num {
943 let pos = self.pos + i as usize * IFD_ENTRY_SIZE;
944 let (_, tag) =
945 complete::u16::<_, nom::error::Error<_>>(endian)(&self.input[pos..]).ok()?;
946 if tag == ExifTag::ExifOffset.code() {
947 let entry_data = self.input.slice_checked(pos..pos + IFD_ENTRY_SIZE)?;
948 let (_, entry) = self.parse_tag_entry(entry_data)?;
949 match entry {
950 IfdEntry::IfdNew(iter) => return Some(iter),
951 IfdEntry::Entry(_) | IfdEntry::Err(_) => return None,
952 }
953 }
954 }
955 None
956 }
957
958 pub fn find_tz_offset(&self) -> Option<String> {
959 let iter = self.find_exif_iter()?;
960 let mut offset = None;
961 for entry in iter {
962 let Some(tag) = entry.0 else {
963 continue;
964 };
965 if tag.code() == ExifTag::OffsetTimeOriginal.code()
966 || tag.code() == ExifTag::OffsetTimeDigitized.code()
967 {
968 return entry.1.as_str().map(|x| x.to_owned());
969 } else if tag.code() == ExifTag::OffsetTime.code() {
970 offset = entry.1.as_str().map(|x| x.to_owned());
971 }
972 }
973
974 offset
975 }
976
977 pub fn parse_gps_info(&mut self) -> Option<GPSInfo> {
979 use crate::exif::gps::{Altitude, LatRef, LonRef, Speed, SpeedUnit};
980
981 let mut latitude_ref = None;
982 let mut latitude = None;
983 let mut longitude_ref = None;
984 let mut longitude = None;
985 let mut altitude_ref = None;
986 let mut altitude_value = None;
987 let mut speed_unit = None;
988 let mut speed_value = None;
989 let mut has_data = false;
990
991 for (tag, entry) in self {
992 let Some(tag) = tag.and_then(|x| x.tag()) else {
993 continue;
994 };
995 has_data = true;
996 match tag {
997 ExifTag::GPSLatitudeRef => {
998 latitude_ref = entry.as_char().and_then(LatRef::from_char);
999 }
1000 ExifTag::GPSLongitudeRef => {
1001 longitude_ref = entry.as_char().and_then(LonRef::from_char);
1002 }
1003 ExifTag::GPSAltitudeRef => {
1004 altitude_ref = entry.as_u8();
1005 }
1006 ExifTag::GPSLatitude => {
1007 if let Some(v) = entry.as_urational_slice() {
1008 latitude = LatLng::try_from(v).ok();
1009 } else if let Some(v) = entry.as_irational_slice() {
1010 latitude = LatLng::try_from(v).ok();
1011 }
1012 }
1013 ExifTag::GPSLongitude => {
1014 if let Some(v) = entry.as_urational_slice() {
1015 longitude = LatLng::try_from(v).ok();
1016 } else if let Some(v) = entry.as_irational_slice() {
1017 longitude = LatLng::try_from(v).ok();
1018 }
1019 }
1020 ExifTag::GPSAltitude => {
1021 if let Some(v) = entry.as_urational() {
1022 altitude_value = Some(*v);
1023 } else if let Some(v) = entry.as_irational() {
1024 if let Ok(u) = URational::try_from(*v) {
1025 altitude_value = Some(u);
1026 }
1027 }
1028 }
1029 ExifTag::GPSSpeedRef => {
1030 speed_unit = entry.as_char().and_then(SpeedUnit::from_char);
1031 }
1032 ExifTag::GPSSpeed => {
1033 if let Some(v) = entry.as_urational() {
1034 speed_value = Some(*v);
1035 } else if let Some(v) = entry.as_irational() {
1036 if let Ok(u) = URational::try_from(*v) {
1037 speed_value = Some(u);
1038 }
1039 }
1040 }
1041 _ => (),
1042 }
1043 }
1044
1045 if !has_data {
1046 tracing::warn!("GPSInfo data not found");
1047 return None;
1048 }
1049
1050 let altitude = match (altitude_ref, altitude_value) {
1051 (Some(0), Some(v)) => Altitude::AboveSeaLevel(v),
1052 (Some(1), Some(v)) => Altitude::BelowSeaLevel(v),
1053 _ => Altitude::Unknown,
1054 };
1055
1056 let speed = match (speed_unit, speed_value) {
1057 (Some(unit), Some(value)) => Some(Speed { unit, value }),
1058 _ => None,
1059 };
1060
1061 Some(GPSInfo {
1062 latitude_ref: latitude_ref.unwrap_or(LatRef::North),
1063 latitude: latitude.unwrap_or_default(),
1064 longitude_ref: longitude_ref.unwrap_or(LonRef::East),
1065 longitude: longitude.unwrap_or_default(),
1066 altitude,
1067 speed,
1068 })
1069 }
1070
1071 fn clone_with_state(&self) -> IfdIter {
1072 let mut it = self.clone();
1073 it.index = self.index;
1074 it.pos = self.pos;
1075 it
1076 }
1077}
1078
1079#[derive(Debug)]
1080pub(crate) enum IfdEntry {
1081 IfdNew(IfdIter), Entry(EntryValue),
1083 Err(EntryError),
1084}
1085
1086impl IfdEntry {
1087 pub fn as_u8(&self) -> Option<u8> {
1088 if let IfdEntry::Entry(EntryValue::U8(v)) = self {
1089 Some(*v)
1090 } else {
1091 None
1092 }
1093 }
1094
1095 pub fn as_char(&self) -> Option<char> {
1096 if let IfdEntry::Entry(EntryValue::Text(s)) = self {
1097 s.chars().next()
1098 } else {
1099 None
1100 }
1101 }
1102
1103 fn as_irational(&self) -> Option<&IRational> {
1104 if let IfdEntry::Entry(EntryValue::IRational(v)) = self {
1105 Some(v)
1106 } else {
1107 None
1108 }
1109 }
1110
1111 fn as_irational_slice(&self) -> Option<&Vec<IRational>> {
1112 if let IfdEntry::Entry(EntryValue::IRationalArray(v)) = self {
1113 Some(v)
1114 } else {
1115 None
1116 }
1117 }
1118
1119 fn as_urational(&self) -> Option<&URational> {
1120 if let IfdEntry::Entry(EntryValue::URational(v)) = self {
1121 Some(v)
1122 } else {
1123 None
1124 }
1125 }
1126
1127 fn as_urational_slice(&self) -> Option<&Vec<URational>> {
1128 if let IfdEntry::Entry(EntryValue::URationalArray(v)) = self {
1129 Some(v)
1130 } else {
1131 None
1132 }
1133 }
1134
1135 fn as_str(&self) -> Option<&str> {
1136 if let IfdEntry::Entry(e) = self {
1137 e.as_str()
1138 } else {
1139 None
1140 }
1141 }
1142}
1143
1144pub(crate) const SUBIFD_TAGS: &[u16] = &[
1147 ExifTag::ExifOffset.code(),
1148 ExifTag::GPSInfo.code(),
1149 ExifTag::InteropOffset.code(),
1150];
1151
1152impl Iterator for IfdIter {
1153 type Item = (Option<TagOrCode>, IfdEntry);
1154
1155 #[tracing::instrument(skip(self))]
1156 fn next(&mut self) -> Option<Self::Item> {
1157 tracing::debug!(
1158 ifd = self.ifd_idx,
1159 index = self.index,
1160 entry_num = self.entry_num,
1161 offset = format!("{:08x}", self.offset),
1162 pos = format!("{:08x}", self.pos),
1163 "next IFD entry"
1164 );
1165 if self.input.len() < self.pos + IFD_ENTRY_SIZE {
1166 return None;
1167 }
1168
1169 let endian = self.header.endian;
1170 if self.index > self.entry_num {
1171 return None;
1172 }
1173 if self.index == self.entry_num {
1174 tracing::debug!(
1175 self.ifd_idx,
1176 self.index,
1177 pos = self.pos,
1178 "try to get next ifd"
1179 );
1180 self.index += 1;
1181
1182 let (_, offset) =
1184 complete::u32::<_, nom::error::Error<_>>(endian)(&self.input[self.pos..]).ok()?;
1185
1186 if offset == 0 {
1187 tracing::debug!(?self, "IFD parsing completed");
1189 return None;
1190 }
1191
1192 return self
1193 .new_ifd_iter(self.ifd_idx + 1, offset, None)
1194 .map(|x| (None, x));
1195 }
1196
1197 let entry_data = self
1198 .input
1199 .slice_checked(self.pos..self.pos + IFD_ENTRY_SIZE)?;
1200 self.index += 1;
1201 self.pos += IFD_ENTRY_SIZE;
1202
1203 let (tag, res) = self.parse_tag_entry(entry_data)?;
1204
1205 Some((Some(TagOrCode::from_code_in(self.kind, tag)), res)) }
1207}
1208
1209#[cfg(test)]
1210mod tests {
1211
1212 use crate::exif::extract_exif_with_mime;
1213 use crate::exif::input_into_iter;
1214 use crate::file::MediaMimeImage;
1215 use crate::slice::SubsliceRange;
1216 use crate::testkit::read_sample;
1217 use crate::Exif;
1218 use test_case::test_case;
1219
1220 #[test_case(
1221 "exif.jpg",
1222 "+08:00",
1223 "2023-07-09T20:36:33+08:00",
1224 MediaMimeImage::Jpeg
1225 )]
1226 #[test_case("exif-no-tz.jpg", "", "2023-07-09 20:36:33", MediaMimeImage::Jpeg)]
1227 #[test_case("broken.jpg", "-", "2014-09-21 15:51:22", MediaMimeImage::Jpeg)]
1228 #[test_case(
1229 "exif.heic",
1230 "+08:00",
1231 "2022-07-22T21:26:32+08:00",
1232 MediaMimeImage::Heic
1233 )]
1234 #[test_case(
1235 "exif.avif",
1236 "+08:00",
1237 "2022-07-22T21:26:32+08:00",
1238 MediaMimeImage::Avif
1239 )]
1240 #[test_case("tif.tif", "-", "-", MediaMimeImage::Tiff)]
1241 #[test_case(
1242 "fujifilm_x_t1_01.raf.meta",
1243 "-",
1244 "2014-01-30 12:49:13",
1245 MediaMimeImage::Raf
1246 )]
1247 fn exif_iter_tz(path: &str, tz: &str, time: &str, img_type: MediaMimeImage) {
1248 let buf = read_sample(path).unwrap();
1249 let (data, _) = extract_exif_with_mime(img_type, &buf, None).unwrap();
1250 let range = data.and_then(|x| buf.subslice_in_range(x)).unwrap();
1251 let iter = input_into_iter(bytes::Bytes::from(buf).slice(range), None).unwrap();
1252 let expect = if tz == "-" {
1253 None
1254 } else {
1255 Some(tz.to_string())
1256 };
1257 assert_eq!(iter.tz, expect);
1258 let exif: Exif = iter.into();
1259 let value = exif.get(crate::ExifTag::DateTimeOriginal);
1260 if time == "-" {
1261 assert!(value.is_none());
1262 } else {
1263 let value = value.unwrap();
1264 assert_eq!(value.to_string(), time);
1265 }
1266 }
1267
1268 #[test]
1269 fn ifd_index_constants() {
1270 use crate::IfdIndex;
1271 assert_eq!(IfdIndex::MAIN.as_usize(), 0);
1272 assert_eq!(IfdIndex::THUMBNAIL.as_usize(), 1);
1273 }
1274
1275 #[test]
1276 fn ifd_index_roundtrip_via_new_and_as_usize() {
1277 use crate::IfdIndex;
1278 for raw in [0, 1, 2, 3, 7, 99] {
1279 assert_eq!(IfdIndex::new(raw).as_usize(), raw);
1280 }
1281 }
1282
1283 #[test]
1284 fn ifd_index_equality_and_hash() {
1285 use crate::IfdIndex;
1286 use std::collections::HashSet;
1287 let mut set: HashSet<IfdIndex> = HashSet::new();
1288 set.insert(IfdIndex::MAIN);
1289 set.insert(IfdIndex::new(0)); set.insert(IfdIndex::THUMBNAIL);
1291 assert_eq!(set.len(), 2);
1292 }
1293
1294 #[test]
1295 fn ifd_index_display_format() {
1296 use crate::IfdIndex;
1297 assert_eq!(format!("{}", IfdIndex::MAIN), "ifd0");
1298 assert_eq!(format!("{}", IfdIndex::new(7)), "ifd7");
1299 }
1300
1301 #[test]
1302 fn tag_or_code_for_known_tag_resolves_to_tag_variant() {
1303 use crate::{ExifTag, TagOrCode};
1304 let t: TagOrCode = ExifTag::Make.code().into();
1305 assert_eq!(t, TagOrCode::Tag(ExifTag::Make));
1306 assert_eq!(t.code(), ExifTag::Make.code());
1307 }
1308
1309 #[test]
1310 fn tag_or_code_for_unknown_tag_resolves_to_unknown_variant() {
1311 use crate::TagOrCode;
1312 let t: TagOrCode = 0xffff_u16.into();
1313 assert_eq!(t, TagOrCode::Unknown(0xffff));
1314 assert_eq!(t.code(), 0xffff);
1315 }
1316
1317 #[test]
1318 fn exif_entry_pub_fields_construct_and_destructure() {
1319 use crate::{EntryValue, ExifEntry, ExifTag, IfdIndex, TagOrCode};
1320 let val = EntryValue::Text("vivo X90 Pro+".into());
1321 let e = ExifEntry {
1322 ifd: IfdIndex::MAIN,
1323 tag: TagOrCode::Tag(ExifTag::Model),
1324 value: &val,
1325 };
1326 let ExifEntry { ifd, tag, value } = e;
1328 assert_eq!(ifd, IfdIndex::MAIN);
1329 assert_eq!(tag.code(), ExifTag::Model.code());
1330 assert!(matches!(value, EntryValue::Text(_)));
1331 let _e2 = e;
1333 let _e3 = e;
1334 }
1335
1336 #[test]
1337 fn exif_iter_entry_value_xor_error_invariant() {
1338 use crate::{MediaParser, MediaSource};
1339 let mut parser = MediaParser::new();
1340 let ms = MediaSource::open("testdata/exif.jpg").unwrap();
1341 for entry in parser.parse_exif(ms).unwrap() {
1342 let has_v = entry.value().is_some();
1344 let has_e = entry.error().is_some();
1345 assert!(has_v ^ has_e, "entry must be value xor error");
1346 match entry.result() {
1348 Ok(v) => assert_eq!(Some(v), entry.value()),
1349 Err(e) => assert_eq!(Some(e), entry.error()),
1350 }
1351 }
1352 }
1353
1354 #[test]
1355 fn exif_iter_entry_into_result_consumes_self() {
1356 use crate::{MediaParser, MediaSource};
1357 let mut parser = MediaParser::new();
1358 let ms = MediaSource::open("testdata/exif.jpg").unwrap();
1359 let mut count_ok = 0usize;
1360 for entry in parser.parse_exif(ms).unwrap() {
1361 if entry.into_result().is_ok() {
1365 count_ok += 1;
1366 }
1367 }
1368 assert!(count_ok > 0);
1369 }
1370
1371 #[test]
1372 fn exif_iter_entry_tag_returns_tag_or_code() {
1373 use crate::{ExifTag, MediaParser, MediaSource, TagOrCode};
1374 let mut parser = MediaParser::new();
1375 let ms = MediaSource::open("testdata/exif.jpg").unwrap();
1376 let make_present = parser
1377 .parse_exif(ms)
1378 .unwrap()
1379 .any(|e| matches!(e.tag(), TagOrCode::Tag(ExifTag::Make)));
1380 assert!(make_present);
1381 }
1382
1383 #[test]
1384 fn exif_iter_rewind_resets_iteration_state() {
1385 use crate::{MediaParser, MediaSource};
1386 let mut parser = MediaParser::new();
1387 let ms = MediaSource::open("testdata/exif.jpg").unwrap();
1388 let mut iter = parser.parse_exif(ms).unwrap();
1389 let first_count = iter.by_ref().count();
1390 assert!(first_count > 0);
1391 assert_eq!(iter.by_ref().count(), 0);
1393 iter.rewind();
1394 let after_rewind = iter.count();
1395 assert_eq!(first_count, after_rewind);
1396 }
1397
1398 #[test]
1399 fn exif_iter_clone_rewound_yields_independent_full_iter() {
1400 use crate::{MediaParser, MediaSource};
1401 let mut parser = MediaParser::new();
1402 let ms = MediaSource::open("testdata/exif.jpg").unwrap();
1403 let mut iter = parser.parse_exif(ms).unwrap();
1404 let _consumed = iter.by_ref().take(2).count();
1405 let cloned = iter.clone_rewound();
1406 let cloned_total = cloned.count();
1408 let remaining = iter.count();
1409 assert!(cloned_total > remaining);
1410 }
1411
1412 #[test]
1413 fn exif_iter_parse_gps_returns_option_no_iteration_advance() {
1414 use crate::{MediaParser, MediaSource};
1415 let mut parser = MediaParser::new();
1416 let ms = MediaSource::open("testdata/exif.jpg").unwrap();
1417 let iter = parser.parse_exif(ms).unwrap();
1418 let gps = iter.parse_gps().unwrap();
1419 assert!(gps.is_some());
1420 let count = iter.count();
1422 assert!(count > 0);
1423 }
1424
1425 #[test]
1432 fn gps_subifd_first_entry_is_gpsversion_id_issue_50() {
1433 use crate::exif::exif_iter::input_into_iter;
1434 #[rustfmt::skip]
1435 let tiff: &[u8] = &[
1436 b'I', b'I', 0x2a, 0x00,
1438 0x08, 0x00, 0x00, 0x00,
1439
1440 0x01, 0x00,
1442 0x25, 0x88, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00,
1443 0x1a, 0x00, 0x00, 0x00,
1444 0x00, 0x00, 0x00, 0x00, 0x05, 0x00,
1448 0x00, 0x00, 0x01, 0x00, 0x04, 0x00, 0x00, 0x00,
1450 0x02, 0x03, 0x00, 0x00,
1451 0x01, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00,
1453 b'N', 0x00, 0x00, 0x00,
1454 0x02, 0x00, 0x05, 0x00, 0x03, 0x00, 0x00, 0x00,
1456 0x5c, 0x00, 0x00, 0x00,
1457 0x03, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00,
1459 b'E', 0x00, 0x00, 0x00,
1460 0x04, 0x00, 0x05, 0x00, 0x03, 0x00, 0x00, 0x00,
1462 0x74, 0x00, 0x00, 0x00,
1463 0x00, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
1467 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
1468 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
1469
1470 0x78, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
1472 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
1473 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
1474 ];
1475
1476 let iter = input_into_iter(tiff.to_vec(), None).unwrap();
1477
1478 let gps = iter
1480 .parse_gps()
1481 .expect("parse_gps must succeed")
1482 .expect("GPS sub-IFD with GPSVersionID first must yield GPSInfo");
1483 assert_eq!(gps.latitude_decimal(), Some(36.0));
1484 assert_eq!(gps.longitude_decimal(), Some(120.0));
1485
1486 let tags: Vec<u16> = iter.map(|e| e.tag().code()).collect();
1489 assert!(
1490 tags.contains(&crate::ExifTag::GPSVersionID.code()),
1491 "GPSVersionID (tag 0) should be visible to iterators; got {tags:?}"
1492 );
1493 }
1494
1495 fn kinds_by_code(path: &str) -> std::collections::HashMap<u16, crate::IfdKind> {
1497 use crate::{ExifIter, MediaParser, MediaSource};
1498 let mut parser = MediaParser::new();
1499 let ms = MediaSource::open(path).unwrap();
1500 let iter: ExifIter = parser.parse_exif(ms).unwrap();
1501 iter.map(|e| (e.tag().code(), e.ifd_kind())).collect()
1502 }
1503
1504 #[test]
1505 fn entries_report_the_ifd_namespace_they_came_from() {
1506 use crate::{ExifTag, IfdKind};
1507 let kinds = kinds_by_code("./testdata/exif.jpg");
1508
1509 assert_eq!(kinds[&ExifTag::Make.code()], IfdKind::Tiff);
1510 assert_eq!(kinds[&ExifTag::DateTimeOriginal.code()], IfdKind::Exif);
1511 assert_eq!(kinds[&ExifTag::GPSLatitude.code()], IfdKind::Gps);
1512 }
1513
1514 #[test]
1515 fn subifd_pointer_entries_belong_to_the_parent_namespace() {
1516 use crate::{ExifTag, IfdKind};
1517 let kinds = kinds_by_code("./testdata/exif.jpg");
1518
1519 assert_eq!(kinds[&ExifTag::ExifOffset.code()], IfdKind::Tiff);
1521 assert_eq!(kinds[&ExifTag::GPSInfo.code()], IfdKind::Tiff);
1522 }
1523
1524 #[test]
1525 fn thumbnail_ifd_entries_are_tiff_namespace() {
1526 use crate::{ExifIter, IfdIndex, IfdKind, MediaParser, MediaSource};
1527 let mut parser = MediaParser::new();
1528 let ms = MediaSource::open("./testdata/exif.jpg").unwrap();
1529 let iter: ExifIter = parser.parse_exif(ms).unwrap();
1530 let thumb: Vec<_> = iter.filter(|e| e.ifd() == IfdIndex::THUMBNAIL).collect();
1531
1532 assert!(!thumb.is_empty(), "exif.jpg should have a thumbnail IFD");
1533 for e in thumb {
1534 assert_eq!(e.ifd_kind(), IfdKind::Tiff, "entry {:?}", e.tag());
1535 }
1536 }
1537}