nom_exif/exif/
exif_exif.rs

1use std::fmt::Debug;
2
3use nom::{
4    branch::alt, bytes::streaming::tag, combinator, number::Endianness, sequence, IResult, Needed,
5};
6
7use crate::{EntryValue, ExifIter, ExifTag, GPSInfo, ParsedExifEntry};
8
9use super::ifd::ParsedImageFileDirectory;
10
11/// Represents parsed Exif information, can be converted from an [`ExifIter`]
12/// like this: `let exif: Exif = iter.into()`.
13#[derive(Clone, Debug, PartialEq)]
14pub struct Exif {
15    ifds: Vec<ParsedImageFileDirectory>,
16    gps_info: Option<GPSInfo>,
17}
18
19impl Exif {
20    fn new(gps_info: Option<GPSInfo>) -> Exif {
21        Exif {
22            ifds: Vec::new(),
23            gps_info,
24        }
25    }
26
27    /// Get entry value for the specified `tag` in ifd0 (the main image).
28    ///
29    /// *Note*:
30    ///
31    /// - The parsing error related to this tag won't be reported by this
32    ///   method. Either this entry is not parsed successfully, or the tag does
33    ///   not exist in the input data, this method will return None.
34    ///
35    /// - If you want to handle parsing error, please consider to use
36    ///   [`ExifIter`].
37    ///
38    /// - If you have any custom defined tag which does not exist in
39    ///   [`ExifTag`], you can always get the entry value by a raw tag code,
40    ///   see [`Self::get_by_ifd_tag_code`].
41    ///
42    ///   ## Example
43    ///
44    ///   ```rust
45    ///   use nom_exif::*;
46    ///
47    ///   fn main() -> Result<()> {
48    ///       let mut parser = MediaParser::new();
49    ///       
50    ///       let ms = MediaSource::file_path("./testdata/exif.jpg")?;
51    ///       let iter: ExifIter = parser.parse(ms)?;
52    ///       let exif: Exif = iter.into();
53    ///
54    ///       assert_eq!(exif.get(ExifTag::Model).unwrap(), &"vivo X90 Pro+".into());
55    ///       Ok(())
56    ///   }
57    pub fn get(&self, tag: ExifTag) -> Option<&EntryValue> {
58        self.get_by_ifd_tag_code(0, tag.code())
59    }
60
61    /// Get entry value for the specified `tag` in the specified `ifd`.
62    ///
63    /// `ifd` value range:
64    /// - 0: ifd0 (the main image)
65    /// - 1: ifd1 (thumbnail image)
66    ///
67    /// *Note*:
68    ///
69    /// - The parsing error related to this tag won't be reported by this
70    ///   method. Either this entry is not parsed successfully, or the tag does
71    ///   not exist in the input data, this method will return None.
72    ///
73    /// - If you want to handle parsing error, please consider to use
74    ///   [`ExifIter`].
75    ///
76    ///   ## Example
77    ///
78    ///   ```rust
79    ///   use nom_exif::*;
80    ///
81    ///   fn main() -> Result<()> {
82    ///       let mut parser = MediaParser::new();
83    ///       
84    ///       let ms = MediaSource::file_path("./testdata/exif.jpg")?;
85    ///       let iter: ExifIter = parser.parse(ms)?;
86    ///       let exif: Exif = iter.into();
87    ///
88    ///       assert_eq!(exif.get_by_ifd_tag_code(0, 0x0110).unwrap(), &"vivo X90 Pro+".into());
89    ///       assert_eq!(exif.get_by_ifd_tag_code(1, 0xa002).unwrap(), &240_u32.into());
90    ///       Ok(())
91    ///   }
92    ///   ```
93    pub fn get_by_ifd_tag_code(&self, ifd: usize, tag: u16) -> Option<&EntryValue> {
94        self.ifds.get(ifd).and_then(|ifd| ifd.get(tag))
95    }
96
97    /// Get entry values for the specified `tags` in ifd0 (the main image).
98    ///
99    /// Please note that this method will ignore errors encountered during the
100    /// search and parsing process, such as missing tags or errors in parsing
101    /// values, and handle them silently.
102    #[deprecated(
103        since = "1.5.0",
104        note = "please use [`Self::get`] or [`ExifIter`] instead"
105    )]
106    pub fn get_values<'b>(&self, tags: &'b [ExifTag]) -> Vec<(&'b ExifTag, EntryValue)> {
107        tags.iter()
108            .zip(tags.iter())
109            .filter_map(|x| {
110                #[allow(deprecated)]
111                self.get_value(x.0)
112                    .map(|v| v.map(|v| (x.0, v)))
113                    .unwrap_or(None)
114            })
115            .collect::<Vec<_>>()
116    }
117
118    /// Get entry value for the specified `tag` in ifd0 (the main image).
119    #[deprecated(since = "1.5.0", note = "please use [`Self::get`] instead")]
120    pub fn get_value(&self, tag: &ExifTag) -> crate::Result<Option<EntryValue>> {
121        #[allow(deprecated)]
122        self.get_value_by_tag_code(tag.code())
123    }
124
125    /// Get entry value for the specified `tag` in ifd0 (the main image).
126    #[deprecated(since = "1.5.0", note = "please use [`Self::get_by_tag_code`] instead")]
127    pub fn get_value_by_tag_code(&self, tag: u16) -> crate::Result<Option<EntryValue>> {
128        Ok(self.get_by_ifd_tag_code(0, tag).map(|x| x.to_owned()))
129    }
130
131    /// Get parsed GPS information.
132    pub fn get_gps_info(&self) -> crate::Result<Option<GPSInfo>> {
133        Ok(self.gps_info.clone())
134    }
135
136    fn put(&mut self, res: &mut ParsedExifEntry) {
137        while self.ifds.len() < res.ifd_index() + 1 {
138            self.ifds.push(ParsedImageFileDirectory::new());
139        }
140        if let Some(v) = res.take_value() {
141            self.ifds[res.ifd_index()].put(res.tag_code(), v);
142        }
143    }
144}
145
146impl From<ExifIter> for Exif {
147    fn from(iter: ExifIter) -> Self {
148        let gps_info = iter.parse_gps_info().ok().flatten();
149        let mut exif = Exif::new(gps_info);
150
151        for mut it in iter {
152            exif.put(&mut it);
153        }
154
155        exif
156    }
157}
158
159pub(crate) const TIFF_HEADER_LEN: usize = 8;
160
161/// TIFF Header
162#[derive(Clone, PartialEq, Eq)]
163pub(crate) struct TiffHeader {
164    pub endian: Endianness,
165    pub ifd0_offset: u32,
166}
167
168impl Default for TiffHeader {
169    fn default() -> Self {
170        Self {
171            endian: Endianness::Big,
172            ifd0_offset: 0,
173        }
174    }
175}
176
177impl Debug for TiffHeader {
178    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
179        let endian_str = match self.endian {
180            Endianness::Big => "Big",
181            Endianness::Little => "Little",
182            Endianness::Native => "Native",
183        };
184        f.debug_struct("TiffHeader")
185            .field("endian", &endian_str)
186            .field("ifd0_offset", &format!("{:#x}", self.ifd0_offset))
187            .finish()
188    }
189}
190
191pub(crate) const IFD_ENTRY_SIZE: usize = 12;
192
193impl TiffHeader {
194    pub fn parse(input: &[u8]) -> IResult<&[u8], TiffHeader> {
195        use nom::number::streaming::{u16, u32};
196        let (remain, endian) = TiffHeader::parse_endian(input)?;
197        let (_, (_, offset)) = sequence::tuple((
198            combinator::verify(u16(endian), |magic| *magic == 0x2a),
199            u32(endian),
200        ))(remain)?;
201
202        let header = Self {
203            endian,
204            ifd0_offset: offset,
205        };
206
207        Ok((remain, header))
208    }
209
210    pub fn parse_ifd_entry_num(input: &[u8], endian: Endianness) -> IResult<&[u8], u16> {
211        let (remain, num) = nom::number::streaming::u16(endian)(input)?; // Safe-slice
212        if num == 0 {
213            return Ok((remain, 0));
214        }
215
216        // 12 bytes per entry
217        let size = (num as usize)
218            .checked_mul(IFD_ENTRY_SIZE)
219            .expect("should fit");
220
221        if size > remain.len() {
222            return Err(nom::Err::Incomplete(Needed::new(size - remain.len())));
223        }
224
225        Ok((remain, num))
226    }
227
228    // pub fn first_ifd<'a>(&self, input: &'a [u8], tag_ids: HashSet<u16>) -> IResult<&'a [u8], IFD> {
229    //     // ifd0_offset starts from the beginning of Header, so we should
230    //     // subtract the header size, which is 8
231    //     let offset = self.ifd0_offset - 8;
232
233    //     // skip to offset
234    //     let (_, remain) = take(offset)(input)?;
235
236    //     IFD::parse(remain, self.endian, tag_ids)
237    // }
238
239    fn parse_endian(input: &[u8]) -> IResult<&[u8], Endianness> {
240        combinator::map(alt((tag("MM"), tag("II"))), |endian_marker| {
241            if endian_marker == b"MM" {
242                Endianness::Big
243            } else {
244                Endianness::Little
245            }
246        })(input)
247    }
248}
249
250pub(crate) fn check_exif_header(data: &[u8]) -> Result<bool, nom::Err<nom::error::Error<&[u8]>>> {
251    tag::<_, _, nom::error::Error<_>>(EXIF_IDENT)(data).map(|_| true)
252}
253
254pub(crate) fn check_exif_header2(i: &[u8]) -> IResult<&[u8], ()> {
255    let (remain, _) = nom::sequence::tuple((
256        nom::number::complete::be_u32,
257        nom::bytes::complete::tag(EXIF_IDENT),
258    ))(i)?;
259    Ok((remain, ()))
260}
261
262pub(crate) const EXIF_IDENT: &str = "Exif\0\0";
263
264#[cfg(test)]
265mod tests {
266    use std::io::Read;
267    use std::thread;
268
269    use crate::partial_vec::PartialVec;
270    use test_case::test_case;
271
272    use crate::exif::input_into_iter;
273    use crate::jpeg::extract_exif_data;
274    use crate::slice::SubsliceRange;
275    use crate::testkit::{open_sample, read_sample};
276    use crate::ParsedExifEntry;
277
278    use super::*;
279
280    #[test]
281    fn header() {
282        let _ = tracing_subscriber::fmt().with_test_writer().try_init();
283
284        let buf = [0x4d, 0x4d, 0x00, 0x2a, 0x00, 0x00, 0x00, 0x08, 0x00];
285
286        let (_, header) = TiffHeader::parse(&buf).unwrap();
287        assert_eq!(
288            header,
289            TiffHeader {
290                endian: Endianness::Big,
291                ifd0_offset: 8,
292            }
293        );
294    }
295
296    #[test_case("exif.jpg")]
297    fn exif_iter_gps(path: &str) {
298        let buf = read_sample(path).unwrap();
299        let (_, data) = extract_exif_data(&buf).unwrap();
300        let data = data
301            .and_then(|x| buf.subslice_in_range(x))
302            .map(|x| PartialVec::from_vec_range(buf, x))
303            .unwrap();
304        let iter = input_into_iter(data, None).unwrap();
305        let gps = iter.parse_gps_info().unwrap().unwrap();
306        assert_eq!(gps.format_iso6709(), "+22.53113+114.02148/");
307    }
308
309    #[test_case("exif.jpg")]
310    fn clone_exif_iter_to_thread(path: &str) {
311        let buf = read_sample(path).unwrap();
312        let (_, data) = extract_exif_data(&buf).unwrap();
313        let data = data
314            .and_then(|x| buf.subslice_in_range(x))
315            .map(|x| PartialVec::from_vec_range(buf, x))
316            .unwrap();
317        let iter = input_into_iter(data, None).unwrap();
318        let iter2 = iter.clone();
319
320        let mut expect = String::new();
321        open_sample(&format!("{path}.txt"))
322            .unwrap()
323            .read_to_string(&mut expect)
324            .unwrap();
325
326        let jh = thread::spawn(move || iter_to_str(iter2));
327
328        let result = iter_to_str(iter);
329
330        // open_sample_w(&format!("{path}.txt"))
331        //     .unwrap()
332        //     .write_all(result.as_bytes())
333        //     .unwrap();
334
335        assert_eq!(result.trim(), expect.trim());
336        assert_eq!(jh.join().unwrap().trim(), expect.trim());
337    }
338
339    fn iter_to_str(it: impl Iterator<Item = ParsedExifEntry>) -> String {
340        let ss = it
341            .map(|x| {
342                format!(
343                    "ifd{}.{:<32} ยป {}",
344                    x.ifd_index(),
345                    x.tag()
346                        .map(|t| t.to_string())
347                        .unwrap_or_else(|| format!("Unknown(0x{:04x})", x.tag_code())),
348                    x.get_result()
349                        .map(|v| v.to_string())
350                        .map_err(|e| e.to_string())
351                        .unwrap_or_else(|s| s)
352                )
353            })
354            .collect::<Vec<String>>();
355        ss.join("\n")
356    }
357}