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;