Raspberry Pi 3 Cookbook for Python Programmers
上QQ阅读APP看书,第一时间看更新

How it works...

We define a general class called Photo; it contains details about itself and provides
functions to access Exchangeable Image File Format (EXIF) information and generate
a preview image.

In the __init__() function, we set values for our class variables and call self.initImage(), which will open the image using the Image() function from the PIL. We then call self.initExif() and self.initDates() and set a flag to indicate whether the file was valid or not. If not valid, the Image() function would raise an IOError exception.

The initExif() function uses PIL to read the EXIF data from the img object, as shown in the following code snippet:

self.exif_info={ 
                ExifTags.TAGS[id]:y 
                for id,y in image._getexif().items() 
                if id in ExifTags.TAGS 
               } 

The previous code is a series of compound statements that results in self.exif_info being populated with a dictionary of tag names and their related values.

ExifTag.TAGS is a dictionary that contains a list of possible tag names linked with their IDs, as shown in the following code snippet:

ExifTag.TAGS={ 
4096: 'RelatedImageFileFormat', 
513: 'JpegIFOffset', 
514: 'JpegIFByteCount', 
40963: 'ExifImageHeight', 
...etc...}

The image._getexif() function returns a dictionary that contains all the values set by the camera of the image, each linked to their relevant IDs, as shown in the following code snippet:

Image._getexif()={ 
256: 3264, 
257: 2448, 
37378: (281, 100), 
36867: '2016:09:28 22:38:08', 
...etc...} 

The for loop will go through each item in the image's EXIF value dictionary and check for its occurrence in the ExifTags.TAGS dictionary; the result will get stored in self.exif_info. The code for this is as follows:

self.exif_info={ 
'YResolution': (72, 1), 
 'ResolutionUnit': 2, 
 'ExposureMode': 0,  
'Flash': 24, 
...etc...} 

Again, if there are no exceptions, we set a flag to indicate that the EXIF data is valid, or if there is no EXIF data, we raise an AttributeError exception.

The initDates() function allows us to gather all the possible file dates and dates from the EXIF data so that we can select one of them as the date we wish to use for the file. For example, it allows us to rename all the images to a filename in the standard date format. We create a self.filedates dictionary that we populate with three dates extracted from the EXIF information. We then add the filesystem dates (created and modified) just in case no EXIF data is available. The os module allows us to use os.path.getctime() and os.path.getmtime() to obtain an epoch value of the file creation. It can also be the date and time when the file was moved – and the file modification – when it was last written to (for example, it often refers to the date when the picture was taken). The epoch value is the number of seconds since January 1, 1970, but we can use datetime.datetime.fromtimestamp() to convert it into years, months, days, hours, and seconds. Adding date() simply limits it to years, months, and days.

Now, if the Photo class was to be used by another module, and we wished to know the date of the image that was taken, we could look at the self.dates dictionary and pick out a suitable date. However, this would require the programmer to know how the self.dates values are arranged, and if we later changed how they are stored, it would break their program. For this reason, it is recommended that we access data in a class through access functions so the implementation is independent of the interfaces (this process is known as encapsulation). We provide a function that returns a date when called; the programmer does not need to know that it could be one of the five available dates or even that they are stored as epoch values. Using a function, we can ensure that the interface will remain the same no matter how the data is stored or collected.

Finally, the last function we want the Photo class to provide is previewPhoto(). This function provides a method to generate a small thumbnail image and saves it as a Portable Pixmap Format (PPM) file. As we will discover in a moment, Tkinter allows us to place images on its Canvas widget, but unfortunately, it does not support JPEGs directly and only supports GIF or PPM. Therefore, we simply save a small copy of the image we want to display in the PPM format – with the added warning that the image pallet must be converted to RGB too – and then get Tkinter to load it onto the Canvas when required.

To summarize, the Photo class we have created is as follows:

Operations

Description

__init__(self,filename)

This is the object initializer.

initImage(self)

This returns img, a PIL-type image object.

initExif(self,image)

This extracts all the EXIF information, if any is present.

initDates(self)

This creates a dictionary of all the dates available from the file and photo information.

getDate(self)

This returns a string of the date when the photo was taken/created.

previewPhoto(self)

This returns a string of the filename of the previewed thumbnail.

The properties and their respective descriptions are as follows:

Properties

Description

self.filename

The filename of the photo.

self.filevalid

This is set to True if the file is opened successfully.

self.exifvalid

This is set to True if the photo contains EXIF information.

self.exif_info

This contains the EXIF information from the photo.

self.filedates

This contains a dictionary of the available dates from the file and photo information.

To test the new class, we will create some test code to confirm that everything is working as we expect; see the following section.