I am trying to create a new user playlist using the cocoa scripting bridge, but cannot seem to get it to work. I have so far:
iTunesApplication *iTunes = [SBApplication
applicationWithBundleIdentifier:@"com.apple.iTunes"];
SBElementArray *iSources = [iTunes sources];
iTunesSource *library = nil;
for (iTunesSource *source in iSources) {
if ([[source name] isEqualToString:@"Library"]) {
library = source;
break;
}
}
// could not find the itunes library
if (!library) {
NSLog(@"Could not connect to the iTunes library");
return;
}
// now look for our playlist
NSString *playlistName = @"new playlist";
SBElementArray *playlists = [library userPlaylists];
iTunesUserPlaylist *playlist = nil;
for (iTunesUserPlaylist *thisList in playlists) {
if ([[thisList name] isEqualToString:playlistName]) {
playlist = thisList;
break;
}
}
// if the playlist was not found, create it
if (!playlist) {
playlist = [[[iTunes classForScriptingClass:@"playlist"] alloc] init];
[playlist setName:playlistName];
[[library userPlaylists] insertObject:playlist atIndex:0];
}
When I try and add a name for the playlist, I get the error message:
iTunesBridge[630:80f] *** -[SBProxyByClass setName:]: object has not been added to a container yet; selector not recognized
Can anyone point me in the correct direction?
You should look into EyeTunes. It's an open-source framework for interacting with iTunes using Objective-C. You code would look much more simple if you did it through EyeTunes.
http://www.liquidx.net/eyetunes/
The error message is telling you that Scripting Bridge objects like your playlist can't receive messages until they've been added to the relevant SBElementArray, so your attempt to set a property on the playlist before adding it to the array fails.
The simplest solution is just to rearrange the last two lines of code, like this:
The other option is to use
initWithProperties:
which according to your comment on another answer is what you ended up doing.Just a quick note that
[[source name] isEqualToString:@"Library"]
definitely does not work on non-english systems. It might be better to simply useiTunesSource *library = [[_iTunes sources] objectAtIndex: 0];
since the first source item is the one at the top, e.g. the main library.This is what I've done to reliably identify the library. I could be doing it wrong.
Making new application objects is dreadfully obfuscated in SB. The pseudo-Cocoa-ish alloc-init-insert procedure bears no resemblance to what's actually going on underneath. While the alloc-init appears to create a regular object that you can manipulate with subsequent method calls, the result is actually a shim whose only function is to be 'inserted' into an 'array', at which point SB sends an actual
make
event to the target process. (See also here and here for SB criticisms.)IIRC, the only point you can actually specify initial properties is in
-initWithProperties:
. You can set them after the object has been 'inserted', but that is a completely different operation (manipulating an object that already exists rather than specifying initial state for an object being created) so can easily have unintended consequences if you aren't careful.At any rate, here's how you'd normally create a new playlist if one doesn't already exist:
And, FWIW, here's how I'd do it in ObjC, using objc-appscript (which I wrote so I wouldn't have to use SB, natch):
(The downside of objc-appscript is that you have to build and embed a copy of the framework in your application bundle. The benefits are that it's more capable, less prone to application compatibility issues, and much less obfuscated. Plus you can use appscript's ASTranslate tool to convert the Apple events sent by the above AppleScript into ObjC syntax - very handy when figuring out how to construct your references and commands.)