# Remove EXIF & Metadata from Images and Videos – Bulk Privacy (`gazidev/media-metadata-remover`) Actor

Strip GPS location, camera model, EXIF, IPTC, XMP and C2PA from photos (JPEG, PNG, WebP, HEIC, AVIF, TIFF, GIF) and videos (MP4, MOV, MKV, WebM) in bulk, losslessly. Before/after privacy report, download links, ZIP. $1 per 1,000 images.

- **URL**: https://apify.com/gazidev/media-metadata-remover.md
- **Developed by:** [Cemal Atakli](https://apify.com/gazidev) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 image cleaneds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Remove EXIF & Metadata from Images and Videos (Bulk, Lossless)

**Strip GPS location, camera model, serial numbers, dates, EXIF, IPTC, XMP and C2PA from photos and videos in bulk** before you publish them. Paste file URLs (or point at an Apify dataset / key-value store), click Start, and download clean copies plus a before/after privacy report for every file.

- Photos: **JPEG, PNG, WebP, GIF** are cleaned **losslessly**: the compressed pixel data is copied byte for byte; only metadata blocks are dropped. **TIFF** is re-saved pixel-identical. **HEIC and AVIF** are re-encoded at high quality.
- Videos: **MP4, MOV, M4V, 3GP, MKV, WebM, AVI** are remuxed with FFmpeg stream copy (**no re-encoding, no quality loss**). The Actor drops global and per-stream tags, chapters, the Apple/Android location and make/model tags, and GoPro/telemetry data streams.
- **Before/after report**: whether GPS was present (with the coordinates that were exposed), the camera or phone model, the serial number, the date taken, software, XMP/IPTC/C2PA, AI prompts in PNG files, and appended data. Each output file is then re-read to confirm it is clean (`verifiedClean`).
- **Download links** for every cleaned file, plus an optional **ZIP of everything**.
- **Options**: keep the Orientation tag so photos stay upright, or rotate the pixels instead. You can also keep copyright/artist and the ICC colour profile.

### Who uses it

- **Marketplaces, real-estate and e-commerce teams**: remove sellers' and agents' home GPS from listing photos before they go live.
- **UGC platforms, newsrooms and NGOs**: sanitise uploads so they don't reveal where or with which device a photo was taken.
- **Privacy- and OSINT-aware users**: check what your photos give away, then clean them in one run.
- **Pipelines**: run it after our Image Downloader, Bulk File Downloader or any scraper (dataset input) and before you publish or share.

### What gets removed

| Metadata | Photos | Videos |
|---|---|---|
| GPS latitude/longitude/altitude | ✅ EXIF GPS + XMP GPS | ✅ `location`, `com.apple.quicktime.location.ISO6709`, Android location |
| Camera/phone make, model, lens, serial number, owner | ✅ | ✅ (`com.apple.quicktime.make/model`, `com.android.*`) |
| Date taken / creation time | ✅ | ✅ (zeroed) |
| Software, maker notes, user comments, thumbnails | ✅ | ✅ encoder tags |
| XMP, IPTC (Photoshop APP13), C2PA / JUMBF Content Credentials | ✅ | ✅ (uuid/udta boxes are not copied) |
| PNG text chunks (incl. Stable Diffusion/ComfyUI prompts), `tIME`, `caBX` | ✅ | – |
| JPEG MPF previews and data appended after the image | ✅ | – |
| Chapters, data/telemetry streams (GoPro GPMF, camm) | – | ✅ |
| Orientation | kept by default (one tag), or applied to the pixels | rotation flag kept, or cleared with `orientation: remove` |
| ICC colour profile | kept by default (`keepColorProfile`) | – |

### Input example

```json
{
  "mediaUrls": [
    "https://raw.githubusercontent.com/ianare/exif-samples/master/jpg/gps/DSCN0010.jpg",
    "https://example-bucket.s3.amazonaws.com/listing-123/kitchen.heic",
    "https://example-bucket.s3.amazonaws.com/listing-123/walkthrough.mov"
  ],
  "orientation": "keep",
  "keepCopyright": false,
  "keepColorProfile": true,
  "createZip": true
}
```

Other ways to give it files:

- `bulkText`: paste a list or a CSV with a `url` column.
- `sourceFileUrl`: a public .txt/.csv file.
- `datasetId` + `datasetField`: the file URLs from another Actor's dataset.
- `keyValueStoreId`: every image or video stored in an Apify key-value store, for example files you uploaded through the API. Use `keyValueStorePrefix` to filter by key.

Links to YouTube, TikTok, Instagram, Facebook, X, Vimeo, Pinterest and other platforms are refused for free. This is a cleaner for files you own or are allowed to process, not a downloader.

### Output example

One dataset row per file, and the cleaned file in the run's key-value store:

```json
{
  "sourceUrl": "https://raw.githubusercontent.com/ianare/exif-samples/master/jpg/gps/DSCN0010.jpg",
  "status": "ok",
  "mediaType": "image",
  "format": "jpeg",
  "method": "metadata-segments-removed",
  "lossless": true,
  "hadGps": true,
  "camera": "NIKON COOLPIX P6000",
  "removed": ["GPS location", "Camera make/model", "Date/time taken", "Software", "Maker notes", "User comment", "EXIF block", "XMP"],
  "kept": [],
  "verifiedClean": true,
  "before": {"gps": true, "gpsLatitude": 43.467448, "gpsLongitude": 11.885127, "camera": "NIKON COOLPIX P6000",
             "software": "Nikon Transfer 1.1 W", "dateTaken": "2008:10:22 16:28:39", "exifTagCount": 48, "xmp": true},
  "after": {"width": 640, "height": 480, "hasExif": false},
  "sizeBefore": 161713,
  "sizeAfter": 146420,
  "fileName": "DSCN0010.jpg",
  "fileUrl": "https://api.apify.com/v2/key-value-stores/<storeId>/records/DSCN0010.jpg",
  "billedEvents": {"image-cleaned": 1}
}
```

The `SUMMARY` record holds the run totals and the ZIP link (`zipUrl`). Failed files get a row with `errorCode` (`HTTP_404`, `NOT_MEDIA`, `FILE_TOO_LARGE`, `UNSUPPORTED_PLATFORM`, `INVALID_URL`, `MEMORY_TOO_LOW`, ...) and are **never charged**.

### Pricing (pay per event)

| Event | Price |
|---|---|
| Actor start | $0.00005 |
| Image cleaned (`image-cleaned`) | **$0.001** ($1 per 1,000 photos) |
| Video cleaned (`video-cleaned`) | **$0.003** per started 50 MB of video (e.g. a 120 MB clip = $0.009) |
| Errors, invalid links, skipped files, ZIP | free |

Set a **maximum total charge** on the run and the Actor stops cleanly when the budget is used up. Files it doesn't process are not charged.

| Actor | Price for 1,000 photos |
|---|---|
| **This Actor** | **$1.00** (+$0.00005 start) |
| perryay/image-metadata-analyzer | $0.02 start + $0.005 per strip = $5.02 |

(Store prices checked 2026-10-21.)

### FAQ

**Does it reduce quality?** No, for JPEG, PNG, WebP, GIF and all videos: the compressed image or video data is copied unchanged. TIFF is re-saved with lossless LZW. HEIC/AVIF have to be re-encoded (quality 92/90), and `lossless: false` in the row tells you when that happened. With `orientation: apply`, JPEGs are rotated with lossless jpegtran when the dimensions allow it. Otherwise they are re-encoded at quality 95.

**Why do my photos still have an Orientation tag?** With the default `orientation: keep`, the only tag left is the one that tells viewers to rotate a phone photo upright. It reveals nothing about you. Choose `apply` to rotate the pixels and have zero EXIF, or `remove` to drop it.

**How do I know it worked?** Each output file is parsed again after cleaning. `verifiedClean: true` means no GPS, camera, date, software, XMP, IPTC, C2PA, comment or prompt metadata was found. We also checked the test outputs with ExifTool.

**Are HDR gain maps / Live Photo data kept?** No. Secondary images in JPEG MPF data and bytes appended after the image (where phones put depth maps, gain maps and preview copies that carry their own EXIF) are removed. The photo still looks the same, but it loses HDR gain-map rendering.

**Video size limit?** 500 MB by default (`maxVideoSizeMb`, up to 2 GB). The run needs about 2.5× the video size in memory: 1 GB of memory handles about 400 MB per video.

**Is C2PA removal OK?** Content Credentials are metadata you are allowed to remove from your own files. Only remove them when you don't need to prove the file's origin.

### Use with AI agents / Apify MCP

The Actor works as a tool through the [Apify MCP server](https://mcp.apify.com) (`gazidev/media-metadata-remover`). An agent can pass `mediaUrls` and read `fileUrl`, `removed` and `verifiedClean` from the dataset, for example: "strip location data from these listing photos and give me the clean links". It is a deterministic tool with small JSON output, so agents can call it cheaply in publishing pipelines.

### Related Actors

- **Image Metadata & EXIF Extractor**: reads the metadata without changing the file (GPS with nearest city, AI-generation signals, hashes).
- **Video Converter & Compressor**: converts and compresses videos (MP4/WebM/GIF/MP3).
- **Bulk File Downloader**: downloads files from URL lists or datasets into a ZIP.

# Actor input Schema

## `mediaUrls` (type: `array`):

Direct links to photo or video FILES: JPEG, PNG, WebP, HEIC/HEIF, AVIF, TIFF, GIF, MP4, MOV, M4V, MKV, WebM, AVI (your CDN, S3/R2/GCS bucket, Dropbox/Drive direct link…). Pages of YouTube, TikTok, Instagram, Facebook, X etc. are refused for free. Duplicates are removed.

## `bulkText` (type: `string`):

One URL per line, or a CSV with a `url` column.

## `sourceFileUrl` (type: `string`):

Public .txt or .csv link (Google Sheets 'Publish to web → CSV' works).

## `datasetId` (type: `string`):

Chain after a scraper or our Image Downloader / Bulk File Downloader.

## `datasetField` (type: `string`):

Arrays of URLs are accepted too.

## `keyValueStoreId` (type: `string`):

Every binary record (image/video) in this store is cleaned - e.g. files you uploaded with the Apify API or another Actor's output store. JSON/text records are skipped for free.

## `keyValueStorePrefix` (type: `string`):

Optional key prefix filter for the key-value store.

## `orientation` (type: `string`):

Phones store portrait photos sideways plus an Orientation tag. 'keep' leaves just that one harmless tag so photos still display upright. 'apply' rotates the pixels (lossless with jpegtran when the size allows, else high-quality re-encode). For videos, 'remove' also clears the rotation flag.

## `keepCopyright` (type: `boolean`):

Keep EXIF Artist/Copyright and PNG Author/Copyright text. Everything else (GPS, camera, dates, XMP, IPTC) is still removed.

## `keepColorProfile` (type: `boolean`):

Keeps colours accurate (Display P3, Adobe RGB photos). An ICC profile does not identify you; turn off for the smallest files.

## `createZip` (type: `boolean`):

One `cleaned-files.zip` download link in the run's key-value store (free).

## `maxItems` (type: `integer`):

0 = all.

## `maxImageSizeMb` (type: `integer`):

Larger images are skipped for free (FILE_TOO_LARGE).

## `maxVideoSizeMb` (type: `integer`):

Larger downloads are stopped and skipped for free. Videos are remuxed without re-encoding (fast), but the run needs about 2.5× the video size in memory: 1 GB memory handles videos up to ~400 MB.

## `maxConcurrency` (type: `integer`):

Videos are additionally limited to 2 at a time.

## `proxyConfiguration` (type: `object`):

Only for hosts that block datacenter IPs.

## Actor input object example

```json
{
  "mediaUrls": [
    "https://raw.githubusercontent.com/ianare/exif-samples/master/jpg/gps/DSCN0010.jpg",
    "https://raw.githubusercontent.com/ianare/exif-samples/master/jpg/Canon_40D.jpg"
  ],
  "datasetField": "url",
  "orientation": "keep",
  "keepCopyright": false,
  "keepColorProfile": true,
  "createZip": false,
  "maxItems": 0,
  "maxImageSizeMb": 50,
  "maxVideoSizeMb": 500,
  "maxConcurrency": 6,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `overview` (type: `string`):

No description

## `report` (type: `string`):

No description

## `results` (type: `string`):

No description

## `files` (type: `string`):

No description

## `summary` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "mediaUrls": [
        "https://raw.githubusercontent.com/ianare/exif-samples/master/jpg/gps/DSCN0010.jpg",
        "https://raw.githubusercontent.com/ianare/exif-samples/master/jpg/Canon_40D.jpg"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("gazidev/media-metadata-remover").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "mediaUrls": [
        "https://raw.githubusercontent.com/ianare/exif-samples/master/jpg/gps/DSCN0010.jpg",
        "https://raw.githubusercontent.com/ianare/exif-samples/master/jpg/Canon_40D.jpg",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("gazidev/media-metadata-remover").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "mediaUrls": [
    "https://raw.githubusercontent.com/ianare/exif-samples/master/jpg/gps/DSCN0010.jpg",
    "https://raw.githubusercontent.com/ianare/exif-samples/master/jpg/Canon_40D.jpg"
  ]
}' |
apify call gazidev/media-metadata-remover --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,gazidev/media-metadata-remover"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/ZSQ5p5wjy7pPWsj4e/builds/dsUIep6mBP7d8Dwwb/openapi.json
