Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

exif-strip

Lossless EXIF / GPS / XMP / IPTC removal for JPEG, PNG and WebP — in the browser or in Node, with zero dependencies.

Most "remove EXIF" tools decode the image and re-save it, which silently re-encodes your pixels (quality loss on every pass) and often happens on someone else's server. This module does neither: it rewrites the container structure and copies the compressed image data across untouched. The output pixels are byte-identical to the input — and there is a test that fails if they ever aren't.

This is the exact engine behind the metadata remover in SolveKit's Image Batch Studio, where it runs client-side on unlimited batches.

How it works

JPEG — walks the segment list and drops:

  • APP1 segments whose payload starts with Exif\0\0 (EXIF, including the GPS IFD) or the Adobe XMP namespace URI
  • APP13 (Photoshop IRB, which is where IPTC lives)
  • COM comment segments

Everything else — DQT, DHT, SOF*, and critically the entropy-coded scan data after SOS — is copied verbatim.

PNG — filters out the metadata chunks tEXt, zTXt, iTXt, eXIf and tIME. Critical chunks (IHDR, PLTE, IDAT, IEND) pass through untouched with their CRCs intact.

WebP — drops the EXIF and XMP RIFF chunks, clears the corresponding VP8X feature flag bits (0x08 and 0x04), and patches the RIFF size field. The VP8 /VP8L bitstream is never touched.

Anything that doesn't identify as one of these three containers is refused (null) rather than risk a corrupting rewrite.

There is also an inspector (inspectMetadata) that reports what a file carries — camera make, capture date, orientation, whether GPS coordinates are present, and how many bytes stripping would remove — via a defensive TIFF/IFD walk that never throws on malformed input.

Usage

import { inspectMetadata, stripMetadata } from "./src/metadata.ts";

const bytes = new Uint8Array(await file.arrayBuffer());

const summary = inspectMetadata(bytes);
// { format: "jpeg", hasExif: true, hasGps: true, camera: "Apple iPhone 15",
//   dateTaken: "2026:07:12 19:03:44", orientation: 6, metadataBytes: 12345, ... }

const result = stripMetadata(bytes);
if (result) {
  // result.bytes     → the clean file (same pixels, byte for byte)
  // result.removedBytes → how much metadata was dropped
  // result.lossless  → always true when a result is returned
}

No DOM, no Node APIs — plain typed-array code that runs anywhere.

Tests

node --test   # requires Node >= 22.6 (runs TypeScript directly)

The fixtures are built byte-by-byte in the tests (no binaries in the repo): a synthetic TIFF/EXIF payload with GPS pointer, a PNG with correct CRCs, and a WebP with VP8X flags. The JPEG test asserts the pixel tail of the output is byte-identical to the input.

What it does NOT do

  • It does not remove metadata from formats it can't verify (TIFF, HEIC, AVIF → refused, not guessed).
  • It does not touch color profiles (APP2/ICC, iCCP) — stripping those changes how pixels render, which violates the whole point.
  • Compress-to-size, resizing and format conversion are different (lossy) jobs — this module only ever removes metadata.

License

MIT © SolveKit

About

Lossless EXIF/GPS/XMP/IPTC removal for JPEG, PNG and WebP. Container rewrite: pixels stay byte-identical. Zero dependencies, browser + Node.

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages