exifr
📑 The fastest and most versatile JavaScript EXIF reading library.
Installation
npm install exifr
Why? Yet another exif library?
Yes, there are many great exif libraries and modules already out there, some of which served as an inspiration for this one. But they are usually left unmaintained with plenty of open issues and no hope for accepting PRs. Most of them are not isomorphic, have questionable output format and performance, unnecessary dependencies and one even extends prototype of Number and Buffer! But no pointing fingers.
So why exifr?
Features
- Isomorphic.
Works in both Node and Browsers. - Wide range of inputs
ArrayBuffer
, Uint8Array
, DataView
, <img>
elements, string URL and paths, Object URL, Base64 URL - Blazing Fast.
Like really fast. Like 1-2ms fast. - Efficient.
Only reads first couple of blocks of the file. - Fine grained parsing
only need GPS coords? No need to parse the whole exif) - Promise based
Uses Node.js 10.x experimental Promise FS API - Comes as UMD module (compiled from ESM).
No need to bundle or browserify. Just import
, require()
or <script>
it in your .mjs, .js or .html file. - Simple output
meaningful descriptive strings instead of enum values, dates converted to Date instances, etc... - Written in ES6 as an ES Module.
- No dependencies.
Supports
- Basic TIFF/EXIF support
- XMP Segments - Additional software/photoshop related data. Returned as string (exifr does not include XML parser).
- IPTC Segments - Captions & copyrights
Usage
Works in both Node.js and web browser and accepts pretty much everything you throw at it. Buffer, ArrayBuffer, Uint8Array, string file path, URL, Object URL, Base64 encoded data URL, even <img> element.
Example of usage in Node
import getExif from 'exifr'
getExif('./myimage.jpg')
.then(exif => console.log('Camera:', exif.Make, exif.Model))
.catch(console.error)
ESM in Node
var getExif = require('exifr')
var fs = require('fs').promises
fs.readFile('./myimage.jpg')
.then(getExif)
.then(exif => {
console.log('Latitude', exif.GPSLatitude)
console.log('Longitude', exif.GPSLongitude)
})
.catch(console.error)
Example of usage in Browser
// index.html
<script src="./node_modules/exifr/index.js"></script>
<script src="./myapp.js"></script>
<img src="./myimage.jpg">
var getExif = window.exifr
getExif(document.querySelector('img'))
.then(exif => console.log('Exposure:', exif.ExposureTime))
ESM in browser
// index.html
<input id="filepicker" type="file" multiple>
<script type="module" src="./myapp.mjs"></script>
import getExif from './node_modules/exifr/index.js'
var picker = document.querySelector('#filepicker')
picker.addEventListener('change', async e => {
var files = Array.from(picker.files)
var promises = files.map(getExif)
var exifs = await Promise.all(promises)
var dates = exifs.map(exif => exif.DateTimeOriginal.toGMTString())
console.log(`${files.length} photos taken on:`, dates)
})
Usage in Worker
main.html
var worker = new Worker('./worker.js')
worker.postMessage('../test/IMG_20180725_163423.jpg')
worker.onmessage = e => console.log(e.data)
worker.js
importScripts('./node_modules/exifr/index.js')
var getExif = self.exifr
self.onmessage = async e => postMessage(await getExif(e.data))
API
getExif(arg[, options])
exifr only exports single function (ESM default export). Accepts two arguments.
arg
can be a string path or URL (even Base64 and Object URL / Blob URL), instance of Buffer, ArrayBuffer, Uint8Array, DataView, File, Blob and <img> element.
options
is optional and can be wither object with custom settings, or boolean that enables/disables parsing of all EXIF segments and blocks.
Default options
{
scanWholeFileForce: false,
scanWholeFileFallback: false,
seekChunkSize: 512,
parseChunkSize: 64 * 1024,
postProcess: true,
mergeOutput: true,
tiff: true,
xmp: false,
icc: false,
iptc: false,
exif: true,
gps: true,
thumbnail: false,
interop: false,
}
Note on performance
As you've already read, this lib was built to be fast. Fast enough to handle whole galleries.
We're able to parse image within couple of milliseconds (tens of millis on phones) thanks to selective disk reads (Node.js) and Blob / ArrayBuffer (Browser) manipulations. Because you don't need to read whole file and parse through a MBs of data if we an educated guess can be made to only read a couple small chunks where EXIF usually is. Plus each supported data type is approached differently to ensure the best performance.
Observations from testing with +-4MB pictures (Highest quality, highest resolution Google Pixel photos, tested on a decade old quad code CPU). Note: These are no scientific measurements.
- Node: Selective disk reads take about 1ms.
- Node: Processing fully buffered data take about 2.5ms on average.
- Browser: ArrayBuffer takes about 2ms
- Browser: Blob can go up to 10ms on average.
- Browser: <img> with Object URL as a src varies between 5ms to 30ms
- Drag-n-dropping gallery of 90 images took 160ms to load, parse and create exif objects. Extracting GPS data and logging it to console took another 60ms (220ms all together).
- Phones are significantly slower. Usually 40-150ms per photo. This is seriously impacted by loading the photo into browser, not so much of a parsing problem. But real world photo-to-exif time can be as slow as 150ms.
TODOs and Future ideas
- ICC
- WebP image support
- Parsing readernotes. Probably as an additional opt-in extension file to keep the core as light as possible. node-exif module already has a few great implementations and PRs (Canon makernote).
Licence
MIT, Mike Kovařík, Mutiny.cz