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 libraryfractions
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!
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"?
Updated
Thank you for your contribution.
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!
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!