My Photo library has evolved over many years, starting out as an iPhoto library, then being merged with an Aperture library, then becoming a Photos library. Over the course of the last 20 or so years I've accumulated thousands of keywords that have zero photos associated with them.
The Photos app itself does not support a 'Delete unused keywords' feature, and I have looked at using AppleScript to do this, but, despite being quite an experienced programmer, I could not work out how to do this (AppleScript drives me nuts quite honestly).
I'm hoping someone has already written such a script, or if not a script then some other utility that will do this for me.
Best Answer
Below is a script I wrote and tested over quite a few days. Whilst Apple's Photos.app is scriptable, you've already observed that it lacks the necessary methods to delete unused keywords. If you are familiar with AppleScript and the notion of UI scripting, this appears to be the only option available.
Note: For UI scripting to work, you need to provide the necessary accessibility privileges to Script Editor.
My personal view towards UI scripting is generally a negative one, but I have taken extra care to try and mitigate the typical temperamental nature and fragility of UI scripts, and done a few tests on my system to observe a reasonably smooth operation.
However, the one specific feature I couldn't (wasn't willing to) check during testing is how the script performs when there are thousands of keywords, and/or an extremely large Photos library. I myself have a Photos library consisting of fewer than 100 photos, and none of them had been tagged with keywords, so I created a sample of 20, of which I randomly assigned about half of them.
In theory, the only affect library volume or keyword count should have on script operation is the length of time it takes to execute. But, with AppleScript, there may be timeout issues that prematurely abort the script; and with UI scripting, the probability of it throwing an error tends to increase with running time.
Specific notes regarding these uncertainties on performance are expanded upon below the script. If you run into any issues, please report back and I shall consider how to implement a fix. There ought to be no adverse effects should the script not perform ideally (i.e. you won't lose any photos). Suboptimal performance ought to only result in an incomplete purge of keywords.
Uncertainties on Threats to Performance
Regarding the volume of photos in your library, the line I am most mindful about is this one:
the effect of which will come into play at the points in the script where the property is dereferenced, i.e.
The function of this line is to retrieve a list of all keywords currently assigned to at least one photo. In order to do this, every single photo in your library needs to be enumerated (retrieved), and its
keywords
property evaluated. This happens virtually immediately at the start of the script; and again after purging keywords to determine if the purge was complete. It's a time-consuming process, and therefore a potential timeout threat for the script.One ought to be able to extend the default timeout value like so: with timeout of 600 seconds set activeKeywords to the list of currentKeywords() end timeout
or it may be necessary to slightly alter the syntax in retrieving the photos so that the script directly targets the Photos app at the point of enumeration, instead of by way of property references; and then to enclose the Photos command within a
timeout
block. But, for now, I've left it to see if the script will run on your system using the default timeout, which might not be a constraint if the enumeration takes place synchronously (and I don't know whether or not it does).Regarding UI scripting potential roadblocks: the Photos AppleScript dictionary doesn't provide a way to retrieve all keywords that exist in the app. The way the script works around this is to open up the Keyword Manager and read off the name of every keyword label that it detects under the section
"Keywords"
. What I don't know is whether every UI element containing a keyword label is loaded when the Keyword Manager window gets created; or whether they get loaded piecewise when a user scrolls through the list. The latter situation would be irksome, as it would result in an incomplete list of keywords and then an incomplete purge.One obvious solution would be to run the script multiple times to perform multiple purges until the are no remaining purgeable items.
When considering the worst-case scenario, analysing the script appears to have one of three possible outcomes (regardless of how the script terminates, be it through completion of its run or by throwing an error):
There does not seem to be a way for the script to fail in a way that negatively impacts the Photos library, so the worst-case scenario appears to be the nil result. However, if we assume I can be wrong, you may want to put a margin on error on the potential worse-case scenario.
This margin is up to you and your judgment, which is tough to make when you perhaps don't know what types of things I'm typically wrong about. If it helps, I would say label it impossible for the script to delete any of your photos, because it does not perform any filesystem operations. If impossible is not good enough, then the clear precaution to take is to backup your entire Photos library beforehand. Depending on the size of your library, this may range from simple to pain-staking for the sake of a near zero-chance event.
What the script does do (obviously) is read and edit lists of keywords. So, while it ought not to be possible, it wouldn't be foolish to consider that all of your keywords for all of your photos might just disappear. Should you want to cover yourself for this unlikely event, then I provide this "scriptlet" that you can run beforehand in order to back up your keywords:
In backing up your keywords, the script will have to enumerate the entire Photos library. Therefore, regardless of whether or not you need the backup, running this script first will give you an indication of how slowly/quickly your library can be read.