@woss/exiftool - v0.3.1
    Preparing search index...

    @woss/exiftool - v0.3.1

    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.

    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.

    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, …).

    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
    

    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.

    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.

    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.

    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
    mod.tsJSR barrel (re-exports the public API)
    src/
    cli.tsCLI entry point (arg normalizationmain())
    exiftool.tsNode ExifTool class (path-based read / write via fs)
    exiftool-core.tsPlatform-free core (readBytes / writeBytes / plugins)
    browser.tsBrowser entry (platform-free bundle)
    plugins.tsPlugin presets (./plugins subpath)
    tag-db.tsTag database (name/id/group lookups)
    types.tsCore types (FileInfo, TagEntry, TagValue, …)
    cli/
    args.tsExifTool-style argument parser (normalizeArgs + parseCliArgs)
    filestat.tsFile-stat tag overlay (FileSize, FileModifyDate, …)
    filter.ts → -if condition evaluation
    glob.tsdirectory recursion / extension filters
    output.tsJSON / XML / CSV / tabular formatters
    stay-open.ts → -stay_open daemon command loop
    verbosity.ts → -v/-q rendering helpers
    format/
    mod.tsPlugin contract + detection (FormatParser, detectParser)
    all.tsBuilt-in plugin set (lazy default)
    jpeg.tsJPEG segment walk (EXIF/XMP/IPTC/ICC/MPF/Adobe)
    png.tsPNG chunk walk (eXIf/iTXt/zTXt/tEXt/iCCP…)
    webp.tsRIFF/VP8X chunk walk
    avif.tsISOBMFF box walk (meta items, colr, pixi)
    exif/
    ifd.tsBounds-checked IFD structure parser
    tiff.tsShared TIFF engine (both endians, GPS/sub-IFDs)
    tiff-builder.tsTIFF serializer for the write path
    values.tsPrintConv value formatting
    composite.tsComposite tag derivation (35mm equiv, LightValue…)
    xmp.tsXMP/RDF extraction
    app13.tsPhotoshop IRB + IPTC IIM extraction
    icc.tsICC profile header/tag parsing
    write/
    pipeline.tsSafe-overwrite pipeline (temp swap + _original backup)
    writers.tsPer-container writers (JPEG APP1 / PNG eXIf / WebP / AVIF)
    utils/
    crc32.tsCRC-32 (PNG chunks)
    encoding.tsString encoding/escaping helpers
    scripts/
    generate-tags.tsGenerate tag DB from ExifTool Perl source
    test-harness.tsCompare output against real exiftool
    coverage-audit.tsEnforce 100% line/function coverage gate
    .github/workflows/
    release.ymlnpm + JSR publish, dist tarball on tags
    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

    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.

    MIT


    Made with Human and AI effort — woss.io