Skip to content

Instantly share code, notes, and snippets.

@akrabat
Created December 28, 2019 16:34
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save akrabat/ebc66501744cb7fa0871cf8255de09b9 to your computer and use it in GitHub Desktop.
Save akrabat/ebc66501744cb7fa0871cf8255de09b9 to your computer and use it in GitHub Desktop.
--
-- For each album, add a hierarchical keyword of the format
-- "PhotosExport>folder1>folder2>album" to each photo.
--
-- Copyright 2019 Rob Allen.
-- License: MIT - https://akrabat.com/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 https://stackoverflow.com/a/1035260/23060
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)
-- FINISHED
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"
repeat
try
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 https://macscripter.net/viewtopic.php?id=8372
-- 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
-- ///////////////////////////////////////////
-- // LOGGING
-- ///////////////////////////////////////////
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)
try
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
try
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
@leifericf
Copy link

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.

@DanielHenriksen
Copy link

Great script. Thank you! However it only operates on top-level albums and not recursive within folders, so the foldername>albumname keyword isn't applied on images in albums within folders. Am I doing something wrong or has Apple changed something in Ventura?

@akrabat
Copy link
Author

akrabat commented Apr 22, 2023

Apple changed something, but I've not needed it recently, so haven't looked into it. This comment on my blog has one solution.

@DanielHenriksen
Copy link

Thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment