Skip to content

photostructure/exiftool-vendored.js

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

exiftool-vendored

Fast, cross-platform Node.js access to ExifTool. Built and supported by PhotoStructure.

npm version Node.js CI GitHub issues

Installation & Quick Start

Requirements: Node.js Active LTS or Maintenance LTS versions only

npm install exiftool-vendored
import { exiftool } from "exiftool-vendored";

// Read metadata
const tags = await exiftool.read("photo.jpg");
console.log(`Camera: ${tags.Make} ${tags.Model}`);
console.log(`Taken: ${tags.DateTimeOriginal}`);
console.log(`Size: ${tags.ImageWidth}x${tags.ImageHeight}`);

// Write metadata
await exiftool.write("photo.jpg", {
  XPComment: "Amazing sunset!",
  Copyright: "Β© 2024 Your Name",
});

// Extract thumbnail
await exiftool.extractThumbnail("photo.jpg", "thumb.jpg");

await exiftool.end();

Why exiftool-vendored?

⚑ Performance

Order of magnitude faster than other Node.js ExifTool modules. Powers PhotoStructure and 1,000+ other projects.

πŸ”§ Robust

  • Cross-platform: macOS, Linux, Windows
  • Comprehensive: Read, write, extract embedded images
  • Reliable: Battle-tested with extensive test coverage

πŸ“š Developer-Friendly

  • TypeScript: Full type definitions for thousands of metadata fields
  • Smart dates: Timezone-aware ExifDateTime classes
  • Auto-generated tags: Based on 10,000+ real camera samples

Core Features

Reading Metadata

const tags = await exiftool.read("photo.jpg");

// Camera info
console.log(tags.Make, tags.Model, tags.LensModel);

// Capture settings
console.log(tags.ISO, tags.FNumber, tags.ExposureTime);

// Location (if available)
console.log(tags.GPSLatitude, tags.GPSLongitude);

// Always check for parsing errors
if (tags.errors?.length > 0) {
  console.warn("Metadata warnings:", tags.errors);
}

Writing Metadata

// Add keywords and copyright
await exiftool.write("photo.jpg", {
  Keywords: ["sunset", "landscape"],
  Copyright: "Β© 2024 Photographer Name",
  "IPTC:CopyrightNotice": "Β© 2024 Photographer Name",
});

// Update all date fields at once
await exiftool.write("photo.jpg", {
  AllDates: "2024:03:15 14:30:00",
});

// Delete tags
await exiftool.write("photo.jpg", {
  UserComment: null,
});

Extracting Images

// Extract thumbnail
await exiftool.extractThumbnail("photo.jpg", "thumbnail.jpg");

// Extract preview (larger than thumbnail)
await exiftool.extractPreview("photo.jpg", "preview.jpg");

// Extract JPEG from RAW files
await exiftool.extractJpgFromRaw("photo.cr2", "processed.jpg");

Understanding Tags

The Tags interface contains thousands of metadata fields from an auto-generated TypeScript file. Each tag includes semantic JSDoc annotations:

/**
 * @frequency πŸ”₯ β˜…β˜…β˜…β˜… (85%)
 * @groups EXIF, MakerNotes
 * @example 100
 */
ISO?: number;

/**
 * @frequency 🧊 β˜…β˜…β˜…β˜† (23%)
 * @groups MakerNotes
 * @example "Custom lens data"
 */
LensSpec?: string;
  • πŸ”₯ = Found on mainstream devices (iPhone, Canon, Nikon, Sony)
  • 🧊 = Only found on more obscure camera makes and models
  • β˜…β˜…β˜…β˜… = Found in >50% of files, β˜†β˜†β˜†β˜† = rare (<1%)
  • @groups = Metadata categories (EXIF, GPS, IPTC, XMP, etc.)
  • @example = Representative values

Code defensively!

  • No fields are guaranteed to be present.
  • Value types are not guaranteed -- assume strings may get in your numeric fields, and handle it gracefully.
  • There may very well be keys returned that are not in the Tags interface.

πŸ“– Complete Tags Documentation β†’

Important Notes

βš™οΈ Configuration

exiftool-vendored provides two levels of configuration:

Library-wide Settings - Global configuration affecting all instances:

import { Settings } from "exiftool-vendored";

// Enable parsing of archaic timezone offsets for historical photos
Settings.allowArchaicTimezoneOffsets.value = true;

Per-instance Options - Configuration for individual ExifTool instances:

import { ExifTool } from "exiftool-vendored";

const exiftool = new ExifTool({
  maxProcs: 8, // More concurrent processes
  useMWG: true, // Use Metadata Working Group tags
  backfillTimezones: true, // Infer missing timezones
});

πŸ“– Complete Configuration Guide β†’

⏰ Dates & Timezones

Images rarely specify timezones. This library uses sophisticated heuristics:

  1. Explicit metadata (TimeZoneOffset, OffsetTime)
  2. GPS location β†’ timezone lookup
  3. UTC timestamps β†’ calculate offset
const dt = tags.DateTimeOriginal;
if (dt instanceof ExifDateTime) {
  console.log("Timezone offset:", dt.tzoffset, "minutes");
  console.log("Timezone:", dt.zone);
}

πŸ“– Date & Timezone Guide β†’

🧹 Resource Cleanup

Always call .end() on ExifTool instances to prevent Node.js from hanging:

import { exiftool } from "exiftool-vendored";

// Use the singleton
const tags = await exiftool.read("photo.jpg");

// Clean up when done
process.on("beforeExit", () => exiftool.end());

Automatic Cleanup with Disposable Interfaces

For TypeScript 5.2+ projects, consider using automatic resource management:

import { ExifTool } from "exiftool-vendored";

// Automatic synchronous cleanup
{
  using et = new ExifTool();
  const tags = await et.read("photo.jpg");
  // ExifTool automatically cleaned up when block exits
}

// Automatic asynchronous cleanup (recommended)
{
  await using et = new ExifTool();
  const tags = await et.read("photo.jpg");
  // ExifTool gracefully cleaned up when block exits
}

Benefits:

  • Guaranteed cleanup: No leaked processes, even with exceptions
  • Timeout protection: Automatic forceful cleanup if graceful shutdown hangs
  • Zero boilerplate: No manual .end() calls needed

Caution:

  • Operating-system startup lag: Linux costs ~50-500ms to launch a new ExifTool process, but macOS can take several seconds (presumably due to Gatekeeper), and Windows can take tens of seconds due to antivirus shenanigans. Don't dispose your instance unless you're really done with it!

🏷️ Tag Completeness

The Tags interface shows the most common fields, but ExifTool can extract many more. Cast to access unlisted fields:

const tags = await exiftool.read("photo.jpg");
const customField = (tags as any).UncommonTag;

Documentation

πŸ“š Guides

πŸ”§ Troubleshooting

πŸ“– API Reference

Performance

The default singleton is throttled for stability. For high-throughput processing:

import { ExifTool } from "exiftool-vendored";

const exiftool = new ExifTool({
  maxProcs: 8, // More concurrent processes
  minDelayBetweenSpawnMillis: 0, // Faster spawning
  streamFlushMillis: 10, // Faster streaming
});

// Process many files efficiently
const results = await Promise.all(filePaths.map((file) => exiftool.read(file)));

await exiftool.end();

Benchmarks: 20+ files/second/thread, 500+ files/second using all CPU cores.

Support & Community

Contributors πŸŽ‰

Matthew McEachen, Joshua Harris, Anton Mokrushin, Luca Ban, Demiurga, David Randler