Awesome
Nom-Exif
nom-exif
is an Exif/metadata parsing library written in pure Rust with
nom.
Supported File Types
- Image
- *.heic, *.heif, etc.
- *.jpg, *.jpeg
- *.tiff, *.tif
- *.RAF (Fujifilm RAW)
- Video/Audio
- ISO base media file format (ISOBMFF): *.mp4, *.mov, *.3gp, etc.
- Matroska based file format: *.webm, *.mkv, *.mka, etc.
Key Features
-
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/rexiftool
is 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),
Seek
rather thanRead
to 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
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;
...