172

I'm using PIL. How do I turn the EXIF data of a picture into a dictionary?

Nicolas Gervais
  • 33,817
  • 13
  • 115
  • 143
TIMEX
  • 259,804
  • 351
  • 777
  • 1,080

10 Answers10

230

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
}
Nicolas Gervais
  • 33,817
  • 13
  • 115
  • 143
payne
  • 13,833
  • 5
  • 42
  • 49
  • 13
    Any Python 3 alternative? – Santosh Kumar Aug 30 '13 at 04:57
  • Python 2.7.2: `AttributeError: 'module' object has no attribute 'ExifTags'` – 2rs2ts Oct 22 '13 at 13:32
  • 2
    @2rs2ts: Try `import ExifTags` (without the `PIL` prefix). – Florian Brucker Nov 09 '13 at 22:09
  • 19
    For python3 use Pillow. It is a fork of PIL, which is still being developed, and has a python3 compatible version – Mzzl Jan 15 '14 at 09:54
  • 1
    Can you test this on this Question, download the images, and try to get the ImageDescription. http://stackoverflow.com/questions/22173902/how-to-get-image-title-in-python-django – A.J. Mar 06 '14 at 07:21
  • See this comment if you use PNG/GIF images: https://gist.github.com/erans/983821#comment-377080 – bfontaine Aug 26 '14 at 13:48
  • This code actually won't work, cause `import PIL` won't magically import anything by itself. Read http://stackoverflow.com/questions/11911480/python-pil-has-no-attribute-image for clarification. – Matteo Nov 10 '15 at 00:44
  • 9
    Just for reference exif codes: http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html – Deus777 May 06 '17 at 21:47
  • Can someone explain that bit of Python-fu in the `exif = {` part? I don't understand the syntax. Perhaps a link to where it would be explained will be helpful. I'm particularly befuddled by `PIL.ExifTags.TAGS[k]: v`. Where do k and v come from? Also, how come that `if` statement doesn't require a colon and pass statement, at least? – Mike S May 15 '18 at 12:04
  • 1
    @MikeS I'm late, but this is a [dictionary comprehension](https://stackoverflow.com/a/14507637/5248987). – Hey Sep 25 '18 at 07:06
  • 5
    This doesn't work with python 3.x and _get_exif is a protected method and shouldn't be used. – Param Kapur May 15 '19 at 19:12
  • @ParamKapur Really? – jtlz2 May 18 '21 at 09:35
  • While Python doesn't have private/protected methods, the `_` underscore in front of `_get_exif` is convention for non-"public" APIs, and is a sign to end users to "you shouldn't be using this, use at your own risk". For Python 3.x and Pillow 6.x, there is now an official `getexif()` method (see [my answer](https://stackoverflow.com/a/56571871/2745495)). – Gino Mempin Aug 25 '22 at 04:01
59

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.

Gino Mempin
  • 25,369
  • 29
  • 96
  • 135
  • 6
    It doesn't work for me, I can see only the exif data using the .info method in binary – G M Mar 16 '20 at 20:23
  • 2
    This omits many fields that show in Windows properties: lat/long, F-stop, ISO speed, Exposure time etc – InnocentBystander Oct 09 '22 at 22:09
  • 1
    @InnocentBystander It might be because of the checking against `ExifTags.TAGS`. It skips out on unknown keys, and doesn't print them out. Try adding an `else` block that prints out the raw key and val. I unfortunately don't work on this kind of thing anymore, don't know how to "fix" it. – Gino Mempin Oct 09 '22 at 23:58
  • 3
    @InnocentBystander The above code is outputting tags such as FNumber, ISOSpeedRatings, ExposureTime, and GPSInfo for me, along with all the obvious tags like Flash, MeteringMode, FocalLength, Make, Copyright, Artist, Orientation, etc. etc. I'm using Pillow 7.2.0 (as shown by command "pip show pillow"). In fact, there are a some fields the above python code shows me that Window's property->Details does NOT show such as lens model... – Swiss Frank Feb 19 '23 at 14:25
45

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)
ianaré
  • 3,230
  • 26
  • 26
  • 1
    Can you test this on this Question, download the images, and try to get the ImageDescription. http://stackoverflow.com/questions/22173902/how-to-get-image-title-in-python-django – A.J. Mar 06 '14 at 07:20
  • 3
    @Clayton for both the images, exifread returns empty dictionary. But I tested on my photos and it works just fine. – tnq177 Nov 16 '15 at 01:31
  • I also receive an empty dictionary for a set of images. Can anyone comment of why this is the case? What kind of images does exifread.process_file() work with? – Momchill Mar 22 '20 at 23:56
  • 1
    @Momchill It depends on the image file. Some images are generated without EXIF data. If it is empty programmatically, try opening the image file in a photo editing software to check if it actually has EXIF data. – Gino Mempin Sep 06 '20 at 00:03
  • After necessary imports and loading the image: `print("\n".join([(ExifTags.TAGS[k] + f": {v}") for (k, v) in img00.getexif().items() if k in ExifTags.TAGS]))` – khaz Apr 23 '22 at 01:10
  • I just used ExifRead for an image from a Canon 5D classic and it worked nicely - outputted way more info then ExifTags from PIL – Jonatas CD Jun 11 '22 at 19:53
21

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')
Nicolas Gervais
  • 33,817
  • 13
  • 115
  • 143
Mike Redrobe
  • 1,248
  • 13
  • 12
  • 6
    Better, you can reverse TAGS with `name2tagnum = dict((name, num) for num, name in TAGS.iteritems())` and then do `name2tagnum['ExposureTime']`. – Ben Dec 08 '13 at 13:21
  • 7
    For Python 3, change `exif.iteritems()` to `exif.items()` – SPRBRN Feb 19 '18 at 15:38
  • 3
    We should not use `_getexif` which is a private method. Instead Pillow's method `getexif` is more appropriate. – guhur Jan 04 '21 at 08:43
15
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)
Rusty Widebottom
  • 985
  • 2
  • 5
  • 14
Kirill Vladi
  • 484
  • 6
  • 14
10

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

Param Kapur
  • 293
  • 3
  • 11
  • 4
    `info['parsed_exif']` requires Pillow 6.0 or newer. `info['exif']` is available in 5.4, but this is a raw bytestring. – Åsmund Jul 14 '19 at 08:52
  • 4
    There is no `info['parsed_exif']` in version 7.0.0; only `info['exif']`. – ZF007 Mar 10 '20 at 23:32
8

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
Raj Stha
  • 1,043
  • 11
  • 18
8

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 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:

greatvovan
  • 2,439
  • 23
  • 43
2

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.

Aman Bagrecha
  • 406
  • 4
  • 9
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())
RBenet
  • 171
  • 1
  • 10