close
Skip to content

sen-ltd/exif-stripper

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

exif-stripper

Bulk EXIF and metadata removal CLI for JPEG, PNG, TIFF, and WebP. Strips GPS coordinates, camera make/model, serial numbers, timestamps, and PNG text chunks, and prints exactly which tags were removed for every file. Designed to be auditable: --dry-run shows what would happen without writing, --report json is suitable for CI, and ICC color profiles + Orientation are preserved by default so cleaned files still display correctly.

# Clone
git clone https://github.com/sen-ltd/exif-stripper
cd exif-stripper

# Build & run via Docker (no Python install needed)
docker build -t exif-stripper .
docker run --rm -v "$PWD:/work" exif-stripper --dry-run vacation/

Screenshot

Why a small dedicated tool?

Camera phones embed a surprising amount in EXIF: GPS coordinates accurate to ~10 meters, camera serial numbers that link photos across years, timestamps to the second, and sometimes Face ID / scene metadata. Anyone publishing photos for journalism, activism, OSINT-sensitive work, or legal disclosure should scrub it first.

Existing options each have a downside:

  • ExifTool is the gold standard, but it's 2+ MB of Perl with a famously baroque CLI. Overkill for a one-flag job.
  • Image editors (Photoshop, Affinity, Preview) can strip metadata, but only one image at a time and only when you remember.
  • Python libraries (Pillow, piexif) work, but you have to glue them together yourself and you don't get a per-file report of what came out.

exif-stripper is --dry-run-able, scriptable, batch-friendly, and small. It does one thing: walk a tree of images, scrub their metadata, and tell you exactly what it removed.

A first run

docker run --rm -v "$PWD:/work" exif-stripper --report human vacation/
Scanned 12 files (9 JPEG, 2 PNG, 1 HEIC)
  STRIP   vacation/IMG_0421.jpg  stripped 8 tags (GPSLatitude, GPSLongitude, Make, Model, +4 more)  saved 4.2 KB
  STRIP   vacation/IMG_0422.jpg  stripped 7 tags (GPSLatitude, GPSLongitude, Make, +4 more)         saved 3.8 KB
  STRIP   vacation/sunset.png    stripped 2 tags (Author, Comment)                                  saved 0.8 KB
  SKIP    vacation/phone.heic    HEIC write not supported
  ...

Total: 11 files stripped, 38 KB saved, 1 skipped, 0 failed

Output formats

--report Use case
human Default. Color when stdout is a TTY.
json CI pipelines, scripts, audit logs.
csv Spreadsheets, BI tools, batch reports for legal.

The JSON payload is structured as {summary, files: [...]} with tags_removed, bytes_saved, icc_removed, and dry_run per file. Every action is reversible inspection — diff a --dry-run JSON against a real-run JSON and you have a complete audit trail.

Flags

Flag Behavior
paths (positional) One or more files or directories. Required.
--no-recurse Do not descend into subdirectories.
--out DIR Write cleaned copies into DIR instead of overwriting in place. Preserves the relative directory structure under each input root.
--dry-run Show what would be removed without modifying any file.
--keep TAG[,TAG...] Preserve specific EXIF tags by name. Most common: --keep Orientation.
--auto-rotate Physically rotate pixels by the Orientation tag, then drop Orientation. Mutually overrides --keep Orientation.
--strip-icc Also remove the ICC color profile. Off by default — removing ICC can shift colors on wide-gamut displays.
--report {human,json,csv} Output format. Default human.
--no-color Disable ANSI color in human output.
--version Print version and exit.

What it strips, what it preserves

Format Stripped Preserved by default
JPEG EXIF (Make, Model, Software, DateTime*), GPS sub-IFD, MakerNotes, Interop IFD ICC color profile, Orientation if --keep Orientation
PNG tEXt, iTXt, zTXt chunks (XMP, Author, Comment) ICC color profile
TIFF EXIF IFDs ICC color profile
WebP EXIF chunk, XMP ICC color profile
HEIC / HEIF read-only — files are skipped with a clear message
Video, RAW (CR2/NEF/ARW) not supported — use ExifTool

Thumbnails embedded inside JPEG EXIF (yes, that's a thing) are dropped along with the rest of the EXIF segment because they live in the same APP1 marker.

In-place safety

In-place writes use the temp-file-and-rename pattern: each cleaned image is written to .foo.jpg.XXXX.tmp in the same directory, then os.replaced into the final path. If the process is killed mid-write, the original file at the target path is left intact instead of being truncated. This matters when you're scrubbing a 10,000-photo archive and your laptop runs out of battery.

Exit codes

Code Meaning
0 All files processed successfully (skipped is OK).
1 At least one file failed (corrupt, permission denied, write error). The rest still ran.
2 Configuration error: missing path, bad output dir, unrecognized format flag.

Running without Docker

pip install .
exif-stripper --dry-run ~/Pictures/vacation/

Requires Python 3.10+ and Pillow.

Tests

docker run --rm --entrypoint pytest exif-stripper -q

Tests generate fixture images at runtime via Pillow with known EXIF (GPS, camera model, timestamps), so no binary fixtures are committed and the suite is fully self-contained.

License

MIT. See LICENSE.

Links

About

Bulk EXIF + GPS scrubber for images, with dry-run and JSON audit

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors