nom_exif/exif/
exif_iter.rs

1use std::{collections::HashSet, fmt::Debug, ops::Range, sync::Arc};
2
3use nom::{number::complete, sequence::tuple};
4use thiserror::Error;
5
6use crate::{
7    partial_vec::{AssociatedInput, PartialVec},
8    slice::SliceChecked,
9    values::{DataFormat, EntryData, IRational, ParseEntryError, URational},
10    EntryValue, ExifTag,
11};
12
13use super::{exif_exif::IFD_ENTRY_SIZE, tags::ExifTagCode, GPSInfo, TiffHeader};
14
15/// Represents an additional TIFF data block to be processed after the primary block.
16/// Used for CR3 files with multiple CMT boxes (CMT1, CMT2, CMT3).
17#[derive(Clone)]
18pub(crate) struct TiffDataBlock {
19    /// Block identifier (e.g., "CMT1", "CMT2", "CMT3")
20    #[allow(dead_code)]
21    pub block_id: String,
22    /// Data range within the shared buffer
23    pub data_range: Range<usize>,
24    /// TIFF header information (optional, if known)
25    pub header: Option<TiffHeader>,
26}
27
28/// Parses header from input data, and returns an [`ExifIter`].
29///
30/// All entries are lazy-parsed. That is, only when you iterate over
31/// [`ExifIter`] will the IFD entries be parsed one by one.
32///
33/// The one exception is the time zone entries. The method will try to find
34/// and parse the time zone data first, so we can correctly parse all time
35/// information in subsequent iterates.
36#[tracing::instrument]
37pub(crate) fn input_into_iter(
38    input: impl Into<PartialVec> + Debug,
39    state: Option<TiffHeader>,
40) -> crate::Result<ExifIter> {
41    let input: PartialVec = input.into();
42    let header = match state {
43        // header has been parsed, and header has been skipped, input data
44        // is the IFD data
45        Some(header) => header,
46        _ => {
47            // header has not been parsed, input data includes IFD header
48            let (_, header) = TiffHeader::parse(&input[..])?;
49
50            tracing::debug!(
51                ?header,
52                data_len = format!("{:#x}", input.len()),
53                "TIFF header parsed"
54            );
55            header
56        }
57    };
58
59    let start = header.ifd0_offset as usize;
60    if start > input.len() {
61        return Err(crate::Error::ParseFailed("no enough bytes".into()));
62    }
63    tracing::debug!(?header, offset = start);
64
65    let mut ifd0 = IfdIter::try_new(0, input.to_owned(), header.to_owned(), start, None)?;
66
67    let tz = ifd0.find_tz_offset();
68    ifd0.tz = tz.clone();
69    let iter: ExifIter = ExifIter::new(input, header, tz, ifd0);
70
71    tracing::debug!(?iter, "got IFD0");
72
73    Ok(iter)
74}
75
76/// An iterator version of [`Exif`](crate::Exif). Use [`ParsedExifEntry`] as
77/// iterator items.
78///
79/// Clone an `ExifIter` is very cheap, the underlying data is shared
80/// through `Arc`.
81///
82/// The new cloned `ExifIter`'s iteration index will be reset to the first one.
83///
84/// If you want to convert an `ExifIter` `into` an [`Exif`](crate::Exif), you probably want
85/// to clone the `ExifIter` and use the new cloned one to do the converting.
86/// Since the original's iteration index may have been modified by
87/// `Iterator::next()` calls.
88pub struct ExifIter {
89    // Use Arc to make sure we won't clone the owned data.
90    input: Arc<PartialVec>,
91    tiff_header: TiffHeader,
92    tz: Option<String>,
93    ifd0: IfdIter,
94
95    // Iterating status
96    ifds: Vec<IfdIter>,
97    visited_offsets: HashSet<usize>,
98
99    // Multi-block support for CR3 files with multiple CMT boxes
100    /// Additional TIFF data blocks to process after the primary block
101    additional_blocks: Vec<TiffDataBlock>,
102    /// Current block index: 0 = primary block, 1+ = additional blocks
103    current_block_index: usize,
104    /// Tags encountered so far for duplicate filtering (ifd_index, tag_code)
105    encountered_tags: HashSet<(usize, u16)>,
106}
107
108impl Debug for ExifIter {
109    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
110        f.debug_struct("ExifIter")
111            .field("data len", &self.input.len())
112            .field("tiff_header", &self.tiff_header)
113            .field("ifd0", &self.ifd0)
114            .field("state", &self.ifds.first().map(|x| (x.index, x.pos)))
115            .field("ifds num", &self.ifds.len())
116            .field("additional_blocks", &self.additional_blocks.len())
117            .field("current_block_index", &self.current_block_index)
118            .finish_non_exhaustive()
119    }
120}
121
122impl Clone for ExifIter {
123    fn clone(&self) -> Self {
124        self.clone_and_rewind()
125    }
126}
127
128impl ExifIter {
129    pub(crate) fn new(
130        input: impl Into<PartialVec>,
131        tiff_header: TiffHeader,
132        tz: Option<String>,
133        ifd0: IfdIter,
134    ) -> ExifIter {
135        let ifds = vec![ifd0.clone()];
136        ExifIter {
137            input: Arc::new(input.into()),
138            tiff_header,
139            tz,
140            ifd0,
141            ifds,
142            visited_offsets: HashSet::new(),
143            additional_blocks: Vec::new(),
144            current_block_index: 0,
145            encountered_tags: HashSet::new(),
146        }
147    }
148
149    /// Clone and rewind the iterator's index.
150    ///
151    /// Clone an `ExifIter` is very cheap, the underlying data is shared
152    /// through Arc.
153    pub fn clone_and_rewind(&self) -> Self {
154        let ifd0 = self.ifd0.clone_and_rewind();
155        let ifds = vec![ifd0.clone()];
156        Self {
157            input: self.input.clone(),
158            tiff_header: self.tiff_header.clone(),
159            tz: self.tz.clone(),
160            ifd0,
161            ifds,
162            visited_offsets: HashSet::new(),
163            additional_blocks: self.additional_blocks.clone(),
164            current_block_index: 0,
165            encountered_tags: HashSet::new(),
166        }
167    }
168
169    /// Try to find and parse gps information.
170    ///
171    /// Calling this method won't affect the iterator's state.
172    ///
173    /// Returns:
174    ///
175    /// - An `Ok<Some<GPSInfo>>` if gps info is found and parsed successfully.
176    /// - An `Ok<None>` if gps info is not found.
177    /// - An `Err` if gps info is found but parsing failed.
178    #[tracing::instrument(skip_all)]
179    pub fn parse_gps_info(&self) -> crate::Result<Option<GPSInfo>> {
180        let mut iter = self.clone_and_rewind();
181        let Some(gps) = iter.find(|x| {
182            tracing::info!(?x, "find");
183            x.tag.tag().is_some_and(|t| t == ExifTag::GPSInfo)
184        }) else {
185            tracing::warn!(ifd0 = ?iter.ifds.first(), "GPSInfo not found");
186            return Ok(None);
187        };
188
189        let offset = match gps.get_result() {
190            Ok(v) => {
191                if let Some(offset) = v.as_u32() {
192                    offset
193                } else {
194                    return Err(EntryError(ParseEntryError::InvalidData(
195                        "invalid gps offset".into(),
196                    ))
197                    .into());
198                }
199            }
200            Err(e) => return Err(e.clone().into()),
201        };
202        if offset as usize >= iter.input.len() {
203            return Err(crate::Error::ParseFailed(
204                "GPSInfo offset is out of range".into(),
205            ));
206        }
207
208        let mut gps_subifd = match IfdIter::try_new(
209            gps.ifd,
210            iter.input.partial(&iter.input[..]),
211            iter.tiff_header,
212            offset as usize,
213            iter.tz.clone(),
214        ) {
215            Ok(ifd0) => ifd0.tag_code(ExifTag::GPSInfo.code()),
216            Err(e) => return Err(e),
217        };
218        Ok(gps_subifd.parse_gps_info())
219    }
220
221    pub(crate) fn to_owned(&self) -> ExifIter {
222        let mut iter = ExifIter::new(
223            self.input.to_vec(),
224            self.tiff_header.clone(),
225            self.tz.clone(),
226            self.ifd0.clone_and_rewind(),
227        );
228        iter.additional_blocks = self.additional_blocks.clone();
229        iter
230    }
231
232    /// Add an additional TIFF data block to be iterated after the current block.
233    /// Used internally for CR3 files with multiple CMT boxes.
234    ///
235    /// # Arguments
236    /// * `block_id` - Identifier for this TIFF block (e.g., "CMT2", "CMT3")
237    /// * `data_range` - Range within the shared input buffer containing the TIFF data
238    /// * `header` - Optional TIFF header if already parsed
239    pub(crate) fn add_tiff_block(
240        &mut self,
241        block_id: String,
242        data_range: Range<usize>,
243        header: Option<TiffHeader>,
244    ) {
245        self.additional_blocks.push(TiffDataBlock {
246            block_id,
247            data_range,
248            header,
249        });
250    }
251}
252
253#[derive(Debug, Clone, Error)]
254#[error("ifd entry error: {0}")]
255pub struct EntryError(ParseEntryError);
256
257impl From<EntryError> for crate::Error {
258    fn from(value: EntryError) -> Self {
259        Self::ParseFailed(value.into())
260    }
261}
262
263/// Represents a parsed IFD entry. Used as iterator items in [`ExifIter`].
264#[derive(Clone)]
265pub struct ParsedExifEntry {
266    // 0: ifd0, 1: ifd1
267    ifd: usize,
268    tag: ExifTagCode,
269    res: Option<Result<EntryValue, EntryError>>,
270}
271
272impl ParsedExifEntry {
273    /// Get the IFD index value where this entry is located.
274    /// - 0: ifd0 (main image)
275    /// - 1: ifd1 (thumbnail)
276    pub fn ifd_index(&self) -> usize {
277        self.ifd
278    }
279
280    /// Get recognized Exif tag of this entry, maybe return `None` if the tag
281    /// is unrecognized.
282    ///
283    /// If you have any custom defined tag which does not exist in [`ExifTag`],
284    /// then you should use [`Self::tag_code`] to get the raw tag code.
285    ///
286    /// **Note**: You can always get the raw tag code via [`Self::tag_code`],
287    /// no matter if it's recognized.
288    pub fn tag(&self) -> Option<ExifTag> {
289        match self.tag {
290            ExifTagCode::Tag(t) => Some(t),
291            ExifTagCode::Code(_) => None,
292        }
293    }
294
295    /// Get the raw tag code of this entry.
296    ///
297    /// In case you have some custom defined tags which doesn't exist in
298    /// [`ExifTag`], you can use this method to get the raw tag code of this
299    /// entry.
300    pub fn tag_code(&self) -> u16 {
301        self.tag.code()
302    }
303
304    /// Returns true if there is an `EntryValue` in self.
305    ///
306    /// Both of the following situations may cause this method to return false:
307    /// - An error occurred while parsing this entry
308    /// - The value has been taken by calling [`Self::take_value`] or
309    ///   [`Self::take_result`] methods.
310    pub fn has_value(&self) -> bool {
311        self.res.as_ref().map(|e| e.is_ok()).is_some_and(|b| b)
312    }
313
314    /// Get the parsed entry value of this entry.
315    pub fn get_value(&self) -> Option<&EntryValue> {
316        match self.res.as_ref() {
317            Some(Ok(v)) => Some(v),
318            Some(Err(_)) | None => None,
319        }
320    }
321
322    /// Takes out the parsed entry value of this entry.
323    ///
324    /// If you need to convert this `ExifIter` to an [`crate::Exif`], please
325    /// don't call this method! Otherwise the converted `Exif` is incomplete.
326    ///
327    /// **Note**: This method can only be called once! Once it has been called,
328    /// calling it again always returns `None`. You may want to check it by
329    /// calling [`Self::has_value`] before calling this method.
330    pub fn take_value(&mut self) -> Option<EntryValue> {
331        match self.res.take() {
332            Some(v) => v.ok(),
333            None => None,
334        }
335    }
336
337    /// Get the parsed result of this entry.
338    ///
339    /// Returns:
340    ///
341    /// - If any error occurred while parsing this entry, an
342    ///   `Err(&EntryError)` is returned.
343    ///
344    /// - Otherwise, an `Ok(&EntryValue)` is returned.
345    #[allow(rustdoc::private_intra_doc_links)]
346    pub fn get_result(&self) -> Result<&EntryValue, &EntryError> {
347        match self.res {
348            Some(ref v) => v.as_ref(),
349            None => panic!("take result of entry twice"),
350        }
351    }
352
353    /// Takes out the parsed result of this entry.
354    ///
355    /// If you need to convert this `ExifIter` to an [`crate::Exif`], please
356    /// don't call this method! Otherwise the converted `Exif` is incomplete.
357    ///
358    /// Returns:
359    ///
360    /// - If any error occurred while parsing this entry, an
361    ///   `Err(EntryError)` is returned.
362    ///
363    /// - Otherwise, an `Ok(EntryValue)` is returned.
364    ///
365    /// **Note**: This method can ONLY be called once! If you call it twice, it
366    /// will **panic** directly!
367    pub fn take_result(&mut self) -> Result<EntryValue, EntryError> {
368        match self.res.take() {
369            Some(v) => v,
370            None => panic!("take result of entry twice"),
371        }
372    }
373
374    fn make_ok(ifd: usize, tag: ExifTagCode, v: EntryValue) -> Self {
375        Self {
376            ifd,
377            tag,
378            res: Some(Ok(v)),
379        }
380    }
381
382    // fn make_err(ifd: usize, tag: ExifTagCode, e: ParseEntryError) -> Self {
383    //     Self {
384    //         ifd,
385    //         tag,
386    //         res: Some(Err(EntryError(e))),
387    //     }
388    // }
389}
390
391impl Debug for ParsedExifEntry {
392    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
393        let value = match self.get_result() {
394            Ok(v) => format!("{v}"),
395            Err(e) => format!("{e:?}"),
396        };
397        f.debug_struct("IfdEntryResult")
398            .field("ifd", &format!("ifd{}", self.ifd))
399            .field("tag", &self.tag)
400            .field("value", &value)
401            .finish()
402    }
403}
404
405const MAX_IFD_DEPTH: usize = 8;
406
407impl ExifIter {
408    /// Attempt to load and start iterating the next additional TIFF block.
409    /// Returns true if a new block was successfully loaded, false if no more blocks.
410    fn load_next_block(&mut self) -> bool {
411        // Move to the next additional block
412        let block_index = self.current_block_index;
413        if block_index >= self.additional_blocks.len() {
414            return false;
415        }
416
417        let block = &self.additional_blocks[block_index];
418        tracing::debug!(
419            block_id = block.block_id,
420            block_index,
421            "Loading additional TIFF block"
422        );
423
424        // Get the data for this block from the shared input
425        let data_range = block.data_range.clone();
426        let header = block.header.clone();
427
428        // Create a PartialVec for the block data
429        let block_data = PartialVec::new(self.input.data.clone(), data_range);
430
431        // Try to create an ExifIter for this block
432        match input_into_iter(block_data, header) {
433            Ok(iter) => {
434                // Update our state with the new block's data
435                self.ifd0 = iter.ifd0;
436                self.ifds = vec![self.ifd0.clone()];
437                self.visited_offsets.clear();
438                self.current_block_index += 1;
439
440                tracing::debug!(block_index, "Successfully loaded additional TIFF block");
441                true
442            }
443            Err(e) => {
444                tracing::warn!(
445                    block_index,
446                    error = %e,
447                    "Failed to load additional TIFF block, skipping"
448                );
449                // Move to next block and try again
450                self.current_block_index += 1;
451                self.load_next_block()
452            }
453        }
454    }
455
456    /// Check if a tag should be included based on duplicate filtering.
457    /// Returns true if the tag should be included, false if it's a duplicate.
458    fn should_include_tag(&mut self, ifd_index: usize, tag_code: u16) -> bool {
459        let tag_key = (ifd_index, tag_code);
460        if self.encountered_tags.contains(&tag_key) {
461            tracing::debug!(ifd_index, tag_code, "Skipping duplicate tag");
462            false
463        } else {
464            self.encountered_tags.insert(tag_key);
465            true
466        }
467    }
468}
469
470impl Iterator for ExifIter {
471    type Item = ParsedExifEntry;
472
473    #[tracing::instrument(skip_all)]
474    fn next(&mut self) -> Option<Self::Item> {
475        loop {
476            if self.ifds.is_empty() {
477                // Current block exhausted, try to load next additional block
478                if !self.load_next_block() {
479                    tracing::debug!(?self, "all IFDs and blocks have been parsed");
480                    return None;
481                }
482                // Continue with the newly loaded block
483                continue;
484            }
485
486            if self.ifds.len() > MAX_IFD_DEPTH {
487                self.ifds.clear();
488                tracing::error!(
489                    ifds_depth = self.ifds.len(),
490                    "ifd depth is too deep, just go back to ifd0"
491                );
492                self.ifds.push(self.ifd0.clone_with_state());
493            }
494
495            let mut ifd = self.ifds.pop()?;
496            let cur_ifd_idx = ifd.ifd_idx;
497            match ifd.next() {
498                Some((tag_code, entry)) => {
499                    tracing::debug!(ifd = ifd.ifd_idx, ?tag_code, "next tag entry");
500
501                    match entry {
502                        IfdEntry::IfdNew(new_ifd) => {
503                            if new_ifd.offset > 0 {
504                                if self.visited_offsets.contains(&new_ifd.offset) {
505                                    // Ignore repeated ifd parsing to avoid dead looping
506                                    continue;
507                                }
508                                self.visited_offsets.insert(new_ifd.offset);
509                            }
510
511                            let is_subifd = if new_ifd.ifd_idx == ifd.ifd_idx {
512                                // Push the current ifd before enter sub-ifd.
513                                self.ifds.push(ifd);
514                                tracing::debug!(?tag_code, ?new_ifd, "got new SUB-IFD");
515                                true
516                            } else {
517                                // Otherwise this is a next ifd. It means that the
518                                // current ifd has been parsed, so we don't need to
519                                // push it.
520                                tracing::debug!("IFD{} parsing completed", cur_ifd_idx);
521                                tracing::debug!(?new_ifd, "got new IFD");
522                                false
523                            };
524
525                            let (ifd_idx, offset) = (new_ifd.ifd_idx, new_ifd.offset);
526                            self.ifds.push(new_ifd);
527
528                            if is_subifd {
529                                // Check for duplicates before returning sub-ifd entry
530                                let tc = tag_code.unwrap();
531                                if !self.should_include_tag(ifd_idx, tc.code()) {
532                                    continue;
533                                }
534                                // Return sub-ifd as an entry
535                                return Some(ParsedExifEntry::make_ok(
536                                    ifd_idx,
537                                    tc,
538                                    EntryValue::U32(offset as u32),
539                                ));
540                            }
541                        }
542                        IfdEntry::Entry(v) => {
543                            let tc = tag_code.unwrap();
544                            // Check for duplicates before returning entry
545                            if !self.should_include_tag(ifd.ifd_idx, tc.code()) {
546                                self.ifds.push(ifd);
547                                continue;
548                            }
549                            let res = Some(ParsedExifEntry::make_ok(ifd.ifd_idx, tc, v));
550                            self.ifds.push(ifd);
551                            return res;
552                        }
553                        IfdEntry::Err(e) => {
554                            tracing::warn!(?tag_code, ?e, "parse ifd entry error");
555                            self.ifds.push(ifd);
556                            continue;
557                        }
558                    }
559                }
560                None => continue,
561            }
562        }
563    }
564}
565
566#[derive(Clone)]
567pub(crate) struct IfdIter {
568    ifd_idx: usize,
569    tag_code: Option<ExifTagCode>,
570
571    // starts from TIFF header
572    input: AssociatedInput,
573
574    // ifd data offset
575    offset: usize,
576
577    header: TiffHeader,
578    entry_num: u16,
579
580    pub tz: Option<String>,
581
582    // Iterating status
583    index: u16,
584    pos: usize,
585}
586
587impl Debug for IfdIter {
588    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
589        f.debug_struct("IfdIter")
590            .field("ifd_idx", &self.ifd_idx)
591            .field("tag", &self.tag_code)
592            .field("data len", &self.input.len())
593            .field("tz", &self.tz)
594            .field("header", &self.header)
595            .field("entry_num", &self.entry_num)
596            .field("index", &self.index)
597            .field("pos", &self.pos)
598            .finish()
599    }
600}
601
602impl IfdIter {
603    pub fn rewind(&mut self) {
604        self.index = 0;
605        // Skip the first two bytes, which is the entry num
606        self.pos = self.offset + 2;
607    }
608
609    pub fn clone_and_rewind(&self) -> Self {
610        let mut it = self.clone();
611        it.rewind();
612        it
613    }
614
615    pub fn tag_code_maybe(mut self, code: Option<u16>) -> Self {
616        self.tag_code = code.map(|x| x.into());
617        self
618    }
619
620    pub fn tag_code(mut self, code: u16) -> Self {
621        self.tag_code = Some(code.into());
622        self
623    }
624
625    #[allow(unused)]
626    pub fn tag(mut self, tag: ExifTagCode) -> Self {
627        self.tag_code = Some(tag);
628        self
629    }
630
631    #[tracing::instrument(skip(input))]
632    pub fn try_new(
633        ifd_idx: usize,
634        input: AssociatedInput,
635        header: TiffHeader,
636        offset: usize,
637        tz: Option<String>,
638    ) -> crate::Result<Self> {
639        if input.len() < 2 {
640            return Err(crate::Error::ParseFailed(
641                "ifd data is too small to decode entry num".into(),
642            ));
643        }
644        // should use the complete header data to parse ifd entry num
645        assert!(offset <= input.len());
646        let ifd_data = input.partial(&input[offset..]);
647        let (_, entry_num) = TiffHeader::parse_ifd_entry_num(&ifd_data, header.endian)?;
648
649        Ok(Self {
650            ifd_idx,
651            tag_code: None,
652            input,
653            offset,
654            header,
655            entry_num,
656            tz,
657            // Skip the first two bytes, which is the entry num
658            pos: offset + 2,
659            index: 0,
660        })
661    }
662
663    fn parse_tag_entry(&self, entry_data: &[u8]) -> Option<(u16, IfdEntry)> {
664        let endian = self.header.endian;
665        let (_, (tag, data_format, components_num, value_or_offset)) = tuple((
666            complete::u16::<_, nom::error::Error<_>>(endian),
667            complete::u16(endian),
668            complete::u32(endian),
669            complete::u32(endian),
670        ))(entry_data)
671        .ok()?;
672
673        if tag == 0 {
674            return None;
675        }
676
677        let df: DataFormat = match data_format.try_into() {
678            Ok(df) => df,
679            Err(e) => {
680                let t: ExifTagCode = tag.into();
681                tracing::warn!(tag = ?t, ?e, "invalid entry data format");
682                return Some((tag, IfdEntry::Err(e)));
683            }
684        };
685        let (tag, res) = self.parse_entry(tag, df, components_num, entry_data, value_or_offset);
686        Some((tag, res))
687    }
688
689    fn get_data_pos(&self, value_or_offset: u32) -> usize {
690        // value_or_offset.saturating_sub(self.offset)
691        value_or_offset as usize
692    }
693
694    fn parse_entry(
695        &self,
696        tag: u16,
697        data_format: DataFormat,
698        components_num: u32,
699        entry_data: &[u8],
700        value_or_offset: u32,
701    ) -> (u16, IfdEntry) {
702        // get component_size according to data format
703        let component_size = data_format.component_size();
704
705        // get entry data
706        let size = components_num as usize * component_size;
707        let data = if size <= 4 {
708            &entry_data[8..8 + size] // Safe-slice
709        } else {
710            let start = self.get_data_pos(value_or_offset);
711            let end = start + size;
712            let Some(data) = self.input.slice_checked(start..end) else {
713                tracing::warn!(
714                    "entry data overflow, tag: {:04x} start: {:08x} end: {:08x} ifd data len {:08x}",
715                    tag,
716                    start,
717                    end,
718                    self.input.len(),
719                );
720                return (tag, IfdEntry::Err(ParseEntryError::EntrySizeTooBig));
721            };
722
723            data
724        };
725
726        if SUBIFD_TAGS.contains(&tag) {
727            if let Some(value) = self.new_ifd_iter(self.ifd_idx, value_or_offset, Some(tag)) {
728                return (tag, value);
729            }
730        }
731
732        let entry = EntryData {
733            endian: self.header.endian,
734            tag,
735            data,
736            data_format,
737            components_num,
738        };
739        match EntryValue::parse(&entry, &self.tz) {
740            Ok(v) => (tag, IfdEntry::Entry(v)),
741            Err(e) => (tag, IfdEntry::Err(e)),
742        }
743    }
744
745    fn new_ifd_iter(
746        &self,
747        ifd_idx: usize,
748        value_or_offset: u32,
749        tag: Option<u16>,
750    ) -> Option<IfdEntry> {
751        let offset = self.get_data_pos(value_or_offset);
752        if offset < self.input.len() {
753            match IfdIter::try_new(
754                ifd_idx,
755                self.input.partial(&self.input[..]),
756                self.header.to_owned(),
757                offset,
758                self.tz.clone(),
759            ) {
760                Ok(iter) => return Some(IfdEntry::IfdNew(iter.tag_code_maybe(tag))),
761                Err(e) => {
762                    tracing::warn!(?tag, ?e, "Create next/sub IFD failed");
763                }
764            }
765            // return (
766            //     tag,
767            //     // IfdEntry::Ifd {
768            //     //     idx: self.ifd_idx,
769            //     //     offset: value_or_offset,
770            //     // },
771            //     IfdEntry::IfdNew(),
772            // );
773        }
774        None
775    }
776
777    pub fn find_exif_iter(&self) -> Option<IfdIter> {
778        let endian = self.header.endian;
779        // find ExifOffset
780        for i in 0..self.entry_num {
781            let pos = self.pos + i as usize * IFD_ENTRY_SIZE;
782            let (_, tag) =
783                complete::u16::<_, nom::error::Error<_>>(endian)(&self.input[pos..]).ok()?;
784            if tag == ExifTag::ExifOffset.code() {
785                let entry_data = self.input.slice_checked(pos..pos + IFD_ENTRY_SIZE)?;
786                let (_, entry) = self.parse_tag_entry(entry_data)?;
787                match entry {
788                    IfdEntry::IfdNew(iter) => return Some(iter),
789                    IfdEntry::Entry(_) | IfdEntry::Err(_) => return None,
790                }
791            }
792        }
793        None
794    }
795
796    pub fn find_tz_offset(&self) -> Option<String> {
797        let iter = self.find_exif_iter()?;
798        let mut offset = None;
799        for entry in iter {
800            let Some(tag) = entry.0 else {
801                continue;
802            };
803            if tag.code() == ExifTag::OffsetTimeOriginal.code()
804                || tag.code() == ExifTag::OffsetTimeDigitized.code()
805            {
806                return entry.1.as_str().map(|x| x.to_owned());
807            } else if tag.code() == ExifTag::OffsetTime.code() {
808                offset = entry.1.as_str().map(|x| x.to_owned());
809            }
810        }
811
812        offset
813    }
814
815    // Assume the current ifd is GPSInfo subifd.
816    pub fn parse_gps_info(&mut self) -> Option<GPSInfo> {
817        let mut gps = GPSInfo::default();
818        let mut has_data = false;
819        for (tag, entry) in self {
820            let Some(tag) = tag.and_then(|x| x.tag()) else {
821                continue;
822            };
823            has_data = true;
824            match tag {
825                ExifTag::GPSLatitudeRef => {
826                    if let Some(c) = entry.as_char() {
827                        gps.latitude_ref = c;
828                    }
829                }
830                ExifTag::GPSLongitudeRef => {
831                    if let Some(c) = entry.as_char() {
832                        gps.longitude_ref = c;
833                    }
834                }
835                ExifTag::GPSAltitudeRef => {
836                    if let Some(c) = entry.as_u8() {
837                        gps.altitude_ref = c;
838                    }
839                }
840                ExifTag::GPSLatitude => {
841                    if let Some(v) = entry.as_urational_array() {
842                        gps.latitude = v.try_into().ok()?;
843                    } else if let Some(v) = entry.as_irational_array() {
844                        gps.latitude = v.try_into().ok()?;
845                    }
846                }
847                ExifTag::GPSLongitude => {
848                    if let Some(v) = entry.as_urational_array() {
849                        gps.longitude = v.try_into().ok()?;
850                    } else if let Some(v) = entry.as_irational_array() {
851                        gps.longitude = v.try_into().ok()?;
852                    }
853                }
854                ExifTag::GPSAltitude => {
855                    if let Some(v) = entry.as_urational() {
856                        gps.altitude = *v;
857                    } else if let Some(v) = entry.as_irational() {
858                        gps.altitude = (*v).into();
859                    }
860                }
861                ExifTag::GPSSpeedRef => {
862                    if let Some(c) = entry.as_char() {
863                        gps.speed_ref = Some(c);
864                    }
865                }
866                ExifTag::GPSSpeed => {
867                    if let Some(v) = entry.as_urational() {
868                        gps.speed = Some(*v);
869                    } else if let Some(v) = entry.as_irational() {
870                        gps.speed = Some((*v).into());
871                    }
872                }
873                _ => (),
874            }
875        }
876
877        if has_data {
878            Some(gps)
879        } else {
880            tracing::warn!("GPSInfo data not found");
881            None
882        }
883    }
884
885    fn clone_with_state(&self) -> IfdIter {
886        let mut it = self.clone();
887        it.index = self.index;
888        it.pos = self.pos;
889        it
890    }
891}
892
893#[derive(Debug)]
894pub(crate) enum IfdEntry {
895    IfdNew(IfdIter), // ifd index
896    Entry(EntryValue),
897    Err(ParseEntryError),
898}
899
900impl IfdEntry {
901    pub fn as_u8(&self) -> Option<u8> {
902        if let IfdEntry::Entry(EntryValue::U8(v)) = self {
903            Some(*v)
904        } else {
905            None
906        }
907    }
908
909    pub fn as_char(&self) -> Option<char> {
910        if let IfdEntry::Entry(EntryValue::Text(s)) = self {
911            s.chars().next()
912        } else {
913            None
914        }
915    }
916
917    fn as_irational(&self) -> Option<&IRational> {
918        if let IfdEntry::Entry(EntryValue::IRational(v)) = self {
919            Some(v)
920        } else {
921            None
922        }
923    }
924
925    fn as_irational_array(&self) -> Option<&Vec<IRational>> {
926        if let IfdEntry::Entry(EntryValue::IRationalArray(v)) = self {
927            Some(v)
928        } else {
929            None
930        }
931    }
932
933    fn as_urational(&self) -> Option<&URational> {
934        if let IfdEntry::Entry(EntryValue::URational(v)) = self {
935            Some(v)
936        } else {
937            None
938        }
939    }
940
941    fn as_urational_array(&self) -> Option<&Vec<URational>> {
942        if let IfdEntry::Entry(EntryValue::URationalArray(v)) = self {
943            Some(v)
944        } else {
945            None
946        }
947    }
948
949    fn as_str(&self) -> Option<&str> {
950        if let IfdEntry::Entry(e) = self {
951            e.as_str()
952        } else {
953            None
954        }
955    }
956}
957
958pub(crate) const SUBIFD_TAGS: &[u16] = &[ExifTag::ExifOffset.code(), ExifTag::GPSInfo.code()];
959
960impl Iterator for IfdIter {
961    type Item = (Option<ExifTagCode>, IfdEntry);
962
963    #[tracing::instrument(skip(self))]
964    fn next(&mut self) -> Option<Self::Item> {
965        tracing::debug!(
966            ifd = self.ifd_idx,
967            index = self.index,
968            entry_num = self.entry_num,
969            offset = format!("{:08x}", self.offset),
970            pos = format!("{:08x}", self.pos),
971            "next IFD entry"
972        );
973        if self.input.len() < self.pos + IFD_ENTRY_SIZE {
974            return None;
975        }
976
977        let endian = self.header.endian;
978        if self.index > self.entry_num {
979            return None;
980        }
981        if self.index == self.entry_num {
982            tracing::debug!(
983                self.ifd_idx,
984                self.index,
985                pos = self.pos,
986                "try to get next ifd"
987            );
988            self.index += 1;
989
990            // next IFD offset
991            let (_, offset) =
992                complete::u32::<_, nom::error::Error<_>>(endian)(&self.input[self.pos..]).ok()?;
993
994            if offset == 0 {
995                // IFD parsing completed
996                tracing::debug!(?self, "IFD parsing completed");
997                return None;
998            }
999
1000            return self
1001                .new_ifd_iter(self.ifd_idx + 1, offset, None)
1002                .map(|x| (None, x));
1003        }
1004
1005        let entry_data = self
1006            .input
1007            .slice_checked(self.pos..self.pos + IFD_ENTRY_SIZE)?;
1008        self.index += 1;
1009        self.pos += IFD_ENTRY_SIZE;
1010
1011        let (tag, res) = self.parse_tag_entry(entry_data)?;
1012
1013        Some((Some(tag.into()), res)) // Safe-slice
1014    }
1015}
1016
1017#[cfg(test)]
1018mod tests {
1019
1020    use crate::exif::extract_exif_with_mime;
1021    use crate::exif::input_into_iter;
1022    use crate::file::MimeImage;
1023    use crate::slice::SubsliceRange;
1024    use crate::testkit::read_sample;
1025    use crate::Exif;
1026    use test_case::test_case;
1027
1028    #[test_case("exif.jpg", "+08:00", "2023-07-09T20:36:33+08:00", MimeImage::Jpeg)]
1029    #[test_case("exif-no-tz.jpg", "", "2023-07-09 20:36:33", MimeImage::Jpeg)]
1030    #[test_case("broken.jpg", "-", "2014-09-21 15:51:22", MimeImage::Jpeg)]
1031    #[test_case("exif.heic", "+08:00", "2022-07-22T21:26:32+08:00", MimeImage::Heic)]
1032    #[test_case("tif.tif", "-", "-", MimeImage::Tiff)]
1033    #[test_case(
1034        "fujifilm_x_t1_01.raf.meta",
1035        "-",
1036        "2014-01-30 12:49:13",
1037        MimeImage::Raf
1038    )]
1039    fn exif_iter_tz(path: &str, tz: &str, time: &str, img_type: MimeImage) {
1040        let buf = read_sample(path).unwrap();
1041        let (data, _) = extract_exif_with_mime(img_type, &buf, None).unwrap();
1042        let subslice_in_range = data.and_then(|x| buf.subslice_in_range(x)).unwrap();
1043        let iter = input_into_iter((buf, subslice_in_range), None).unwrap();
1044        let expect = if tz == "-" {
1045            None
1046        } else {
1047            Some(tz.to_string())
1048        };
1049        assert_eq!(iter.tz, expect);
1050        let exif: Exif = iter.into();
1051        let value = exif.get(crate::ExifTag::DateTimeOriginal);
1052        if time == "-" {
1053            assert!(value.is_none());
1054        } else {
1055            let value = value.unwrap();
1056            assert_eq!(value.to_string(), time);
1057        }
1058    }
1059}