Originally posted at jeffmackinnon.com
Share this article on: Twitter Linkedin Email

Way back in the day, when I was using Flickr as one of my daily visits, I really enjoyed learning HOW these amazing photographers made these pictures happen through the exif data. This is where I learned about the affect of aperature, etc. I would copy the settings with my old Canon XSi and grow.
When I added the galleries to this site with the pelican-photos the only way to do this was adding an exif.txt file to each folder. There is a recommended third-party software to do it, but I didn't find it intuitive and gave up.
This weekend however, I had a very little bit of time and decided to see what file metadata I could pull using python.
Using Python to extract EXIF data
I tried the piexif module without much luck, and then found this GIST from @jpstroop that did most of the work for me.
def _map_key(k): try: return TAGS[k] except KeyError: return GPSTAGS.get(k, k)# Creates a dictionary of image EXIF meta data from an image, borrowed from a github gist
def get_exif(image_path):
metadata = {}
with Image.open(image_path) as i:
info = i._getexif()
try:
[ metadata.setitem(_map_key(k), v) for k, v in info.items() ]
return metadata
except AttributeError:
return None
I use the exact fucntions from @jpstroop.
Walking and writing with Python
The next step was to figure out how to walk all the directories in my photo gallery and make sure that I was getting them all, for that I do this:
# The directory of the source photos, the same as PHOTO_LIBRARY in pelicanconf.py # I run this in a WSL instance, but pelican is run on the host windows machine, hence the sample directory dir = '/mnt/c/Users/jeff/Pictures'for root, subdirectories, files in os.walk(dir):
for subdirectory in subdirectories:
print(os.path.join(root, subdirectory))
This is goes through each directory and prints the path. I don't need that for the final product, but I like knowing that something is happening when I run the script, so I kept it.
Creating a text file with everything
Once I made sure that was working the way that I hoped, I wrote a function that will be called for every subdirectory. I called it write_exif_file(directory), this creates the text file and then loops over every .jpg file there and writes the exif data in the way needed for pelican-photos and that I think looks "good". [*]
That function looks like this: [†]
def write_exif_file(directory): # Open the exif.txt file, create if it doesn't exist, in the current folder. txt = open(directory+'/exif.txt', 'w')for file in os.listdir(directory):
<span class="c1"># check the files which are end with specific extension</span> <span class="c1"># Everything that I want to have EXIF information for is a jpg, but you can add others too.</span> <span class="k">if</span> <span class="n">file</span><span class="o">.</span><span class="n">endswith</span><span class="p">(</span><span class="s2">".jpg"</span><span class="p">):</span> <span class="c1"># Need to iterate through the files in the list and pass each to the</span> <span class="n">image</span> <span class="o">=</span> <span class="n">get_exif</span><span class="p">(</span><span class="n">directory</span> <span class="o">+</span><span class="s1">'/'</span><span class="o">+</span><span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">file</span><span class="p">))</span> <span class="n">txt</span><span class="o">.</span><span class="n">write</span><span class="p">(</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">file</span><span class="p">)</span> <span class="o">+</span> <span class="s1">': '</span> <span class="o">+</span> <span class="nb">str</span><span class="p">(</span><span class="n">image</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">'Model'</span><span class="p">))</span> <span class="o">+</span> <span class="s1">' with a '</span> <span class="o">+</span> <span class="nb">str</span><span class="p">(</span><span class="n">image</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">'LensModel'</span><span class="p">))</span> <span class="o">+</span> <span class="s1">' - '</span> <span class="o">+</span> <span class="nb">str</span><span class="p">(</span><span class="n">image</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">'ExposureTime'</span><span class="p">))</span> <span class="o">+</span> <span class="s1">'s, '</span> <span class="o">+</span> <span class="c1"># I want to change this to fractions, but it works for now.</span> <span class="s1">'f/'</span> <span class="o">+</span> <span class="nb">str</span><span class="p">(</span><span class="n">image</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">'FNumber'</span><span class="p">))</span> <span class="o">+</span> <span class="s1">' at ISO-'</span> <span class="o">+</span> <span class="nb">str</span><span class="p">(</span><span class="n">image</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">'ISOSpeedRatings'</span><span class="p">))</span> <span class="o">+</span> <span class="s1">'</span><span class="se">\n</span><span class="s1">'</span> <span class="p">)</span>
# Close the text file now that we are done with it.
txt.close()
return None
For now I'm only including:
- Model,
- Lens,
- Exposure,
- F-Stop, and
- ISO
I may add GPS to them in the future, but as for the settings needed to replicate these shots with your own camera, I think this is enough.
Finding the code snippet
I created a GIST for this code snippet here - pelican-photos-create-exif-files. Until this weekend I didn't even know that GIST was a site. As I go through creating these little scripts for myself I am way more likely to neeed a function, or code-snippet than an entire repo. I will probably be adding some more here as I go along, as a backup storage if nothing else.
If you have any suggestions on how to make this "slicker". If you think its useful and want to copy/paste it as a function in the plugin be my guest. I don't have the bandwidth right now to figure out how to do a proper pull-request for the plugin, or the expereince to know how to update the code in a way that won't break everything else.
Footnotes
[*] | There is still some work to do there, especially for the exposure time. I want it to be fractional for less than a second, but its "good enough" for this iteration. |
[†] | If anyone knows CSS and wants to help me add syntac highlighting to this theme, let me know. |