17 February 2009

Укрощение iTunes с помощью XCode и scripting Bridge.

Development for iOS
Прошлым летом случилось страшное… Накрылся винт в моем МакБуке. Все важные данные были легко восстановлены. Слава Машине Времени! А вот музыкальной коллекции, которую я кропотливо взращивал годами, не повезло. Саму музыку восстановить не проблема, а вот рейтинги и количество прослушиваний, чья история уходит корнями в самое появление iTunes под Windows, ой как жалко. Пережила фонотека многое, в том числе перенос с Windows на Хакинтош, а затем и на МакБук.

Благо мадиатека была потеряна не вся — остались рожки да ножки, а точнее заветный файл "iTunes Library". На днях я решился его реанимировать.

Решение под Mac OS


imageОчевидное решение — использовать AppleScript. С помощью него практически любое приложение в Mac OS можно автоматизировать. Язык настолько близок к английскому и далек от других языков программирования, что ввел меня в ступор. Я не знал с какой стороны к нему подступиться.
Пришлось использовать XCode и немного знакомый Objective-C. Работа с XML не вызвала практически никаких проблем. NSXML… и MSXML оказались API очень похожими не только названием.

С общением с iTunes дела обстоят сложнее. Помимо собственно AppleScript, который можно использовать в проектах XCode, я нашел всего два способа. Оба по сути являются обертками для AppleScript.


1. AppScript Framework
Open Source проект от сторонних разработчиков. Подходит для Mac OS X начиная с 10.3.9. Поддерживает Objective-C, Ruby и Python. Синтаксис показался сложным, да и скомпилировать его правильно мне не удалось, поэтому с ним не разбирался.

2. Scripting Bridge Framework
Собственное запатентованное решение от Apple, которое увидело свет лишь в Mac OS 10.5. Как и следует из названия, Scripting Bridge "динамически реализует мост между Objective-C и приложениями с поддержкой AppleScript. При этом генерируются Objective-C классы на основе описания скриптового интерфейса. Они включают объекты и методы представляющие свойства, элементы, команды и так далее."
На втором варианте я и остановился.
Первое что надо сделать, это добавить /System/Library/Frameworks/ScriptingBridge.framework к проекту. Затем создать специальный заголовочный файл, чтобы знать как обращаться к конкретному приложению с поддержкой ActionScript.
Делается это командой в терминале:
sdef /Applications/iTunes.app | sdp -fh --basename iTunes

В текущей папке появится файл iTunes.h, который надо добавить в проект XCode и можно обращаться к iTunes.
Аналогично можно поступить с любым приложением поддерживающим AppleScript.

Пример общения


Для примера код, который сохраняет список песен, рейтинг и число исполнений:

- (void) ExportLibrary
{
Boolean shouldExportTrack = NO;
// Связываемся с iTunes по идентификатору приложения
iTunesApplication *iTunes = [SBApplication applicationWithBundleIdentifier:@"com.apple.iTunes"];

// Получаем библиотеку iTunes (если библиотек вдруг несколько или есть общие библиотеки, может и не ту выдать)
iTunesLibraryPlaylist *library = [[[[iTunes sources] objectAtIndex:0] playlists] objectAtIndex:0];
iTunesTrack *track;

// Создаем пустой XML и получаем его корневой элемент
NSXMLDocument *xmlNew = [[NSXMLDocument alloc] initWithXMLString:@"<?xml version=\"1.0\"?><LIBRARY/>" options:0 error:NULL];
NSXMLElement *rootNode = [xmlNew rootElement], *curNode;

// число песен в библиотеке
NSInteger curLibTrack = 0, nLibTracks = [[library tracks] count];
NSLog(@"%i", nLibTracks);

// Перебираем все песни
for (curLibTrack = 0; curLibTrack < nLibTracks; curLibTrack++)
{
track = [[library tracks] objectAtIndex:curLibTrack];
shouldExportTrack = (track.rating > 0 || track.playedCount > 0); // читаем только прослушанные или любимые песни
if (shouldExportTrack)
{
// Создаем элемент XML и сохраняем в него нужные данные о песне
curNode = [[NSXMLElement alloc] initWithXMLString:@"<TRACK n=\"\" name=\"\" played=\"\" rating=\"\" />" error:NULL];
[[curNode attributeForName:@"n"] setStringValue:[NSString stringWithFormat:@"%i", curLibTrack]];
// Здесь используется собственная функция getTrackMetaName, которая выдает название, исполнителя и альбом
// одной строкой в нижнем регистре, без пробелов, лишнего мусора и учета диакритических символов (привет Beyoncé и Anggun)
[[curNode attributeForName:@"name"] setStringValue:[self getTrackMetaName:track]];
[[curNode attributeForName:@"played"] setStringValue:[NSString stringWithFormat:@"%i", track.playedCount]];
[[curNode attributeForName:@"rating"] setStringValue:[NSString stringWithFormat:@"%i", track.rating]];
[rootNode addChild:curNode];
[curNode release];
}

}

// Не мудрствуя лукаво сохраняем на рабочий стол
[[xmlNew XMLData] writeToFile:(@"/Users/max/Desktop/old-library.xml") atomically:NO];
[xmlNew release];

}


* This source code was highlighted with Source Code Highlighter.


Заключение


Работа над восстановлением многострадальной библиотеки пока не закончена. Нужно разобраться с сортировкой треков по хэшу MetaName и с эффективным поиском совпадений. Сложилось впечатление, что быстрее было бы вручную все поправить, чем часами разбираться во всем этом =)
Все отлично работает. Но в полуавтоматическом режиме, ибо не было желания возиться дальше.

С другой стороны узнал много нового, вместо того чтобы заниматься подобной рутиной.

Если есть возможность что-то автоматизировать, лучше сделать это один раз, чем потом периодически тратить время на ручную работу.
Tags:itunesxcodeapplescriptscripting bridgemac os xcocoaobjective-c
Hubs: Development for iOS
+5
1.4k 10
Comments 3
Top of the last 24 hours