Skip to content

Repository files navigation

exiftool-ts logo

exiftool-ts

TypeScript rewrite of ExifTool for Node.js. Read and write metadata from JPEG, PNG, WebP, AVIF/HEIF, and TIFF-family images — with a fully typed library API and a drop-in CLI.

Why

ExifTool is the gold standard for metadata — but it's a 30k-line Perl program. Every invocation pays a Perl startup cost, embedding it in a JS/TS service means shelling out or managing sidecars, and there are no types.

exiftool exists to bring that capability natively into the TypeScript ecosystem:

  • Typed end to endread() returns an inferred FileInfo; no parsing strings.
  • Embeddable — zero runtime dependencies; import the library directly or ship the CLI alongside your service. No child processes.
  • Trust ExifTool as ground truth — our parity suite diffs our output against the real exiftool binary on every run; formatting bugs get caught, not shipped.
  • Auto-generated tag database — tag definitions are parsed from the ExifTool Perl source at build time (~thousands of tags), not hand-maintained.

Status & parity

Early development, moving fast. The full, maintained status matrix lives in the parity docs and the working register in DIVERGENCES.md.

Value-level parity is enforced by src/cli/exiftool-parity.test.ts, which runs the real exiftool binary on shared fixtures and fails on any undocumented divergence. Remaining gaps are registered in KNOWN_DIVERGENCES inside that file with reasons (file-date timezone offsets, makernote lens lookups, …).

Install

npm (CLI + library, Node ≥ 18):

npm i -g @woss/exiftool
exiftool-ts photo.jpg

The package ships compiled JS with full .d.ts types and has zero runtime dependencies.

JSR (for Deno consumers, runs from TypeScript source):

deno install -A -n exiftool-ts jsr:@woss/exiftool

Library usage

The package ships .d.ts declarations, so consumers get full types and autocomplete out of the box:

import { ExifTool } from "@woss/exiftool";

const tool = new ExifTool();

const info = await tool.read("photo.jpg");
info.tags.Make; // "Canon"        — typed as TagValue
info.tags.ExposureTime; // "1/200"        — formatted like exiftool
Object.keys(info.tags); // browse everything

// In-memory buffers work too:
const meta = await tool.readBytes(imageBuffer);

// Writing (creates photo.jpg_original unless suppressed):
await tool.write("photo.jpg", { Artist: "me", Copyright: "(c)" });

Exported surface: ExifTool, TagDb, writeTags, UnsupportedFormatError, and the types FileInfo, TagValue, WriteResult, ParseHints, ReadOptions, TagEntry, TagGroups.

Plugins

Formats are plugins. By default ExifTool loads every built-in one, but you can restrict an instance to a set — the bundler then ships only those parsers:

import { ExifTool } from "@woss/exiftool";
import { MODERN_PLUGINS } from "@woss/exiftool/plugins";

const tool = new ExifTool({ plugins: MODERN_PLUGINS }); // JPEG, PNG, WebP, AVIF, TIFF-family RAW (read-only)

MODERN_PLUGINS / ALL_PLUGINS cover the shipped formats; individual parsers (jpegParser, pngParser, webpParser, avifParser, tiffRawParser) and fully custom plugins ({ format, extensions, canParse, parse, writeBytes? }) come from the same subpath. A plugin without writeBytes is read-only. The default — every built-in — loads lazily, so new ExifTool() keeps working unchanged.

Browser

The parser core is platform-free. The browser export condition resolves to a bundle with no Node builtins; reads and writes work on in-memory buffers:

import { ExifTool, MODERN_PLUGINS } from "@woss/exiftool/browser";

const tool = new ExifTool({ plugins: MODERN_PLUGINS });
const { bytes, written } = await tool.writeBytes(imageBuffer, { Artist: "me" });
const info = await tool.readBytes(bytes);

Path-based read/write stay Node-only (main entry); browser consumers get readBytes/writeBytes.

CLI usage

exiftool-ts photo.jpg                           # default tabular dump
exiftool-ts -j photo.jpg                        # JSON
exiftool-ts -X photo.jpg > out.xml              # XML
exiftool-ts -csv *.jpg > out.csv                # CSV
exiftool-ts -r -ext jpg .                       # recurse a directory tree
exiftool-ts -if '$Make eq Canon' *.jpg          # condition filter
exiftool-ts -ee -j multi-picture.jpg            # embedded images as extra docs
exiftool-ts '-Artist=me' photo.jpg              # write a tag (_original backup)
exiftool-ts --overwrite-original '-Software=x' photo.jpg
printf -- '-j\nphoto.jpg\n-execute\n-stay_open\nFalse\n' \
  | exiftool-ts -stay_open True                 # persistent daemon

Architecture

mod.ts               → JSR barrel (re-exports the public API)
src/
  cli.ts             → CLI entry point (arg normalization → main())
  exiftool.ts        → Node ExifTool class (path-based read / write via fs)
  exiftool-core.ts   → Platform-free core (readBytes / writeBytes / plugins)
  browser.ts         → Browser entry (platform-free bundle)
  plugins.ts         → Plugin presets (./plugins subpath)
  tag-db.ts          → Tag database (name/id/group lookups)
  types.ts           → Core types (FileInfo, TagEntry, TagValue, …)
  cli/
    args.ts          → ExifTool-style argument parser (normalizeArgs + parseCliArgs)
    filestat.ts      → File-stat tag overlay (FileSize, FileModifyDate, …)
    filter.ts        → -if condition evaluation
    glob.ts          → directory recursion / extension filters
    output.ts        → JSON / XML / CSV / tabular formatters
    stay-open.ts     → -stay_open daemon command loop
    verbosity.ts     → -v/-q rendering helpers
  format/
    mod.ts           → Plugin contract + detection (FormatParser, detectParser)
    all.ts           → Built-in plugin set (lazy default)
    jpeg.ts          → JPEG segment walk (EXIF/XMP/IPTC/ICC/MPF/Adobe)
    png.ts           → PNG chunk walk (eXIf/iTXt/zTXt/tEXt/iCCP…)
    webp.ts          → RIFF/VP8X chunk walk
    avif.ts          → ISOBMFF box walk (meta items, colr, pixi)
  exif/
    ifd.ts           → Bounds-checked IFD structure parser
    tiff.ts          → Shared TIFF engine (both endians, GPS/sub-IFDs)
    tiff-builder.ts  → TIFF serializer for the write path
    values.ts        → PrintConv value formatting
    composite.ts     → Composite tag derivation (35mm equiv, LightValue…)
    xmp.ts           → XMP/RDF extraction
    app13.ts         → Photoshop IRB + IPTC IIM extraction
    icc.ts           → ICC profile header/tag parsing
  write/
    pipeline.ts      → Safe-overwrite pipeline (temp swap + _original backup)
    writers.ts       → Per-container writers (JPEG APP1 / PNG eXIf / WebP / AVIF)
  utils/
    crc32.ts         → CRC-32 (PNG chunks)
    encoding.ts      → String encoding/escaping helpers
scripts/
  generate-tags.ts   → Generate tag DB from ExifTool Perl source
  test-harness.ts    → Compare output against real exiftool
  coverage-audit.ts  → Enforce 100% line/function coverage gate
.github/workflows/
  release.yml        → npm + JSR publish, dist tarball on tags

Development

pnpm test                  # run the full test suite
pnpm check                 # typecheck
pnpm coverage-audit        # tests + enforce 100% line/function coverage
pnpm generate-tags         # regenerate tag DB (requires exiftool source)

# Parity vs real exiftool (needs `exiftool` on PATH):
npx vitest run src/cli/exiftool-parity.test.ts

Project scope

Near term: deepen XMP/IPTC writing, extend the container list toward the formats exiftool covers. EXIF (IFD0/ExifIFD/GPS) writing and MakerNote decoding for the core 6 vendors are shipped. The long-term target remains parity with ExifTool's reading surface across its supported formats — tracked in the parity docs.

License

MIT — © woss (woss.io), covering this repository's TypeScript code only.

Credits

ExifTool was created by Phil Harvey, who maintains it single-handedly since 2003. This project exists because of his work and would not be possible without it. Phil's ExifTool is licensed under the same terms as Perl (Artistic License or GPL); nothing from his codebase is used here — this is an independent clean-room TypeScript implementation that uses his tool only as a behavioral reference for output parity. All credit for the ExifTool concept, design, and decades of metadata expertise belongs to him. Consider supporting his work.


Made with Human and AI effort — woss.io

About

Exiftool rewritten by Human and AI in Typescript, available for node and browser

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages