managing digital photo files via python

In the post before last I mentioned that I’d written a utility in Python to copy files off of my various camera’s SDXC-type digital cards to a location on my Macbook. Years ago I’d grown dependant on Adobe’s Lightroom to do the copying for me, keeping them stored on an external drive. When Adobe went all-in with software-as-a-service (SaaS) and replaced Lightroom 6 with its cloud equivalent, and then started charging a monthly fee to use an application I’d previously paid for just once (except for occasional updates),  I knew my days with Adobe were numbered. Unfortunately for me those days stretched into years until very recently, when I pulled the plug on Lightroom and its monthly fee. I should have done this sooner but I never seemed to find the time to Getting Around To It.

This example is meant to run on a M1 Macbook Pro with Ventura 13.5.2. I’ve tested against Python versions 3.9.6 (native to macOS), 3.10.13 and 3.11.5. The last two versions were installed via brew and inside a virtual environment for each one. You don’t have to install any other Python versions for this utility; the version native to macOS is just fine.

I’ve tried to write readable code and avoid my penchant to write complex single-line code that performs multiple functions. Yes, it’s Python, but it makes it difficult to read, I don’t care how adept you are writing Python. Anyway…

Lightroom managed all my original photo files in a directory structure that directly reflected the date the photo was taken. It was easy to navigate such a structure, even as they grew ever larger over time. The problem with that structure is that it lumped all photos taken on the same date, but with different cameras, in the same date folder. In my example I’ve actually created a directory structure that reflects all the cameras I currently own, and on occasion use:

Photography├── EM5├── EP2├── G4├── G9├── OM1M2└── PENF

When I want to save a set of photos, I plug the camera’s card into an adapter on my Macbook, then step into the directory that corresponds with the camera I’m copying from. I then run the script, which then copies all those files in, in the process creating new directories based on the camera photo file’s time stamp. This is what my Pen F folder looks like after a run.

Photography...└── PENF├── 2023_08_13├── 2023_08_15├── 2023_08_29├── 2023_08_30├── 2023_08_31├── 2023_09_02├── 2023_09_06└── 2023_09_09

Thus, if I want to see all the photos taken on 9 September 2023 then I look in the folder named 2023_09_09, and there are all the photos I took on that date using my Pen F.

On my Macbook I have the script in the Photography folder. When I want to copy photos I step into the folder corresponding to the camera I use, then execute ../copy-photos.py. The script will

  1. Check to see if a card is plugged in, discriminating between my Olympus cameras and my Panasonic cameras. If it can’t find either one it politely exits with a message.
  2. Perform a directory listing on the camera card, creating a set of folder names based on the photo files date it was taken, as well as a list of all the files.
  3. Create folders based on the unique dates it found via the photo files. If the folder already exists it won’t try to create it.
  4. Copy all the files from the card into each of the data folders. The copy also brings over each photo files metadata, so that the date the photo file was created is kept with each file, not the date it was copied over to the Macbook. If the file already exists it will not perform a new copy but skip it.

I’ve also made some minor changes on my Macbook, essentially in Finder, setting each folder holding photos to show the folder contents as a gallery. This is another essential feature that Lightroom has that is now a feature of macOS’ Finder.

Finder in gallery mode, showing all images

macOS will not only show JPEG contents, but the OS understands Olympus and Panasonic RAW file contents to properly display them in gallery mode. I have Finder to automatically associate my photo files with Affinity Photo 2. If I need to perform additional editing I can do it there, and then export to another folder. I now have every essential Lightroom feature I used, reproduced on my Macbook and macOS. As far as I’m concerned my migration away from Adobe is complete.

#!/usr/bin/env python3## Utility script to copy files from an attached digital camera# card to another location such as a folder on a Mac.#import osimport shutilfrom pathlib import Pathfrom datetime import datetime# The function converts the year/month/day portion of# a file's timestamp into a string that can be used to# create a directory, or can be used as a copy path later# when moving photos off the card and onto my system.#def create_timestamp(timestamp):d = datetime.utcfromtimestamp(timestamp)return d.strftime('%Y_%m_%d')# Set up the various global resources we'll need.## These are the known digital camera card file paths.#olym_volumn = '/Volumes/Untitled/DCIM/100OLYMP/'pana_volumn = '/Volumes/LUMIX/DCIM/100_PANA/'basepath = ''if os.path.exists(olym_volumn):basepath = olym_volumnprint('Found Olympus digital camera card.')elif os.path.exists(pana_volumn):basepath = pana_volumnprint('Found Panasonic digital camera card.')else:print("No known digital camera card found. Exiting.")exitphotopath = Path(basepath)## photo_dirs is a set. We can add many entries,# and the set functionality will make sure that# only one instance is in the set. The set# filters out duplicates.#photo_dirs = set()photos_on_card = set()# Find all the photographs on my camera's SDXC card.#for photo in photopath.iterdir():if photo.is_file():if photo.name[0] == 'P':photos_on_card.add(photo)info = photo.stat()tstamp = create_timestamp(info.st_mtime)photo_dirs.add(tstamp)# Create folders based on the unique timestamps.# Skip the make directory if the folder already exists.#for photo_dir in sorted(photo_dirs):if not os.path.exists(photo_dir):print(f'{photo_dir} - created')os.mkdir(photo_dir)else:print(f'{photo_dir} - skipped')# We now have our list of photos in the attached SDXC card.# Build full paths to copy from each photo's location on the card# to the current location where the script is executing.## If the photo on the card has already been copied, then don't copy# it again.#for photo in photos_on_card:source_photo = basepath + photo.namedestination_folder = create_timestamp(photo.stat().st_mtime)destination_target = destination_folder + '/' + photo.nameif not os.path.exists(destination_target):print(f'copy {source_photo} to {destination_target}')shutil.copy2(source_photo, destination_target)

september caturday with joan jett


Today’s Caturday is special for a number of reasons. Reason number one is the focus (pun intended) on one cat in particular, which for today would be Joan Jett, the dilute tortie. Joan is the only cat we’ve adopted where we left her with the name she was given at the shelter. It just seemed to fit.

Reason number two is all of these photos are straight out of camera (or SOOC). I’ve shut down my monthly Adobe Lightroom cloud account this month, for reasons I’ll talk about at the end of this post. In order to move my digital photographs off the camera’s SDXC card, I’ve written a Python script to do a simple catalog of everything on a card, and then use that information to copy all the photographs locally to my Mac. I’m going to write another separate post about that Python utility.




All of the photos were taken with the Olympus Pen F and m.Zuiko 75mm/1.8 at f/2. No cropping, not fiddling with exposure, no touchup.

As I mentioned earlier, there’s a reason I’m dropping Adobe and going my own way with my own tools. I’ve spent years paying Adobe $10/month for the privilege of renting to play with their software. It’s been going on since Adobe announced no more updates to their last stand-alone Lightroom 6. I kept using LR6 for as long as I could, before a macOS update to full 64-bit made that almost impossible. If LR6 still worked under macOS I suppose I’d still be there.

I’ve known for some time that the only reason I stayed with Lightroom was because it kept my digital photographs catalogued and filed, going all the way back to 2009 when I first started to use Lightroom. I still have all those old digital files, still accessable as regular RAW and JPEG. It’s not like my work is locked away from me. But I came to realize that staying with Adobe because of its cataloging feature was not a good reason. After all, it’s folder based. My Python utility does all of that, and starting today I’ve successfully started to pull off all my camera photos and storing them in an area that helps me keep up with them.

And I have other tools I intend to learn how to use if I want to make image modifications to the RAW images. Again, more about those later.

So far I’m happy with the utility. Everything is pulled over, including each photo’s metadata, meaning I retain the photograph’s creation timestamp. This represents an interesting crossover of two of my interests, programming in Python and digital photography. I should do more of this…