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()})| Tag | Why it matters |
|---|---|
Orientation | Tells viewers how to rotate; ignoring it makes portrait photos sideways |
DateTimeOriginal | Capture time, used for sorting and renaming |
Make / Model | Device identification |
ExposureTime / FNumber | Photographic settings |
GPSInfo | Latitude and longitude, a privacy risk |
ICCProfile | Colour 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=.Related
Opening, inspecting and saving images Batch image processing scripts and performance
Last refreshed 2026-09-18.