Back to blog

How I hand built a way to organize and publish my photo library (and why)

Organizing by day, month, and media type

All media starts in ~/dump/

I had a habit of creating new folders for a project, but then not really finishing that, and all of the media would grow cobwebs.

Apple Shortcuts & Automator

The first step is getting my files organized on my local computer. This process usually starts with plugging in an SD card from my fujifilm camera, helmet cam, or field audio recorder.

Import SD card to ~/dump/

First I use Applescript to show a dialog to ask myself whether to import or not.

display dialog "New Volume connected- import to ~/dump?" buttons {"Yes", "No"} default button 2

if the button returned of the result is "No" then

error number -128 (* user cancelled *)

end if

If the user selects "Yes" we continue on to this shell script

#!/bin/bash

totalFiles=0
totalSize=0

handle_folder() {
    for aFile in "$1"/*; do
        if [ -d "$aFile" ]; then
            handle_folder "$aFile"
        elif [[ "$(echo $aFile | tr '[:upper:]' '[:lower:]')" =~ \.(jpe?g|mp[34]|raf|wav)$ ]]; then
            echo "Copying file $aFile"  # debugging line
            newFilePath=$HOME/dump/$(basename "$aFile")
            cp "$aFile" "$newFilePath"
                
            totalFiles=$((totalFiles + 1))
            totalSize=$((totalSize + $(du -k "$aFile" | cut -f1)))
        fi
    done
}

mkdir -p $HOME/dump

# List all volumes and handle each
for sdcard in /Volumes/*; do
    if [ "$sdcard" != "/Volumes/Macintosh HD" ] && [ -d "$sdcard" ]; then  # Exclude Macintosh HD, the main disk of macOS
        echo "Handling SD Card: $sdcard"  # debugging line
        handle_folder "$sdcard"
    fi
done

# Convert size to GB
totalSizeGB=$(echo "scale=2; $totalSize/1024/1024" | bc)

osascript -e "display notification \"Successfully copied $totalFiles files totaling $totalSizeGB GB to the dump folder\" with title \"File Operation Summary\""

Organize ~/dump/ into ~/media/

Every once in a while, when the folder grows a little too big and unwieldy, I have a shell script as part of a shortcut (which means I can run it from an icon in my dock) that takes everything in ~/dump/ and organizes it into a folder structure by month, day, and media type.

Draft script to move from ~/dump/ into organized folders in ~/media/

#!/bin/bash

shopt -s globstar nullglob

# get creation date and format it to YYYY-MM-DD
getDate() {
    date -r "$1" +'%Y-%m-%d'
}

#iterate recursively over all files in the target directory.
for file in ~/dump/**/*.*; do
    # Ensure path is a file
    if [ -f "$file" ]; then
        #get creation date of file
        file_date=$(getDate "$file")

        #get mime-type of file
        mime_type=$(file --mime-type -b "$file" | awk -F'/' '{print $1}')
        
        #needs write permissions on /media/
        dir_name=~/media/"$file_date"/"$mime_type"

        #creating Target Directory
        mkdir -p "$dir_name"
        
        #move files
        mv "$file" "$dir_name"/

        echo "$file : moved to $dir_name" >> ~/dump/_logs.txt
    fi
done

#Emptying
rm -r ~/dump/*

#Logs
echo "Organizing Completed $(date +"%T")" >> ~/dump/_logs.txt
cat ~/dump/_logs.txt > ~/logs.txt

Upload to the web

The finder is an extremely powerful way to look at, organize, and tag files. Instead of recreating these things haphazardly in my own media library, I'd rather use one of the best ones in existence and augment it a bit to do my bidding.

I naturally found myself using a system of tagging photos with yellow for "maybe" and green for "publish". I'd import an SD card for a folder, scroll through it in finder, and slowly gather my picks. Then I'd select all the green files and drag them to another folder, or upload and publish them somewhere. I figured it would be best to try and automate what I was already doing.

So I created a very basic shortcut that uploads yellow/green files in the current directory to Cloudinary. This also allows you to right-click on a folder and upload all the green-tagged media to Cloudinary.

# Navigate to your media directory
cd ~/media/

# Find all yellow/green tagged files
for file in $(mdfind -onlyin . "kMDItemFSLabel == 2 || kMDItemFSLabel == 6")
do
  # Upload file to Cloudinary
  cloudinary upload $file --use_filename --unique_filename false
  
  # (Optional) To save the Cloudinary URL in the EXIF data, you would need an additional tool such as ExifTool.
done

Get all Cloudinary photos for a week

Now that all of this content has been stored on Cloudinary, we want a way to get it back out. We are going to need to access the Cloudinary admin, and use our secret, so this has to be done on the server side instead of the client side.

Here's a rough mockup of what we are going to want to do:

const cloudinary = require('cloudinary').v2;

cloudinary.config({
   cloud_name: 'your_cloud_name',
   api_key: 'your_api_key',
   api_secret: 'your_api_secret'
});

async function getImagesByWeek(year, week) {
   // Use a library like date-fns to determine the start and end dates of the week
   const startDate = new Date(dateFns.startOfWeek(new Date(year, 0, 2 + (week - 1) * 7)));
   const endDate = new Date(dateFns.endOfWeek(new Date(year, 0, 2 + (week - 1) * 7)));

   let expression = `resource_type:image AND creation_date>=${startDate} AND creation_date<=${endDate}`;

   try {
       let result = await cloudinary.search
           .expression(expression)
           .sort_by('upload_date', 'desc')
           .execute();

       return result.resources;
   } 
   catch (err) {
        console.error(err);
        return [];
   }
}

I am creating my gallery as a Nuxt app (https://github.com/ejfox/nuxt-template-2023) so I think the best way to fetch my new photos is to create them as a Nuxt Server API so that when I go to myapp/api/latest-photos this script will run, fetch the latest photos, add them to the DB if necessary, and return the list so it can be displayed.