mindeng / nom-exif

Exif/metadata parsing library written in pure Rust, both image (jpeg/heif/heic/jpg/tiff/raf etc.) and video/audio (mov/mp4/3gp/webm/mkv/mka, etc.) files are supported.
https://crates.io/crates/nom-exif
MIT License
47 stars 7 forks source link
exif heif jpeg matroska metadata mkv mov mp4 nom parser quicktime raw rust tiff webm

Nom-Exif

crates.io Documentation LICENSE CI

nom-exif is an Exif/metadata parsing library written in pure Rust with nom.

Supported File Types

Key Features

Unified Workflow for Various File Types

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(())
}

Sync API: MediaSource + MediaParser

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.

Async API: AsyncMediaSource + AsyncMediaParser

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.

GPS Info

ExifIter provides a convenience method for parsing gps information. (Exif & TrackInfo also provide a get_gps_info mthod).

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.

CLI Tool rexiftool

Human Readable Output

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/

Json Dump

cargo run --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"
}

Parsing Files in Directory

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;
...

Changelog

CHANGELOG.md