Skip to main content

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;