Spotlight Scripting for iTunes
Tiger's Spotlight technology allows you to do some pretty thorough searching. Here's how you can create your own iTunes Spotlight search routines with AppleScript.
Metadata
Spotlight uses the metadata in the files on your computer to quickly determine matches for search terms. Metadata, as the saying goes, is data about data. Every file contains metadata and usually this is a list of various file properties. Different kinds of files can contain many different types of metadata. For instance, audio files contain information about bit rate, artist, album, and so on; whereas a graphics file will contain information about pixel width, or camera information, and so on.
Apple has introduced three new UNIX commands specifically for working with metadata and Spotlight (mdls, mdfind, and mdutil -- do a man in Terminal for the specifics). We can use these commands in do shell script routines within an AppleScript.
What metadata do we want?
I'm only concerned with audio files in this article, but you can use the same techniques with other types of files. The first thing you'll want to know is the metadata a typical audio file contains. To find out, use the mdls command. This command takes a POSIX-type file path as a parameter and returns its metadata. I've been using it like this:
set the_meta_data to (do shell script "mdls " & quoted form of POSIX path of ((choose file) as string))
I just have the Event Log selected in the Data Window of Script Editor, run the script, choose a file, and the result is something like this:
- kMDItemAlbum = "39 Minutes of Bliss (In an Otherwise Meaningless World)"
- kMDItemAttributeChangeDate = 2005-05-06 11:06:49 -0400
- kMDItemAudioBitRate = 127944
- kMDItemAudioChannelCount = 2
- kMDItemAudioTrackNumber = 4
- kMDItemAuthors = (Caesars)
- kMDItemCodecs = ("")
- kMDItemContentCreationDate = 2005-02-23 20:29:07 -0500
- kMDItemContentModificationDate = 2005-02-23 21:58:19 -0500
- kMDItemContentType = "com.apple.protected-mpeg-4-audio"
- kMDItemContentTypeTree = ( "com.apple.protected-mpeg-4-audio", "public.audio", "public.audiovisual-content", "public.data", "public.item", "public.content" )
- kMDItemDisplayName = "Jerk It Out.m4p"
- kMDItemDurationSeconds = 196.1383333333333
- kMDItemFSContentChangeDate = 2005-02-23 21:58:19 -0500
- kMDItemFSCreationDate = 2005-02-23 20:29:07 -0500
- kMDItemFSCreatorCode = 1752133483
- kMDItemFSFinderFlags = 0
- kMDItemFSInvisible = 0
- kMDItemFSLabel = 0
- kMDItemFSName = "Jerk It Out.m4p"
- kMDItemFSNodeCount = 0
- kMDItemFSOwnerGroupID = 80
- kMDItemFSOwnerUserID = 501
- kMDItemFSSize = 3474926
- kMDItemFSTypeCode = 0
- kMDItemID = 44192
- kMDItemKind = "MPEG-4 Audio File (Protected)"
- kMDItemLastUsedDate = 2005-05-06 11:06:49 -0400
- kMDItemMediaTypes = (Sound)
- kMDItemMusicalGenre = "Pop"
- kMDItemStreamable = 0
- kMDItemTitle = "Jerk It Out"
- kMDItemTotalBitRate = 127944
- kMDItemUsedDates = (2005-05-05 20:00:00 -0400)
Lotta good stuff in there! You may notice that some of the info contained in a file's metadata is accessible by an info for via Finder scripting. But what's really important here is not so much the values but the labels, those "kMDItem-" things. These are the parameters for the other new UNIX command we will want to use, mdfind.
Making the search script
The mdfind command works very simply. Its syntax, using Terminal, looks something like this:
mdfind "some kMDlabel == 'some string' another kMDlabel == 'some other string'"
This command returns a string containing the filepaths of any result separated by returns.
Here's a basic AppleScript you can try yourself
set the_command to ("mdfind " & "\"kMDItemTitle == 'Jerk It Out'\" ") as string set returned_list to (do shell script the_command)
Now, if you have an audio file on your Spotlight-indexed system that is titled "Jerk It Out" (not the filename, the actual name of the track which is embedded in the metadata), the result will be the filepath (or filepaths) to the found files.
Of course, we can perform much more interesting searches by using select metadata labels. This script will grab the filepaths of all the files whose artist is "Caesars" and album is "39 Minutes of Bliss (In an Otherwise Meaningless World)". Notice how I have used the particular labels in the search query:
set the_command to ("mdfind " & "\"kMDItemAuthors == 'Caesars' kMDItemAlbum == '39 Minutes of Bliss (In an Otherwise Meaningless World)'\"") as string set returned_list to (do shell script the_command)
It's fast, too!
Now, all this is well and good if you want to hard-code your strings. But we can use iTunes scripting to grab specific tags and dump them into our query. First, select a song, get the tag data you want, and make it usable in the do shell script. Let's search for some duplicate files using the Song Name and the size of a selected track:
tell application "iTunes" -- select one track: if selection of front browser window is not {} then set selected_track to item 1 of selection of front browser window -- get the tag data tell selected_track to set {nom, siz} to {name, size} end if end tell set the_command to ("mdfind " & "\"kMDItemTitle == '" & nom & "' kMDItemFSSize == '" & siz & "'\"") as string set returned_list to (do shell script the_command)
OK, that's fine, but there's a coupla things:
First of all, the filepaths are still only listed in Script Editor. I'll insert a routine that "reveals" them in the Finder in just a sec'.
Second of all, and more of a concern to me, is my problem with single-quotes in strings. Now, I'm pretty good at figuring out quote-escaping, but for some reason, Tiger either does it differently or I'm a Monkey's Uncle. Anyway, notice how your search terms have to be surrounded in single-quotes. If a search string contains a single-quote, like an apostrophe, the script will fail because the do shell script interprets the next occurrence of a single-quote as the end of the string. Yes, yes, I tried "quoted form of" but that made things even worse! Dunno why it is.
So, we will include a handler that escapes the single-quotes for us. I don't like doing it that way, but, afterall, I am a Monkey's Uncle.
So back to "revealing" the found files. The mdfind command returns a string of results, not an AppleScript list. To convert the string to a list, which will be handy, I'll use my trusty "text_to_list()" handler. Next, to escape any single-quotes, I will use the equally trusty "replace_chars()" handler. Once I have my results, I will repeat through them and have the Finder reveal each file:
tell application "iTunes" -- select one track: if selection of front browser window is not {} then set selected_track to item 1 of selection of front browser window -- get the tag data tell selected_track to set {nom, siz} to {name, size} end if end tell -- escape any single-quotes in text strings, -- in this case, any that may be in the nom variable set nom to replace_chars(nom, "'", "\\'") set the_command to ("mdfind " & "\"kMDItemTitle == '" & nom & "' kMDItemFSSize == '" & siz & "'\"") as string set returned_list to (do shell script the_command) if returned_list is not "" then -- we got some results -- make a list from the string; items are separated by carriage returns set new_list to text_to_list(returned_list, return) -- show each file in the Finder repeat with this_file in new_list set this_file to POSIX file this_file tell application "Finder" reveal this_file activate end tell end repeat end if on replace_chars(txt, srch, repl) set AppleScript's text item delimiters to the srch set the item_list to every text item of txt set AppleScript's text item delimiters to the repl set txt to the item_list as string set AppleScript's text item delimiters to "" return txt end replace_chars on text_to_list(txt, delim) set saveD to AppleScript's text item delimiters try set AppleScript's text item delimiters to {delim} set theList to every text item of txt on error errStr number errNum set AppleScript's text item delimiters to saveD error errStr number errNum end try set AppleScript's text item delimiters to saveD return (theList) end text_to_list
Fun, huh?
More UNIX Commands
You can also use UNIX commands in your Spotlight query. The script below uses logical AND operator (&&) to get any file that has an Album metadata tag, using the "*" wildcard", AND whose label color is blue, which is designated with the parameter value "4". (The count of these files is logged in the script, but with Event Log turned on when running from Script Editor you will see the filepath results.):
set the_command to ("mdfind " & "\"kMDItemAlbum == '*' && kMDItemFSLabel == 4\"") as string set returned_list to (do shell script the_command) log (count of text_to_list(returned_list, return)) on text_to_list(txt, delim) set saveD to AppleScript's text item delimiters try set AppleScript's text item delimiters to {delim} set theList to every text item of txt on error errStr number errNum set AppleScript's text item delimiters to saveD error errStr number errNum end try set AppleScript's text item delimiters to saveD return (theList) end text_to_list
Get another script
I have posted a script called Bring Out Yer Dead that searches for possibly-existing files of "dead tracks", which uses the same routines here. It uses the Song Name, Artist, and Album of a "dead track" and does a Spotlight search for potentially matching files. Then you can decide if you want to re-add them to iTunes, deleting the "dead track" and copying the original "dead track's" tags over to the new track. Just the tip of the ice berg as far as I'm concerned. Let me know what you come up with.
You can find out more about Spotlight metadata at this ADC page and more power-user tips using Spotlight in general on this page.