How to Get Metadata of an Image in Python
A complete, working guide to reading, adding, and removing Exif, GPS, IPTC, and XMP metadata from images using Pillow, exifread, piexif, and ExifTool — with fixes for the method Pillow removed.
img._getexif(), a private Pillow method that was removed in Pillow 10 and now raises an AttributeError. Every example below uses the current public API, adds human-readable tag names (which most competing tutorials skip), and adds GPS decoding and IPTC/XMP coverage that weren’t in the original article.
Metadata is the information a camera, phone, or editing tool stores inside an image file alongside the actual pixels — things like the capture date, camera model, exposure settings, and sometimes GPS coordinates. If you’re building a photo organizer, a forensic tool, a bulk watermarking script, or just need to strip GPS data before publishing a photo, Python gives you several ways to read and write it. This guide covers all of them.
What image metadata actually is
Metadata generally falls into three standards, and which one you need depends on the task:
- Exif (Exchangeable Image File Format) — camera settings and capture conditions: aperture, shutter speed, ISO, focal length, timestamp, and GPS location. Present mainly in JPEG and TIFF files from cameras and phones.
- IPTC (International Press Telecommunications Council) — editorial fields used in journalism and stock photography: caption, keywords, byline, and copyright notice. Added manually or by asset-management software, not by the camera.
- XMP (Extensible Metadata Platform) — an Adobe-designed, XML-based format that can carry Exif- and IPTC-like fields plus custom data, and can be embedded in far more file types (PDF, video, PNG) than Exif can.
Pillow and exifread only read Exif. For IPTC and XMP you need a separate library, covered further down.
Setup
Install the libraries used in this guide as you need them — you don’t need all of them for every task:
pip install Pillow exifread piexif iptcinfo3 python-xmp-toolkit
You’ll also need a sample image that actually contains Exif data. Photos exported from Instagram, WhatsApp, or Photoshop’s “Save for Web” are frequently stripped of metadata — use an original, unedited photo straight off a phone or camera to follow along.
Reading metadata with Pillow
Pillow is the most common way to touch image metadata in Python. The method used in most older tutorials, img._getexif(), was undocumented and has been removed as of Pillow 10. Use Image.getexif() instead, paired with TAGS so you get readable field names rather than numeric IDs.
from PIL import Image from PIL.ExifTags import TAGS img = Image.open("example.jpg") exif_data = img.getexif() if not exif_data: print("No Exif data found in this image.") else: for tag_id, value in exif_data.items(): tag_name = TAGS.get(tag_id, tag_id) print(f"{tag_name:25}: {value}")
This prints things like camera make, model, and orientation. Some fields — including GPS data — live in a nested IFD (image file directory) rather than the top-level dictionary, which is why a plain loop sometimes looks emptier than you’d expect from a photo you know has metadata. Pillow exposes those nested IFDs through get_ifd():
from PIL.ExifTags import IFD exif_ifd = exif_data.get_ifd(IFD.Exif) for tag_id, value in exif_ifd.items(): tag_name = TAGS.get(tag_id, tag_id) print(f"{tag_name:25}: {value}")
img.info instead — tools like Stable Diffusion and many screenshot utilities store metadata as plain text chunks there (img.info.get("parameters") is a common key for AI-generated images, for example).
Reading metadata with exifread
exifread is a dedicated Exif parser that tends to surface more tags — including GPS, thumbnail, and maker-note fields — than Pillow does by default, and it works without decoding the full image.
import exifread with open("example.jpg", "rb") as f: tags = exifread.process_file(f, details=False) for tag, value in tags.items(): if tag not in ("JPEGThumbnail", "TIFFThumbnail"): print(f"{tag:30}: {value}")
details=False skips the thumbnail and maker-note blobs, which keeps the output readable for a quick inspection.
Extracting GPS coordinates
This is the part most tutorials on this topic skip entirely, even though it’s usually the actual reason someone wants image metadata — checking or stripping the location a photo was taken at. GPS data is stored as degrees/minutes/seconds, so it needs a conversion step to become a usable latitude/longitude pair.
from PIL import Image from PIL.ExifTags import TAGS, GPSTAGS def get_gps_coordinates(path): img = Image.open(path) exif_data = img.getexif() gps_ifd = exif_data.get_ifd(next(k for k, v in TAGS.items() if v == "GPSInfo")) if not gps_ifd: return None gps = {GPSTAGS.get(k, k): v for k, v in gps_ifd.items()} def to_decimal(dms, ref): degrees, minutes, seconds = dms decimal = float(degrees) + float(minutes) / 60 + float(seconds) / 3600 if ref in ("S", "W"): decimal = -decimal return decimal lat = to_decimal(gps["GPSLatitude"], gps["GPSLatitudeRef"]) lon = to_decimal(gps["GPSLongitude"], gps["GPSLongitudeRef"]) return lat, lon coords = get_gps_coordinates("example.jpg") print(coords or "No GPS data in this image.")
The result is a plain (latitude, longitude) tuple you can drop straight into Google Maps, a geocoding API, or a mapping library like Folium.
Reading everything with ExifTool
ExifTool isn’t a Python library — it’s a standalone command-line tool — but it reads more metadata formats (Exif, IPTC, XMP, maker notes, and video metadata) than any single Python package, so it’s worth calling from a script via subprocess when you need full coverage rather than just Exif.
# Windows (via Chocolatey) choco install exiftool # macOS brew install exiftool # Debian/Ubuntu sudo apt install libimage-exiftool-perl
import subprocess, json result = subprocess.run( ["exiftool", "-json", "example.jpg"], capture_output=True, text=True ) metadata = json.loads(result.stdout)[0] for key, value in metadata.items(): print(f"{key:25}: {value}")
Using -json gives you a structured dictionary instead of parsing plain-text output, which is far easier to work with programmatically.
Reading IPTC and XMP metadata
Neither Pillow nor exifread touches IPTC or XMP. If a task needs captions, keywords, or copyright fields set by editorial software, reach for a library built for that standard instead.
IPTC with iptcinfo3
from iptcinfo3 import IPTCInfo info = IPTCInfo("example.jpg", force=True) print("Caption:", info["caption/abstract"]) print("Keywords:", info["keywords"]) print("Byline:", info["by-line"])
XMP with python-xmp-toolkit
from libxmp.utils import file_to_dict xmp = file_to_dict("example.jpg") for namespace, fields in xmp.items(): for field in fields: print(namespace, field)
python-xmp-toolkit wraps Adobe’s Exempi C library, so on Linux you may need sudo apt install libexempi8 before pip install will build successfully.
Writing and adding metadata
To modify Exif data, use piexif — it handles the binary Exif structure so you don’t have to build it by hand.
Editing an existing tag
import piexif from PIL import Image img = Image.open("example.jpg") exif_dict = piexif.load(img.info.get("exif", b"")) exif_dict["0th"][piexif.ImageIFD.Artist] = "Your Name" exif_bytes = piexif.dump(exif_dict) img.save("example_with_metadata.jpg", exif=exif_bytes)
Using img.info.get("exif", b"") instead of indexing img.info["exif"] directly avoids a KeyError on images that have no Exif block yet.
Adding new tags from scratch
import piexif from PIL import Image img = Image.open("example.jpg") exif_dict = { "0th": { piexif.ImageIFD.Artist: "Your Name", piexif.ImageIFD.Make: "Your Camera Make", piexif.ImageIFD.Model: "Your Camera Model", }, "Exif": { piexif.ExifIFD.DateTimeOriginal: "2026:09:01 10:00:00", }, } exif_bytes = piexif.dump(exif_dict) img.save("example_with_new_metadata.jpg", exif=exif_bytes)
Adding metadata with ExifTool instead
import subprocess subprocess.run([ "exiftool", "-Artist=Your Name", "-Make=Your Camera Make", "-Model=Your Camera Model", "-DateTimeOriginal=2026:09:01 10:00:00", "-overwrite_original", "example.jpg", ])
ExifTool can also write IPTC and XMP fields the same way (for example, -Caption-Abstract=... or -XMP:Title=...), which piexif cannot, since piexif is Exif-only.
Removing metadata
Stripping metadata is one of the most common real-world reasons to touch this at all — usually before publishing a photo publicly.
# Option 1: Pillow — re-save without passing exif data from PIL import Image img = Image.open("example.jpg") data = list(img.getdata()) clean_img = Image.new(img.mode, img.size) clean_img.putdata(data) clean_img.save("clean.jpg") # Option 2: ExifTool — strips Exif, IPTC, and XMP in one pass import subprocess subprocess.run(["exiftool", "-all=", "example.jpg", "-overwrite_original"])
The Pillow approach rebuilds the image from raw pixel data, which drops the Exif block entirely. ExifTool’s -all= is the more thorough option since it also clears IPTC and XMP, which the Pillow method leaves untouched.
Library comparison
| Library | Reads | Writes | Formats supported | Best for |
|---|---|---|---|---|
Pillow | Exif, GPS (via IFD) | No (pair with piexif) | JPEG, TIFF, PNG text chunks | Quick reads inside an existing image-processing pipeline |
exifread | Exif, GPS, thumbnails | No | JPEG, TIFF | Deep Exif inspection without decoding pixels |
piexif | Exif | Yes | JPEG, TIFF | Adding or editing Exif tags in Python |
iptcinfo3 | IPTC | Yes | JPEG | Editorial captions, keywords, bylines |
python-xmp-toolkit | XMP | Yes | JPEG, PNG, PDF, video | XMP packets and custom namespaces |
ExifTool (CLI) | Exif, IPTC, XMP, maker notes | Yes | 500+ formats | One tool that reads and writes everything |
Common errors and fixes
| Error | Cause | Fix |
|---|---|---|
AttributeError: 'Image' object has no attribute '_getexif' | Pillow 10+ removed the private method | Use img.getexif() |
exif_data is empty or None | Image has no Exif block (PNG, screenshot, or metadata already stripped) | Check with if not exif_data before iterating |
KeyError: 'exif' on piexif.load(img.info['exif']) | Image has no existing Exif segment to load | Use img.info.get("exif", b"") |
Tag values print as raw bytes, e.g. b'2026:09:01' | Some Exif fields are stored as bytes, not strings | Call .decode() on the value if isinstance(value, bytes) |
GPS tags missing after looping exif_data.items() | GPS lives in a nested IFD, not the top level | Use exif_data.get_ifd(...) as shown above |
A privacy note worth acting on
FAQ
What is image metadata?
Information embedded in an image file describing the image itself — capture date, camera settings, GPS location, author, and copyright — separate from the pixel data.
How do I get metadata from an image in Python?
Use Image.getexif() from Pillow together with PIL.ExifTags.TAGS for human-readable names, or use exifread for a wider set of Exif and GPS tags.
Why does img._getexif() fail in newer Pillow versions?
It was a private, undocumented method removed in Pillow 10. Use the public Image.getexif() method instead.
How do I extract GPS coordinates from a photo in Python?
Read the GPSInfo Exif tag, map its sub-tags with GPSTAGS, then convert the degrees/minutes/seconds values to decimal latitude and longitude, as shown in the GPS section above.
Can Python read IPTC and XMP metadata, not just Exif?
Yes — use iptcinfo3 for IPTC fields and python-xmp-toolkit for XMP packets, since Pillow and exifread only cover Exif.
How do I add metadata to a JPG in Python?
Build an Exif dictionary with piexif, dump it to bytes with piexif.dump(), and pass it to img.save(path, exif=exif_bytes).
How do I remove all metadata from an image?
Re-save the image with Pillow without an exif argument, or run exiftool -all= file.jpg -overwrite_original to strip Exif, IPTC, and XMP together.
Does Exif work on PNG files?
Rarely. PNG doesn’t natively store Exif the way JPEG and TIFF do; check img.info for text chunks instead, which is where tools like screenshot utilities and AI image generators often store metadata.
Is ExifTool a Python library?
No — it’s a standalone command-line program, called from Python via the subprocess module. It covers more metadata formats than any single Python package.
Why is my exif_data empty?
Most likely the image genuinely has no Exif block — this is common for images downloaded from social media, screenshots, or files already run through a metadata stripper.
Leave a Reply
You must be logged in to post a comment.