Fast, cross-platform Node.js access to ExifTool.
If links are broken, please read this on exiftool-vendored.js.org.
-
Best-of-class cross-platform performance and reliability.
Expect an order of magnitude faster performance than other packages.
-
Proper extraction of
- dates with correct timezone offset encoding, when available)
- latitudes & longitudes as floats (where negative values indicate west or south of the meridian)
-
Support for
- extracting embedded binaries, like thumbnail and preview images
- writing tags
- rescuing metadata
-
Robust type definitions of the top 99.5% tags used by over 6,000 different camera makes and models (see an example)
-
Auditable ExifTool source code (the vendored code is checksum verified)
-
Automated updates to ExifTool (as new versions come out monthly)
-
Robust test coverage, performed with the latest Node v6, v8, and v10 on Linux, Mac, & Windows.
yarn add exiftool-vendored
or
npm install --save exiftool-vendored
Note that exiftool-vendored
provides an installation of ExifTool relevant
for your local platform through
optionalDependencies.
You shouldn't include either the exiftool-vendored.exe
or
exiftool-vendored.pl
as direct dependencies to your project.
See the Version History for breaking changes since you last updated.
There are many configuration options to ExifTool, but all values have (more or less sensible) defaults.
Those defaults have been used to create an exported
exiftool
singleton. Feel free to review the ExifTool
constructor parameters and override default
values where appropriate if the defaults wont' work for you, but you should use
your singleton to minimize system load. Note that if you don't use the default
singleton, note that you don't need to .end()
it.
// We're using the singleton here for convenience:
const exiftool = require("exiftool-vendored").exiftool
// And to verify everything is working:
exiftool
.version()
.then(version => console.log(`We're running ExifTool v${version}`))
ExifTool.read()
returns a Promise to a Tags instance. Note
that errors may be returned either by rejecting the promise, or for less
severe problems, via the errors
field.
All other public ExifTool methods return Promise<void>
, and will reject
the promise if the operation is not successful.
exiftool
.read("path/to/image.jpg")
.then((tags /*: Tags */) =>
console.log(
`Make: ${tags.Make}, Model: ${tags.Model}, Errors: ${tags.errors}`
)
)
.catch(err => console.error("Something terrible happened: ", err))
Extract the low-resolution thumbnail in path/to/image.jpg
, write it to
path/to/thumbnail.jpg
, and return a Promise<void>
that is fulfilled
when the image is extracted:
exiftool.extractThumbnail("path/to/image.jpg", "path/to/thumbnail.jpg")
Extract the Preview
image (only found in some images):
exiftool.extractPreview("path/to/image.jpg", "path/to/preview.jpg")
Extract the JpgFromRaw
image (found in some RAW images):
exiftool.extractJpgFromRaw("path/to/image.cr2", "path/to/fromRaw.jpg")
Extract the binary value from "tagname" tag in path/to/image.jpg
and write it to dest.bin
(which cannot exist already
and whose parent directory must already exist):
exiftool.extractBinaryTag("tagname", "path/to/file.exf", "path/to/dest.bin")
Note that only a portion of tags are writable. Refer to the documentation and look under the "Writable" column.
If you apply malformed values or ask to write to tags that aren't
supported, the returned Promise
will be rejected.
Only string and numeric primitive are supported as values to the object
Write a comment to the given file so it shows up in the Windows Explorer Properties panel:
exiftool.write("path/to/file.jpg", { XPComment: "this is a test comment" })
Change the DateTimeOriginal, CreateDate and ModifyDate tags (using the AllDates shortcut) to 4:56pm UTC on February 6, 2016:
exiftool.write("path/to/file.jpg", { AllDates: "2016-02-06T16:56:00" })
If you edit a timestamp tag, realize that the difference between the
changed timestamp tag and the GPS value is used by exiftool-vendored
to
infer the timezone.
In other words, if you only edit the CreateDate
and don't edit the GPS
timestamps, your timezone will either be incorrect or missing. See the
section about Dates below for more information.
You may find that some of your images have corrupt metadata, and that writing new dates, or editing the rotation information, for example, fails. ExifTool can try to repair these images by rewriting all the metadata into a new file, along with the original image content. See the documentation for more details about this functionality.
rewriteAllTags
returns a void Promise that will be rejected if there are any
errors.
exiftool.rewriteAllTags("problematic.jpg", "rewritten.jpg")
Remember to call .end()
.
ExifTool processes consume system resources. If you're done with it, turn
it off with .end()
, which returns a Promise<void>
if you want to wait
for the shutdown to be complete.
If you use mocha v4 or later, and you don't call
exiftool.end()
, you will find that your test suite hangs. The relevant
change is described here,
and can be solved by adding an after
block that shuts down the instance
of ExifTool that your tests are using:
after(() => exiftool.end()) // assuming your singleton is called `exiftool`
Generally, EXIF tags encode dates and times with no timezone offset. Presumably the time is captured in local time, but this means parsing the same file in different parts of the world results in a different absolute timestamp for the same file.
Rather than returning a Date which always includes a timezone, this library returns classes that encode the date, the time of day, or both, with an optional tzoffset. It's up to you, then, to do what's right.
In many cases, though, a tzoffset can be determined, either by the composite
TimeZone
tag, or by looking at the difference between the local
DateTimeOriginal
and GPSDateTime
tags. GPSDateTime
is present in most
smartphone images.
If a tzoffset can be determined, it is encoded in all related ExifDateTime
tags for those files.
Note also that some smartphones record timestamps with microsecond precision (not just millis!),
and both ExifDateTime
and ExifTime
have floating point milliseconds.
Official EXIF tag names
are PascalCased, like
AFPointSelected
and ISO
. ("Fixing" the field names to be camelCase, would
result in ungainly aFPointSelected
and iSO
atrocities).
The Tags interface is autogenerated by the mktags
script, which parses through over 6,000 unique camera make and model images, in
large part sourced from the ExifTool site. mktags
groups tags, extracts their
type, popularity, and example values such that your IDE can autocomplete.
For an example of a group of tags, see the EXIFTags interface.
Tags marked with β β β β , like MIMEType, should be expected in most files. You'll find that very few tags are found in general populations. You'll need to do your own research to determine which tags are valid for your uses.
Note that if parsing fails (for, example, a datetime string), the raw string will be returned. Consuming code should verify both existence and type as reasonable for safety.
The npm run mktags
target reads all tags found in a batch of sample images and
parses the results.
Using exiftool-vendored
:
Read 2236 unique tags from 3011 files.
Parsing took 16191ms (5.4ms / file) # win32, core i7, maxProcs 4
Parsing took 27141ms (9.0ms / file) # ubuntu, core i3, maxProcs 1
Parsing took 12545ms (4.2ms / file) # ubuntu, core i3, maxProcs 4
Using the exiftool
npm package takes 7x longer (and doesn't work on Windows):
Reading 3011 files...
Parsing took 85654ms (28.4ms / file) # ubuntu, core i3
Starting the perl version of ExifTool is expensive, and is especially expensive on the Windows version of ExifTool.
On Windows, for every invocation, exiftool
installs a distribution of
Perl and extracts the ~1000 files that make up ExifTool, and then runs
the perl script. Windows virus scanners prevent reads on these files while they
are scanned, which makes this approach even more costly.
Using ExifTool's -stay_open
batch mode means we can reuse a single
instance of ExifTool across many requests, dropping response latency
dramatically as well as reducing system load.
To avoid overwhelming your system, the exiftool
singleton is configured with a
maxProcs
set to a quarter the number of CPUs on the current system (minimally
1); no more than maxProcs
instances of exiftool
will be spawned. If the
system is CPU constrained, however, you may want a smaller value. If you have
very fast disk IO, you may see a speed increase with larger values of
maxProcs
, but note that each child process can consume 100 MB of RAM.
Providing the flexibility to reversion the API or UPDATE version slots as features or bugfixes arise and using ExifTool's version number is at odds with eachother, so this library follows Semver, and the vendored versions of ExifTool match the version they vendor.
- π Non-backward-compatible API changes
- π² New releases of ExifTool that have externally visible changes
- π± New releases of ExifTool with no externally visible changes
- β¨ Backwards-compatible features
- π Backwards-compatible bug fixes
- π¦ Minor packaging changes
- π¦ Typedoc fails to render
Partial<>
composites, somktags
now renders each Tag as optional. TheTag
interface should be exactly the same as from v6.0.0.
- π
ExifTool
's many constructor parameters turned out to be quite unweildy. Version 6 now takes an options hash. If you used the defaults, those haven't changed, and your code won't need to change. - π
ExifTool.enqueueTask
takes a Task thunk now, to enable cleaner task retry code. I don't expect many consumers will have used this API directly. - π In prior versions, when maxTasksPerProcess was reached, on some OSes, the host process would exit.
- β¨ Rebuilt
Tags
based on new phone and camera models - π¦ Files are not
stat
ed before passing them on to ExifTool, as it seems to be faster on all platforms without this check. If you were error-matching on ENOENT, you'll need to switch to looking for "File not found". - π BatchCluster was updated, which has a robust PID-exists implementation, but
those signatures now return Promises rather than being synchronous, so the
exported
running
function has changed to return aPromise<number[]>
. - π± ExifTool upgraded to v11.09.
- π± ExifTool upgraded to v11.08.
- β¨ Some photo sharing sites will set the
CreateDate
orSubSecCreateDate
to invalid values like0001:01:01 00:00:00.00
. These values are now returned as strings so consumers can more consistently discriminate invalid metadata.
-
β¨ Prior versions of
ExifTool.read()
always added the-fast
option. This read mode omits metadata found after the image payload. This makes reads much faster, but means that a few tags, likeOriginalImageHeight
, may not be extracted. See https://sno.phy.queensu.ca/~phil/exiftool/#performance for more details.Cuneytt reported this and I realized I should make
-fast
a user preference. To maintain existing behavior, I've made the optional second argument ofExifTool.read
default to["-fast"]
. If you want to use "slow mode", just give an empty array to the second argument. If you want-fast2
mode, provide["-fast2"]
as the second argument.
- π± ExifTool upgraded to v11.06.
- π¦ Removed node 9 from the build graph, as it isn't supported anymore: https://github.com/nodejs/Release#release-schedule
- π¦ Pull in latest dependencies
- β¨ new
exiftool.rewriteAllTags()
, which may repair problematic image metadata. - π± ExifTool upgraded to v11.02.
- π¦ taskRetries default is now 1, which should allow recovery of the rare RPC/fork error, but actual corrupt files and realy errors can be rejected sooner.
- π¦ Pull in latest dependencies, include new batch-cluster.
- π/β¨ Task rejections are always
Error
s now, andExifTool.on
observers will be provided Errors on failure cases. Previously, rejections and events had been a mixture of strings and Errors. I'm bumping the major version just to make sure people adjust their code accordingly, but I'm hoping this is a no-op for most people. Thanks for the suggestion, Nils Knappmeier!
- π± ExifTool upgraded to v11.01. Note that ExifTool doesn't really follow semver, so this shouldn't be a breaking change, so we'll stay on v4.
- π¦ Pull in latest dependencies, including batch-cluster and TypeScript.
- π¦ Fix version spec because exiftool now has a left-zero-padded version that semver is not happy about.
- π± ExifTool upgraded to v10.98
- π± ExifTool upgraded to v10.95
- π¦ Fix
.pl
dependency to omit test files and documentation
- π± ExifTool upgraded to v10.94
- π¦ Pull in latest dependencies, including more robust BatchCluster exiting (which may help with rare child zombies during long-lived parent processes on macOS)
- π¦ Pull in latest dependencies, including less-verbose BatchCluster
- β¨ Before reading or writing tags, we stat the file first to ensure it exists.
Expect
ENOENT
rejections fromExifTool.read
andExifTool.write
now. - π¦ Expose batch-cluster lifecycle events and logger
- π± ExifTool upgraded to v10.92
- β¨ Support for Electron. Added
exiftoolPath
to theExifTool
constructor. See the wiki for more information. - π± ExifTool upgraded to v10.89
- π± ExifTool upgraded to v10.86
- π¦ Pick up batch-cluster 1.10.0 to possibly address this issue.
- π± ExifTool upgraded to v10.81
- π¦ Update deps, including batch-cluster 1.9.0
- π¦ Dropped support for node 4 (EOLs in 1 month).
- π± ExifTool upgraded to v10.79
- π¦ Update deps, including TypeScript 2.7.2
- π¦ Removed π±
- π± ExifTool upgraded to v10.78
- π¦ Update deps, including TypeScript 2.7.1
- π± ExifTool upgraded to v10.76
- π¦ Update deps
- π¦ Update deps
- π Use
spawn
instead ofexecFile
, as the latter has buggymaxBuffer
exit behavior and could leak exiftool processes on windows - π The
.exiftool
singleton now properly uses aDefaultMaxProcs
const.
- π± ExifTool upgraded to v10.70
- π¦ Replace tslint and tsfmt with prettier
- π¦ Add test coverage report
(due to buggy interactions between yarn
and np
, v4.13.0 was published in
an incomplete state and subsequently unpublished)
- π¦ Rollback the rollback, as it's a known issue with par. If this happens again I'll add a windows-specific validation of the par directory.
- π Rollback to ExifTool v10.65 to avoid this windows issue
- β¨ Support for non-latin filenames and tag values Thanks, Demiurga!
- π± ExifTool upgraded to v10.67
- π¦ More conservative default for
maxProcs
:Math.max(1, system cpus / 4)
.
- π¦ Expose
ExifTool.ended
- β¨ Corrected the type interface to
ExifTool.write()
to be only string or numeric values with keys fromTags
so intellisense can work it's magicks - π¦ Updated the README with more examples
- π¦ Added timestamp write tests
- β¨ Metadata writing is now supported. Closes #6
- π± ExifTool upgraded to v10.66
- β¨ Pull in new
batch-cluster
with more aggressive child process management (usestaskkill
on win32 platforms andkill -9
on unixish platforms) - β¨ ExifTool constructor defaults were relaxed to handle slow NAS
- β¨ Upgraded to Mocha 4.0. Added calls to
exiftool.end()
in testafter
blocks and the README so--exit
isn't necessary. - π¦
salita --update
- π± ExifTool upgraded to v10.64
- π¦ reverted batch-cluster reference
- π± ExifTool upgraded to v10.61
- π Re-added the "-stay_open\nFalse" ExifTool exit command, which may be more reliable than only using signal traps.
- π¦
yarn upgrade --latest
- π± ExifTool upgraded to v10.60
- π¦ Upgraded all dependencies
- π± ExifTool upgraded to v10.58
- π¦ Added
QuickTimeTags
from several example movies (previous versions ofTags
didn't have movie tag exemplar values) - π± ExifTool upgraded to v10.57
- π All
Tags
fields are now marked as possibly undefined, as there are no EXIF, IPTC, or other values that are guaranteed to be set. Sorry for the major break, but the prior signature that promised all values were always set was strictly wrong. - β¨ Added support for all downstream batch-cluster options in the ExifTool constructor.
- π¦ Added
ExifTool.pids
(used by a couple new integration tests) - π¦ Rebuilt
Tags
with additional sample images and looser tag filtering.
- π± ExifTool upgraded to v10.54
- π¦ Pulled in batch-cluster v1.2.0 that supports more robust child process cleanup
- β¨ Yarn and
platform-dependent-modules
don't play nicely. Anton Mokrushin submitted several PRs to address this. Thanks!
- π Fixed
package.json
references totypes
andmain
- π¦ Completed jsdocs for ExifTool constructor
- π¦ Pulled in batch-cluster v1.1.0 that adds both
on("beforeExit")
andon("exit")
handlers
- β¨ Extracted batch-cluster to power child process management. Task timeout, retry, and failure handling has excellent test coverage now.
- π Switched from debug to node's debuglog to reduce external dependencies
- π± ExifTool upgraded to v10.51
- π¦ Using
.npmignore
instead of package.json'sfiles
directive to specify the contents of the module.
- π Rounded milliseconds were not set by
ExifDateTime.toDate()
when timezone was not available. Breaking tests were added.
- π¦ Exposed datetime parsing via static methods on
ExifDateTime
,ExifDate
, andExifTime
.
- β¨ Some newer smartphones (like the Google Pixel) render timestamps with microsecond precision, which is now supported.
- β¨ Added example movies to the sample image corpus used to build the tag definitions.
- β¨ Added
taskTimeoutMillis
, which will cause the promise to be rejected if exiftool takes longer than this value to parse the file. Note that this timeout only starts "ticking" when the task is enqueued to an idle ExifTool process. - β¨ Pump the
onIdle
method everyonIdleIntervalMillis
(defaults to every 10 seconds) to ensure all requested tasks are serviced. - β¨ If
ECONN
orECONNRESET
is raised from the child process (which seems to happen for roughly 1% of requests), the current task is re-enqueued and the current exiftool process is recycled. - β¨ Rebuilt
Tags
definitions using more (6,412!) sample image files (vianpm run mktags ~/sample-images
), including many RAW image types (like.ORF
,.CR2
, and.NEF
).
- β¨ Added
maxReuses
before exiftool processes are recycled - π± ExifTool upgraded to v10.50
- π Fixed
gps.toDate is not a function
- π± ExifTool upgraded to v10.47
- β¨ Added call to
.kill()
on.end()
in case the stdin command was missed by ExifTool
- β¨ Added support for Node 4. TypeScript builds under es5 mode.
- π± ExifTool upgraded to v10.46
- π± ExifTool upgraded to v10.44
- π¦ Upgraded to TypeScript 2.2
- π
update/io.ts
error message didn't handle null statuscodes properly - π
update/mktags.ts
had a counting bug exposed by TS 2.2
- β¨ More robust error handling for child processes (previously there was no
.on("error")
added to the process itself, only onstderr
of the child process).
- π± ExifTool upgraded to v10.41
- β¨
Orientation
is rendered as a string by ExifTool, which was surprising (to me, at least). By exposing optional args inExifTool.read
, the caller can choose how ExifTool renders tag values.
- π
LANG
andLC_
environment variables were passed through to exiftool (and subsequently, perl). These are now explicitly unset when exec'ing ExifTool, both to ensure tag names aren't internationalized, and to prevent perl errors from bubbling up to the caller due to missing locales.
- β¨
extractBinaryTag
exposed because there are a lot of binary tags (and they aren't all embedded images) - π
JpgFromRaw
was missing inTag
(no raw images were in the example corpus!)
- β¨
extractJpgFromRaw
implemented to pull out EXIF-embedded images from RAW formats
- π± ExifTool upgraded to v10.40
- β¨
extractThumbnail
andextractPreview
implemented to pull out EXIF-embedded images - π¦ Rebuilt package.json.files section
- π Switched from home-grown
logger
to debug
- π¦ Switch back to
platform-dependent-modules
. npm warnings aren't awesome. - π¦ Don't include tests or updater in the published package
- π± ExifTool upgraded to v10.38
- π¦ Use
npm
's os-specific optionalDependencies rather thanplatform-dependent-modules
.
- π Several imports (like
process
) name-collided on the globals imported by Electron
- π± ExifTool upgraded to v10.37
- π± ExifTool upgraded to v10.36
- β¨
Tag.Error
exposed for unsupported file types.
- π It was too easy to miss calling
ExifTool.end()
, which left child ExifTool processes running. The constructor to ExifTool now adds a shutdown hook to send all child processes a shutdown signal.
- β¨ Added
toString()
for all date/time types
- β¨ Added typings reference in the package.json
- π± Upgraded vendored exiftool to 10.33
- π¦ Fixed packaging (maintain jsdocs in .d.ts and added typings reference)
- π¦ Using np for packaging
- β¨ Multithreading support with the
maxProcs
ctor param - β¨ Added tests for reading images with truncated or missing EXIF headers
- β¨ Added tests for timezone offset extraction and rendering
- β¨ Subsecond resolution from the Google Pixel has 8 significant digits(!!), added support for that.
- β¨ More rigorous TimeZone extraction from assets, and added the
ExifTimeZoneOffset
to handle theTimeZone
composite tag - β¨ Added support for millisecond timestamps
π±β¨ Initial Release. Packages ExifTool v10.31.
<script src="//twemoji.maxcdn.com/2/twemoji.min.js?11.0"></script> <script>twemoji.parse(document.body, { size: "svg", ext: ".svg" })</script> <style> .emoji { height: 18px; } </style>