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/
You can name it whatever you like — just remember to match the path in the scripts later.
Step 2: Open Automator & Start Building
- Open Automator from Spotlight
- Click “New Document” > choose “Application”
- 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"
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
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
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)}")
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
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"
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
- Apple Automator Guide
- Photos Library Structure Explained
- Python pandas documentation
- AppleScript Language Guide
⸻
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:
- LinkedIn: linkedin.com/in/stanley-j
- X (Twitter): @istealersn_dev
- Substack.com: substack.com/@istealersn
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.