DEV Community

Cover image for DIY iCloud Photo Backup (With Album Structure!)
Stanley J
Stanley J

Posted on • Originally published at istealersn.substack.com

DIY iCloud Photo Backup (With Album Structure!)

Skip the Storage Fees - A Weekend Project

Hey folks — if you’ve ever hit that dreaded “iCloud storage full” alert and groaned at paying Apple even more money, you’re definitely not alone. I was in the exact same boat. My iCloud Photos library was eating up gigabytes like candy, and I needed a way to offload originals somewhere safe without losing my carefully organized albums.

So I figured — why not automate the whole thing AND keep my album structure intact? I personally LOVE having photo albums, keeps them together labelled with the moment!

Here’s how I ended up building a smarter macOS app using Automator, AppleScript, and a pinch of Python. This thing backs up all my full-resolution iCloud Photos with their album organization from my Mac to an external SSD with just a click. No third-party apps. No monthly subscriptions. Just clean, local control with your albums preserved.

What We’re Up Against

Apple’s options for photo management are… okay, but frustratingly limited:

  • “Optimise Mac Storage” is great until you need full-res copies offline
  • Manual export from Photos? Tedious and loses album structure
  • Basic file copying? Sure, but say goodbye to knowing which album photos belonged to
  • Third-party apps exist, but many are paid, bloated, or weirdly intrusive

I wanted something that just works, is native, preserves my organization, and doesn’t feel like a hack.

Why This Approach Works

Yeah, mixing Automator with Python might seem overkill. But here’s why it’s actually brilliant:

  • Automator App: gives you a clickable .app that runs everything
  • AppleScript: talks directly to Photos app to extract album info
  • Python: does the smart comparison work to avoid duplicating effort
  • Native integration: no sketchy permissions or third-party dependencies

It’s like mixing LEGO blocks: each piece does what it’s best at, and together they solve the whole problem.

What This Smart Backup Does

  • Checks if your SSD is connected (no accidents!)
  • Extracts your complete album structure from Photos app
  • Compares what’s already backed up vs. what’s new
  • Only exports missing originals (saves tons of time)
  • Preserves album organization on your SSD
  • Tells you exactly what happened when it’s done

Let’s Build It (The Real Steps)

🛡️ Before We Start: Quick Safety Check

This involves accessing your Photos Library directly, so:

  • Go to System Preferences > Security & Privacy > Privacy > Full Disk Access
  • Add Automator to the list (trust me, you’ll need this)
  • Make sure you have Python 3 with pandas installed
  • Grab a coffee on first runs — they can take a while with large libraries

Step 1: Set Up Your SSD Structure

Create a folder on your external drive where we’ll organize the photos:

/Volumes/MySSD/iCloud Photos/
Enter fullscreen mode Exit fullscreen mode

You can name it whatever you like — just remember to match the path in the scripts later.

Step 2: Open Automator & Start Building

  1. Open Automator from Spotlight
  2. Click “New Document” > choose “Application”

Automator from macOs

  1. Now we’ll add script blocks in sequence

This becomes a clickable app that runs your entire backup workflow.

Step 3: Check SSD Connection (Safety First!)

Drop in a Run AppleScript block and paste this:

set ssdMounted to (do shell script "test -d '/Volumes/MySSD/iCloud Photos' && echo yes || echo no")
if ssdMounted is "no" then
    display dialog "❌ SSD not mounted. Please plug in your SSD and try again." buttons {"OK"} default button "OK"
    error number -128
end if

display notification "SSD is Mounted" with title "SSD Mount Status"
Enter fullscreen mode Exit fullscreen mode

This stops everything if your drive isn’t connected — because nobody wants to accidentally fill up their main drive!

Step 4: Export Album Structure from Photos

Add another Run AppleScript block with this code that actually talks to your Photos app:

display notification "Exporting album list from Photos app..." with title "iCloud Sync"

tell application "Photos"
    activate
end tell

delay 2

set exportList to ""

tell application "Photos"
    set albumList to albums
    repeat with a from 1 to count of albumList
        set albumName to name of item a of albumList
        set photoList to media items of item a of albumList
        repeat with p from 1 to count of photoList
            try
                set photoItem to item p of photoList
                set photoName to filename of photoItem
                if photoName is not missing value then
                    set exportList to exportList & albumName & "," & photoName & linefeed
                end if
            on error
                -- Skip items without filename (happens sometimes)
                log "Skipped item in album: " & albumName
            end try
        end repeat
    end repeat
end tell

-- Save to file (update this path to match your setup)
set outFile to POSIX file "/Users/yourusername/Documents/Python Scripts/photos_app_list.txt"
set fileRef to open for access outFile with write permission
set eof of fileRef to 0
write exportList to fileRef as «class utf8»
close access fileRef
Enter fullscreen mode Exit fullscreen mode

Important: Update /Users/yourusername/Documents/ to your actual username and desired path.

Step 5: Smart Comparison with Python

⚠️ Virtual Environment Note: If your Python script uses external packages (like PIL, pandas, etc.), ensure you have a virtual environment set up and activated as shown below to avoid "ModuleNotFoundError" issues.

Add a Run Shell Script block that triggers your Python comparison:

# Navigate to your script directory
cd ~/Documents/Python\ Scripts

# Activate virtual environment (if you use one)
source .venv/bin/activate

# Run the comparison
python3 compare_ssd_photos.py
Enter fullscreen mode Exit fullscreen mode

Now create compare_ssd_photos.py in that directory:

import os
import csv
import pandas as pd
import re

# === SETTINGS (Update these paths!) ===
BASE_PATH = "/Volumes/MySSD/iCloud Photos"
PHOTOS_APP_LIST_PATH = "./photos_app_list.txt"
MISSING_CSV = "./photos_missing_locally.csv"
EXTRA_CSV = "./photos_extra_locally.csv"

# === Normalization Helper ===
def normalize(name):
    """Clean up filenames for better matching"""
    name = name.lower()
    name = re.sub(r"\s\(\d+\)", "", name) #remove dups like (1), (2)    
    name = name.replace(" ", "")
    return name

# Read what Photos app says you have
app_data = []

with open(PHOTOS_APP_LIST_PATH, "r") as f:
    for line in f:
        if "," in line:
            album, filename = line.strip().split(",", 1)
            app_data.append({
                "Album": album.strip(),
                "Filename": filename.strip(),
                "Normalized": normalize(filename)
            })
app_df = pd.DataFrame(app_data)

# Read what's actually on your SSD
ssd_data = []

for album in os.listdir(BASE_PATH):
    album_path = os.path.join(BASE_PATH, album)
    if os.path.isdir(album_path):
        for file in os.listdir(album_path):
            if file.startswith("._"):  # Skip macOS metadata
               continue
             ssd_data.append({
                "Album": album,
                "Filename": file,
                "Normalized": normalize(file)
            })
ssd_df = pd.DataFrame(ssd_data)

# Find what's missing and what's extra
missing = pd.merge(app_df, ssd_df, on="Normalized", how="left", indicator=True)
missing = missing[missing["_merge"] == "left_only"]
missing[["Album_x", "Filename_x"]].rename(columns={"Album_x": "Album", "Filename_x": "Filename"}).to_csv(MISSING_CSV, index=False)
extra = pd.merge(ssd_df, app_df, on="Normalized", how="left", indicator=True)
extra = extra[extra["_merge"] == "left_only"]
extra[["Album_x", "Filename_x"]].rename(columns={"Album_x": "Album", "Filename_x": "Filename"}).to_csv(EXTRA_CSV, index=False)

# Tell you what happened
print(f"\n Comparison Complete!")
print(f"Albums scanned: {app_df['Album'].nunique()}")
print(f"Missing locally: {len(missing)}")
print(f"Extra locally: {len(extra)}")
Enter fullscreen mode Exit fullscreen mode

Step 6: Export Missing Files (The Magic Happens)

Add another Run AppleScript block that handles the intelligent export process:

display notification "Exporting missing files..." with title "iCloud Sync"

tell application "Photos"
    activate
end tell

delay 2

set exportList to ""

tell application "Photos"
    set albumList to albums
    repeat with a from 1 to count of albumList
        set albumName to name of item a of albumList
        set photoList to media items of item a of albumList

        repeat with p from 1 to count of photoList
            try
                set photoItem to item p of photoList
                set photoName to filename of photoItem
                if photoName is not missing value then
                    set exportList to exportList & albumName & "," & photoName & linefeed
                end if
            on error errMsg number errNum
                -- Skip items without filename (happens with some photo types)
                log "Skipped item in album: " & albumName
            end try
        end repeat
    end repeat
end tell

-- Update the album list (change this path to match your setup)
set outFile to (POSIX file "/Users/yourusername/Documents/Python Scripts/photos_app_list.txt") as text
set fileRef to open for access file outFile with write permission
set eof of fileRef to 0
write exportList to fileRef as «class utf8»
close access fileRef
Enter fullscreen mode Exit fullscreen mode

Important: Update /Users/yourusername/Documents/Python Scripts/ to match your actual path.

This is the beautiful part — this script re-scans your Photos library after the comparison step, ensuring it captures any changes you might have made since the initial scan. It's smart enough to skip problematic files and keeps your export list fresh.

Step 7: Completion Notification

Add a final Run AppleScript block:

display notification "✅ iCloud Photos Sync Completed!" with title "Sync Status"
Enter fullscreen mode Exit fullscreen mode

The complete 6-step Automator workflow in action - from checking SSD mount status to final notification, all automated with a mix of AppleScript and Python integration.

The complete 6-step Automator workflow in action - from checking SSD mount status to final notification, all automated with a mix of AppleScript and Python integration.

Let your future self know everything went smoothly.

🧪 Step 8: Test Drive (Seriously, Do This First)

Before going full throttle:

  • Create a small test album with a few photos
  • Run your app and see if the CSV files get generated correctly
  • Check that the comparison logic makes sense
  • Make sure file paths are all correct for your setup

Trust me, 10 minutes of testing beats hours of “where did my photos go?”

Why This Fully Automated Setup Actually Works

  • ✅ Detects exactly what's missing instead of guessing
  • ✅ Zero duplicate work — only exports what's actually needed
  • ✅ Album structure stays perfectly intact on your SSD
  • ✅ Native macOS tools, no sketchy third-party apps
  • ✅ Run it weekly with literally just a double-click
  • ✅ Haven't paid Apple a rupee more for storage

The best part? You literally just run the app and walk away. It figures out what's missing, exports only those files, organizes them by album, and tells you when it's done. That's proper automation.

Wrapping It Up

This was a fun weekend project that scratched a real itch: keep my photo organization without paying Apple’s storage tax. Sure, the final export step isn’t 100% automated, but the intelligence is there — you know exactly what needs backing up and where it should go.

I now run this app every couple weeks. My SSD has all my originals organized by album. I don’t worry about storage alerts. And I definitely haven’t given Apple any more money for the privilege of accessing my own photos.

If you’re into DIY productivity tools or just hate subscription fees for basic file storage, give this a shot. The comparison logic alone saves tons of time figuring out what’s actually missing.

Useful Links

Hope this helps 👍 someone else avoid the iCloud storage subscription trap while keeping their photos actually organized.

Feel free to fork, remix, or improve this flow. And if you figure out how to make Photos app export more reliably, definitely let me know!

This Weekend's Project Stats:

  • 4 hours of weekend coding
  • ₹0 spent on cloud storage upgrades
  • 15,000 photos safely backed up with album structure intact
  • That satisfying feeling of building exactly what I needed 😍

Was it worth skipping Netflix for a weekend? Absolutely.

💸 The Real Win: I was literally about to upgrade my iCloud plan to 200GB (₹219/month) when I built this instead.

That's ₹2,628/year I don't have to pay Apple just to access my own photos.

About Me

Frontend engineer and technical writer sharing practical patterns for modern web development. Passionate about clean code, scalable architectures, and helping developers build better applications.

Connect with Stanley:

Follow for more frontend engineering insights, API design patterns, and practical development tutorials.

Top comments (0)

Some comments may only be visible to logged-in visitors. Sign in to view all comments.