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