Skip to main content

nom_exif/
lib.rs

1//! `nom-exif` is an Exif/metadata parsing library written in pure Rust with
2//! [nom](https://github.com/rust-bakery/nom).
3//!
4//! ## Supported File Types
5//!
6//! - Image
7//!   - .heic, .heif, etc.
8//!   - .jpg, .jpeg
9//!   - .tiff, .tif, .iiq (Phase One IIQ images), etc.
10//!   - .RAF (Fujifilm RAW)
11//!   - .CR3 (Canon RAW)
12//! - Video/Audio
13//!   - ISO base media file format (ISOBMFF): .mp4, .mov, .3gp, etc.
14//!   - Matroska based file format: .webm, .mkv, .mka, etc.
15//!
16//! ## Key Features
17//!
18//! - Ergonomic Design
19//!
20//!   - **Unified Workflow** for Various File Types
21//!   
22//!     Now, multimedia files of different types and formats (including images,
23//!     videos, and audio) can be processed using a unified method. This consistent
24//!     API interface simplifies user experience and reduces cognitive load.
25//!     
26//!     The usage is demonstrated in the following examples. `examples/rexiftool`
27//!     is also a good example.
28//!   
29//!   - Two style APIs for Exif
30//!   
31//!     *iterator* style ([`ExifIter`]) and *get* style ([`Exif`]). The former is
32//!     parse-on-demand, and therefore, more detailed error information can be
33//!     captured; the latter is simpler and easier to use.
34//!   
35//! - Performance
36//!
37//!   - *Zero-copy* when appropriate: Use borrowing and slicing instead of
38//!     copying whenever possible.
39//!     
40//!   - Minimize I/O operations: When metadata is stored at the end/middle of a
41//!     large file (such as a QuickTime file does), `Seek` rather than `Read`
42//!     to quickly locate the location of the metadata (if the reader supports
43//!     `Seek`).
44//!
45//!   - Supports both *seekable* and *unseekable* data sources, see [`MediaSource::seekable`]
46//!     for more information.
47//!   
48//!   - Share I/O and parsing buffer between multiple parse calls: This can
49//!     improve performance and avoid the overhead and memory fragmentation
50//!     caused by frequent memory allocation. This feature is very useful when
51//!     you need to perform batch parsing.
52//!     
53//!   - Pay as you go: When working with [`ExifIter`], all entries are
54//!     lazy-parsed. That is, only when you iterate over [`ExifIter`] will the
55//!     IFD entries be parsed one by one.
56//!     
57//! - Robustness and stability
58//!
59//!   Through long-term [Fuzz testing](https://github.com/rust-fuzz/afl.rs), and
60//!   tons of crash issues discovered during testing have been fixed. Thanks to
61//!   [@sigaloid](https://github.com/sigaloid) for [pointing this
62//!   out](https://github.com/mindeng/nom-exif/pull/5)!
63//!
64//! - Supports both *sync* and *async* APIs
65//!
66//! ## Unified Workflow for Various File Types
67//!
68//! By using `MediaSource` & `MediaParser`, multimedia files of different types and
69//! formats (including images, videos, and audio) can be processed using a unified
70//! method.
71//!
72//! Here's an example:
73//!
74//! ```rust
75//! use nom_exif::*;
76//!
77//! fn main() -> Result<()> {
78//!     let mut parser = MediaParser::new();
79//!
80//!     let files = [
81//!         "./testdata/exif.heic",
82//!         "./testdata/exif.jpg",
83//!         "./testdata/tif.tif",
84//!         "./testdata/meta.mov",
85//!         "./testdata/meta.mp4",
86//!         "./testdata/webm_480.webm",
87//!         "./testdata/mkv_640x360.mkv",
88//!         "./testdata/mka.mka",
89//!         "./testdata/3gp_640x360.3gp"
90//!     ];
91//!
92//!     for f in files {
93//!         let ms = MediaSource::file_path(f)?;
94//!
95//!         if ms.has_exif() {
96//!             // Parse the file as an Exif-compatible file
97//!             let mut iter: ExifIter = parser.parse(ms)?;
98//!             // ...
99//!         } else if ms.has_track() {
100//!             // Parse the file as a track
101//!             let info: TrackInfo = parser.parse(ms)?;
102//!             // ...
103//!         }
104//!     }
105//!
106//!     Ok(())
107//! }
108//! ```
109//!
110//! ## Sync API: `MediaSource` + `MediaParser`
111//!
112//! `MediaSource` is an abstraction of multimedia data sources, which can be
113//! created from any object that implements the `Read` trait, and can be parsed by
114//! `MediaParser`.
115//!
116//! Example:
117//!
118//! ```rust
119//! use nom_exif::*;
120//! use chrono::DateTime;
121//!
122//! fn main() -> Result<()> {
123//!     let mut parser = MediaParser::new();
124//!     
125//!     let ms = MediaSource::file_path("./testdata/exif.heic")?;
126//!     assert!(ms.has_exif());
127//!     
128//!     let mut iter: ExifIter = parser.parse(ms)?;
129//!     let exif: Exif = iter.into();
130//!     assert_eq!(exif.get(ExifTag::Make).unwrap().as_str().unwrap(), "Apple");
131//!     assert_eq!(exif.get(ExifTag::Model).unwrap().as_str().unwrap(), "iPhone 12 Pro");
132//!
133//!     let dto_value = exif.get(ExifTag::DateTimeOriginal).unwrap();
134//!     let (ndt, offset) = dto_value.as_time_components().unwrap();
135//!     if let Some(offset) = offset {
136//!         // The DateTimeOriginal value has an offset, so we can convert it to
137//!         // a DateTime like this:
138//!         let dt = ndt.and_local_timezone(offset).unwrap();
139//!         assert_eq!(dt, DateTime::parse_from_str("2022-07-22T21:26:32+08:00", "%+").unwrap());
140//!     } else {
141//!         // The DateTimeOriginal value has no offset, use `ndt` directly
142//!     }
143//!
144//!     let ms = MediaSource::file_path("./testdata/meta.mov")?;
145//!     assert!(ms.has_track());
146//!     
147//!     let info: TrackInfo = parser.parse(ms)?;
148//!     assert_eq!(info.get(TrackInfoTag::Make), Some(&"Apple".into()));
149//!     assert_eq!(info.get(TrackInfoTag::Model), Some(&"iPhone X".into()));
150//!     assert_eq!(info.get(TrackInfoTag::GpsIso6709), Some(&"+27.1281+100.2508+000.000/".into()));
151//!     assert_eq!(info.get_gps_info().unwrap().latitude_ref, 'N');
152//!     assert_eq!(
153//!         info.get_gps_info().unwrap().latitude,
154//!         [(27, 1), (7, 1), (68, 100)].into(),
155//!     );
156//!
157//!     // `MediaSource` can also be created from a `TcpStream`:
158//!     // let ms = MediaSource::tcp_stream(stream)?;
159//!
160//!     // Or from any `Read + Seek`:
161//!     // let ms = MediaSource::seekable(stream)?;
162//!     
163//!     // From any `Read`:
164//!     // let ms = MediaSource::unseekable(stream)?;
165//!     
166//!     Ok(())
167//! }
168//! ```
169//!
170//! See [`MediaSource`] & [`MediaParser`] for more information.
171//!
172//! ## Async API: `AsyncMediaSource` + `AsyncMediaParser`
173//!
174//! Likewise, `AsyncMediaParser` is an abstraction for asynchronous multimedia data
175//! sources, which can be created from any object that implements the `AsyncRead`
176//! trait, and can be parsed by `AsyncMediaParser`.
177//!
178//! Enable `async` feature flag for `nom-exif` in your `Cargo.toml`:
179//!
180//! ```toml
181//! [dependencies]
182//! nom-exif = { version = "2", features = ["async"] }
183//! ```
184//!
185//! See [`AsyncMediaSource`] & [`AsyncMediaParser`] for more information.
186//!
187//! ## GPS Info
188//!
189//! `ExifIter` provides a convenience method for parsing gps information. (`Exif` &
190//! `TrackInfo` also provide a `get_gps_info` method).
191//!     
192//! ```rust
193//! use nom_exif::*;
194//!
195//! fn main() -> Result<()> {
196//!     let mut parser = MediaParser::new();
197//!     
198//!     let ms = MediaSource::file_path("./testdata/exif.heic")?;
199//!     let iter: ExifIter = parser.parse(ms)?;
200//!
201//!     let gps_info = iter.parse_gps_info()?.unwrap();
202//!     assert_eq!(gps_info.format_iso6709(), "+43.29013+084.22713+1595.950CRSWGS_84/");
203//!     assert_eq!(gps_info.latitude_ref, 'N');
204//!     assert_eq!(gps_info.longitude_ref, 'E');
205//!     assert_eq!(
206//!         gps_info.latitude,
207//!         [(43, 1), (17, 1), (2446, 100)].into(),
208//!     );
209//!     Ok(())
210//! }
211//! ```
212//!
213//! For more usage details, please refer to the [API
214//! documentation](https://docs.rs/nom-exif/latest/nom_exif/).
215//!
216//! ## CLI Tool `rexiftool`
217//!
218//! ### Human Readable Output
219//!
220//! `cargo run --example rexiftool testdata/meta.mov`:
221//!
222//! ``` text
223//! Make                            => Apple
224//! Model                           => iPhone X
225//! Software                        => 12.1.2
226//! CreateDate                      => 2024-02-02T08:09:57+00:00
227//! DurationMs                      => 500
228//! ImageWidth                      => 720
229//! ImageHeight                     => 1280
230//! GpsIso6709                      => +27.1281+100.2508+000.000/
231//! ```
232//!
233//! Enabling option `--debug` to turn on tracing logs:
234//!
235//! `cargo run --example rexiftool -- --debug ./testdata/meta.mov`
236//!
237//! ### Json Dump
238//!
239//! `cargo run --features json_dump --example rexiftool testdata/meta.mov -j`:
240//!
241//! ``` text
242//! {
243//!   "ImageWidth": "720",
244//!   "Software": "12.1.2",
245//!   "ImageHeight": "1280",
246//!   "Make": "Apple",
247//!   "GpsIso6709": "+27.1281+100.2508+000.000/",
248//!   "CreateDate": "2024-02-02T08:09:57+00:00",
249//!   "Model": "iPhone X",
250//!   "DurationMs": "500"
251//! }
252//! ```
253//!
254//! ### Parsing Files in Directory
255//!
256//! `rexiftool` also supports batch parsing of all files in a folder
257//! (non-recursive).
258//!
259//! `cargo run --example rexiftool testdata/`:
260//!
261//! ```text
262//! File: "testdata/embedded-in-heic.mov"
263//! ------------------------------------------------
264//! Make                            => Apple
265//! Model                           => iPhone 15 Pro
266//! Software                        => 17.1
267//! CreateDate                      => 2023-11-02T12:01:02+00:00
268//! DurationMs                      => 2795
269//! ImageWidth                      => 1920
270//! ImageHeight                     => 1440
271//! GpsIso6709                      => +22.5797+113.9380+028.396/
272//!
273//! File: "testdata/compatible-brands-fail.heic"
274//! ------------------------------------------------
275//! Unrecognized file format, consider filing a bug @ https://github.com/mindeng/nom-exif.
276//!
277//! File: "testdata/webm_480.webm"
278//! ------------------------------------------------
279//! CreateDate                      => 2009-09-09T09:09:09+00:00
280//! DurationMs                      => 30543
281//! ImageWidth                      => 480
282//! ImageHeight                     => 270
283//!
284//! File: "testdata/mka.mka"
285//! ------------------------------------------------
286//! DurationMs                      => 3422
287//! ImageWidth                      => 0
288//! ImageHeight                     => 0
289//!
290//! File: "testdata/exif-one-entry.heic"
291//! ------------------------------------------------
292//! Orientation                     => 1
293//!
294//! File: "testdata/no-exif.jpg"
295//! ------------------------------------------------
296//! Error: parse failed: Exif not found
297//!
298//! File: "testdata/exif.jpg"
299//! ------------------------------------------------
300//! ImageWidth                      => 3072
301//! Model                           => vivo X90 Pro+
302//! ImageHeight                     => 4096
303//! ModifyDate                      => 2023-07-09T20:36:33+08:00
304//! YCbCrPositioning                => 1
305//! ExifOffset                      => 201
306//! MakerNote                       => Undefined[0x30]
307//! RecommendedExposureIndex        => 454
308//! SensitivityType                 => 2
309//! ISOSpeedRatings                 => 454
310//! ExposureProgram                 => 2
311//! FNumber                         => 175/100 (1.7500)
312//! ExposureTime                    => 9997/1000000 (0.0100)
313//! SensingMethod                   => 2
314//! SubSecTimeDigitized             => 616
315//! OffsetTimeOriginal              => +08:00
316//! SubSecTimeOriginal              => 616
317//! OffsetTime                      => +08:00
318//! SubSecTime                      => 616
319//! FocalLength                     => 8670/1000 (8.6700)
320//! Flash                           => 16
321//! LightSource                     => 21
322//! MeteringMode                    => 1
323//! SceneCaptureType                => 0
324//! UserComment                     => filter: 0; fileterIntensity: 0.0; filterMask: 0; algolist: 0;
325//! ...
326//! ```
327
328pub use parser::{MediaParser, MediaSource};
329pub use video::{TrackInfo, TrackInfoTag};
330
331#[cfg(feature = "async")]
332pub use parser_async::{AsyncMediaParser, AsyncMediaSource};
333
334pub use exif::{Exif, ExifIter, ExifTag, GPSInfo, LatLng, ParsedExifEntry};
335pub use values::{EntryValue, IRational, URational};
336
337#[allow(deprecated)]
338pub use exif::parse_exif;
339#[cfg(feature = "async")]
340#[allow(deprecated)]
341pub use exif::parse_exif_async;
342
343#[allow(deprecated)]
344pub use heif::parse_heif_exif;
345#[allow(deprecated)]
346pub use jpeg::parse_jpeg_exif;
347
348pub use error::Error;
349pub type Result<T> = std::result::Result<T, Error>;
350pub(crate) use skip::{Seekable, Unseekable};
351
352#[allow(deprecated)]
353pub use file::FileFormat;
354
355#[allow(deprecated)]
356pub use mov::{parse_metadata, parse_mov_metadata};
357
358mod bbox;
359mod buffer;
360mod cr3;
361mod ebml;
362mod error;
363mod exif;
364mod file;
365mod heif;
366mod jpeg;
367mod loader;
368mod mov;
369mod parser;
370#[cfg(feature = "async")]
371mod parser_async;
372mod partial_vec;
373mod raf;
374mod skip;
375mod slice;
376mod utils;
377mod values;
378mod video;
379
380#[cfg(test)]
381mod testkit;