In Python, how do I read the exif data for an image?
Question:
I’m using PIL. How do I turn the EXIF data of a picture into a dictionary?
Answers:
You can use the _getexif()
protected method of a PIL Image.
import PIL.Image
img = PIL.Image.open('img.jpg')
exif_data = img._getexif()
This should give you a dictionary indexed by EXIF numeric tags. If you want the dictionary indexed by the actual EXIF tag name strings, try something like:
import PIL.ExifTags
exif = {
PIL.ExifTags.TAGS[k]: v
for k, v in img._getexif().items()
if k in PIL.ExifTags.TAGS
}
You can also use the ExifRead module:
import exifread
# Open image file for reading (binary mode)
f = open(path_name, 'rb')
# Return Exif tags
tags = exifread.process_file(f)
I use this:
import os,sys
from PIL import Image
from PIL.ExifTags import TAGS
for (k,v) in Image.open(sys.argv[1])._getexif().items():
print('%s = %s' % (TAGS.get(k), v))
or to get a specific field:
def get_field (exif,field) :
for (k,v) in exif.items():
if TAGS.get(k) == field:
return v
exif = image._getexif()
print get_field(exif,'ExposureTime')
import sys
import PIL
import PIL.Image as PILimage
from PIL import ImageDraw, ImageFont, ImageEnhance
from PIL.ExifTags import TAGS, GPSTAGS
class Worker(object):
def __init__(self, img):
self.img = img
self.exif_data = self.get_exif_data()
self.lat = self.get_lat()
self.lon = self.get_lon()
self.date =self.get_date_time()
super(Worker, self).__init__()
@staticmethod
def get_if_exist(data, key):
if key in data:
return data[key]
return None
@staticmethod
def convert_to_degress(value):
"""Helper function to convert the GPS coordinates
stored in the EXIF to degress in float format"""
d0 = value[0][0]
d1 = value[0][1]
d = float(d0) / float(d1)
m0 = value[1][0]
m1 = value[1][1]
m = float(m0) / float(m1)
s0 = value[2][0]
s1 = value[2][1]
s = float(s0) / float(s1)
return d + (m / 60.0) + (s / 3600.0)
def get_exif_data(self):
"""Returns a dictionary from the exif data of an PIL Image item. Also
converts the GPS Tags"""
exif_data = {}
info = self.img._getexif()
if info:
for tag, value in info.items():
decoded = TAGS.get(tag, tag)
if decoded == "GPSInfo":
gps_data = {}
for t in value:
sub_decoded = GPSTAGS.get(t, t)
gps_data[sub_decoded] = value[t]
exif_data[decoded] = gps_data
else:
exif_data[decoded] = value
return exif_data
def get_lat(self):
"""Returns the latitude and longitude, if available, from the
provided exif_data (obtained through get_exif_data above)"""
# print(exif_data)
if 'GPSInfo' in self.exif_data:
gps_info = self.exif_data["GPSInfo"]
gps_latitude = self.get_if_exist(gps_info, "GPSLatitude")
gps_latitude_ref = self.get_if_exist(gps_info, 'GPSLatitudeRef')
if gps_latitude and gps_latitude_ref:
lat = self.convert_to_degress(gps_latitude)
if gps_latitude_ref != "N":
lat = 0 - lat
lat = str(f"{lat:.{5}f}")
return lat
else:
return None
def get_lon(self):
"""Returns the latitude and longitude, if available, from the
provided exif_data (obtained through get_exif_data above)"""
# print(exif_data)
if 'GPSInfo' in self.exif_data:
gps_info = self.exif_data["GPSInfo"]
gps_longitude = self.get_if_exist(gps_info, 'GPSLongitude')
gps_longitude_ref = self.get_if_exist(gps_info, 'GPSLongitudeRef')
if gps_longitude and gps_longitude_ref:
lon = self.convert_to_degress(gps_longitude)
if gps_longitude_ref != "E":
lon = 0 - lon
lon = str(f"{lon:.{5}f}")
return lon
else:
return None
def get_date_time(self):
if 'DateTime' in self.exif_data:
date_and_time = self.exif_data['DateTime']
return date_and_time
if __name__ == '__main__':
try:
img = PILimage.open(sys.argv[1])
image = Worker(img)
lat = image.lat
lon = image.lon
date = image.date
print(date, lat, lon)
except Exception as e:
print(e)
Here’s the one that may be little easier to read. Hope this is helpful.
from PIL import Image
from PIL import ExifTags
exifData = {}
img = Image.open(picture.jpg)
exifDataRaw = img._getexif()
for tag, value in exifDataRaw.items():
decodedTag = ExifTags.TAGS.get(tag, tag)
exifData[decodedTag] = value
I have found that using ._getexif
doesn’t work in higher python versions, moreover, it is a protected class and one should avoid using it if possible.
After digging around the debugger this is what I found to be the best way to get the EXIF data for an image:
from PIL import Image
def get_exif(path):
return Image.open(path).info['parsed_exif']
This returns a dictionary of all the EXIF data of an image.
Note: For Python3.x use Pillow instead of PIL
For Python3.x and starting Pillow==6.0.0
, Image
objects now provide a "public"/official getexif()
method that returns a <class 'PIL.Image.Exif'>
instance or None
if the image has no EXIF data.
From Pillow 6.0.0 release notes:
getexif()
has been added, which returns an Exif
instance. Values can
be retrieved and set like a dictionary. When saving JPEG, PNG or WEBP,
the instance can be passed as an exif
argument to include any changes
in the output image.
As stated, you can iterate over the key-value pairs of the Exif
instance like a regular dictionary. The keys are 16-bit integers that can be mapped to their string names using the ExifTags.TAGS
module.
from PIL import Image, ExifTags
img = Image.open("sample.jpg")
img_exif = img.getexif()
print(type(img_exif))
# <class 'PIL.Image.Exif'>
if img_exif is None:
print('Sorry, image has no exif data.')
else:
for key, val in img_exif.items():
if key in ExifTags.TAGS:
print(f'{ExifTags.TAGS[key]}:{val}')
# ExifVersion:b'0230'
# ...
# FocalLength:(2300, 100)
# ColorSpace:1
# ...
# Model:'X-T2'
# Make:'FUJIFILM'
# LensSpecification:(18.0, 55.0, 2.8, 4.0)
# ...
# DateTime:'2019:12:01 21:30:07'
# ...
Tested with Python 3.8.8 and Pillow==8.1.0
.
I usually use pyexiv2 to set exif information in JPG files, but when I import the library in a script QGIS script crash.
I found a solution using the library exif:
https://pypi.org/project/exif/
It’s so easy to use, and with Qgis I don,’t have any problem.
In this code I insert GPS coordinates to a snapshot of screen:
from exif import Image
with open(file_name, 'rb') as image_file:
my_image = Image(image_file)
my_image.make = "Python"
my_image.gps_latitude_ref=exif_lat_ref
my_image.gps_latitude=exif_lat
my_image.gps_longitude_ref= exif_lon_ref
my_image.gps_longitude= exif_lon
with open(file_name, 'wb') as new_image_file:
new_image_file.write(my_image.get_file())
To read image url and get tags
from PIL import Image
from urllib.request import urlopen
from PIL.ExifTags import TAGS
def get_exif(filename):
image = Image.open(filename)
image.verify()
return image._getexif()
def get_labeled_exif(exif):
labeled = {}
for (key, val) in exif.items():
labeled[TAGS.get(key)] = val
return labeled
my_image= urlopen(url)
exif = get_exif(my_image)
labeled = get_labeled_exif(exif)
print(labeled)
and to get GPS coordinate, Jayson DeLancey has excellent blog post.
Feb 2023 Pillow information
Starting version 8.2.0 API of PIL changed slightly, hiding most of tags a bit deeper into methods of Exif
. All other answers became slightly outdated, showing only few tags (around 14).
The modern way of doing it:
from PIL import Image
from PIL.ExifTags import TAGS, GPSTAGS, IFD
from pillow_heif import register_heif_opener # HEIF support
register_heif_opener() # HEIF support
def print_exif(fname: str):
img = Image.open(fname)
exif = img.getexif()
print('>>>>>>>>>>>>>>>>>>', 'Base tags', '<<<<<<<<<<<<<<<<<<<<')
for k, v in exif.items():
tag = TAGS.get(k, k)
print(tag, v)
for ifd_id in IFD:
print('>>>>>>>>>', ifd_id.name, '<<<<<<<<<<')
try:
ifd = exif.get_ifd(ifd_id)
if ifd_id == IFD.GPSInfo:
resolve = GPSTAGS
else:
resolve = TAGS
for k, v in ifd.items():
tag = resolve.get(k, k)
print(tag, v)
except KeyError:
pass
Only some of useful tags are available on the root level of Exif
now (e.g. Make, Model, DateTime, Orientation, Software. In order to access other useful tags, such as ShutterSpeedValue, ApertureValue, ISOSpeedRatings, WhiteBalance, DateTimeOriginal, DateTimeDigitized, ExposureBiasValue, FocalLength, ExifImageWidth, ExifImageHeight, etc, you need to get an IFD called Exif
. For GPS information, use IFD GPSInfo
. Also note that GPS tags have another tag-to-int encoding dictionary.
These two lines
from pillow_heif import register_heif_opener
register_heif_opener()
are required only if you want to have support of HEIF format, that is configured by default on modern Apple devices (.HEIC file extension). If you don’t need to work with HEIF, you can omit them, the code will work for the rest of image formats supported by PIL.
Package references:
- Pillow, the way to work with images in Python.
- pillow-heif or it’s lighter version pi-heif (no
save()
support).
I’m using PIL. How do I turn the EXIF data of a picture into a dictionary?
You can use the _getexif()
protected method of a PIL Image.
import PIL.Image
img = PIL.Image.open('img.jpg')
exif_data = img._getexif()
This should give you a dictionary indexed by EXIF numeric tags. If you want the dictionary indexed by the actual EXIF tag name strings, try something like:
import PIL.ExifTags
exif = {
PIL.ExifTags.TAGS[k]: v
for k, v in img._getexif().items()
if k in PIL.ExifTags.TAGS
}
You can also use the ExifRead module:
import exifread
# Open image file for reading (binary mode)
f = open(path_name, 'rb')
# Return Exif tags
tags = exifread.process_file(f)
I use this:
import os,sys
from PIL import Image
from PIL.ExifTags import TAGS
for (k,v) in Image.open(sys.argv[1])._getexif().items():
print('%s = %s' % (TAGS.get(k), v))
or to get a specific field:
def get_field (exif,field) :
for (k,v) in exif.items():
if TAGS.get(k) == field:
return v
exif = image._getexif()
print get_field(exif,'ExposureTime')
import sys
import PIL
import PIL.Image as PILimage
from PIL import ImageDraw, ImageFont, ImageEnhance
from PIL.ExifTags import TAGS, GPSTAGS
class Worker(object):
def __init__(self, img):
self.img = img
self.exif_data = self.get_exif_data()
self.lat = self.get_lat()
self.lon = self.get_lon()
self.date =self.get_date_time()
super(Worker, self).__init__()
@staticmethod
def get_if_exist(data, key):
if key in data:
return data[key]
return None
@staticmethod
def convert_to_degress(value):
"""Helper function to convert the GPS coordinates
stored in the EXIF to degress in float format"""
d0 = value[0][0]
d1 = value[0][1]
d = float(d0) / float(d1)
m0 = value[1][0]
m1 = value[1][1]
m = float(m0) / float(m1)
s0 = value[2][0]
s1 = value[2][1]
s = float(s0) / float(s1)
return d + (m / 60.0) + (s / 3600.0)
def get_exif_data(self):
"""Returns a dictionary from the exif data of an PIL Image item. Also
converts the GPS Tags"""
exif_data = {}
info = self.img._getexif()
if info:
for tag, value in info.items():
decoded = TAGS.get(tag, tag)
if decoded == "GPSInfo":
gps_data = {}
for t in value:
sub_decoded = GPSTAGS.get(t, t)
gps_data[sub_decoded] = value[t]
exif_data[decoded] = gps_data
else:
exif_data[decoded] = value
return exif_data
def get_lat(self):
"""Returns the latitude and longitude, if available, from the
provided exif_data (obtained through get_exif_data above)"""
# print(exif_data)
if 'GPSInfo' in self.exif_data:
gps_info = self.exif_data["GPSInfo"]
gps_latitude = self.get_if_exist(gps_info, "GPSLatitude")
gps_latitude_ref = self.get_if_exist(gps_info, 'GPSLatitudeRef')
if gps_latitude and gps_latitude_ref:
lat = self.convert_to_degress(gps_latitude)
if gps_latitude_ref != "N":
lat = 0 - lat
lat = str(f"{lat:.{5}f}")
return lat
else:
return None
def get_lon(self):
"""Returns the latitude and longitude, if available, from the
provided exif_data (obtained through get_exif_data above)"""
# print(exif_data)
if 'GPSInfo' in self.exif_data:
gps_info = self.exif_data["GPSInfo"]
gps_longitude = self.get_if_exist(gps_info, 'GPSLongitude')
gps_longitude_ref = self.get_if_exist(gps_info, 'GPSLongitudeRef')
if gps_longitude and gps_longitude_ref:
lon = self.convert_to_degress(gps_longitude)
if gps_longitude_ref != "E":
lon = 0 - lon
lon = str(f"{lon:.{5}f}")
return lon
else:
return None
def get_date_time(self):
if 'DateTime' in self.exif_data:
date_and_time = self.exif_data['DateTime']
return date_and_time
if __name__ == '__main__':
try:
img = PILimage.open(sys.argv[1])
image = Worker(img)
lat = image.lat
lon = image.lon
date = image.date
print(date, lat, lon)
except Exception as e:
print(e)
Here’s the one that may be little easier to read. Hope this is helpful.
from PIL import Image
from PIL import ExifTags
exifData = {}
img = Image.open(picture.jpg)
exifDataRaw = img._getexif()
for tag, value in exifDataRaw.items():
decodedTag = ExifTags.TAGS.get(tag, tag)
exifData[decodedTag] = value
I have found that using ._getexif
doesn’t work in higher python versions, moreover, it is a protected class and one should avoid using it if possible.
After digging around the debugger this is what I found to be the best way to get the EXIF data for an image:
from PIL import Image
def get_exif(path):
return Image.open(path).info['parsed_exif']
This returns a dictionary of all the EXIF data of an image.
Note: For Python3.x use Pillow instead of PIL
For Python3.x and starting Pillow==6.0.0
, Image
objects now provide a "public"/official getexif()
method that returns a <class 'PIL.Image.Exif'>
instance or None
if the image has no EXIF data.
From Pillow 6.0.0 release notes:
getexif()
has been added, which returns anExif
instance. Values can
be retrieved and set like a dictionary. When saving JPEG, PNG or WEBP,
the instance can be passed as anexif
argument to include any changes
in the output image.
As stated, you can iterate over the key-value pairs of the Exif
instance like a regular dictionary. The keys are 16-bit integers that can be mapped to their string names using the ExifTags.TAGS
module.
from PIL import Image, ExifTags
img = Image.open("sample.jpg")
img_exif = img.getexif()
print(type(img_exif))
# <class 'PIL.Image.Exif'>
if img_exif is None:
print('Sorry, image has no exif data.')
else:
for key, val in img_exif.items():
if key in ExifTags.TAGS:
print(f'{ExifTags.TAGS[key]}:{val}')
# ExifVersion:b'0230'
# ...
# FocalLength:(2300, 100)
# ColorSpace:1
# ...
# Model:'X-T2'
# Make:'FUJIFILM'
# LensSpecification:(18.0, 55.0, 2.8, 4.0)
# ...
# DateTime:'2019:12:01 21:30:07'
# ...
Tested with Python 3.8.8 and Pillow==8.1.0
.
I usually use pyexiv2 to set exif information in JPG files, but when I import the library in a script QGIS script crash.
I found a solution using the library exif:
https://pypi.org/project/exif/
It’s so easy to use, and with Qgis I don,’t have any problem.
In this code I insert GPS coordinates to a snapshot of screen:
from exif import Image
with open(file_name, 'rb') as image_file:
my_image = Image(image_file)
my_image.make = "Python"
my_image.gps_latitude_ref=exif_lat_ref
my_image.gps_latitude=exif_lat
my_image.gps_longitude_ref= exif_lon_ref
my_image.gps_longitude= exif_lon
with open(file_name, 'wb') as new_image_file:
new_image_file.write(my_image.get_file())
To read image url and get tags
from PIL import Image
from urllib.request import urlopen
from PIL.ExifTags import TAGS
def get_exif(filename):
image = Image.open(filename)
image.verify()
return image._getexif()
def get_labeled_exif(exif):
labeled = {}
for (key, val) in exif.items():
labeled[TAGS.get(key)] = val
return labeled
my_image= urlopen(url)
exif = get_exif(my_image)
labeled = get_labeled_exif(exif)
print(labeled)
and to get GPS coordinate, Jayson DeLancey has excellent blog post.
Feb 2023 Pillow information
Starting version 8.2.0 API of PIL changed slightly, hiding most of tags a bit deeper into methods of Exif
. All other answers became slightly outdated, showing only few tags (around 14).
The modern way of doing it:
from PIL import Image
from PIL.ExifTags import TAGS, GPSTAGS, IFD
from pillow_heif import register_heif_opener # HEIF support
register_heif_opener() # HEIF support
def print_exif(fname: str):
img = Image.open(fname)
exif = img.getexif()
print('>>>>>>>>>>>>>>>>>>', 'Base tags', '<<<<<<<<<<<<<<<<<<<<')
for k, v in exif.items():
tag = TAGS.get(k, k)
print(tag, v)
for ifd_id in IFD:
print('>>>>>>>>>', ifd_id.name, '<<<<<<<<<<')
try:
ifd = exif.get_ifd(ifd_id)
if ifd_id == IFD.GPSInfo:
resolve = GPSTAGS
else:
resolve = TAGS
for k, v in ifd.items():
tag = resolve.get(k, k)
print(tag, v)
except KeyError:
pass
Only some of useful tags are available on the root level of Exif
now (e.g. Make, Model, DateTime, Orientation, Software. In order to access other useful tags, such as ShutterSpeedValue, ApertureValue, ISOSpeedRatings, WhiteBalance, DateTimeOriginal, DateTimeDigitized, ExposureBiasValue, FocalLength, ExifImageWidth, ExifImageHeight, etc, you need to get an IFD called Exif
. For GPS information, use IFD GPSInfo
. Also note that GPS tags have another tag-to-int encoding dictionary.
These two lines
from pillow_heif import register_heif_opener
register_heif_opener()
are required only if you want to have support of HEIF format, that is configured by default on modern Apple devices (.HEIC file extension). If you don’t need to work with HEIF, you can omit them, the code will work for the rest of image formats supported by PIL.
Package references:
- Pillow, the way to work with images in Python.
- pillow-heif or it’s lighter version pi-heif (no
save()
support).