Socket
Socket
Sign inDemoInstall

exifr

Package Overview
Dependencies
Maintainers
1
Versions
41
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

exifr - npm Package Compare versions

Comparing version 2.1.4 to 3.0.0

dist/core.esm.js

58

CHANGELOG.md

@@ -5,2 +5,51 @@ # Changelog

## [3.0.0]
### Breaking changes
#### Exports
- renamed `ExifParser` class to `Exifr`.
- renamed `thumbnailBuffer()` function to `thumbnail()`. It now also returns `Uint8Array` instead of `ArrayBuffer` in browser. Node.js version keeps returning `Buffer`.
#### Output format
- Renamed `options.image` block to `options.ifd0`.
- Renamed `options.thumbnail` block to `options.ifd1`.
- renamed & simplified behavior of `seekChunkSize` and `parseChunkSize`. See `firstChunkSize`, `firstChunkSizeBrowser`, `firstChunkSizeNode`.
- Changed EXIF & IPTC tag dictionary to match [ExifTool](https://exiftool.org/TagNames/EXIF.html). Most tag names remain the same. Some might be changed slightly. You can check out the `src/dicts/*` files for reference. For example: before `{ExposureBiasValue: 0}`, after `{ExposureCompensation: 0}`; before `{WhiteBalance: 'Auto white balance'}`, after `{WhiteBalance: 'Auto'}`
#### Options
- Renamed `output.image` block to `output.ifd0`.
- Renamed `output.thumbnail` block to `output.ifd1`.
- removed `postProcess` property and split its behavior to new properties `sanitize`, `translateKeys`, `translateValues` and `reviveValues`.
- Changed behavior of `options.wholeFile` and renamed to `options.chunked`
#### library bundles
- The library now comes in multiple bundles, with varying number of parsers & tag dictonaries. `lite` bundle is now **recommend as the default for browser** use because of its small footprint.
- Broken down parsers and tag dictionaries into multiple files. No all of them are included in `lite` or `mini` builds.
- `package.json` defined module as `"type": "module"`. All `.js` files are treated as ES Modules by Node.js.
### Added
- ICC Parser
- Older browsers support
- multiple new output builds (so users can prevent importing unused code)
- tags filtering (`pick`/`skip` options)
- `exifr.gps()`
### Changed
- major rewrite of a whole input reader pipeline
- implemented `BufferView` wrapper class for all forms of binary data.
- reimplemented chunked reader
- major rewrite of a whole parser pipeline
- broken the code into separate parser classes & files (TIFF, XMP, IPTC, ICC)
- TIFF is no longer the main parser
- All APP segments are now first searched in the file and then parsed
- implemented base parser class than can be used to implement custom APP-segment parsers by user
- exposed segment parsers
- rewrote readme
## [2.1.4] - 2019-11-10
### Changed
- udpated dependencies
- tweaked demo page
## [2.1.3] - 2019-11-10

@@ -51,2 +100,3 @@

## [1.2.0] - 2019-04-27
### Fixed

@@ -56,2 +106,3 @@ - issue #1

## [1.1.0] - 2018-09-29
### Added

@@ -61,7 +112,10 @@ - AMD module support

## [1.0.0] - 2018-08-01
### Added
- initial implementation
[Unreleased]: https://github.com/MikeKovarik/exifr/compare/v2.1.3...HEAD
[2.1.2]: https://github.com/MikeKovarik/exifr/compare/v2.1.2...v2.1.3
[Unreleased]: https://github.com/MikeKovarik/exifr/compare/v3.0.0...HEAD
[3.0.0]: https://github.com/MikeKovarik/exifr/compare/v2.1.3...v3.0.0
[2.1.4]: https://github.com/MikeKovarik/exifr/compare/v2.1.3...v2.1.4
[2.1.3]: https://github.com/MikeKovarik/exifr/compare/v2.1.2...v2.1.3
[2.1.2]: https://github.com/MikeKovarik/exifr/compare/v2.1.1...v2.1.2

@@ -68,0 +122,0 @@ [2.1.1]: https://github.com/MikeKovarik/exifr/compare/v2.1.0...v2.1.1

56

package.json
{
"name": "exifr",
"version": "2.1.4",
"version": "3.0.0",
"description": "📷 The fastest and most versatile JavaScript EXIF reading library.",

@@ -9,14 +9,14 @@ "author": "Mike Kovarik",

"exif",
"tiff",
"jpg",
"jpeg",
"metadata",
"makernote",
"heic",
"tiff",
"xmp",
"gps",
"image",
"picture",
"photo",
"buffer",
"uint8array"
"icc",
"iptc",
"jfif",
"metadata",
"orientation",
"makernote"
],

@@ -29,17 +29,36 @@ "homepage": "https://mutiny.cz/exifr/",

},
"main": "dist/full.esm.js",
"type": "module",
"types": "index.d.ts",
"scripts": {
"test": "mocha test/test.js",
"test": "mocha test/**.spec.js --exclude test/bundle-mini.spec.js --exclude test/bundle-lite.spec.js --exclude test/bundle-full.spec.js",
"test-bundles": "mocha test/bundle-mini.spec.js && mocha test/bundle-lite.spec.js && mocha test/bundle-full.spec.js",
"test-full": "mocha test/bundle-full.spec.js",
"test-lite": "mocha test/bundle-lite.spec.js",
"test-mini": "mocha test/bundle-mini.spec.js",
"coverage": "c8 npm test",
"build": "rollup -c rollup.config.prod.js",
"dev": "rollup -c rollup.config.dev.js -w"
"build": "rollup -c rollup.config.js"
},
"devDependencies": {
"@babel/core": "^7.6.4",
"@babel/plugin-proposal-class-properties": "^7.5.5",
"@babel/preset-env": "^7.6.3",
"aurelia-script": "^1.5.2",
"c8": "^7.0.1",
"chai": "^4.2.0",
"coveralls": "^3.0.7",
"mocha": "^7.0.0-esm1",
"rollup": "^1.29.0",
"rollup-plugin-notify": "^1.0.1"
"decorate": "^1.0.0",
"mocha": "7.0.0-esm1",
"rollup": "^1.25.2",
"rollup-plugin-babel": "^4.3.3",
"rollup-plugin-notify": "^1.0.1",
"rollup-plugin-terser": "^5.2.0"
},
"c8": {
"exclude": [
"src/file-readers/BlobReader.js",
"src/file-readers/UrlFetcher.js",
"src/util/debug.js",
"test/**.*"
],
"reporter": [

@@ -49,6 +68,5 @@ "html",

"text-summary"
],
"check-coverage": false,
"cache": true
}
]
},
"dependencies": {}
}

@@ -1,14 +0,69 @@

<img src="https://raw.githubusercontent.com/MikeKovarik/exifr/next-major-rewrite/logo/blue-small.png" width="160" alt="exifr">
<img src="https://raw.githubusercontent.com/MikeKovarik/exifr/master/logo/blue-small.png" width="160" alt="exifr">
📷 The fastest and most versatile JavaScript EXIF reading library.
[![Build Status](https://travis-ci.org/MikeKovarik/exifr.svg?branch=master)](https://travis-ci.org/MikeKovarik/exifr)
[![Coverage Status](https://coveralls.io/repos/github/MikeKovarik/exifr/badge.svg)](https://coveralls.io/github/MikeKovarik/exifr)
[![gzip size](http://img.badgesize.io/https://cdn.jsdelivr.net/npm/exifr/dist/mini.umd.js?compression=gzip)](https://www.jsdelivr.com/package/npm/exifr?path=dist)
[![Dependency Status](https://david-dm.org/MikeKovarik/exifr.svg)](https://david-dm.org/MikeKovarik/exifr)
[![gzip size](http://img.badgesize.io/https://unpkg.com/exifr/index.js?compression=gzip)](https://unpkg.com/exifr)
[![Coverage Status](https://coveralls.io/repos/github/MikeKovarik/exifr/badge.svg)](https://coveralls.io/github/MikeKovarik/exifr)
[![jsDelivr downloads](https://data.jsdelivr.com/v1/package/npm/exifr/badge?style=rounded)](https://www.jsdelivr.com/package/npm/exifr)
[![npm downloads size](https://img.shields.io/npm/dm/exifr)](https://npmjs.org/package/exifr)
[![NPM Version](https://img.shields.io/npm/v/exifr.svg?style=flat)](https://npmjs.org/package/exifr)
[![License](http://img.shields.io/npm/l/exifr.svg?style=flat)](LICENSE)
[Usage](#usage)
[Installation](#installation)
[Quick start](#examples)
[API](#api)
[Performance](#performance)
📷 The fastest and most versatile JavaScript EXIF reading library.
Try it yourself - [demo page & playground](https://mutiny.cz/exifr/).
## Features
<img src="https://raw.githubusercontent.com/MikeKovarik/exifr/master/logo/readme-gif.gif" width="35%" align="right">
Works everywhere, parses everything and handles anything you throw at it.
* 🏎️ **Fastest EXIF lib**: +-1ms per file
* 🗃️ **Any input**: buffers, url, &lt;img&gt; tag, and more
* 📷 Files: **.jpg**, **.tif**, **.heic** photos
* 🔎 Segments: **TIFF** (EXIF, GPS, etc...), **XMP**, **ICC**, **IPTC**, **JFIF**
* 📑 **Reads only first few bytes**
* 🔬 **Skips parsing tags you don't need**
* ✨ **Isomorphic**: Browser & Node.js
* 🗜️ **No dependencies**
* 🖼️ Extracts thumbnail
* 💔 Salvages broken files
* 🧩 Modular
* 📚 Customizable tag dictionaries
* 📦 Bundled as UMD/CJS or ESM
* ✔ Tested and benchmarked
* 🤙 Promises
* 🕸 Supports even IE11
You don't need to read the whole file to tell if there's EXIF in it. And you don't need to extract all the data when you're looking for just a few tags. Exifr just jumps through the file structure, from pointer to pointer. Instead of reading it byte by byte, from beginning to end.
Exifr does what no other JS lib does. It's **efficient** and **blazing fast**!
## Usage
`file` can be any binary format (`Buffer`, `Uint8Array`, `Blob` and more), `<img>` element, string path or url.
`options` specify what segments and blocks to parse, filters what tags to pick or skip.
| API | Returns | Description |
|-|-|-|
|`exifr.parse(file)`|`object`|Parses IFD0, EXIF, GPS blocks|
|`exifr.parse(file, true)`|`object`|Parses everything|
|`exifr.parse(file, ['Model', 'FNumber', ...])`|`object`|Parses only specified tags|
|`exifr.parse(file, {options})`|`object`|Custom settings|
|`exifr.gps(file)`|`{latitude, longitude}`|Parses only GPS coords|
|`exifr.orientation(file)`|`number`|Parses only orientation|
|`exifr.thumbnail(file)`|`Buffer|Uint8Array` binary|Extracts embedded thumbnail|
|`exifr.thumbnailUrl(file)`|`string` Object URL|Browser only|
## Installation

@@ -20,62 +75,93 @@

also availabe as UMD bundle transpiled for ES5
Exifr comes in four prebuilt bundles. It's a good idea to start development with `full` and then scale down to `lite`, `mini`, or better yet, build your own around `core` build.
**Old Node.js users**: This library is written as an ES module. If you still use CommonJS, you need load UMD bundle.
```js
// node.js
import * as exifr from 'exifr' // loads 'full' build by default
// older Node.js or CJS project
var exifr = require('exifr/dist/full.umd.js')
// modern browser
import * as exifr from 'node_modules/exifr/dist/mini.esm.js'
```
https://unpkg.com/exifr
**Browser users**: UMD is also compatible with RequireJS. Otherwise exports everything as `window.exifr`
```html
<script src="https://cdn.jsdelivr.net/npm/exifr/dist/lite.umd.js"></script>
```
## Features
`mini` and `lite` are recommended for browsers because of balance between features and file size.
Works everywhere and accepts pretty much everything you throw at it.
Need to support older browsers? Use `legacy` build along with polyfills. Learn more about IE11 at [examples/legacy.html](examples/legacy.html).
* **Isomorphic**.
<br> *Works in both Node and Browsers.*
* **Wide range of inputs**
<br> *`ArrayBuffer`, `Uint8Array`, `DataView`, `<img>` elements, string URL and paths, Object URL, Base64 URL*
* **Blazing Fast**.
<br> *Like really fast. Like 1-2ms fast.*
* **Efficient**.
<br> *Only reads first few bytes of the file.*
* Fine grained parsing
<br> *only need GPS coords? No need to parse the whole exif*
* Promise based
<br> *Uses Node.js 10.x experimental Promise FS API*
* Comes as UMD module (along with ESM source).
<br> *No need to bundle or browserify. Just `import`, `require()` or `<script>` it in your .mjs, .js or .html file.*
* Simple output
<br> *meaningful descriptive strings instead of enum values, dates converted to Date instances, etc...*
* **No dependencies**
#### Bundles
### Supports
* **full** - Contains everything. Intended for use in Node.js.
* **lite** - Reads JPEG and HEIC. Parses TIFF/EXIF and XMP. Includes chunked reader.
* **mini** - Stripped down to basics. Parses most useful TIFF/EXIF from JPEGs. No dictionaries.
* **core** - Contains nothing. It's up to you to import readers, parser and dictionaries you need.
* Basic TIFF/EXIF support
* XMP Segments - Additional software/photoshop related data. Returned as a string (exifr does not include XML parser).
* IPTC Segments - Captions & copyrights
* Embedded thumbnail extraction
Of course, you can use the `full` version in browser, or use any other build in Node.js.
## Usage
| | full | lite | mini | core |
|-----------------|------|------|------|------|
| chunked<br>file readers | BlobReader<br>UrlFetcher<br>FsReader<br>Base64Reader | BlobReader<br>UrlFetcher | BlobReader | none |
| file parsers | `*.jpg`<br>`*.heic`<br>`*.tif` | `*.jpg`<br>`*.heic` | `*.jpg` | none |
| segment<br>parsers | TIFF (EXIF)<br>IPTC<br>XMP<br>ICC<br>JFIF | TIFF (EXIF)<br>XMP | TIFF (EXIF) | none |
| dictionaries | TIFF (+ less frequent tags)<br>IPTC<br>ICC | only TIFF keys<br>(IFD0, EXIF, GPS) | none | none |
| size +- | 60 Kb | 40 Kb | 25 Kb | 15 Kb |
| gzipped | 22 Kb | 12 Kb | 8 Kb | 4 Kb |
| file | `full.esm.js`<br>`full.umd.js`<br>`full.legacy.umd.js` | `lite.esm.js`<br>`lite.umd.js`<br>`lite.legacy.umd.js` | `mini.esm.js`<br>`mini.umd.js`<br>`mini.legacy.umd.js` | `core.esm.js`<br>`core.umd.js` |
ESM in Node.js
## Examples
```js
import * as exifr from 'exifr'
// exifr handles disk reading. Only reads a few hundred bytes.
// exifr reads the file from disk, only a few hundred bytes.
exifr.parse('./myimage.jpg')
.then(exif => console.log('Camera:', exif.Make, exif.Model))
.catch(console.error)
.then(output => console.log('Camera:', output.Make, output.Model))
// Or read the file on your own and feed the buffer into exifr.
fs.readFile('./myimage.jpg')
.then(exifr.parse)
.then(output => console.log('Camera:', output.Make, output.Model))
```
CJS in Node.js
Extract only GPS
```js
let exifr = require('exifr')
let fs = require('fs').promises
// Or read the file on your own and feed the buffer into exifr.
fs.readFile('./myimage.jpg')
.then(exifr.parse)
.then(exif => console.log('lat lon', exif.latitude, exif.longitude))
.catch(console.error)
let {latitude, longitude} = await exifr.gps('./myimage.jpg')
```
Extract only certain tags
```js
let output = await exifr.parse(file, ['ISO', 'Orientation', 'LensModel'])
```
Extracting thumbnail
```js
let thumbBuffer = await exifr.thumbnail(file)
// or get object URL (browser only)
img.src = await exifr.thumbnailUrl(file)
```
Web Worker
```js
let worker = new Worker('./worker.js')
worker.postMessage('../test/IMG_20180725_163423.jpg')
worker.onmessage = e => console.log(e.data)
// tip: try Transferable Objects with ArrayBuffer
worker.postMessage(arrayBuffer, [arrayBuffer])
```
```js
// worker.js
importScripts('./node_modules/exifr/dist/lite.umd.js')
self.onmessage = async e => postMessage(await exifr.parse(e.data))
```
UMD in Browser

@@ -85,7 +171,6 @@

<img src="./myimage.jpg">
<script src="./node_modules/exifr/index.js"></script>
<script src="./node_modules/exifr/dist/lite.umd.js"></script>
<script>
// UMD module exposed on global window.exifr object
exifr.parse(document.querySelector('img'))
.then(exif => console.log('Exposure:', exif.ExposureTime))
let img = document.querySelector('img')
window.exifr.parse(img).then(exif => console.log('Exposure:', exif.ExposureTime))
</script>

@@ -99,8 +184,6 @@ ```

<script type="module">
import {parse} from './node_modules/exifr/index.js'
import {parse} from './node_modules/exifr/dist/lite.esm.js'
document.querySelector('#filepicker').addEventListener('change', async e => {
let files = Array.from(e.target.files)
let promises = files.map(parse)
let exifs = await Promise.all(promises)
let exifs = await Promise.all(files.map(parse))
let dates = exifs.map(exif => exif.DateTimeOriginal.toGMTString())

@@ -112,190 +195,668 @@ console.log(`${files.length} photos taken on:`, dates)

Extracting thumbnail
### Demos & more examples
* [playground](https://mutiny.cz/exifr)
* [examples/thumbnail.html](https://mutiny.cz/exifr/examples/thumbnail.html), [code](examples/thumbnail.html)
* [examples/worker.html](https://mutiny.cz/exifr/examples/worker.html), [code](examples/worker.html)
* [examples/legacy.html](https://mutiny.cz/exifr/examples/legacy.html), [code](examples/legacy.html) (view this in IE11)
* [benchmark/formats-reading.html](https://mutiny.cz/exifr/benchmark/formats-reading.html), [code](benchmark/formats-reading.html) (tests speed of processing of various input types)
* [benchmark/gps-dnd.html](https://mutiny.cz/exifr/benchmark/gps-dnd.html), [code](benchmark/gps-dnd.html) (Drag-n-Drop multiple photos and mesure the time it takes to extract GPS)
and a lot more in the [examples/](examples/) folder
## API
exifr exports `parse()`, `gps()`, `orientation()`, `thumbnail()`, `thumbnailUrl()` functions and `Exifr` class
### `parse(file[, options])`
Returns: `Promise<object>`
Accepts [file](#file-argument) (in any format), parses it and returns exif object. Optional [options](#options-argument) argument can be specified.
### `gps(file)`
Returns: `Promise<object>`
Extracts only GPS coordinates from a photo.
*Uses `pick`/`skip` filters and perf improvements to only extract latitude and longitude tags from GPS block. And to get GPS-IFD pointer it only scans through IFD0 without reading any other unrelated data.*
Check out [examples/gps.js](examples/gps.js) to learn more.
### `orientation(file)`
Returns: `Promise<number>`
Extracts only photo's orientation.
### `thumbnail(file)`
Returns: `Promise<Buffer|Uint8Array>`
Extracts embedded thumbnail from the photo, returns `Uint8Array`.
*Only parses as little EXIF as necessary to find offset of the thumbnail.*
Check out [examples/thumbnail.html](examples/thumbnail.html) and [examples/thumbnail.js](examples/thumbnail.js) to learn more.
### `thumbnailUrl(file)`
Returns: `Promise<string>`
<br>
browser only
Exports the thumbnail wrapped in [Object URL](https://developer.mozilla.org/en-US/docs/Web/API/File/Using_files_from_web_applications#Example_Using_object_URLs_to_display_images). The URL has to be revoked when not needed anymore.
### `Exifr` class
Aforementioned functions are wrappers that internally:
1) instantiate `new Exifr(options)` class
2) call `.read(file)` to read the file
3) call `.parse()` or `.extractThumbnail()`
You can instantiate `Exif` yourself to parse metadata and extract thumbnail efficiently at the same time. In Node.js it's also necessary to close the file with `.file.close()` if it's read in chunked mode.
```js
let img = document.querySelector("#thumb")
document.querySelector('input[type="file"]').addEventListener('change', async e => {
let file = e.target.files[0]
img.src = await exifr.thumbnailUrl(file)
})
let exr = new Exifr(options)
let output = await exr.read(file)
let buffer = await exr.extractThumbnail()
if (exr.file.chunked) await exr.file.close()
```
### `file` argument
* `string`
* file path
* URL, [Object URL](https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL)
* Base64 or [Base64 URL](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs)
* `Buffer`
* `ArrayBuffer`
* `Uint8Array`
* `DataView`
* `Blob`, `File`
* `<img>` element
### `options` argument
* `array` of tags to parse, shortcut for [`options.pick`](#optionspick)
* `true` shortcut to parse all [segments](#app-segments) and [blocks](#tiff-ifd-blocks)
* `object` with granular settings
All other and undefined properties are inherited from defaults:
```js
let thumbBuffer = await exifr.thumbnailBuffer(imageBuffer)
let defaultOptions = {
// APP Segments
jfif: false,
tiff: true,
xmp: false,
icc: false,
iptc: false,
// TIFF Blocks
ifd0: true, // aka image
ifd1: false, // aka thumbnail
exif: true,
gps: true,
interop: false,
// Other TIFF tags
makerNote: false,
userComment: false,
// Filters
skip: [],
pick: [],
// Formatters
translateKeys: true,
translateValues: true,
reviveValues: true,
sanitize: true,
mergeOutput: true,
silentErrors: true,
// Chunked reader
chunked: true,
firstChunkSize: undefined,
firstChunkSizeNode: 512,
firstChunkSizeBrowser: 65536, // 64kb
chunkSize: 65536, // 64kb
chunkLimit: 5
}
```
Usage in WebWorker
### Tag filters
Exifr can avoid reading certain tags, instead of reading but not including them in the output, like other exif libs do. For example MakerNote tag from EXIF block is isually very large - tens of KBs. Reading such tag is a waste of time if you don't need it.
*Tip: Using numeric tag codes is even faster than string names because exifr doesn't have to look up the strings in dictionaries.*
#### `options.pick`
Type: `Array<string|number>`
Array of the only tags that will be parsed.
Specified tags are looked up in a dictionary. Their respective blocks are enabled for parsing, all other blocks are disabled. Parsing ends as soon as all requested tags are extracted.
```js
let worker = new Worker('./worker.js')
worker.postMessage('../test/IMG_20180725_163423.jpg')
worker.onmessage = e => console.log(e.data)
// Only extracts three tags from EXIF block. IFD0, GPS and other blocks disabled.
{pick: ['ExposureTime', 'FNumber', 'ISO']}
// Only extracts three tags from EXIF block and one tag from GPS block.
{pick: ['ExposureTime', 'FNumber', 'ISO', 'GPSLatitude']}
// Extracts two tags from GPS block and all of IFD0 and EXIF blocks which are enabled by default.
{gps: {pick: ['GPSLatitude', 0x0004]}}
```
#### `options.skip`
Type: `Array<string|number>`
<br>
Default: `['MakerNote', 'UserComments']`
Array of the tags that will not be parsed.
By default, MakerNote and UserComment tags are skipped. But that is configured [elsewhere](#notable-tiff-tags).
```js
// worker.js
importScripts('./node_modules/exifr/index.js')
let exifr = self.exifr // UMD
self.onmessage = async e => postMessage(await exifr.parse(e.data))
// Skips reading these three tags in any block
{skip: ['ImageWidth', 'Model', 'FNumber', 'GPSLatitude']}
// Skips reading three tags in EXIF block
{exif: {skip: ['ImageUniqueID', 42033, 'SubSecTimeDigitized']}}
```
## API
### Segments & Blocks
exifr exports `parse`, `thumbnailBuffer`, `thumbnailUrl` functions and `ExifParser` class
EXIF became synonymous for all image metadata, but it's actually just one of many blocks inside TIFF segment. And there are more segment than just TIFF.
### `parse(input[, options])` => `Promise<object>`
#### APP Segments
Accepts any input argument, parses it and returns exif object.
Jpeg stores various formats of data in APP-Segments. Heic and Tiff file formats use different structures or naming conventions but the idea is the same, so we refer to TIFF, XMP, IPTC, ICC and JFIF as Segments.
### `thumbnailBuffer(input)` => `Promise<Buffer|ArrayBuffer>`
* `options.tiff` type `bool|object|Array` default: `true`
<br>TIFF APP1 Segment - Basic TIFF/EXIF tags, consists of blocks: IFD0 (image), IFD1 (thumbnail), EXIF, GPS, Interop
* `options.jfif` type `bool` default: `false`
<br>JFIF APP0 Segment - Additional info
* `options.xmp` type `bool` default: `false`
<br>XMP APP1 Segment - additional XML data
* `options.iptc` type `bool` default: `false`
<br>IPTC APP13 Segment - Captions and copyrights
* `options.icc` type `bool` default: `false`
<br>ICC APP2 Segment - Color profile
Extracts embedded thumbnail from the photo and returns it as a `Buffer` (Node.JS) or an `ArrayBuffer` (browser).
#### TIFF IFD Blocks
Only parses as little EXIF as necessary to find offset of the thumbnail.
TIFF Segment consists of various IFD's (Image File Directories) aka blocks.
### `thumbnailUrl(input)` => `Promise<string>`
* `options.ifd0` (alias `options.image`) type `bool|object|Array` default: `true`
<br>IFD0 - Basic info about the image
* `options.ifd1` (alias `options.thumbnail`) type `bool|object|Array` default: `false`
<br>IFD1 - Info about embedded thumbnail
* `options.exif` type `bool|object|Array` default: `true`
<br>EXIF SubIFD - Detailed info about photo
* `options.gps` type `bool|object|Array` default: `true`
<br>GPS SubIFD - GPS coordinates
* `options.interop` type `bool|object|Array` default: `false`
<br>Interop SubIFD - Interoperability info
Browser only - exports the thumbnail wrapped in Object URL.
#### Notable TIFF tags
User is expected to revoke the URL when not needed anymore.
Notable large tags from EXIF block that are not parsed by default but can be enabed if needed.
### `ExifParser` class
* `options.makerNote` type: `bool` default: `false`
<br>0x927C MakerNote tag
* `options.userComment` type: `bool` default: `false`
<br>0x9286 UserComment tag
Afore mentioned functions are wrappers that internally instantiate `new ExifParse(options)` class, then call `parser.read(input)`, and finally call either `parser.parse()` or `parser.extractThumbnail()`.
#### Shortcuts
To do both parsing EXIF and extracting thumbnail efficiently you can use this class yourself.
`options.tiff` serves as a shortcut for configuring all TIFF blocks:
* `options.tiff = true` enables all TIFF blocks (sets them to `true`).
* `options.tiff = false` disables all TIFF blocks (sets them to `false`) except for those explicitly set to `true` in `options`.
* `options.tiff = {...}` applies the same sub-options to all TIFF blocks that are enabled.
`options.tiff = false` can be paired with any other block(s) to disable all other blocks except for said block.
```js
let parser = new ExifParser(options)
let exif = await parser.read(input)
let thumb = await parser.extractThumbnail()
{interop: true, tiff: false}
// is a shortcut for
{interop: true, ifd0: false, exif: false, gps: false, ifd1: true}
```
### Arguments and options
Each TIFF block and the whole `tiff` segment can also be configured with `object` or `array`, much like the [`options`](#options-argument) argument.
#### `input`
can be:
* `string` file path
* `string` URL
* `string` Base64
* `string` Object URL / Blob URL
* `Buffer`
* `ArrayBuffer`
* `Uint8Array`
* `DataView`
* `File`
* `Blob`
* `<img>` element
* `object` - enabled with custom options - [filters](#tag-filters) ([`pick`](#optionspick), [`skip`](#optionsskip)) and [formatters](#output-format) ([`translateKeys`](#optionstranslatekeys), [`translateValues`](#optionstranslatevalues), [`reviveValues`](#optionsrevivevalues))
* `array` - enabled, but only [picks](#optionspick) tags from this array
#### `options`
is optional argument and can be either:
* `object` with granular settings
* `boolean` shortcut to enable parsing all segments and blocks
TIFF blocks automatically inherit from `options.tiff` and then from `options`.
#### Reading file from disk or fetching url
```js
// Only extract FNumber + ISO tags from EXIF and GPSLatitude + GPSLongitude from GPS
{
exif: true,
gps: true,
pick: ['FNumber', 'ISO', 'GPSLatitude', 0x0004] // 0x0004 is GPSLongitude
}
// is a shortcut for
{
exif: ['FNumber', 'ISO'],
gps: ['GPSLatitude', 0x0004]
}
// which is another shortcut for
{
exif: {pick: ['FNumber', 'ISO']},
gps: {pick: ['GPSLatitude', 0x0004]}
}
```
##### Chunked mode
### Chunked reader
In browser it's sometimes better to fetch a larger chunk in hope that it contains the whole EXIF (and not just its beginning like in case of `options.seekChunkSize`) in prevention of additional loading and fetching. `options.parseChunkSize` sets that number of bytes to download at once. Node.js only relies on the `options.seekChunkSize`.
#### `options.chunked`
Type: `bool`
<br>
Default: `true`
##### Whole file mode
Exifr can read only a few chunks instead of the whole file. It's much faster, saves memory and unnecessary disk reads or network fetches. Works great with complicated file structures - .tif files may point to metadata scattered throughout the file.
If you're not concerned about performance and time (mostly in Node.js) you can tell `exifr` to just read the whole file into memory at once.`
**How it works:** A first small chunk (of `firstChunkSize`) is read to determine if the file contains any metadata at all. If so, reading subsequent chunks (of `chunkSize`) continues until all requested segments are found or until `chunkLimit` is reached.
* `options.wholeFile` `bool/undefined` default `undefined`
<br>Sets whether to read the file as a whole or just by small chunks.
<br>*Used when file path or url to the image is given.*
* `true` - whole file mode
<br>forces fetching/reading whole file
* `undefined` - chunked mode, **default value**
<br>Reads first few bytes of the file to look for EXIF in (`seekChunkSize`) and allows reading/fetching additional chunks.
<br>Ends up with multiple small disk reads for each segment (xmp, icc, iptc)
<br>*NOTE: Very efficient in Node.js, especially with SSD. Not ideal for browsers*
* `false` - chunked mode
<br>Reads only one much larger chunk (`parseChunkSize`) in hopes that the EXIF isn't larger then the chunk.
<br>Disallows further disk reads. i.e. ignores any EXIF found beyond the chunk.
**Supported inputs:** Chunked is only effective with `Blob`, `<img>` element, `string` url, disk path, or base64. These inputs are not yet processed or read into memory. Each input format is implemented in a separate file reader class. Learn more about [file readers and modularity here](#modularity-pugin-api).
* `options.seekChunkSize` `number` default: `512` Bytes (0.5 KB)
<br>Byte size of the first chunk that will be read and parsed for EXIF.
<br>*EXIF is usually within the first few bytes of the file. If not than there likely is no EXIF. It's not necessary to read through the whole file.*
<br>Node.js: Used for all input types.
<br>Browser: Used when input `arg` is buffer. Otherwise, `parseChunkSize` is used.
**If you use URL as input:** Fetching chunks (implemented in `UrlFetcher`) from web server uses [HTTP Range Requests](https://developer.mozilla.org/en-US/docs/Web/HTTP/Range_requests). Range request may fail if your server does not support ranges, if it's not configured properly or if the fetched file is smaller than the first chunk size. Test your web server or disable chunked reader with `{chunked: false}` when in doubt.
* `options.parseChunkSize` `number` default: `64 * 1024` (64KB)
<br>Size of the chunk to fetch in the browser in chunked mode.
<br>*Much like `seekChunkSize` but used in the browser (and only if we're given URL) where subsequent chunk fetching is more expensive than fetching one larger chunk with hope that it contains the EXIF.*
<br>Node.js: Not used.
<br>Browser: Used when input `arg` is string URL. Otherwise, `seekChunkSize` is used.
#### `options.firstChunkSize`
Type: `number`
<br>
Default: `512` Bytes in Node / `65536` (64 KB) in browser
If parsing file known to have EXIF fails try:
* Increasing `seekChunkSize`
* Increasing `parseChunkSize` in the browser if file URL is used as input.
* Disabling chunked mode (read whole file)
Size (in bytes) of the first chunk that probes the file for traces of exif or metadata.
#### Segments & Blocks
*In browser, it's usually better to read just a larger chunk in hope that it contains the whole EXIF (and not just the beginning) instead of loading multiple subsequent chunks. Whereas in Node.js it's preferable to read as little data as possible and `fs.read()` does not cause slowdowns.*
* `options.tiff: true` - APP1 - TIFF
<br>The basic EXIF tags (image, exif, gps)
<br>TIFF contains the following blocks / is requred for reading the following block:
* `options.exif: true` - Sub Exif.
* `options.gps: true` - GPS latitue and longitude data.
* `options.thumbnail: false` - Size and other information about embedded thumbnail.
* `options.interop: false` - This is a thing too.
* `options.xmp: false` - APP1 - XMP
<br>XML based extension, often used by editors like Photoshop.
* ~~`options.icc: false` - APP2 - ICC~~
<br>~~Not implemented yet~~
* `options.iptc: false` - APP13 - IPTC
<br>Captions and copyrights
#### `options.chunkSize`
Type: `number`
<br>
Default: `65536` Bytes (64 KB)
#### Output format
Size of subsequent chunks that may be read after the first chunk.
* `options.postProcess` `number` default: `true`
<br>Translate enum values to strings, convert dates to Date instances, etc...
#### `options.chunkLimit`
Type: `number`
<br>
Default: `5`
* `options.mergeOutput` `number` default: `true`
<br>Changes output format by merging all segments and blocks into a single object.
Max amount of subsequent chunks allowed to read in which exifr searches for data segments and blocks. I.e. failsafe that prevents from reading the whole file if it does not contain all of the segments or blocks requested in `options`.
## Usage with Webpack, Parcel, Rollup and other bundlers.
This limit is bypassed if multi-segment segments ocurs in the file and if `options.multiSegment` allows reading all of them.
Out of the box the library comes in:
1) **index.mjs** - the modern **ES Module** format
<br>*Not bundled, index.mjs further imports few other files from src/ folder*
<br>*You may want to bundle & treeshake this yourself*
2) **index.js** - **UMD bundle** which
<br>*combines the classic Node.js CommonJS `require('exifr')` with AMD/Require.js as well as browser-friendly `<script src="node_modules/exifr/index.js">`*
<br>*All in one file*
<br>*Prebundled with Rollup*
*If the exif isn't found within N chunks (64\*5 = 320KB) it probably isn't in the file and it's not worth reading anymore.*
Under the hood `exifr` dynamically imports Node.js `fs` module, but only ran under Node.js. The import is obviously not triggered in browser. Your bundler may however pick up on it and fail with something like `Error: Can't resolve 'fs'`.
### Output format
Parcel works out of the box and Webpack should too because we added the ignore magic comment to the library's source code `import(/* webpackIgnore: true */ 'fs')`.
#### `options.mergeOutput`
Type: `bool`
<br>
Default: `true`
If this does not work for you, try adding `node: {fs: 'empty'}` and `target: 'web'` or `target: 'webworker'` to your Webpack config.
Merges all parsed segments and blocks into a single object.
Or try adding similar settings to your bundler of choice.
**Warning**: `mergeOutput: false` should not be used with `translateKeys: false` or when parsing both `ifd0` (image) and `ifd1` (thumbnail). Tag keys are numeric, sometimes identical and may collide.
## Note on performance
<table><tr>
<td>mergeOutput: false</td>
<td>mergeOutput: true</td>
</tr><tr><td><pre>
{
Make: 'Google',
Model: 'Pixel',
FNumber: 2,
Country: 'Czech Republic',
xmp: '&lt;x:xmpmeta&gt;&lt;rdf:Description>...'
}
</pre></td><td><pre>
{
ifd0: {
Make: 'Google',
Model: 'Pixel'
},
exif: {
FNumber: 2
},
iptc: {
Country: 'Czech Republic'
},
xmp: '&lt;x:xmpmeta&gt;&lt;rdf:Description>...'
}
</pre></td></tr></table>
As you've already read, this lib was built to be fast. Fast enough to handle whole galleries.
#### `options.sanitize`
Type: `bool`
<br>
Default: `true`
We're able to parse image within a 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 the whole file and parse through a MBs of data if we an educated guess can be made to only read a couple of small chunks where EXIF usually is. Plus each supported data type is approached differently to ensure the best performance.
Cleans up unnecessary, untransformed or internal tags (IFD pointers) from the output.
Observations from testing with +-4MB pictures (*Highest quality, highest resolution Google Pixel photos, tested on a decade old quad core CPU*). Note: These are no scientific measurements.
#### `options.silentErrors`
Type: `bool`
<br>
Default: `true`
* 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.
Error messages are stored at `output.errors` instead of thrown as `Error` instances and causing promise rejection.
## Next version
The library is already production ready and battle-tested, but there's always room for improvement.
Failing silently enables reading broken files. But only file-structure related errors are caught.
Version 3.0.0 is currently underway and developed on a branch [next-major-rewrite](https://github.com/MikeKovarik/exifr/tree/next-major-rewrite). It is a complete rewrite with focus on modularization and even better performance and stability.
#### `options.translateKeys`
Type: `bool`
<br>
Default: `true`
## Licence
Translates tag keys from numeric codes to understandable string names. I.e. uses `Model` instead of `0x0110`.
Most keys are numeric. To access the `Model` tag use `output.ifd0[0x0110]` or `output.ifd0[272]`
Learn more about [dictionaries](#modularity-pugin-api).
**Warning**: `translateKeys: false` should not be used with `mergeOutput: false`. Keys may collide because ICC, IPTC and TIFF segments use numeric keys starting at 0.
<table><tr>
<td>translateKeys: false</td>
<td>translateKeys: true</td>
</tr><tr><td><pre>{
0x0110: 'Pixel', // IFD0
90: 'Vsetín', // IPTC
64: 'Perceptual', // ICC
desc: 'sRGB IEC61966-2.1', // ICC
}</pre></td><td><pre>{
Model: 'Pixel', // IFD0
City: 'Vsetín', // IPTC
RenderingIntent: 'Perceptual', // ICC
ProfileDescription: 'sRGB IEC61966-2.1', // ICC
}</pre></td></tr></table>
#### `options.translateValues`
Type: `bool`
<br>
Default: `true`
Translates tag values from raw enums to understandable strings.
Learn more about [dictionaries](#modularity-pugin-api).
<table><tr>
<td>translateValues: false</td>
<td>translateValues: true</td>
</tr><tr><td><pre>
{
Orientation: 1,
ResolutionUnit: 2,
DeviceManufacturer: 'GOOG'
}
</pre></td><td><pre>
{
Orientation: 'Horizontal (normal)',
ResolutionUnit: 'inches',
DeviceManufacturer: 'Google'
}
</pre></td></tr></table>
#### `options.reviveValues`
Type: `bool`
<br>
Default: `true`
Converts dates from strings to a Date instances and modifies few other tags to a more readable format.
Learn more about [dictionaries](#modularity-pugin-api).
<table><tr>
<td>reviveValues: false</td>
<td>reviveValues: true</td>
</tr><tr><td><pre>
{
GPSVersionID: [0x02, 0x02, 0x00, 0x00],
ModifyDate: '2018:07:25 16:34:23',
}
</pre></td><td><pre>
{
GPSVersionID: '2.2.0.0',
ModifyDate: &lt;Date instance: 2018-07-25T14:34:23.000Z&gt;,
}
</pre></td></tr></table>
## Advanced
Tips for advanced users. You don't need to read further unless you're into customization and bundlers.
#### Modularity, Pugin API
This is mostly **relevant for Web Browsers**, where file size and unused code elimination is important.
The library's functionality is divided into four categories.
* **File reader** reads different input data structures by chunks.
<br> `BlobReader` (browser), `UrlFetcher` (browser), `FsReader` (Node.js), `Base64Reader`
<br>See [`src/file-parsers/`](src/file-readers).
* **File parser** looks for metadata in different file formats
<br>`.jpg`, `.tiff`, `.heic`
<br>See [`src/file-parsers/`](src/file-parsers).
* **Segment parser** extracts data from various metadata formats (JFIF, TIFF, XMP, IPTC, ICC)
<br>TIFF/EXIF (IFD0, EXIF, GPS), XMP, IPTC, ICC, JFIF
<br>See [`src/segment-parsers/`](src/segment-parsers).
* **Dictionary** affects the way the parsed output looks.
<br>See [`src/dicts/`](src/dicts).
Each reader, parser and dictionary is broken into a separate file that can be loaded and used independently. This way you can build your own bundle with only what you need, eliminate dead code and save tens of KBs of unused dictionaries.
Any file format can be read out of the box. But custom reader class for each format is needed to enable chunked reading.
### Translation dictionaries
EXIF Data are mostly numeric enums, stored under numeric code. Dictionaries are needed to translate them into meaningful output. But they take up a lot of space (40 KB out of `full` build's 60 KB). So it's a good idea to make your own bundle and shave off the dicts you don't need.
* **Key dict** translates object keys from numeric codes to string names (`output.Model` instead of `output[0x0110]`)
* **Value dict** translates vales from enum to string description (`Orientation` becomes `'Rotate 180'` instead of `3`)
* **Reviver** further modifies the value (converts date string to an instance of `Date`)
Exifr's dictionaries are based on [exiftool.org](https://exiftool.org). Specifically these:
TIFF ([EXIF](https://exiftool.org/TagNames/EXIF.html) & [GPS](https://exiftool.org/TagNames/GPS.html)),
[ICC](https://exiftool.org/TagNames/ICC_Profile.html),
[IPTC](https://exiftool.org/TagNames/IPTC.html),
[JFIF](https://exiftool.org/TagNames/JFIF.html)
<details>
<summary><b>Configure your own exifr</b></summary>
Check out <a href="examples/custom-build.js">examples/custom-build.js</a>.
Scenario 1: We're using `lite` build and it's ok, but some tags are left untranslated. To fix that we need to import extension of TIFF dictionary with less frequent tags.
```js
// Lite bundle only contains basic set of TIFF tags
import * as exifr from 'exifr/dist/lite.esm.js'
// Load TIFF dict extension with names of less frequent tags.
import 'exifr/src/dicts/tiff-other-keys.js'
```
Scenario 2: We'll be handling `.jpg` files in blob format and we want to extract ICC data in human-readable format. For that we'll need dictionaries for ICC segment.
```js
// Core bundle has nothing in it
import * as exifr from 'exifr/dist/core.esm.js'
// Now we import what we need
import 'exifr/src/file-readers/BlobReader.js'
import 'exifr/src/file-parsers/jpeg.js'
import 'exifr/src/segment-parsers/icc.js'
import 'exifr/src/dicts/icc-keys.js'
import 'exifr/src/dicts/icc-values.js'
```
Scenario 3: We want to parse `.heic` and `.tiff` photos, extract EXIF block (of TIFF segment). We only need the values to be translated. Keys will be left untranslated but we don't mind accessing them with raw numeric keys - `output[0xa40a]` instead of `output.Sharpness`. Also, we're not importing any (chunked) file reader because we only work with Uint8Array data.
```js
import * as exifr from 'exifr/dist/core.esm.js'
import 'exifr/src/file-parsers/heic.js'
import 'exifr/src/file-parsers/tiff.js'
import 'exifr/src/segment-parsers/tiff.js'
import 'exifr/src/dicts/tiff-exif-values.js'
```
</details>
<details>
<summary><b>Dictionary customization</b></summary>
```js
// Modify single tag's 0xa409 (Saturation) translation
import {tagKeys, tagValues} from 'exifr'
let exifKeys = tagKeys.get('exif')
let exifValues = tagValues.get('exif')
exifKeys.set(0xa409, 'Saturation')
exifValues.set(0xa409, {
0: 'Normal',
1: 'Low',
2: 'High'
})
```
```js
// Modify single tag's GPSDateStamp value is processed
import {tagRevivers} from 'exifr'
let gpsRevivers = tagRevivers.get('gps')
gpsRevivers.set(0x001D, rawValue => {
let [year, month, day] = rawValue.split(':').map(str => parseInt(str))
return new Date(year, month - 1, day)
})
```
```js
// Create custom dictionary for GPS block
import {tagKeys, createDictionary} from 'exifr'
createDictionary(tagKeys, 'gps', [
[0x0001, 'LatitudeRef'],
[0x0002, 'Latitude'],
[0x0003, 'LongitudeRef'],
[0x0004, 'Longitude'],
])
```
```js
// Extend existing IFD0 dictionary
import {tagKeys, createDictionary} from 'exifr'
createDictionary(tagKeys, 'ifd0', [
[0xc7b5, 'DefaultUserCrop'],
[0xc7d5, 'NikonNEFInfo'],
...
])
```
</details>
<details>
<summary><b>Usage with Webpack, Parcel, Rollup, Gatsby, etc...</b></summary>
Under the hood exifr dynamically imports Node.js <code>fs</code> module. The import is obviously only used in Node.js and not triggered in a browser. But your bundler may, however, pick up on it and fail with something like <code>Error: Can't resolve 'fs'</code>.
Parcel works out of the box and Webpack should too because of <code>webpackIgnore</code> magic comment added to the library's source code <code>import(/* webpackIgnore: true */ 'fs')</code>.
If this does not work for you, try adding <code>node: {fs: 'empty'}</code> and <code>target: 'web'</code> or <code>target: 'webworker'</code> to your Webpack config. Or similar settings for your bundler of choice.
Alternatively, create your own bundle around <code>core</code> build and do not include <code>FsReader</code> in it.
Exifr is written using modern syntax, mainly async/await. You may need to add <code>regenerator-runtime</code> or reconfigure babel.
</details>
<details>
<summary><b>Custom XMP parser</b></summary>
Exifr does not come with an XML parser out of the box to keep the library simple and light-weight. There's plenty of XML parsers on npm. Exifr only extracts the XMP string and you can parse it.
You can also inject XML parser into exifr and have it process the XMP string.
```js
// Exifr offers you an API for using your own XML parser while parsing XMP.
// 1) get the XmlParser class.
let XmpParser = Exifr.segmentParsers.get('xmp')
// 2) Implement parseXml() method. It accepts string argument. What is returned ends up as output.xmp.
XmpParser.prototype.parseXml = function(xmpString) {
return 'Bring Your Own XML parser here: ' + xmpString
}
```
</details>
## Performance
### Tips for better performance
Here are a few tips for when you need to squeeze an extra bit of speed out of exifr when processing a large amount of files. Click to expand.
<details>
<summary><b>Use <code>options.pick</code> if you only need certain tags</b></summary>
Unlike other libraries, exifr can only parse certain tags, avoid unnecessary reads and end when the last picked tag was found.
```js
// do this:
let {ISO, FNumber} = await exifr.parse(file, {exif: ['ISO', 'FNumber']})
// not this:
let {ISO, FNumber} = await exifr.parse(file)
```
</details>
<details>
<summary><b>Disable <code>options.ifd0</code> if you don't need the data</b></summary>
Even though IFD0 (Image block) stores pointers to EXIF and GPS blocks and is thus necessary to be parsed to access said blocks. Exifr doesn't need to read the whole IFD0, it just looks for the pointers.
```js
// do this:
let options = {ifd0: false, exif: true}
// not this:
let options = {exif: true}
```
</details>
<details>
<summary><b>Use <code>exifr.gps()</code> if you only need GPS</b></summary>
If you only need to extract GPS coords, use <code>exifr.gps()</code> because it is fine-tuned to do exactly this and nothing more. Similarly there's <code>exifr.orientation()</code>.
```js
// do this:
exifr.gps(file)
// not this:
exifr.parse(file, {gps: true})
```
</details>
<details>
<summary><b>Cache <code>options</code> object</b></summary>
If you parse multiple files with the same settings, you should cache the <code>options</code> object instead of inlining it. Exifr uses your <code>options</code> to create an instance of <code>Options</code> class under the hood and uses <code>WeakMap</code> to find previously created instance instead of creating q new one each time.
```js
// do this:
let options = {exif: true, iptc: true}
for (let file of files) exif.parse(file, options)
// not this:
for (let file of files) exif.parse(file, {exif: true, iptc: true})
```
</details>
### Remarks
**File reading:** You don't need to read the whole file and parse through a MBs of data. Exifr takes an educated guess to only read a small chunk of the file where metadata is usually located. Each platform, file format, and data type is approached differently to ensure the best performance.
**Finding metadata:** Other libraries use brute force to read through all bytes until `'Exif'` string is found. Whereas exifr recognizes the file structure, consisting of segments (JPEG) or nested boxes (HEIC). This allows exifr to read just a few bytes here and there, to get the offset and size of the segment/box and pointers to jump to the next.
**HEIC:** Simply finding the exif offset takes 0.2-0.3ms with exifr. Compare that to [https://github.com/exif-heic-js/exif-heic-js](https://github.com/exif-heic-js/exif-heic-js) which takes about 5-10ms on average. Exifr is up to 30x faster.
### Benchmarks
Try the benchmark yourself at [benchmark/chunked-vs-whole.js](https://github.com/MikeKovarik/exifr/blob/master/benchmark/chunked-vs-whole.js)
```
user reads file 8.4 ms
exifr reads whole file 8.2 ms
exifr reads file by chunks 0.5 ms <--- !!!
only parsing, not reading 0.2 ms <--- !!!
```
Observations from testing with +-4MB pictures (*Highest quality Google Pixel photos. Tested on a mid-range dual-core i5 machine with SSD*).
* Node: Parsing after `fs.readFile` = 0.3ms
* Node: Reading & parsing by chunks = 0.5ms
* Browser: Processing `ArrayBuffer` = 3ms
* Browser: Processing `Blob` = 7ms
* Browser: `<img>` with Object URL = 3ms
* Drag-n-dropping gallery of 100 images and extracting GPS data takes about 65ms.
* Phones are about 4x slower. Usually 4-30ms per photo.
Be sure to visit [**the exifr playground**](https://mutiny.cz/exifr) or [benchmark/gps-dnd.html](https://mutiny.cz/exifr/benchmark/gps-dnd.html), drop in your photos and watch the *parsed in* timer.
## Notable breaking changes, migration from 2.x.x
see [`CHANGELOG.md`](CHANGELOG.md)
## License
MIT, Mike Kovařík, Mutiny.cz

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc