Skip to content

Instantly share code, notes, and snippets.

What would you like to do?
-- For each album, add a hierarchical keyword of the format
-- "PhotosExport>folder1>folder2>album" to each photo.
-- Copyright 2019 Rob Allen.
-- License: MIT -
-- Variables to control how many albums to process per run
-- change appropriately per run if you need to do in batches
set theStartAlbumIndex to 1 -- theStartAlbumIndex is 1 based, not zero based
set theNumberOfAlbumsToProcess to 1000000
global theLogFile
set theLogFile to ("/Users/rob/Desktop/" as POSIX file as text) & "KeywordAlbum.log"
-- Variables to keep track of what we're doing
set theCount to 1
set theEndAlbumIndex to theStartAlbumIndex + theNumberOfAlbumsToProcess
set mgStart to current date
set thePhotoCount to 0
-- top level albums to skip with "PhotosExport>" prefix added
set albumsToSkip to {"PhotosExport>Search", "PhotosExport>Today", "PhotosExport>Camera Remote", "PhotosExport>Instagram", "PhotosExport>PlayMemories Mobile"}
my logThis("Started. Processing " & theNumberOfAlbumsToProcess & " albumns starting from album " & theStartAlbumIndex)
with timeout of 72000 seconds
tell application "Photos"
set allAlbums to albums
my logThis("Number of Albums: " & (count of allAlbums))
repeat with theAlbum in allAlbums
-- Start fake loop to simulate "continue". See
repeat 1 times
if theCount is less than theStartAlbumIndex then
exit repeat -- "continue"
end if
-- Get the hierarchy keyword
set theKeyword to my getAlbumKeyword(theAlbum)
set theMsg to "" & theCount & ": Album: " & name of theAlbum & ", " & theKeyword
if albumsToSkip contains theKeyword then
set theMsg to theMsg & ". *** Skipping ***"
my logThis(theMsg)
exit repeat -- "continue"
end if
my logThis(theMsg)
-- get all the photos in this album and add the keywords
set thePhotos to every media item in theAlbum
repeat with thePhoto in thePhotos
set thePhotoCount to thePhotoCount + 1
my addKeywordsToItem(thePhoto, theKeyword)
-- log "Added keywords to '" & filename of thePhoto & "'"
end repeat
end repeat -- End of simulation of continue
set theCount to theCount + 1
if theCount is theEndAlbumIndex then
exit repeat
end if
end repeat
end tell
end timeout
set mgStop to current date
my logThis("Number of photos processed: " & thePhotoCount)
my logThis("Finished. This run took " & (mgStop - mgStart) & " seconds.")
my writeToFile("", theLogFile, true)
on getAlbumKeyword(targetObject)
set theLevel to 1
set theParents to {}
-- start with the album name
set theKeword to the name of targetObject
-- interate up over all parents of targetObject
tell application "Photos"
set theName to the name of parent of targetObject
set thisID to id of parent of targetObject
-- prefix with parent name
set theKeword to theName & ">" & theKeword
-- update targetObject to be its parent
if class of parent of targetObject is folder then
set targetObject to folder id thisID
else if class of parent of targetObject is album then
set targetObject to album id thisID
end if
on error
-- all done: no parent of targetObject
set theKeword to "PhotosExport>" & theKeword
return theKeword
end try
end repeat
end tell
end getAlbumKeyword
on addKeywordsToItem(mediaItem, theKeyword)
tell application "Photos"
set existingKeywords to (keywords of mediaItem)
if existingKeywords is missing value then
set existingKeywords to {}
end if
set (keywords of mediaItem) to my uniqueItems(existingKeywords & {theKeyword})
end tell
end addKeywordsToItem
on uniqueItems(a)
-- from
-- A "Serge" object. Accessing items of a list is done
-- faster with object references. Why? We don't know.
script o
property p : a
end script
-- "Serge" doesn't speed up appending to a list
set b to {}
repeat with i from 1 to a's length
-- A "Garvey" tell statement. In some situations, the 2 calls
-- to "it" are faster than 2 calls to "o's p's item i".
tell o's p's item i to if ({it} is not in b) then set b's end to it
end repeat
return b
end uniqueItems
-- ///////////////////////////////////////////
-- ///////////////////////////////////////////
on getCurrentTimestamp(theDate)
set yyyy to text -4 thru -1 of ("0000" & (year of theDate))
set mm to text -2 thru -1 of ("00" & ((month of theDate) as integer))
set dd to text -2 thru -1 of ("00" & (day of theDate))
set hh to text -2 thru -1 of ("00" & (hours of theDate))
set mins to text -2 thru -1 of ("00" & (minutes of theDate))
set ss to text -2 thru -1 of ("00" & (seconds of theDate))
return yyyy & ":" & mm & ":" & dd & ":" & hh & ":" & mins & ":" & ss
end getCurrentTimestamp
on logThis(theText)
set theText to (my getCurrentTimestamp((current date))) & ": " & theText
log theText --to console
my writeToFile(theText, theLogFile, true) -- and persist to log file
end logThis
on writeToFile(thisData, targetFile, shouldAppend) -- (string, file path as string, boolean)
set the targetFile to the targetFile as text
set the openTargetFile to open for access file targetFile with write permission
if shouldAppend is false then set eof of the openTargetFile to 0
-- write the line and a \n character ..
write thisData & return to the openTargetFile starting at eof
close access the openTargetFile
return true
on error errorMessage number errorNumber
log "Exception logging. Details: " & errorMessage & " Error number " & errorNumber & ". Data to be written was: " & thisData
close access file targetFile
end try
return false
end try
end writeToFile
on convertListToString(theList)
set AppleScript's text item delimiters to ", "
set theString to theList as string
set AppleScript's text item delimiters to ""
return theString
end convertListToString

This comment has been minimized.

Copy link

IRLeif commented Apr 26, 2020

Thank you for sharing this information, Rob! I ran your KeywordAlbum.applescript on my Photos library consisting of 23 819 images across 307 albums. It worked like a charm, even with Norwegian letters and special characters in file- and album names. Zero errors. I have a mixture of RAW + JPEG and PNGs from different cameras, including Nikon, Canon and Leica. You really saved me a lot of hassle.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.