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;