I'm trying to get a list of tracks out of iTunes via Scripting Bridge. I'm using NSPredicate
because that's the recommended way. This works very well in some cases, and is unusably slow in others. For instance, this will execute very quickly:
NSString *formatString = @"artist == ABC AND album == XYZ";
NSPredicate *trackFilter = [NSPredicate predicateWithFormat:formatString];
NSArray *tracksToPlay = [[libraryPlaylist fileTracks] filteredArrayUsingPredicate:trackFilter];
(libraryPlaylist
is an iTunesLibraryPlaylist object that was created elsewhere.)
But if I add either kind
or videoKind
to the mix, iTunes hits 100% CPU for a minute or more.
NSString *formatString = @"artist == ABC AND album == XYZ AND kind != 'PDF document' AND videoKind == %@", ;
NSPredicate *trackFilter = [NSPredicate predicateWithFormat:formatString, [NSAppleEventDescriptor descriptorWithTypeCode:iTunesEVdKNone]];
NSArray *tracksToPlay = [[libraryPlaylist fileTracks] filteredArrayUsingPredicate:trackFilter];
But that will eventually work. The real failure is albumArtist
. If I try
NSString *formatString = @"albumArtist == ABC AND album == XYZ";
NSPredicate *trackFilter = [NSPredicate predicateWithFormat:formatString];
NSArray *tracksToPlay = [[libraryPlaylist fileTracks] filteredArrayUsingPredicate:trackFilter];
iTunes will go to 100% CPU and sit there for I don't know how long. (I gave up after 3 or 4 minutes.) Am I missing something or is this a bug in iTunes?
My code takes the resulting tracks and calls another method to add them to a playlist (also using Scripting Bridge). I noticed when trying to filter by kind, the tracks would slowly pop onto the list one by one while iTunes hammered the CPU. This can only mean that filteredArrayUsingPredicate
has already returned its results, so what is iTunes working so hard on?
Another post indirectly helped me find the answer.
Using the “Library” playlist causes a number of unusual problems. Using the “Music” playlist instead seems to fix them. In the example above, setting libraryPlaylist
this way is what caused the problem:
iTunesApplication *iTunes = [SBApplication applicationWithBundleIdentifier:@"com.apple.iTunes"];
iTunesSource *library = [[[[iTunes sources] get] filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"kind == %i", iTunesESrcLibrary]] objectAtIndex:0];
iTunesLibraryPlaylist *libraryPlaylist = [[[library libraryPlaylists] objectAtIndex:0];
Getting the "Music" playlist instead of the "Library" playlist is the answer:
iTunesApplication *iTunes = [SBApplication applicationWithBundleIdentifier:@"com.apple.iTunes"];
iTunesSource *library = [[[[iTunes sources] get] filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"kind == %i", iTunesESrcLibrary]] objectAtIndex:0];
iTunesLibraryPlaylist *libraryPlaylist = [[[[library playlists] get] filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"specialKind == %i", iTunesESpKMusic]] objectAtIndex:0];
Other things to be aware of
The "albumArtist == ABC AND album == XYZ"
filter in the original question was actually running pretty quickly. What's slow is anything you do with the result afterward. Calling get
right away is a partial solution. (get
runs as slow as anything else, but by doing it up front, you limit the slowness to a single operation. Also note that get
only works on an SBElementArray
.)
I've also found that calling fileTracks
re-introduces some slowness. Using tracks
instead fixes that. So the filter should read:
NSArray *tracksToPlay = [(SBElementArray *)[[libraryPlaylist tracks] filteredArrayUsingPredicate:trackFilter] get];
(When using "Library", only fileTracks
would return objects with a location
property, which you need to add them to a playlist. After switching to "Music", tracks
seems to return objects with a location as well.)