# Image Metadata & EXIF Extractor: GPS, Camera, Dates (Bulk) (`data-done-right/image-metadata-exif-extractor`) Actor

Image metadata and EXIF extractor for lists of image URLs. Get camera make and model, lens, ISO, exposure, date taken, GPS latitude and longitude with a map link, orientation, ICC profile, XMP and IPTC as clean flat JSON.

- **URL**: https://apify.com/data-done-right/image-metadata-exif-extractor.md
- **Developed by:** [Data Done Right](https://apify.com/data-done-right) (community)
- **Categories:** Developer tools, Automation, Other
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.40 / 1,000 image analyzeds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

## Image Metadata & EXIF Extractor: GPS, Camera, Dates (Bulk)

Use this **EXIF extractor** to read **image metadata** from a list of image URLs. For every photo you get the camera make and model, lens, focal length, ISO, shutter speed, aperture, the date taken as ISO 8601, GPS latitude, longitude and altitude as signed decimals with a ready map link, orientation, software, image size, format and color mode. It also summarizes the ICC color profile, XMP and IPTC. No browser, no login, no API key.

One flat dataset row per image with stable field names, so the **EXIF data** goes straight into a spreadsheet, a database or your next automation step. Need every tag? Turn on `includeRawExif` and get the full raw EXIF object too.

> **Privacy note.** GPS coordinates and dates are sensitive personal data. Use this Actor on your own images, or on images you have the right to analyze. It reads files you point it at; it does not search for people, scrape social networks, or log in anywhere.

### What can this image metadata extractor read

- **Camera and exposure:** `make`, `model`, `lens`, `focalLength`, `focalLength35mm`, `iso`, `exposureTime` (like `1/250`), `exposureSeconds`, `aperture`, `exposureProgram`, `meteringMode`, `flash`.
- **Dates:** `dateTaken`, `dateDigitized`, `dateModified` as ISO 8601 (sub-seconds and the time-zone offset are added when the camera recorded them).
- **GPS:** `gpsLatitude`, `gpsLongitude`, `gpsAltitude` (south, west and below sea level are negative), `gpsDirection`, `gpsTimestamp` and a `mapsUrl` that opens the spot in a map.
- **Image facts:** `format`, `mimeType`, `width`, `height`, `megapixels`, `colorMode`, `frames` (animated GIF/WebP), `orientation` and `orientationLabel`, `fileSizeBytes`, `sha256` (find duplicates).
- **Authoring:** `software`, `artist`, `copyright`, `description`.
- **Color profile, XMP, IPTC:** an `icc` summary (name, color space, class), basic `xmp` fields (title, creator, rights, rating, keywords) and `iptc` fields (title, keywords, byline, city, country, copyright, caption) when the file has them.
- **Formats:** JPEG, PNG, WebP, TIFF, GIF, BMP and HEIC/HEIF (iPhone photos).
- **Bulk and API-friendly:** up to 5,000 images per run, optional dataset input (the output of another Actor), one bad URL never stops the run.

### How to extract EXIF data from images in bulk

1. Paste direct image links in **Image URLs** (one per line), or pick a dataset that contains image URLs.
2. Optional: switch on **Include raw EXIF object** if you want every tag.
3. Run it. Each image becomes one row. Download the dataset as JSON, CSV or Excel.

### How to find where a photo was taken

Look at `gpsLatitude`, `gpsLongitude` and `mapsUrl`. `hasGps` is `true` only when valid coordinates exist. Many phones and cameras have location off, and almost every social network and messenger strips GPS on upload, so photos downloaded from those sites usually show `hasGps: false`. That is the real file, not an error.

### Input example

```json
{
  "urls": [
    "https://raw.githubusercontent.com/ianare/exif-samples/5332a9cf0220e9c6c93f88a187daa90808051e10/jpg/gps/DSCN0010.jpg"
  ],
  "includeRawExif": false
}
```

Other options:

- `{"datasetId": "<dataset id>", "datasetField": "imageUrl"}` : analyze the images found in a scraper's output.
- `{"maxImages": 500, "maxFileSizeMb": 50, "concurrency": 8}` : bigger batches.

### Output example

```json
{
  "index": 1,
  "sourceUrl": "https://raw.githubusercontent.com/ianare/exif-samples/.../DSCN0010.jpg",
  "fileName": "DSCN0010.jpg",
  "status": "ok",
  "error": null,
  "fileSizeBytes": 161713,
  "format": "JPEG",
  "mimeType": "image/jpeg",
  "width": 640,
  "height": 480,
  "hasExif": true,
  "hasGps": true,
  "make": "NIKON",
  "model": "COOLPIX P6000",
  "lens": null,
  "focalLength": 24,
  "focalLength35mm": 112,
  "iso": 64,
  "exposureTime": "1/75",
  "aperture": 5.9,
  "dateTaken": "2008-10-22T16:28:39",
  "gpsLatitude": 43.467448,
  "gpsLongitude": 11.885127,
  "gpsAltitude": null,
  "mapsUrl": "https://www.google.com/maps?q=43.467448,11.885127",
  "orientation": 1,
  "orientationLabel": "Horizontal (normal)",
  "software": "Nikon Transfer 1.1 W",
  "icc": null,
  "xmp": null,
  "iptc": null
}
```

- `status`: `ok` (analyzed, charged), `skipped` (duplicate URL, `maxImages`, SVG, spending limit) or `failed` (download error, not an image). `error` says why.
- Fields the file does not have are `null`, so every row has the same columns.
- The `SUMMARY` record has the totals: analyzed, with EXIF, with GPS, charged, plus notes such as "none of the images had EXIF data".

### How much does it cost to extract image metadata

Pay per event: **$0.003 per image analyzed** (event `image-analyzed`, $3 per 1,000 images), plus the normal Apify platform usage of the run, which is small because only the file headers are parsed. There is no start fee. An image counts as analyzed when it downloads and is a readable image, even if it has no EXIF block (you still get size, format and color mode). Failed downloads, non-images, duplicates and skipped URLs are free. Set a maximum spending limit on your run and it stops cleanly when reached.

### Use cases

- Audit a photo library: which camera, lens and settings were used, and when.
- Check whether your own images leak GPS location before you publish them.
- Sort and deduplicate photos by date taken, camera or checksum.
- Photo and image forensics, provenance and insurance documentation of your own images.
- Add location, camera and date columns to an image dataset from a scraper.
- Real estate, e-commerce and archive teams validating image size, color profile and orientation in bulk.

### Limits (honest)

- The Actor downloads public image URLs. It does not log in and does not work around hotlink protection: a 401, 403 or 429 from the host is reported as `failed`.
- It reads metadata that is in the file. Many sites strip EXIF when images are uploaded or resized, so a thumbnail from a web page often has none. Use the original file URL when you can.
- Max 25 MB per file by default (up to 100 MB). SVG (vector) files have no EXIF and are skipped.
- Reads EXIF through Pillow. Manufacturer-specific MakerNote blocks (lens serial numbers, shot counts) are not decoded; the raw object only shows their size. Camera RAW formats (CR2, NEF, ARW, DNG) are not supported.
- `dateTaken` is the camera's local clock time. It only has a time-zone offset when the camera wrote one.
- XMP is read with a lightweight parser for the common basic fields, not the full XMP standard.

### FAQ

**How do I extract EXIF data from an image URL?**
Paste the direct link to the image file in `urls` and run the Actor. The row contains the camera, settings, date and GPS fields.

**Why is `hasExif` false or the GPS missing?**
The file does not contain it. Social networks, messengers and many CMSs remove EXIF and GPS, and some cameras have location turned off. Try the original file.

**Is the GPS latitude and longitude in decimal degrees?**
Yes. EXIF stores degrees, minutes and seconds with a N/S/E/W letter; the Actor converts them to signed decimals (south and west are negative) that you can paste into any map or GIS tool.

**Can it read iPhone HEIC photos?**
Yes. HEIC/HEIF files are read, including their EXIF and GPS.

**Can I get every EXIF tag, not only the common ones?**
Yes, set `includeRawExif` to `true`. The `exif` object holds all tags with readable names, and GPS under `GPSInfo`.

**Is it OK to analyze other people's photos?**
Only with the right to do so. GPS and date information is personal data. Use your own images or ones you are allowed to process.

### Related tools

- **Image Compressor & Resizer: Compress Images in Bulk** : shrink images and strip EXIF and GPS.
- **HEIC to JPG Converter: Convert HEIC/HEIF to JPG & PNG** : turn iPhone photos into JPG or PNG.
- **Download Images from URLs: Bulk Image & File Downloader** : collect the image files first, then analyze them.

# Actor input Schema

## `urls` (type: `array`):

Direct links to the images to analyze, one per line. JPG, PNG, WebP, TIFF, GIF, BMP and HEIC work. Use your own images or images you have the right to process; GPS data can reveal where a photo was taken. Example: https://example.com/photo.jpg.

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

Optional. Pick an Apify dataset (for example the output of a scraper) and every image URL in the field below is analyzed too. Example: a dataset ID such as aBcD1234eFgH5678i.

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

Field of each dataset item that holds the image URL (or a list of URLs). Dotted paths work. Example: imageUrl or images.0.url. Default: url.

## `includeRawExif` (type: `boolean`):

Add an 'exif' field with every EXIF tag Pillow can read (main, Exif and GPS blocks, with readable tag names). Large binary blocks such as MakerNote are summarized by size. Off by default to keep rows small.

## `maxImages` (type: `integer`):

Most images analyzed in this run. URLs beyond this are skipped and not charged. Example: 100.

## `maxFileSizeMb` (type: `integer`):

Images larger than this are skipped before they are fully downloaded. Example: 25.

## `concurrency` (type: `integer`):

How many images are processed at the same time. Default 8.

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

Optional. Download images through Apify Proxy or your own proxies. Usually not needed.

## Actor input object example

```json
{
  "urls": [
    "https://raw.githubusercontent.com/ianare/exif-samples/5332a9cf0220e9c6c93f88a187daa90808051e10/jpg/gps/DSCN0010.jpg"
  ],
  "datasetField": "url",
  "includeRawExif": false,
  "maxImages": 100,
  "maxFileSizeMb": 25,
  "concurrency": 8,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

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

All result items of this run (the default dataset), as JSON, CSV, Excel and more.

## `records` (type: `string`):

Key-value store records: the run summary and notes on any problems.

# 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 = {
    "urls": [
        "https://raw.githubusercontent.com/ianare/exif-samples/5332a9cf0220e9c6c93f88a187daa90808051e10/jpg/gps/DSCN0010.jpg"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("data-done-right/image-metadata-exif-extractor").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 = { "urls": ["https://raw.githubusercontent.com/ianare/exif-samples/5332a9cf0220e9c6c93f88a187daa90808051e10/jpg/gps/DSCN0010.jpg"] }

# Run the Actor and wait for it to finish
run = client.actor("data-done-right/image-metadata-exif-extractor").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 '{
  "urls": [
    "https://raw.githubusercontent.com/ianare/exif-samples/5332a9cf0220e9c6c93f88a187daa90808051e10/jpg/gps/DSCN0010.jpg"
  ]
}' |
apify call data-done-right/image-metadata-exif-extractor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,data-done-right/image-metadata-exif-extractor"
        }
    }
}
```

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/mZOfgCQ9cAXsxO9zd/builds/lQ6OsGtHMiTdBm37l/openapi.json
