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;