Python Tutorial: Cool EXIF Captions for Photographers with Pillow and Beem

in #utopian-io6 years ago (edited)

As a photographer, I like to provide my readers with some EXIF-Information about my photos. Besides the informative value of EXIF photo captions, it also adds whitespace between the photos which I find much more pleasant to look at than the default way steemit.com displays photo galleries. However, especially when writing longer posts, nobody would want to add EXIF Captions for 20+ photos manually.


1/640s; 140mm; f/5.6; ISO 640

I didn't have a photo of a python, so here are some monkeys that I captured at Mt. Popa in Myanmar three days ago. Note the short exposure time that I needed because they were moving so fast and that you can now see in the cool EXIF caption - awesome, isn't it?

Repository

https://github.com/holgern/beem
https://github.com/python-pillow/Pillow

What Will I Learn?

  • How to read and format EXIF-Data with Pillow
  • How to upload photos to steemitimages using beem.imageuploader
  • How to create cool EXIF captions
  • How to add a watermark with Pillow

Requirements

  • Working Python installation
  • Beem Python library by @holger80 with a valid posting key added to the CLI wallet and the environment variable UNLOCK set to the CLI wallet passphrase
  • Pillow Python library
  • fractions library (comes with Python) for EXIF calculations

Difficulty

  • Intermediate

Tutorial

In this tutorial you will learn how to write a Python script that will read the EXIF-Data of all images in a folder, upload them to steemitimages and output a convenient HTML-snippet for each photo including the Exif caption.

Before
After

Reading EXIF with Pillow

Before we start, we need to define a default function for Pillow for reading the EXIF of a photo:

def get_exif (exif,field) :
  for (k,v) in exif.items():
     if TAGS.get(k) == field:
        return v

Now, we open the image and call our get_exif() functions with the parameters we want to know:

exif = img._getexif()
tmp_exp = get_exif(exif,'ExposureTime')
tmp_focal = get_exif(exif,'FocalLength')
tmp_fstop = get_exif(exif,'FNumber')
ISO = "ISO "+str(get_exif(exif,'ISOSpeedRatings'))

Formatting the EXIF outputs

You may have wondered why we called the variables tmp_. Let's quickly print out the results with print(tmp_exp, tmp_focal, tmp_fstop, ISO) to see what is wrong with them:

(1, 50) (280, 10) (18, 10) ISO 560

This is not what we want to get! The numbers are correct though, we just have to format them correctly. To prevent empty values from crashing the script, we use proper exception handling that returns the default value "None" if the EXIF field in question is empty or could not be processed:

try:
    ExposureTime = str(Fraction(tmp_exp[0]/tmp_exp[1]).limit_denominator())+"s"
except:
    ExposureTime = "None"
try:
    FocalLength = str(int(tmp_focal[0]/tmp_focal[1]))+"mm"
except: 
    FocalLength = "None"
try: 
    FNumber = "f/"+str(tmp_fstop[0]/tmp_fstop[1])
except:
    FNumber = "None"

Let's now create a string out of all the values. If the value of any of the EXIF data is "None", we return an empty string.

settings = "" if ExposureTime == "None" or FocalLength == "None" or FNumber == "None" or ISO == "None" else ExposureTime+"; "+FocalLength+"; "+FNumber+"; "+ISO

With our example values from above, we now get the following output:

1/50s; 28mm; f/1.8; ISO 560

That looks much better!

Uploading photos with beem.imageuploader

Uploading the photos to steemitimages is very easy thanks to beem:

steem = Steem()
iu = ImageUploader(steem_instance=steem)
steem.wallet.unlock(walletpw)
postimage = iu.upload(path+"/"+imgurl, postaccount)

Now, we only need one line to print out a HTML snippet for each image containing the image that we just uploaded to steemitimages and its EXIF data that we previously saved into the string settings as a caption.

print("<center><img src='"+postimage['url']+".jpg' /><br><sup>"+settings+"</sup></center>")

Finishing the function get_image()

Now, let's wrap this code in the function process_image():

def process_image(imgurl):
    try:
        """ 
        Reading the Exif-Data with Pillow
        """
        img = Image.open(path+"/"+imgurl)
        exif = img._getexif()
        tmp_exp = get_exif(exif,'ExposureTime')
        tmp_focal = get_exif(exif,'FocalLength')
        tmp_fstop = get_exif(exif,'FNumber')
        ISO = "ISO "+str(get_exif(exif,'ISOSpeedRatings'))
        """
        Formatting the EXIF-Data
        """
        try:
                ExposureTime = str(Fraction(tmp_exp[0]/tmp_exp[1]).limit_denominator())+"s"
        except:
                ExposureTime = "None"
        try:
                FocalLength = str(int(tmp_focal[0]/tmp_focal[1]))+"mm"
        except: 
                FocalLength = "None"
        try: 
                FNumber = "f/"+str(tmp_fstop[0]/tmp_fstop[1])
        except:
                FNumber = "None"
        settings = "" if ExposureTime == "None" or FocalLength == "None" or FNumber == "None" or ISO == "None" else ExposureTime+"; "+FocalLength+"; "+FNumber+"; "+ISO
        """
        Uploading to steemitimages using beem
        """
        steem = Steem()
        iu = ImageUploader(steem_instance=steem)
        steem.wallet.unlock(walletpw)
        postimage = iu.upload(path+"/"+imgurl, postaccount)
        """ 
        Printing out the result
        """
        print("<center><img src='"+postimage['url']+".jpg' /><br><sup>"+settings+"</sup></center>")
    except Exception as error:
        print("Error during image processing "+repr(error))

Queuing Images

Finally, let's write the main function. It calls our function process_image() for each file in our folder path with the filename as parameter. We could also add a filter to only queue files with image filetypes, but this would go too far for this tutorial.

if __name__ == '__main__':
    for image in os.listdir(path):
        process_image(image)
    print("Finished")

Remember to also set your own values on the top of the script for path (path to your folder that contains the image files) and postaccount (your steem account previously added to the beem wallet). Remember to set the environment variable UNLOCK or alternatively define walletpw as your beem CLI wallet password.

path = '00steemit'
postaccount = 'yoursteemaccount'
walletpw = os.environ.get('UNLOCK')

Now we can run the script and get an output similar to this:

<center><img src='https://cdn.steemitimages.com/DQmXmWn4NaBzUaKhEGru2hgVrcMXbCE86r49SLUuqeeWp4P/image.jpg' /><br><sup>1/50s; 28mm; f/1.8; ISO 560</sup></center>
<center><img src='https://cdn.steemitimages.com/DQmYNcx8Tzx8tVHEqubqPNrGY6movmdzDVdEPq53ALZ56pR/image.jpg' /><br><sup>1/30s; 20mm; f/1.8; ISO 5000</sup></center>
<center><img src='https://cdn.steemitimages.com/DQmP6LsHirykSpubdzW2NXu13LSZQecDQ41fpbuhxA7fpma/image.jpg' /><br><sup>1/500s; 19mm; f/1.8; ISO 900</sup></center>
Finished

Bonus: More fun with Pillow

Pillow can do much more than just reading EXIF-Data, it is also capable of editing images.
I use it to add a watermark to my photos: Instead of simply uploading the photo with postimage = iu.upload(path+"/"+imgurl, postaccount), we make a watermarked copy of the photo in a second folder:

watermarkedpath = '00steemit'
watermarkpath = 'watermark.png'
watermark = Image.open(watermarkpath)
destination = img.copy()
position = ((destination.width - watermark.width - 32), (destination.height - watermark.height - 30))
destination.paste(watermark, position, watermark)
waterimgurl = watermarkedpath+'/'+imgurl
destination.save(waterimgurl)
postimage = iu.upload(waterimgurl, postaccount)

Of course, you could also read more EXIF Data such as the lens used with Pillow, add the title as an ALT-Attribute to the image for SEO optimation or link each image to its steemitimages URL for a fullscreen preview.

I run the script from a folder containing the script itself, the folder 00steemit where I put my photo files, the optional watermark file watermark.png and the optional folder for watermarked images 01steemit. I run the script with python exifcaptions.py > output.txt which saves the HTML snippets generated into the file output.txt which contents I then copy into my steemit post.

Enjoy the cool EXIF captions!

Proof of Work Done

You can find the complete code on Github:
https://github.com/tiotdev/steem-tutorials/blob/master/cool-exif-captions/exifcaptions.py


I would love to see this functionality implemented in some Steem frontends such as @steempeak or @busy.org!

Sort:  
  • Please edit your tutorial and place the Phyton repository.
  • Put your repository in the "Proof of Work Done".

Let me know when you edit your tutorial so I can do the review.


Need help? Write a ticket on https://support.utopian.io/.
Chat with us on Discord.
[utopian-moderator]

Sorry, I don't understand, could you try to explain again please? Which repository should I place where and what is "proof of work done"?

Thank you for your contribution.

  • Nice work on the explanations of your code, although adding a bit more comments to the code can be helpful as well

Your contribution has been evaluated according to Utopian policies and guidelines, as well as a predefined set of questions pertaining to the category.

To view those questions and the relevant answers related to your post, click here.


Need help? Write a ticket on https://support.utopian.io/.
Chat with us on Discord.
[utopian-moderator]

Thank you for your review, @portugalcoin!

So far this week you've reviewed 4 contributions. Keep up the good work!

pixresteemer_incognito_angel_mini.png
Congratz, your post has been resteemed and, who knows, will maybe appear in the next edition of the #dailyspotlights (Click on my face if you want to know more about me...)
Check the rules of the Daily Spotlights if you want to nominate someone!
Pixresteemer is also listed as promoter on The Steemians Directory

This functionality baked in would be awesome. I'm gonna have to give it a go when I get some time, thanks for the writeup!

Welcome :) Let me know if you need any help with setting it up!

Hey @jpphotography
Thanks for contributing on Utopian.
We’re already looking forward to your next contribution!

Want to chat? Join us on Discord https://discord.gg/h52nFrV.

Vote for Utopian Witness!

Awesome, thanks!