nom-exif is an Exif/metadata parsing library written in pure Rust with nom.
- Image
- .heic, .heif, etc.
- .jpg, .jpeg
- .tiff, .tif, .iiq (Phase One IIQ images), etc.
- .RAF (Fujifilm RAW)
- Video/Audio
- ISO base media file format (ISOBMFF): .mp4, .mov, .3gp, etc.
- Matroska based file format: .webm, .mkv, .mka, etc.
-
Ergonomic Design
-
Unified Workflow for Various File Types
Now, multimedia files of different types and formats (including images, videos, and audio) can be processed using a unified method. This consistent API interface simplifies user experience and reduces cognitive load.
The usage is demonstrated in the following examples.
examples/rexiftoolis also a good example. -
Two style APIs for Exif
iterator style ([
ExifIter]) and get style ([Exif]). The former is parse-on-demand, and therefore, more detailed error information can be captured; the latter is simpler and easier to use.
-
-
Performance
-
Zero-copy when appropriate: Use borrowing and slicing instead of copying whenever possible.
-
Minimize I/O operations: When metadata is stored at the end/middle of a large file (such as a QuickTime file does),
Seekrather thanReadto quickly locate the location of the metadata (if the reader supportsSeek). -
Share I/O and parsing buffer between multiple parse calls: This can improve performance and avoid the overhead and memory fragmentation caused by frequent memory allocation. This feature is very useful when you need to perform batch parsing.
-
Pay as you go: When working with [
ExifIter], all entries are lazy-parsed. That is, only when you iterate over [ExifIter] will the IFD entries be parsed one by one.
-
-
Robustness and stability
Through long-term Fuzz testing, and tons of crash issues discovered during testing have been fixed. Thanks to @sigaloid for pointing this out!
-
Supports both sync and async APIs
By using MediaSource & MediaParser, multimedia files of different types and formats (including images, videos, and audio) can be processed using a unified method.
Here's an example:
use nom_exif::*; fn main() -> Result<()> { let mut parser = MediaParser::new(); let files = [ "./testdata/exif.heic", "./testdata/exif.jpg", "./testdata/tif.tif", "./testdata/meta.mov", "./testdata/meta.mp4", "./testdata/webm_480.webm", "./testdata/mkv_640x360.mkv", "./testdata/mka.mka", "./testdata/3gp_640x360.3gp" ]; for f in files { let ms = MediaSource::file_path(f)?; if ms.has_exif() { // Parse the file as an Exif-compatible file let mut iter: ExifIter = parser.parse(ms)?; // ... } else if ms.has_track() { // Parse the file as a track let info: TrackInfo = parser.parse(ms)?; // ... } } Ok(()) }MediaSource is an abstraction of multimedia data sources, which can be created from any object that implements the Read trait, and can be parsed by MediaParser.
Example:
use nom_exif::*; fn main() -> Result<()> { let mut parser = MediaParser::new(); let ms = MediaSource::file_path("./testdata/exif.heic")?; assert!(ms.has_exif()); let mut iter: ExifIter = parser.parse(ms)?; let exif: Exif = iter.into(); assert_eq!(exif.get(ExifTag::Make).unwrap().as_str().unwrap(), "Apple"); let ms = MediaSource::file_path("./testdata/meta.mov")?; assert!(ms.has_track()); let info: TrackInfo = parser.parse(ms)?; assert_eq!(info.get(TrackInfoTag::Make), Some(&"Apple".into())); assert_eq!(info.get(TrackInfoTag::Model), Some(&"iPhone X".into())); assert_eq!(info.get(TrackInfoTag::GpsIso6709), Some(&"+27.1281+100.2508+000.000/".into())); assert_eq!(info.get_gps_info().unwrap().latitude_ref, 'N'); assert_eq!( info.get_gps_info().unwrap().latitude, [(27, 1), (7, 1), (68, 100)].into(), ); // `MediaSource` can also be created from a `TcpStream`: // let ms = MediaSource::tcp_stream(stream)?; // Or from any `Read + Seek`: // let ms = MediaSource::seekable(stream)?; // From any `Read`: // let ms = MediaSource::unseekable(stream)?; Ok(()) }See [MediaSource] & [MediaParser] for more information.
Likewise, AsyncMediaParser is an abstraction for asynchronous multimedia data sources, which can be created from any object that implements the AsyncRead trait, and can be parsed by AsyncMediaParser.
Enable async feature flag for nom-exif in your Cargo.toml:
[dependencies] nom-exif = { version = "1", features = ["async"] }See [AsyncMediaSource] & [AsyncMediaParser] for more information.
ExifIter provides a convenience method for parsing gps information. (Exif & TrackInfo also provide a get_gps_info method).
use nom_exif::*; fn main() -> Result<()> { let mut parser = MediaParser::new(); let ms = MediaSource::file_path("./testdata/exif.heic")?; let iter: ExifIter = parser.parse(ms)?; let gps_info = iter.parse_gps_info()?.unwrap(); assert_eq!(gps_info.format_iso6709(), "+43.29013+084.22713+1595.950CRSWGS_84/"); assert_eq!(gps_info.latitude_ref, 'N'); assert_eq!(gps_info.longitude_ref, 'E'); assert_eq!( gps_info.latitude, [(43, 1), (17, 1), (2446, 100)].into(), ); Ok(()) }For more usage details, please refer to the API documentation.
cargo run --example rexiftool testdata/meta.mov:
Make => Apple Model => iPhone X Software => 12.1.2 CreateDate => 2024-02-02T08:09:57+00:00 DurationMs => 500 ImageWidth => 720 ImageHeight => 1280 GpsIso6709 => +27.1281+100.2508+000.000/ Enabling option --debug to turn on tracing logs:
cargo run --example rexiftool -- --debug ./testdata/meta.mov
cargo run --features json_dump --example rexiftool testdata/meta.mov -j:
{ "ImageWidth": "720", "Software": "12.1.2", "ImageHeight": "1280", "Make": "Apple", "GpsIso6709": "+27.1281+100.2508+000.000/", "CreateDate": "2024-02-02T08:09:57+00:00", "Model": "iPhone X", "DurationMs": "500" } rexiftool also supports batch parsing of all files in a folder (non-recursive).
cargo run --example rexiftool testdata/:
File: "testdata/embedded-in-heic.mov" ------------------------------------------------ Make => Apple Model => iPhone 15 Pro Software => 17.1 CreateDate => 2023-11-02T12:01:02+00:00 DurationMs => 2795 ImageWidth => 1920 ImageHeight => 1440 GpsIso6709 => +22.5797+113.9380+028.396/ File: "testdata/compatible-brands-fail.heic" ------------------------------------------------ Unrecognized file format, consider filing a bug @ https://github.com/mindeng/nom-exif. File: "testdata/webm_480.webm" ------------------------------------------------ CreateDate => 2009-09-09T09:09:09+00:00 DurationMs => 30543 ImageWidth => 480 ImageHeight => 270 File: "testdata/mka.mka" ------------------------------------------------ DurationMs => 3422 ImageWidth => 0 ImageHeight => 0 File: "testdata/exif-one-entry.heic" ------------------------------------------------ Orientation => 1 File: "testdata/no-exif.jpg" ------------------------------------------------ Error: parse failed: Exif not found File: "testdata/exif.jpg" ------------------------------------------------ ImageWidth => 3072 Model => vivo X90 Pro+ ImageHeight => 4096 ModifyDate => 2023-07-09T20:36:33+08:00 YCbCrPositioning => 1 ExifOffset => 201 MakerNote => Undefined[0x30] RecommendedExposureIndex => 454 SensitivityType => 2 ISOSpeedRatings => 454 ExposureProgram => 2 FNumber => 175/100 (1.7500) ExposureTime => 9997/1000000 (0.0100) SensingMethod => 2 SubSecTimeDigitized => 616 OffsetTimeOriginal => +08:00 SubSecTimeOriginal => 616 OffsetTime => +08:00 SubSecTime => 616 FocalLength => 8670/1000 (8.6700) Flash => 16 LightSource => 21 MeteringMode => 1 SceneCaptureType => 0 UserComment => filter: 0; fileterIntensity: 0.0; filterMask: 0; algolist: 0; ...