Reading and writing EXIF and image metadata

Read orientation, camera data and GPS, apply the orientation tag correctly, and strip metadata before publishing files.

Reading EXIF

from PIL import Image, ExifTags

im = Image.open("phone.jpg")
exif = im.getexif()
print(exif.get(ExifTags.Base.Make), exif.get(ExifTags.Base.Model))
print(exif.get(ExifTags.Base.DateTimeOriginal) or exif.get(306))

# tagged values you will actually use
for tag_id, value in exif.items():
    name = ExifTags.TAGS.get(tag_id, tag_id)
    if name in ("Orientation", "DateTime", "ExposureTime", "FNumber"):
        print(name, "=", value)

gps = exif.get_ifd(ExifTags.IFD.GPSInfo)
print({ExifTags.GPSTAGS.get(k, k): v for k, v in gps.items()})
TagWhy it matters
OrientationTells viewers how to rotate; ignoring it makes portrait photos sideways
DateTimeOriginalCapture time, used for sorting and renaming
Make / ModelDevice identification
ExposureTime / FNumberPhotographic settings
GPSInfoLatitude and longitude, a privacy risk
ICCProfileColour profile, stored outside the EXIF block

Orientation

from PIL import Image, ImageOps

im = Image.open("sideways.jpg")
print(im.getexif().get(274, 1))          # 1 means already correct

# apply the tag to the pixels and remove the tag
fixed = ImageOps.exif_transpose(im)
print(fixed.getexif().get(274, 1))       # now 1, or the tag is gone

# the manual equivalent, when you need it
from PIL import Image as I
ops = {3: I.Transpose.ROTATE_180, 6: I.Transpose.ROTATE_270, 8: I.Transpose.ROTATE_90}
tags = im.getexif().get(274, 1)
if tags in ops:
    fixed = im.transpose(ops[tags])

exif_transpose is the correct fix. Rotating the pixels yourself leaves the orientation tag in place, so a viewer that honours the tag rotates the picture a second time.

Writing and stripping

# carry EXIF forward when converting
im = Image.open("phone.jpg")
icc = im.info.get("icc_profile")
exif_bytes = im.getexif().tobytes()
im.save("out.jpg", quality=90, exif=exif_bytes, icc_profile=icc)

# strip everything before publishing
clean = Image.open("phone.jpg")
data = list(clean.getdata())
stripped = Image.new(clean.mode, clean.size)
stripped.putdata(data)
stripped.save("published.jpg", quality=90)     # no exif, no icc_profile
⚠️
A phone photo carries GPS coordinates precise enough to identify a home address, plus a serial number in the camera tag. Strip EXIF and the ICC profile for anything published on the web unless you have a specific reason to keep them.

FAQ

Why does metadata disappear after I resize?
Many operations return a new image object that does not carry the source's info dictionary. Read the EXIF bytes before transforming, then pass exif= again on save if you want to keep them.
How do I set the DPI or add a description?
Use save-time parameters: save(path, dpi=(300, 300)) writes the resolution tags. For arbitrary tags, build an Image.Exif object, assign integer tag ids, and pass it as exif=.

Opening, inspecting and saving images Batch image processing scripts and performance

Last refreshed 2026-09-18.