Complete examples for common exiftool-vendored use cases.
import { exiftool } from "exiftool-vendored";
// or: const { exiftool } = require("exiftool-vendored");
// Verify installation
console.log(`ExifTool v${await exiftool.version()}`);
// Shut down the `exiftool` child process so node can exit cleanly:
await exiftool.end();
const tags = await exiftool.read("photo.jpg");
console.log("Camera:", tags.Make, tags.Model);
console.log("Size:", tags.ImageWidth, "x", tags.ImageHeight);
console.log("Taken:", tags.DateTimeOriginal);
console.log("Location:", tags.GPSLatitude, tags.GPSLongitude);
const tags = await exiftool.read("photo.jpg");
// Handle optional values safely
const camera = tags.Make ? `${tags.Make} ${tags.Model}` : "Unknown camera";
const dimensions =
tags.ImageWidth && tags.ImageHeight
? `${tags.ImageWidth}x${tags.ImageHeight}`
: "Unknown size";
// Use nullish coalescing for fallbacks
const timestamp = tags.DateTimeOriginal ?? tags.DateTime ?? tags.FileModifyDate;
const title = tags.Title ?? tags.DocumentName ?? tags.FileName;
try {
const tags = await exiftool.read("photo.jpg");
// Check for parsing warnings
if (tags.errors && tags.errors.length > 0) {
console.warn("Metadata warnings:", tags.errors);
}
console.log("Successfully read metadata");
} catch (error) {
console.error("Failed to read file:", error.message);
}
// Add comment and copyright
await exiftool.write("photo.jpg", {
XPComment: "Beautiful sunset",
Copyright: "© 2024 Your Name",
});
// Update capture date
await exiftool.write("photo.jpg", {
DateTimeOriginal: "2024:03:15 14:30:00",
});
// Write to specific metadata groups
await exiftool.write("photo.jpg", {
"IPTC:Keywords": "sunset, landscape, nature",
"IPTC:CopyrightNotice": "© 2024 Photographer Name",
"XMP:Title": "Sunset Over Mountains",
"XMP:Description": "A stunning sunset captured in the mountains",
});
// Delete specific tags by setting to null
await exiftool.write("photo.jpg", {
UserComment: null,
ImageDescription: null,
"IPTC:Keywords": null,
});
// Update all date fields at once
await exiftool.write("photo.jpg", {
AllDates: "2024:03:15 14:30:00",
});
// This is equivalent to setting:
// - DateTimeOriginal
// - CreateDate
// - ModifyDate
// Set GPS location (decimal degrees)
await exiftool.write("photo.jpg", {
GPSLatitude: 40.7128,
GPSLongitude: -74.006,
GPSAltitude: 10, // meters above sea level
});
// Extract EXIF thumbnail
try {
await exiftool.extractThumbnail("photo.jpg", "thumbnail.jpg");
console.log("Thumbnail extracted successfully");
} catch (error) {
console.log("No thumbnail found or extraction failed");
}
// Extract preview image (larger than thumbnail)
try {
await exiftool.extractPreview("photo.jpg", "preview.jpg");
console.log("Preview extracted successfully");
} catch (error) {
console.log("No preview found or extraction failed");
}
import { parseJSON, ExifDateTime } from "exiftool-vendored";
import { readFile, writeFile } from "node:fs/promises";
// Read and serialize
const tags = await exiftool.read("photo.jpg");
const jsonString = JSON.stringify(tags);
// Save to file or send over network
await writeFile("metadata.json", jsonString);
// Later, deserialize
const savedJson = await readFile("metadata.json", "utf8");
const restoredTags = parseJSON(savedJson);
// restoredTags has proper ExifDateTime objects restored
console.log(restoredTags.DateTimeOriginal instanceof ExifDateTime); // true
Node.js will not exit cleanly while any ExifTool
instance has any running exiftool
child processes due to the way that Node.js handles stdin/stdout/stderr streams.
At least with this library, it's up to you end what you start.
Note: depending on the platform, starting and ending an exiftool
instance may be (very!) time consuming -- like, 5-30 seconds on Windows -- so ultrathink your code a bit to ensure you aren't spawning and killing exiftool instances needlessly.
yes I said ultrathink as if it was Proper English but you know you thought it was funny
import { ExifTool } from "exiftool-vendored";
const exiftool = new ExifTool();
try {
const tags = await exiftool.read("photo.jpg");
console.log(tags.Make, tags.Model);
} finally {
// Always clean up to prevent hanging processes
await exiftool.end();
}
For TypeScript 5.2+ projects with proper tsconfig.json configuration:
{
"compilerOptions": {
"target": "ES2022",
"lib": ["ES2022", "DOM"]
}
}
import { ExifTool } from "exiftool-vendored";
// Block scope with automatic cleanup
{
using et = new ExifTool();
const tags = await et.read("photo.jpg");
console.log(`Camera: ${tags.Make} ${tags.Model}`);
// ExifTool.end(false) called automatically when block exits
}
// Multiple files with automatic cleanup
function processPhotos(filePaths) {
using et = new ExifTool({ maxProcs: 4 });
return Promise.all(
filePaths.map(async (file) => {
const tags = await et.read(file);
return { file, camera: `${tags.Make} ${tags.Model}` };
}),
);
// Cleanup happens even if Promise.all() throws
}
import { ExifTool } from "exiftool-vendored";
// Graceful cleanup with timeout protection
{
await using et = new ExifTool();
const tags = await et.read("photo.jpg");
await et.write("photo.jpg", {
XPComment: "Processed with exiftool-vendored, golly gee whiz it's neato",
Copyright: "© 2024",
});
// ExifTool.end(true) called automatically with timeout protection
// If graceful cleanup times out, forceful cleanup is attempted
}
// Function with automatic cleanup
async function batchProcessPhotos(filePaths) {
await using et = new ExifTool({
maxProcs: 8,
taskTimeoutMillis: 30000,
});
const results = [];
for (const file of filePaths) {
try {
const tags = await et.read(file);
// Add copyright
await et.write(file, {
Copyright: "© 2025 Your Company",
});
results.push({ file, success: true, camera: tags.Make });
} catch (error) {
results.push({ file, success: false, error: error.message });
}
}
return results;
// Automatic cleanup happens here, even with exceptions
}
import { ExifTool } from "exiftool-vendored";
async function robustProcessing(file) {
try {
await using et = new ExifTool();
const tags = await et.read(file);
if (tags.errors?.length > 0) {
console.warn(`Metadata warnings for ${file}:`, tags.errors);
}
return tags;
} catch (error) {
if (error.message.includes("ENOENT")) {
throw new Error(`File not found: ${file}`);
}
throw error;
}
// ExifTool cleanup guaranteed, even with exceptions
}
import { ExifTool } from "exiftool-vendored";
// Custom timeout configuration
{
await using et = new ExifTool({
disposalTimeoutMs: 2000, // 2 seconds for sync disposal
asyncDisposalTimeoutMs: 30_000, // 30 seconds for async disposal
});
// Your processing here
const tags = await et.read("large-file.tiff");
}
.end()
calls or complex try/finally blocksusing
: Simple synchronous cleanup, fire-and-forget scenariosawait using
: Production code requiring graceful cleanup (recommended).end()
: Pre-TypeScript 5.2 environments or fine-grained control