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