Skip to main content

nom_exif/exif/
exif_exif.rs

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