Skip to main content

nom_exif/
exif.rs

1use crate::error::{nom_error_to_parsing_error_with_state, ParsingError, ParsingErrorState};
2use crate::file::MimeImage;
3use crate::parser::{BufParser, ParsingState, ShareBuf};
4use crate::raf::RafInfo;
5use crate::skip::Skip;
6use crate::slice::SubsliceRange;
7use crate::{heif, jpeg, MediaParser, MediaSource};
8#[allow(deprecated)]
9use crate::{partial_vec::PartialVec, FileFormat};
10use exif_exif::check_exif_header2;
11pub use exif_exif::Exif;
12use exif_iter::input_into_iter;
13pub use exif_iter::{ExifIter, ParsedExifEntry};
14pub use gps::{GPSInfo, LatLng};
15pub use tags::ExifTag;
16
17use std::io::Read;
18use std::ops::Range;
19
20pub(crate) mod ifd;
21pub(crate) use exif_exif::{check_exif_header, TiffHeader};
22pub(crate) use travel::IfdHeaderTravel;
23
24mod exif_exif;
25mod exif_iter;
26mod gps;
27mod tags;
28mod travel;
29
30/// *Deprecated*: Please use [`crate::MediaParser`] instead.
31///
32/// Read exif data from `reader`, and build an [`ExifIter`] for it.
33///
34/// ~~If `format` is None, the parser will detect the file format automatically.~~
35/// *The `format` param will be ignored from v2.0.0.*
36///
37/// Currently supported file formats are:
38///
39/// - *.heic, *.heif, etc.
40/// - *.jpg, *.jpeg, etc.
41///
42/// *.tiff/*.tif is not supported by this function, please use `MediaParser`
43/// instead.
44///
45/// All entries are lazy-parsed. That is, only when you iterate over
46/// [`ExifIter`] will the IFD entries be parsed one by one.
47///
48/// The one exception is the time zone entries. The parser will try to find and
49/// parse the time zone data first, so we can correctly parse all time
50/// information in subsequent iterates.
51///
52/// Please note that the parsing routine itself provides a buffer, so the
53/// `reader` may not need to be wrapped with `BufRead`.
54///
55/// Returns:
56///
57/// - An `Ok<Some<ExifIter>>` if Exif data is found and parsed successfully.
58/// - An `Ok<None>` if Exif data is not found.
59/// - An `Err` if Exif data is found but parsing failed.
60#[deprecated(since = "2.0.0")]
61#[allow(deprecated)]
62pub fn parse_exif<T: Read>(reader: T, _: Option<FileFormat>) -> crate::Result<Option<ExifIter>> {
63    let mut parser = MediaParser::new();
64    let iter: ExifIter = parser.parse(MediaSource::unseekable(reader)?)?;
65    let iter = iter.to_owned();
66    Ok(Some(iter))
67}
68
69#[tracing::instrument(skip(reader))]
70pub(crate) fn parse_exif_iter<R: Read, S: Skip<R>>(
71    parser: &mut MediaParser,
72    mime_img: MimeImage,
73    reader: &mut R,
74) -> Result<ExifIter, crate::Error> {
75    let out = parser.load_and_parse::<R, S, _, _>(reader, |buf, state| {
76        extract_exif_range(mime_img, buf, state)
77    })?;
78
79    range_to_iter(parser, out)
80}
81
82type ExifRangeResult = Result<Option<(Range<usize>, Option<TiffHeader>)>, ParsingErrorState>;
83
84fn extract_exif_range(img: MimeImage, buf: &[u8], state: Option<ParsingState>) -> ExifRangeResult {
85    let (exif_data, state) = extract_exif_with_mime(img, buf, state)?;
86    let header = state.and_then(|x| match x {
87        ParsingState::TiffHeader(h) => Some(h),
88        ParsingState::HeifExifSize(_) => None,
89    });
90    Ok(exif_data
91        .and_then(|x| buf.subslice_in_range(x))
92        .map(|x| (x, header)))
93}
94
95fn range_to_iter(
96    parser: &mut impl ShareBuf,
97    out: Option<(Range<usize>, Option<TiffHeader>)>,
98) -> Result<ExifIter, crate::Error> {
99    if let Some((range, header)) = out {
100        tracing::debug!(?range, ?header, "Got Exif data");
101        let input: PartialVec = parser.share_buf(range);
102        let iter = input_into_iter(input, header)?;
103
104        Ok(iter)
105    } else {
106        tracing::debug!("Exif not found");
107        Err("Exif not found".into())
108    }
109}
110
111#[cfg(feature = "async")]
112#[tracing::instrument(skip(reader))]
113pub(crate) async fn parse_exif_iter_async<
114    R: AsyncRead + Unpin + Send,
115    S: crate::skip::AsyncSkip<R>,
116>(
117    parser: &mut crate::AsyncMediaParser,
118    mime_img: MimeImage,
119    reader: &mut R,
120) -> Result<ExifIter, crate::Error> {
121    use crate::parser_async::AsyncBufParser;
122
123    let out = parser
124        .load_and_parse::<R, S, _, _>(reader, |buf, state| {
125            extract_exif_range(mime_img, buf, state)
126        })
127        .await?;
128
129    range_to_iter(parser, out)
130}
131
132pub(crate) fn extract_exif_with_mime(
133    img_type: crate::file::MimeImage,
134    buf: &[u8],
135    state: Option<ParsingState>,
136) -> Result<(Option<&[u8]>, Option<ParsingState>), ParsingErrorState> {
137    let (exif_data, state) = match img_type {
138        MimeImage::Jpeg => jpeg::extract_exif_data(buf)
139            .map(|res| (res.1, state.clone()))
140            .map_err(|e| nom_error_to_parsing_error_with_state(e, state))?,
141        MimeImage::Heic | crate::file::MimeImage::Heif => heif_extract_exif(state, buf)?,
142        MimeImage::Tiff => {
143            let (header, data_start) = match state {
144                Some(ParsingState::TiffHeader(ref h)) => (h.to_owned(), 0),
145                None => {
146                    let (_, header) = TiffHeader::parse(buf)
147                        .map_err(|e| nom_error_to_parsing_error_with_state(e, None))?;
148                    if header.ifd0_offset as usize > buf.len() {
149                        let clear_and_skip =
150                            ParsingError::ClearAndSkip(header.ifd0_offset as usize);
151                        let state = Some(ParsingState::TiffHeader(header));
152                        return Err(ParsingErrorState::new(clear_and_skip, state));
153                    }
154                    let start = header.ifd0_offset as usize;
155                    (header, start)
156                }
157                _ => unreachable!(),
158            };
159
160            // full fill TIFF data
161            let mut iter =
162                IfdHeaderTravel::new(&buf[data_start..], header.ifd0_offset, header.endian);
163            iter.travel_ifd(0)
164                .map_err(|e| ParsingErrorState::new(e, state.clone()))?;
165
166            (Some(buf), state)
167        }
168        MimeImage::Raf => RafInfo::parse(buf)
169            .map(|res| (res.1.exif_data, state.clone()))
170            .map_err(|e| nom_error_to_parsing_error_with_state(e, state))?,
171    };
172    Ok((exif_data, state))
173}
174
175fn heif_extract_exif(
176    state: Option<ParsingState>,
177    buf: &[u8],
178) -> Result<(Option<&[u8]>, Option<ParsingState>), ParsingErrorState> {
179    let (data, state) = match state {
180        Some(ParsingState::HeifExifSize(size)) => {
181            let (_, data) = nom::bytes::streaming::take(size)(buf)
182                .map_err(|e| nom_error_to_parsing_error_with_state(e, state.clone()))?;
183            (Some(data), state)
184        }
185        None => {
186            let (_, meta) = heif::parse_meta_box(buf)
187                .map_err(|e| nom_error_to_parsing_error_with_state(e, state))?;
188
189            if let Some(meta) = meta {
190                if let Some(range) = meta.exif_data_offset() {
191                    if range.end > buf.len() {
192                        let state = ParsingState::HeifExifSize(range.len());
193                        let clear_and_skip = ParsingError::ClearAndSkip(range.start);
194                        return Err(ParsingErrorState::new(clear_and_skip, Some(state)));
195                    } else {
196                        (Some(&buf[range]), None)
197                    }
198                } else {
199                    return Err(ParsingErrorState::new(
200                        ParsingError::Failed("no exif offset in meta box".into()),
201                        None,
202                    ));
203                }
204            } else {
205                (None, None)
206            }
207        }
208        _ => unreachable!(),
209    };
210
211    let data = data.and_then(|x| check_exif_header2(x).map(|x| x.0).ok());
212
213    Ok((data, state))
214}
215
216#[cfg(feature = "async")]
217use tokio::io::AsyncRead;
218
219/// *Deprecated*: Please use [`crate::MediaParser`] instead.
220///
221/// `async` version of [`parse_exif`].
222#[allow(deprecated)]
223#[cfg(feature = "async")]
224#[deprecated(since = "2.0.0")]
225pub async fn parse_exif_async<T: AsyncRead + Unpin + Send>(
226    reader: T,
227    _: Option<FileFormat>,
228) -> crate::Result<Option<ExifIter>> {
229    use crate::{AsyncMediaParser, AsyncMediaSource};
230
231    let mut parser = AsyncMediaParser::new();
232    let exif: ExifIter = parser
233        .parse(AsyncMediaSource::unseekable(reader).await?)
234        .await?;
235    Ok(Some(exif))
236}
237
238#[cfg(test)]
239#[allow(deprecated)]
240mod tests {
241    use std::{sync::mpsc, thread, time::Duration};
242
243    use crate::{
244        file::MimeImage,
245        testkit::{open_sample, read_sample},
246        values::URational,
247    };
248    use test_case::test_case;
249
250    use super::*;
251
252    #[test_case("exif.heic", "+43.29013+084.22713+1595.950CRSWGS_84/")]
253    #[test_case("exif.jpg", "+22.53113+114.02148/")]
254    fn gps(path: &str, gps_str: &str) {
255        let f = open_sample(path).unwrap();
256        let iter = parse_exif(f, None)
257            .expect("should be Ok")
258            .expect("should not be None");
259        let gps_info = iter
260            .parse_gps_info()
261            .expect("should be parsed Ok")
262            .expect("should not be None");
263
264        // let gps_info = iter
265        //     .consume_parse_gps_info()
266        //     .expect("should be parsed Ok")
267        //     .expect("should not be None");
268        assert_eq!(gps_info.format_iso6709(), gps_str);
269    }
270
271    #[cfg(feature = "async")]
272    #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
273    #[test_case("exif.heic", "+43.29013+084.22713+1595.950CRSWGS_84/")]
274    #[test_case("exif.jpg", "+22.53113+114.02148/")]
275    async fn gps_async(path: &str, gps_str: &str) {
276        use std::path::Path;
277        use tokio::fs::File;
278
279        let f = File::open(Path::new("testdata").join(path)).await.unwrap();
280        let iter = parse_exif_async(f, None)
281            .await
282            .expect("should be Ok")
283            .expect("should not be None");
284
285        let gps_str = gps_str.to_owned();
286        let _ = tokio::spawn(async move {
287            let exif: Exif = iter.into();
288            let gps_info = exif.get_gps_info().expect("ok").expect("some");
289            assert_eq!(gps_info.format_iso6709(), gps_str);
290        })
291        .await;
292    }
293
294    #[test_case(
295        "exif.jpg",
296        'N',
297        [(22, 1), (31, 1), (5208, 100)].into(),
298        'E',
299        [(114, 1), (1, 1), (1733, 100)].into(),
300        0u8,
301        (0, 1).into(),
302        None,
303        None
304    )]
305    #[allow(clippy::too_many_arguments)]
306    fn gps_info(
307        path: &str,
308        latitude_ref: char,
309        latitude: LatLng,
310        longitude_ref: char,
311        longitude: LatLng,
312        altitude_ref: u8,
313        altitude: URational,
314        speed_ref: Option<char>,
315        speed: Option<URational>,
316    ) {
317        let _ = tracing_subscriber::fmt().with_test_writer().try_init();
318
319        let buf = read_sample(path).unwrap();
320        let (data, _) = extract_exif_with_mime(MimeImage::Jpeg, &buf, None).unwrap();
321        let data = data.unwrap();
322
323        let subslice_range = buf.subslice_in_range(data).unwrap();
324        let iter = input_into_iter((buf, subslice_range), None).unwrap();
325        let exif: Exif = iter.into();
326
327        let gps = exif.get_gps_info().unwrap().unwrap();
328        assert_eq!(
329            gps,
330            GPSInfo {
331                latitude_ref,
332                latitude,
333                longitude_ref,
334                longitude,
335                altitude_ref,
336                altitude,
337                speed_ref,
338                speed,
339            }
340        )
341    }
342
343    #[test_case("exif.heic")]
344    fn tag_values(path: &str) {
345        let f = open_sample(path).unwrap();
346        let iter = parse_exif(f, None).unwrap().unwrap();
347        let tags = [ExifTag::Make, ExifTag::Model];
348        let res: Vec<String> = iter
349            .clone()
350            .filter(|e| e.tag().is_some_and(|t| tags.contains(&t)))
351            .filter(|e| e.has_value())
352            .map(|e| format!("{} => {}", e.tag().unwrap(), e.get_value().unwrap()))
353            .collect();
354        assert_eq!(res.join(", "), "Make => Apple, Model => iPhone 12 Pro");
355    }
356
357    #[test]
358    fn endless_loop() {
359        let (sender, receiver) = mpsc::channel();
360
361        thread::spawn(move || {
362            let name = "endless_loop.jpg";
363            let f = open_sample(name).unwrap();
364            let iter = parse_exif(f, None).unwrap().unwrap();
365            let _: Exif = iter.into();
366            sender.send(()).unwrap();
367        });
368
369        receiver
370            .recv_timeout(Duration::from_secs(1))
371            .expect("There is an infinite loop in the parsing process!");
372    }
373}