exiftool-vendored
    Preparing search index...

    exiftool-vendored

    exiftool-vendored

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

    npm version Node.js CI GitHub issues

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

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

    • Cross-platform: macOS, Linux, Windows
    • Comprehensive: Read, write, extract embedded images
    • Reliable: Battle-tested with extensive test coverage
    • 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
    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);
    }
    // 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,
    });
    // 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");

    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
    • 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 โ†’

    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 โ†’

    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 โ†’

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

    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!

    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;

    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.

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