{"id":50901695,"url":"https://github.com/didrod205/scrubpix","last_synced_at":"2026-06-16T03:04:42.198Z","repository":{"id":361749794,"uuid":"1253517782","full_name":"didrod205/scrubpix","owner":"didrod205","description":"See \u0026 remove hidden image metadata — EXIF, GPS, comments — losslessly and 100% locally. No re-encode, no upload. Web app + zero-dependency library.","archived":false,"fork":false,"pushed_at":"2026-06-01T06:07:56.000Z","size":44,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-01T06:08:11.284Z","etag":null,"topics":["exif","gps","image-metadata","lossless","metadata","photo-privacy","privacy","remove-exif","strip-exif","zero-dependency"],"latest_commit_sha":null,"homepage":"https://didrod205.github.io/scrubpix/","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/didrod205.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"custom":["https://elab-studio.lemonsqueezy.com/checkout/buy/5d059b89-51d0-456b-b33a-ed56994f7010"]}},"created_at":"2026-05-29T14:45:43.000Z","updated_at":"2026-06-01T06:08:00.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/didrod205/scrubpix","commit_stats":null,"previous_names":["didrod205/scrubpix"],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/didrod205/scrubpix","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/didrod205%2Fscrubpix","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/didrod205%2Fscrubpix/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/didrod205%2Fscrubpix/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/didrod205%2Fscrubpix/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/didrod205","download_url":"https://codeload.github.com/didrod205/scrubpix/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/didrod205%2Fscrubpix/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34388683,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-16T02:00:06.860Z","response_time":126,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["exif","gps","image-metadata","lossless","metadata","photo-privacy","privacy","remove-exif","strip-exif","zero-dependency"],"created_at":"2026-06-16T03:04:42.119Z","updated_at":"2026-06-16T03:04:42.192Z","avatar_url":"https://github.com/didrod205.png","language":"TypeScript","funding_links":["https://elab-studio.lemonsqueezy.com/checkout/buy/5d059b89-51d0-456b-b33a-ed56994f7010"],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n\n# 🧼 scrubpix\n\n### See — and remove — the hidden metadata in your photos. Locally, losslessly.\n\n[![npm version](https://img.shields.io/npm/v/scrubpix.svg?color=success)](https://www.npmjs.com/package/scrubpix)\n[![bundle size](https://img.shields.io/bundlephobia/minzip/scrubpix?label=gzip)](https://bundlephobia.com/package/scrubpix)\n[![CI](https://github.com/didrod205/scrubpix/actions/workflows/ci.yml/badge.svg)](https://github.com/didrod205/scrubpix/actions/workflows/ci.yml)\n[![types](https://img.shields.io/npm/types/scrubpix.svg)](https://www.npmjs.com/package/scrubpix)\n[![license](https://img.shields.io/npm/l/scrubpix.svg)](./LICENSE)\n\n**[🌐 Try the free web app →](https://didrod205.github.io/scrubpix/)** \u0026nbsp;·\u0026nbsp; drop photos, see thumbnails + what they reveal, and strip them all to a **.zip** in one click. Nothing uploaded.\n\n\u003c/div\u003e\n\n---\n\nThat photo you're about to post probably knows **exactly where you took it.**\nPhone cameras embed **EXIF metadata** in every image: GPS coordinates, the\ndevice and lens, the precise date \u0026 time, sometimes your name. Screenshots and\nexported graphics can carry editor history and comments too. You can't see any\nof it — but anyone who downloads the file can.\n\n**scrubpix** shows you that hidden metadata and **strips it out** — and it does\nthe removal **losslessly** (it surgically removes the metadata segments instead\nof re-encoding, so your image quality is byte-for-byte untouched). All **100%\nlocally**: your photo never leaves your device.\n\n\u003e 📸 _Screenshot / demo GIF:_ `./web/screenshot.png` — record the [live app](https://didrod205.github.io/scrubpix/) dropping a geotagged photo, revealing the map pin, and downloading the clean copy.\n\n## Why it exists\n\n- **The irony of \"remove EXIF online\" sites:** they make you *upload your\n  sensitive photo to a stranger's server*. A privacy tool must run locally.\n  scrubpix does.\n- **AI can't do this.** Metadata lives in the file's bytes, not its pixels — a\n  vision model can't read it, and asking a chatbot to \"strip EXIF\" is meaningless\n  without the file. It's a precise, binary problem for a small, deterministic tool.\n- **Lossless matters.** The common \"draw to canvas and re-export\" trick *does*\n  remove metadata — by re-compressing and degrading your image. scrubpix keeps\n  the original image data intact.\n\n## Who it's for\n\n**Anyone who shares images:** creators \u0026 photographers (don't leak your home\nlocation), marketers (clean brand assets), journalists \u0026 activists (protect\nsources), ops/support (sanitize screenshots), and everyday people posting to\nthe web. Plus developers who want a tiny, dependency-free metadata library.\n\n## Install\n\n**No install —** just open the **[web app](https://didrod205.github.io/scrubpix/)**.\n\n**Command line** (clean a whole folder in one shot):\n\n```bash\nnpx scrubpix scan ./photos        # see what's hidden (flags GPS!)\nnpx scrubpix strip ./photos -i    # strip every image in place\n```\n\n**Library:**\n\n```bash\nnpm install scrubpix\n```\n\nZero runtime dependencies. ESM + CJS + TypeScript types. Runs in the browser, Node, Deno and Bun.\n\n## CLI\n\n```bash\nscrubpix scan  \u003cpaths...\u003e    # inspect images, print metadata, warn on GPS\nscrubpix strip \u003cpaths...\u003e    # remove metadata (writes *-clean by default)\n```\n\n```text\n$ scrubpix scan ./photos\n✓ photos/clean.png — clean\n⚠ photos/vacation.jpg — 2 field(s) 📍 GPS\n    gps    GPS Latitude: 37.5\n    gps    GPS Longitude: 127\n    → reveals location: https://www.openstreetmap.org/?mlat=37.5\u0026mlon=127#map=15/37.5/127\n\nScanned 3 image(s) — 1 with metadata, 1 with GPS.\n```\n\n| Option | Description |\n| ------ | ----------- |\n| `-i, --in-place` | Overwrite the originals (strip) |\n| `-o, --out \u003cdir\u003e` | Write cleaned files into a directory |\n| `--suffix \u003cs\u003e` | Suffix for cleaned files (default `-clean`) |\n| `--json` | Machine-readable output (scan) |\n| `-q, --quiet` | Only show images that have metadata |\n\nPaths can be **files or directories** (recursed). `scan` exits non-zero when any\nimage still has metadata — drop it into CI as a **privacy gate** so a geotagged\nasset never lands in your repo.\n\n## Usage\n\n```ts\nimport { readMetadata, stripMetadata, detectFormat } from \"scrubpix\";\n\nconst bytes = new Uint8Array(await file.arrayBuffer());\n\n// 1) See what's hidden\nconst meta = readMetadata(bytes);\nmeta.format;       // \"jpeg\" | \"png\" | \"webp\" | \"heic\" | \"unknown\"\nmeta.gps;          // { latitude: 37.5, longitude: 127.0 }  ← if geotagged\nmeta.fields;       // [{ name: \"Make\", value: \"Apple\", group: \"image\" }, ...]\n\n// 2) Remove it — losslessly\nconst { data, bytesRemoved } = stripMetadata(bytes);\n// `data` is a clean Uint8Array with identical pixels; save/download it.\n```\n\n### Node\n\n```ts\nimport { readFileSync, writeFileSync } from \"node:fs\";\nimport { stripMetadata } from \"scrubpix\";\n\nconst { data } = stripMetadata(readFileSync(\"photo.jpg\"));\nwriteFileSync(\"photo-clean.jpg\", data);\n```\n\n### Browser download\n\n```ts\nconst { data } = stripMetadata(bytes);\nconst url = URL.createObjectURL(new Blob([data], { type: file.type }));\n// trigger a download with \u003ca download\u003e …\n```\n\n## What it reads \u0026 removes\n\n| Format | Reads | Strips (losslessly) |\n| ------ | ----- | ------------------- |\n| **JPEG** | EXIF (camera, lens, dates, **GPS**), XMP, IPTC, comments | All `APPn` (n≥1) + comment segments; image scan preserved |\n| **PNG** | `tEXt` / `zTXt` / `iTXt`, `tIME`, `eXIf` (incl. GPS) | All text/time/EXIF chunks; IHDR/IDAT/PLTE/IEND preserved |\n| **WebP** | `EXIF` (incl. GPS), `XMP` chunks | `EXIF`/`XMP` chunks; VP8/VP8L bitstream, `ICCP` profile \u0026 `VP8X` header preserved |\n| **HEIC** _(read-only)_ | EXIF (camera, dates, **GPS**), XMP — via the ISOBMFF `iloc`/`iinf` item table | _Not stripped — see note below_ |\n\n\u003e **Why HEIC is read-only:** in HEIC the EXIF bytes live in `mdat`, referenced by\n\u003e absolute offsets in the `iloc` box. Removing them means recomputing every\n\u003e offset — one mistake corrupts the photo. scrubpix won't take that risk: it\n\u003e **shows** you what an iPhone HEIC reveals (e.g. your GPS location) but won't\n\u003e rewrite the file. `canStrip` is `false` and `stripMetadata` returns the bytes\n\u003e unchanged (`stripped: false`). Convert to JPEG to strip.\n\n## API\n\n| Function | Description |\n| -------- | ----------- |\n| `readMetadata(input)` | `{ format, hasMetadata, fields[], gps?, canStrip }`. |\n| `stripMetadata(input)` | `{ data, format, bytesRemoved, stripped }` — cleaned bytes. |\n| `detectFormat(input)` | `\"jpeg\" \\| \"png\" \\| \"webp\" \\| \"heic\" \\| \"unknown\"`. |\n| `canStrip(format)` | Whether scrubpix can losslessly strip this format (HEIC → `false`). |\n| `hasMetadata(input)` | Boolean shortcut. |\n\n`input` is a `Uint8Array` or `ArrayBuffer`. Low-level helpers\n(`readJpeg`/`stripJpeg`, `readWebp`/`stripWebp`, `readHeic`, `parseTiff`, …) are\nalso exported for advanced use.\n\n## FAQ\n\n**Is my photo uploaded anywhere?**\nNo. The web app and library run entirely on your device — no server, no\ntelemetry, works offline.\n\n**Will stripping reduce image quality?**\nNo. scrubpix removes metadata segments without touching the compressed image\ndata, so the result is visually and byte-for-byte identical (just smaller).\n\n**Does it remove the GPS location?**\nYes — GPS lives in the EXIF (JPEG/WebP) or `eXIf` chunk (PNG), which scrubpix\nstrips. The web app even shows you the map pin first, so you can see what you're\nremoving.\n\n**Which formats are supported?**\nJPEG, PNG and WebP are fully supported (read **and** lossless strip). **HEIC**\n(iPhone photos) is **read-only**: scrubpix detects and shows its metadata —\nincluding GPS — but won't rewrite the file (see the note above). Standalone TIFF\nis on the roadmap.\n\n**Can it remove GPS from my iPhone HEIC photo?**\nIt will *show* you the GPS the HEIC embeds so you know it's there, but it won't\nstrip it in place (that risks corrupting the file). The reliable fix today:\nconvert the HEIC to JPEG, then `scrubpix strip` it.\n\n**Can it strip a whole folder?**\nYes — `scrubpix strip ./photos -i` recurses a directory and cleans every image.\nIn the web app, drop multiple files and hit **\"Strip all \u0026 download .zip\"** to get\nevery cleaned image back in one archive (the zip is built locally too — no\ndependency, no upload).\n\n## Contributing\n\nContributions welcome! See [CONTRIBUTING.md](./CONTRIBUTING.md) and the\n[Code of Conduct](./CODE_OF_CONDUCT.md).\n\n```bash\ngit clone https://github.com/didrod205/scrubpix.git\ncd scrubpix\nnpm install\nnpm test          # run the suite\nnpm run dev       # run the web app locally\n```\n\n## 💖 Sponsor\n\nscrubpix is free, MIT-licensed, and built in spare time. If it kept your\nlocation (or your client's) off the internet, please consider supporting it:\n\n- ⭐ **Star this repo** — free, and it genuinely helps others find it.\n- 🍋 **[Sponsor via Lemon Squeezy](https://elab-studio.lemonsqueezy.com/checkout/buy/5d059b89-51d0-456b-b33a-ed56994f7010)** — one-time or recurring support.\n\n**Where your support goes:** more formats (HEIC/TIFF), deeper EXIF tag coverage,\na \"verify clean\" re-scan in the web app, drag-a-folder batch mode, keeping the\nfree web app online, and fast issue responses.\n\n## License\n\n[MIT](./LICENSE) © scrubpix contributors\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdidrod205%2Fscrubpix","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdidrod205%2Fscrubpix","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdidrod205%2Fscrubpix/lists"}