Создать плейлист iTunes с помощью скриптового моста

Я пытаюсь создать новый пользовательский плейлист с использованием моста сценариев какао, но не могу заставить его работать. У меня так далеко

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];
}

Когда я пытаюсь добавить имя для списка воспроизведения, я получаю сообщение об ошибке:

iTunesBridge [630: 80f] *** - [SBProxyByClass setName:]: объект еще не добавлен в контейнер; селектор не распознан

Кто-нибудь может указать мне правильное направление?

5 ответов

Сообщение об ошибке говорит вам, что объекты Scripting Bridge, такие как ваш список воспроизведения, не могут получать сообщения, пока они не будут добавлены в соответствующий SBElementArray, поэтому ваша попытка установить свойство в списке воспроизведения перед добавлением его в массив не удалась.

Самое простое решение - просто переставить две последние строки кода, например так:

// 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];
}

Другой вариант заключается в использовании initWithProperties: что в соответствии с вашим комментарием на другой ответ, что вы в конечном итоге делать.

Создание новых объектов приложения ужасно запутано в SB. Процедура псевдо-Cocoa-ish alloc-init-insert не имеет никакого сходства с тем, что на самом деле происходит под ней. В то время как alloc-init создает обычный объект, которым вы можете манипулировать с последующими вызовами методов, на самом деле результатом является shim, единственная функция которого должна быть "вставлена" в "массив", после чего SB отправляет фактический make событие для целевого процесса. (См. Также здесь и здесь для критики SB.)

IIRC, единственная точка, которую вы действительно можете указать начальные свойства в -initWithProperties:, Вы можете установить их после того, как объект был "вставлен", но это совершенно другая операция (манипулирование объектом, который уже существует, а не указание начального состояния для создаваемого объекта), поэтому может легко привести к непреднамеренным последствиям, если вы не будете осторожны,

В любом случае, вот как вы обычно создаете новый плейлист, если он еще не существует:

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

И, FWIW, вот как я это сделаю в ObjC, используя objc-appscript (который я написал, чтобы мне не пришлось использовать 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];

(Недостаток objc-appscript заключается в том, что вам необходимо создать и встроить копию инфраструктуры в свой пакет приложений. Преимущества заключаются в том, что она более способна, менее подвержена проблемам совместимости приложений и намного менее запутана. Плюс вы можете использовать приложения Инструмент ASTranslate для преобразования событий Apple, отправленных вышеприведенным AppleScript, в синтаксис ObjC - очень удобно при выяснении того, как создавать ссылки и команды.)

Просто быстрое замечание, что [[source name] isEqualToString:@"Library"] определенно не работает на неанглийских системах. Может быть, лучше просто использовать iTunesSource *library = [[_iTunes sources] objectAtIndex: 0]; поскольку первый исходный элемент находится сверху, например, в главной библиотеке.

Это то, что я сделал, чтобы надежно идентифицировать библиотеку. Я мог бы сделать это неправильно.

- (iTunesSource *)iTunesLibrary
{
  NSArray *librarySource = [[[self iTunes] sources] filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"kind == %@", [NSAppleEventDescriptor descriptorWithTypeCode:iTunesESrcLibrary]]];
  if ([[librarySource lastObject] exists]) {
    return [librarySource lastObject];
  }
  return nil;
}

Вы должны посмотреть в EyeTunes. Это платформа с открытым исходным кодом для взаимодействия с iTunes с помощью Objective-C. Ваш код выглядел бы намного проще, если бы вы делали это через EyeTunes.

http://www.liquidx.net/eyetunes/

Другие вопросы по тегам