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