create iTunes playlist with scripting bridge

2019-04-22 19:20发布

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?

5条回答
叛逆
2楼-- · 2019-04-22 19:59

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/

查看更多
相关推荐>>
3楼-- · 2019-04-22 20:14

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:

// if the playlist was not found, create it
if (!playlist) {
    playlist = [[[iTunes classForScriptingClass:@"playlist"] alloc] init];
    [[library userPlaylists] insertObject:playlist atIndex:0];
    [playlist setName:playlistName];
}

The other option is to use initWithProperties: which according to your comment on another answer is what you ended up doing.

查看更多
该账号已被封号
4楼-- · 2019-04-22 20:14

Just a quick note that [[source name] isEqualToString:@"Library"] definitely does not work on non-english systems. It might be better to simply use iTunesSource *library = [[_iTunes sources] objectAtIndex: 0]; since the first source item is the one at the top, e.g. the main library.

查看更多
爷的心禁止访问
5楼-- · 2019-04-22 20:20

This is what I've done to reliably identify the library. I could be doing it wrong.

- (iTunesSource *)iTunesLibrary
{
  NSArray *librarySource = [[[self iTunes] sources] filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"kind == %@", [NSAppleEventDescriptor descriptorWithTypeCode:iTunesESrcLibrary]]];
  if ([[librarySource lastObject] exists]) {
    return [librarySource lastObject];
  }
  return nil;
}
查看更多
劫难
6楼-- · 2019-04-22 20:23

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:

set playlistName to "new playlist"
tell application "iTunes"
    if not (exists playlist playlistName) then
        make new playlist with properties {name:playlistName}
    end if
end tell

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):

#import "ITGlue/ITGlue.h"

NSString *playlistName = @"new playlist";

ITApplication *itunes = [ITApplication applicationWithName: @"iTunes"];
ITReference *playlist = [[itunes playlists] byName: playlistName];

if ([[[playlist exists] send] boolValue])
    playlist = [playlist getItem];
else
    playlist = [[[[itunes make] new_: [ITConstant playlist]] 
                      withProperties: [NSDictionary dictionaryWithObject: playlistName
                                                                  forKey: [ITConstant name]]] send];

(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.)

查看更多
登录 后发表回答