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");

// The singleton instance automatically cleans up on process exit by default.
// If you need immediate cleanup or have disabled registerExitHandlers:
// 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 6,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)
  • 🧊 = 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

Important: The interface isn't comprehensive - unknown fields may still exist in returned objects.

πŸ“– Complete Tags Documentation β†’

Important Notes

⏰ 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, use 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

🏷️ 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