Pull to refresh

Подробная анатомия простого плагина для XBMC

Reading time 20 min
Views 33K

Предисловие


Похожая статья на Хабре уже публиковалась, но в ней основное внимание было уделено парсингу сайта с видео — так сказать, бизнес-логике плагина, а вопросы взаимодействия с XBMC затронуты вскользь. Я же хочу рассказать о том, что превращает скрипт на языке Python (далее — Питон) в плагин XBMC.
Данный плагин предназначен для просмотра видеоподкастов с сайта www.cnet.com в обычном или высоком качестве, в зависимости от настроек. Плагин не имеет никакого отношения к плагину просмотра подкастов CNET, доступному в официальном репозитории XBMC. Он полностью написан с 0 как учебный проект в рамках самостоятельного изучения собственно написания плагинов к XBMC. Плагин не претендует ни на полезность для широкой аудитории, ни на особо выдающиеся программные решения. Однако он полностью рабочий, и в нем есть большинство элементов, присущих «серьезным» плагинам: файл XML с метаданными, основной скрипт, внешний импортируемый модуль, панель настроек, файлы локализации, файлы ресурсов (картинки). Надеюсь, что плагин и данная статья смогут послужить отправной точкой как для начинающих плагинописателей, так и для опытных программистов на Питоне, которые захотят приобщиться к написанию плагинов для XBMC.

Полностью плагин можно скачать отсюда.

Примечание: далее в тексте в отношении некоторых файлов и папок используется термин «служебный». Это означает, что XBMC ищет такие папки и файлы без прямого указания на них в коде плагина. «Служебный» != «обязательный», и более простые плагины могут не содержать каких-то служебных файлов.

Общая информация


Для написания плагинов XBMC используется язык Питон. XBMC под Windows компилируется с версией 2.7, а под Linux, как я понимаю, версия Питона зависит от установленных девелоперских библиотек, используемых при сборке. В любом случае, при написании кода плагина стоит ориентироваться на синтаксис и возможности Питона 2.7.
Питон в XBMC полнофункциональный: присутствует почти вся стандартная библиотека, кроме, разве что, Tkinter/ttk/tix (которые здесь явно лишние). Для взаимодействия с XBMC добавлены 5 модулей: xbmc, xbmcgui, xbmcplugin, xbmcaddon и xbmcvfs, которые вместе составляют XBMC Python API. Краткую справку по модулям можно найти здесь. К сожалению, в справке встречаются неточности.
Кроме того, оператор print вместо консоли пишет в лог XBMC с уровнем Notice, что можно использовать при отладке, например для вывода значений переменных.
Немного о терминологии: изначально разработчики XBMC использовали термин «аддон» (addon) для любых дополнений, которые, в свою очередь, подразделяются на плагины (источники контента), скрипты (программы), скреперы (загрузчики информации к медиаконтенту) и прочие. Однако со временем понятия «аддон» и «плагин» перепутались, и сейчас их часто используют для обозначения любого дополнения.

Настройка IDE


Как уже было сказано, плагины XBMC пишутся на языке Питон. Программа на этом языке представляет собой по сути текстовый файл, с которым можно работать в любом текстовом редакторе, начиная с Блокнота Windows и заканчивая «продвинутыми вариантами», вроде Notepad++. Однако для написания любого более-менее сложного кода лучше использовать IDE (Integrated Development Environment), которая предлагает целый ряд удобств: подсветка кода, автодополнение ключевых слов, браузер кода, краткая справка по объектам и т. п. Многие программисты на Питоне часто используют связку Eclipse + PyDev, но лично я предпочитаю более легковесный PyScripter, в котором есть почти все функции полноценной IDE, кроме, разве что, сворачивания кода (code folding). Правда, проект не развивается с 2012 года, но для Питона 2.х его возможностей более чем хватает.
Для облегчения разработки плагинов на Питоне в IDE добрые люди (включая меня) составили набор питоновских модулей-заглушек, имитирующих реальные модули XBMC Python API. Эти модули нужно добавить в пути импорта проекта («Properties» > «PYTHONPATH» > «Source folders» в Eclipse/PyDev или «Extra Python Path…» в PyScripter), и получаем Profit — начинают работать автодополнение кода и краткая справка для классов, методов и прочих элементов XBMC Python API.

Внимание: это именно модули-заглушки, не содержащие полезного кода, и попытка запустить программу, которая на них ссылается, прямо из IDE приведет к непредсказуемым результатам. Впрочем, желающие могут добавить в эти модули отладочный код, имитирующий поведение реальных функций и методов XBMC Python API.

Структура плагина


Мой «учебный» плагин имеет следующую структуру:
addon.xml
changelog.txt
default.py
fanart.jpg
icon.png
License.txt
\resources\
	settings.xml
	\language\
		\English
			strings.po
		\Russian\
			strings.po
		\Ukrainian\
			strings.po
	\lib\
		feeds.py
	\thumbnails\

Теперь подробнее о назначении всех этих файлов и папок.

addon.xml


addon.xml — служебный файл плагина, из которого встроенный каталогизатор плагинов XBMC берет всю необходимую информацию. Это единственный обязательный файл плагина, и некоторые типы плагинов (скреперы, репозитории и т. п.) могут вообще не содержать программного кода и других служебных файлов.

Содержимое файла:
<?xml version="1.0" encoding="UTF-8"?>
<!-- Основная информация -->
<addon id="plugin.video.cnet"
version="0.0.1"
name="CNET Video Podcasts"
provider-name="Roman_V_M">
<!-- Зависимости -->
<requires>
  <import addon="xbmc.python" version="2.1"/>
</requires>
<!-- Тип (назначение) плагина -->
<extension point="xbmc.python.pluginsource" library="default.py">
  <provides>video</provides>
</extension>
<!-- Описание -->
<extension point="xbmc.addon.metadata"> 
  <summary lang="en">CNET Video Podcasts</summary>
  <summary lang="ru">Видео-подкасты CNET</summary>
  <summary lang="uk">Відео-подкасти CNET</summary>
  <description lang="en">Addon for watching CNET video podcasts from XBMC.</description>
  <description lang="ru">Дополнение для просмотра видео-подкастов CNET из XBMC.</description>
  <description lang="uk">Надбудова для прегляду відео-подкастів CNET з XBMC.</description>
  <platform>all</platform> 
</extension>
</addon>


Теперь подробнее о содержимом. Для отображения номеров строк откройте файл в текстовом редакторе, поддерживающем такую возможность.

Строки 3—6 содержат основную информацию о плагине: уникальный идентификатор, совпадающий с именем папки плагина; версия; название, отображаемое в интерфейсе XBMC; автор(-ы) плагина.

Строки 8—10 содержат информацию о внешних зависимостях — других компонентах, необходимых для работы плагина. Как видно, данный плагин требует только XBMC Python API версии 2.1. Такая версия поддерживается в XBMC 13.x (Gotham).
Если для работы вашего плагина требуется какой-либо другой плагин, указываем его здесь в таком же тэге. Например, если для вашего плагина зачем-то требуется плагин Youtube, в раздел нужно добавить следующий тэг:
<import addon="plugin.video.youtube" version="4.0.0"/>

Теоретически, отсутствующие зависимости должны установиться автоматически при установке плагина.

Строки 12—14 содержат информацию о типе плагина. extension point="xbmc.python.pluginsource" означает, что это плагин-источник контента, library="default.py" указывает на стартовый скрипт плагина, а video означает, что это видео-плагин. Если плагин предоставляет доступ к разному контенту, в тэге можно указать типы доступного контента через пробел. Возможные значения: video, music и image. Эти значения определяют, в каком разделе будет отображаться этот плагин: «Видео», «Музыка» или «Фото». Плагин для доступа к разным видам контента может отображаться в нескольких разделах.
Как уже было сказано, «xbmc.python.pluginsource» означает, что это плагин-источник контента. Т. е., используя терминологию разработчиков XBMC, это и есть настоящий плагин, в отличие от скриптов и прочих типов аддонов. Основное различие между плагинами-источниками и плагинами-скриптами (программными плагинами) заключается в том, что плагин-источник представляет пользователю мультимедийное содержание в виде многоуровневых списков (каталогов), содержащих папки (виртуальные и/или реальные) и файлы (локальные и/или в сети), в то время как скрипт может иметь произвольный интерфейс (в пределах возможностей API) или не иметь его вовсе. В случае программного скрипта параметр в двойных кавычках имел бы вид «xbmc.python.script» и в тэге
 стояло бы executable, т. е.:
<extension point="xbmc.python.script" library="default.py"> <provides>executable</provides> </extension>

Подобные плагины отображаются в разделе «Программы». Плагины-скрипты также могут использоваться и в подразделах «Видео», «Музыка» или «Фото» (video, music или image в тэге
), но в этом случае интерфейс такого плагина-скрипта нужно целиком создавать самому, поскольку он не получает runtime-идентификатор (об этом ниже).

Полный перечень возможных типов плагинов можно найти здесь.

Строки 16—22 содержат полное и краткое описание плагина на разных языках. Эти описания отображаются в интерфейсе XBMC. В строке 23 указано, что плагин предназначен для всех платформ.

icon.png


icon.png — служебный файл со значком (иконкой) плагина, которая отображается в интерфейсе XBMC. Рекомендованный формат — PNG 256x256 пикселей без прозрачного фона или с неширокой прозрачной границей по краям.

fanart.jpg


fanart.jpg — служебный файл с обоями (фанартом), которые отображаются на заднем фоне интерфейса при выборе плагина. Рекомендованный формат — JPG 1280x720 пикселей. Внимание: эти обои отображаются только в интерфейсе выбора плагинов (дополнений). Обои, отображаемые «внутри» плагина, можно/нужно задавать отдельно.

changelog.txt


Служебный файл changelog.txt содержит список изменений в разных версиях плагина.

License.txt


Файл License.txt содержит текст лицензии плагина. Это не служебный файл, но рекомендуется его включать в состав плагина. Кроме того, этот файл является обязательным, если вы хотите добавить свой плагин в официальный репозиторий XBMC.

default.py


Файл default.py — стартовый скрипт плагина. Раньше имя «default.py» было обязательным, но теперь стартовый скрипт может иметь любое имя (указывается в параметре library= первого тэга extension), и сейчас имя default.py, — скорее, дань традиции.
Код плагина написан с соблюдением стайл-гайда Питона PEP 8, насколько это возможно, и в нем нет любимых другими плагинописателями переменных с двойными __подчеркиваниями__, которые зарезервированы для внутренних переменных Питона.

Содержимое файла:
# -*- coding: utf-8 -*- # Name: plugin.video.cnet # Licence: GPL v.3: http://www.gnu.org/copyleft/gpl.html # Импортируем нужные модули стандартной библиотеки import sys, os, urllib2, socket, xml.dom.minidom # Импортируем нужные модули XBMC import xbmc, xbmcplugin, xbmcaddon, xbmcgui # Определяем параметры плагина _ADDON_NAME = 'plugin.video.cnet' _addon = xbmcaddon.Addon(id=_ADDON_NAME) _addon_id = int(sys.argv[1]) _addon_url = sys.argv[0] _addon_path = _addon.getAddonInfo('path').decode('utf-8') # Импортируем собственный модуль sys.path.append(os.path.join(_addon_path, 'resources', 'lib')) import feeds # Вспомогательная функция для отображения строк интерфейса def _string(string_id): return _addon.getLocalizedString(string_id).encode('utf-8') # Парсер URL-encoded параметров вызова плагина def get_feed_name(): paramstring = sys.argv[2] if paramstring: feed_name = feeds.NAMES[paramstring.replace('?feed=', '')] else: feed_name = '' return feed_name # Парасер RSS-ленты def rss_parser(url): listing = [] try: HEADER = {'User-Agent' : 'Mozilla/5.0 (Windows NT 6.1; rv:18.0) Gecko/20100101 Firefox/20.0'} request = urllib2.Request(url, None, HEADER) rss = xml.dom.minidom.parse(urllib2.urlopen(request, None, 3)) except urllib2.URLError, socket.timeout: pass else: titles = rss.getElementsByTagName('title') links = rss.getElementsByTagName('link') for title, link in zip(titles[2:], links[2:]): title = title.toxml().replace('<title><![CDATA[', '').replace(']]></title>', '') link = link.toxml().replace('<link>', '').replace('</link>', '') listing.append([title, link]) return listing # Составляем список лент с подкастами (виртуальных папок). def feed_list(addon_id, addon_url, fanart, thumbpath, feeds): names = dict.keys(feeds) for name in names: thumb = os.path.join(thumbpath, feeds[name]['thumb']) # Создаем элемент списка. list_item = xbmcgui.ListItem(name, thumbnailImage=thumb) # Определяем доп. свойства. list_item.setProperty('fanart_image', fanart) # URL, который передается в качестве параметра рекурсивного вызова плагина. url = addon_url + '?feed=' + name.replace(' ', '').replace('*', '') # Добавляем элемент в к списку. isFolder=True означает, что это папка (виртуальная). xbmcplugin.addDirectoryItem(addon_id, url, list_item, isFolder=True) # Сортируем список -- по алфавиту, игнорировать артикли. xbmcplugin.addSortMethod(addon_id, xbmcplugin.SORT_METHOD_LABEL_IGNORE_THE) # Конец списка xbmcplugin.endOfDirectory(addon_id) # Переключаемся на нужный вид. switch_view() # Переключаемся на нужный вид в зависимости от текущего скина. def switch_view(): skin_used = xbmc.getSkinDir() if skin_used == 'skin.confluence': xbmc.executebuiltin('Container.SetViewMode(500)') # Вид "Эскизы". elif skin_used == 'skin.aeon.nox': xbmc.executebuiltin('Container.SetViewMode(512)') # Вид "Инфо-стена" # Составляем список подкастов. def podcast_list(addon_id, fanart, thumb, listing): for item in listing: # Создаем элемент списка. list_item = xbmcgui.ListItem(item[0], thumbnailImage=thumb) # Определяем доп. свойства. list_item.setProperty('fanart_image', fanart) # Добавляем элемент к списку. isFolder=False означает, что это файл для проигрывания. xbmcplugin.addDirectoryItem(addon_id, item[1], list_item, isFolder=False) # Конец списка. xbmcplugin.endOfDirectory(addon_id) def main(): # Пути к картинкам. thumbpath = os.path.join(_addon_path, 'resources', 'thumbnails') fanart = os.path.join(_addon_path, 'fanart.jpg') # Получаем имя нужной ленты из параметра вызова плагина. feed = get_feed_name() # Если плагин запущен прямо из XBMC (не рекурсивно), то имя ленты будет пустое. if feed: # Получаем нужное качество подкастов из настроек плагина. quality = _addon.getSetting('quality') # Получаем список подкастов. listing = rss_parser(url=feeds.FEEDS[feed][quality]) if listing: # Пишем в лог. xbmc.log('%s: Started - Opening podcasts List' % _ADDON_NAME, xbmc.LOGNOTICE) thumb = os.path.join(thumbpath, feeds.FEEDS[feed]['thumb']) # Выводим список подкастов. podcast_list(addon_id=_addon_id, fanart=fanart, thumb=thumb, listing=listing) else: # Если не удалось открыть ленту (ошибка сети), пишем об этом в лог xbmc.log('%s: Failed to retrieve %s feed data!' % (_ADDON_NAME, feed), xbmc.LOGERROR) # и выводим сообщение об ошибке в интерфейсе XBMC. xbmc.executebuiltin('Notification(%s,%s)' % (_string(100501), _string(100502))) else: # Если плагин запущен из XBMC (не рекурсивно), то сначала пишем в лог, xbmc.log('%s: Started - Opening feeds List' % _ADDON_NAME, xbmc.LOGNOTICE) # а затем выводим список лент. feed_list(addon_id=_addon_id, addon_url=_addon_url, fanart=fanart, thumbpath=thumbpath, feeds=feeds.FEEDS) if __name__ == '__main__': main()

Далее подробный разбор. Очевидные вещи пропускаю.

Строки 11—15 — определение параметров плагина. В большинстве плагинов используются переменные с двойными подчеркиваниями, но мне они эстетически не нравятся, и, к тому же, их не рекомендует PEP 8.

Построчный разбор:

11: задаем имя плагина. Имя плагина должно совпадать с именем, указанным в addon.xml, и именем папки (в случае несовпадения плагин может не работать).

12: создаем экземпляр класса Addon для доступа к настройкам плагина.

13: получаем runtime-идентификатор плагина (handle). Идентификатор — это целое число, которое передается как 2-й аргумент при вызове плагина из XBMC. Данный идентификатор используется как один из аргументов функции формирования списков контента addDirectoryItem(). Здесь стоит подробнее остановиться на параметрах вызова плагина. Как было сказано выше, отличие от плагинов-источников контента и плагинов-скриптов заключается в том, что в плагины-источники представляют собой списки (каталоги) контента, в то время как скрипты могут делать всё что угодно в пределах возможностей языка Питон и XBMC API. Дополнительное отличие заключается в параметрах вызова, передаваемых плагину. При вызове из интерфейса XBMC или рекурсивном вызове из самого себя (об этом ниже) плагин-источник получает 3 параметра в списке sys.argv: свой виртуальный URL, runtime-идентификатор (строка, содержащая целое число), и дополнительные параметры вызова в виде URL-encoded последовательности. Плагин-скрипт, в свою очередь, не получает никаких параметров, и для него sys.argv представляет собой пустой список. Поэтому функции и методы, требующие runtime-идентификатор плагина в качестве аргумента, например addDirectoryItem(), в плагинах-скриптах работать не будут. О виртуальном URL и дополнительных параметрах вызова ниже.

14: получаем виртуальный URL плагина. Виртуальный URL представляет собой адрес в виде ''plugin://'' + (имя плагина) + ''/''. Виртуальный URL нашего плагина будет иметь вид: "plugin://plugin.video.cnet/". Этот URL используется для формирования виртуальных адресов виртуальных папок (прошу прощения за тавтологию) в каталоге контента, формируемого скриптом. Это значит, что, встретив подобный адрес, XBMC будет знать, что для открытия (фактически — формирования) соответствующего подраздела списка ему нужно запустить указанный скрипт. Здесь, я чувствую, нужны дополнительные пояснения.
Виртуальный каталог (список), формируемый плагином, во многом похож на папки и файлы на диске. Добавив диск, локальную или сетевую папку в раздел «Видео», «Музыка» или «Фото» XBMC при помощи меню «Добавить источник», мы можем зайти в этот источник, перемещаться по его папкам и подпапкам вверх и вниз, проигрывать медиафайлы. Виртуальный каталог создает аналогичную структуру папок и файлов для содержимого некоего веб-сайта с контентом, где виртуальные папки и подпапки — разделы и подразделы сайта, а файлы — реальные мультимедийные файлы, размещенные на этом сайте. Соответственно, задача парсера сайта (бизнес-логики плагина) — представить сложную структуру выбранного сайта в виде иерархического каталога виртуальных папок и файлов в интерфейсе XBMC. Я употребляю слово «виртуальный» потому, что эти файлы и папки не связанны с реальными файлами и папками на дисках (локальных или сетевых) пользователя. Хотя ничто не мешает сделать плагин для доступа к локальному контенту. Функция addDirectoryItem() принимает и нормальные пути к файлам и папкам, но об этом ниже. Продолжим разбор кода плагина.

15: метод getAddonInfo('path') возвращает путь к папке плагина, который затем можно использовать, например, для формирования пути импорта внешних модулей, доступа к файлам и прочего. Однако этот метод имеет большие подводные грабли: под Windows, если путь плагину содержит русские буквы (или любые другие символы, отличные от ASCII), эти буквы превращаются в мусор, и при попытке использовать такой путь плагин завершает работу с исключением (exeption). Чаще всего это происходит, если имя пользователя Windows содержит русские буквы. Для нормализации пути и служит дополнительный метод decode('utf-8'). К сожалению, плагинописатели часто игнорируют этот момент, что приводит к проблемам с запуском плагинов у русскоязычных пользователей Windows.
Использование метода getAddonInfo('path') — единственный правильный способ (по версии разработчиков XBMC) получения пути к плагину. Использовать os.path.dirname(__file__) настоятельно не рекомендуется, а os.getcwd() и вовсе запрещается. Последнее, в общем, справедливо и для Питона вообще, безотносительно XBMC.

18—19: импортируем внешний модуль feeds из подпапки /resources/lib. Модуль содержит список подкастов сайта cnet.com, их URL и файлов логотипов в виде питоновского словаря (dictionary). Правильно, конечно, было бы парсить соответствующую страничку — www.cnet.com/podcasts — для получения актуального списка подкастов, но здесь моя задача — продемонстрировать, как правильно импортировать внешние модули, и фиксированный список в виде модуля вполне сойдет.

22—23: вспомогательная функция для отображения строк интерфейса. Метод .getLocalizedString() возвращает строку интерфейса с указанным номером на текущем языке, но для правильного отображения русских символов требуется привести строку в UTF-8. В других плагинах подобные функции, как правило, не используются, но мне кажется, что так удобнее и нагляднее.

26—32: функция-парсер параметров вызова плагина в виде URL-encoded строки. Подобные функции присутствуют почти во всех плагинах-источниках контента, имеющих многоуровневую структуру. Нужна эта функция потому, что многоуровневые списки контента в плагинах XBMC формируются путем рекурсивного вызова плагином самого себя с передачей дополнительных параметров в виде URL-encoded строки. При этом ссылка для рекурсивного вызова формируется из виртуального URL плагина в специальной нотации (см. выше) и URL-encoded строки с параметрами.
Рассмотрим абстрактный пример. Предположим, у нас есть плагин plugin.video.acme, который предоставляет доступ к некоему сайту с фильмами, сериалами и прочим, причем в каждом разделе имеются подразделы с жанрами. Тогда ссылка для открытия подраздела «Action» раздела «Movies» будет иметь вид plugin://plugin.video.acme/?=Movies&=Action. Эта ссылка используется в функции addDirectoryItem как ссылка на виртуальную папку (подробнее об этом ниже). Соответственно, при открытии этой ссылки будет рекурсивно запущен дочерний экземпляр плагина plugin.video.acme, и ему в sys.argv[2] будет передана строка «?=Movies&=Action». В свою очередь, плагин должен уметь парсить эту строку с параметрами, чтобы определить, как он был вызван: прямо из XBMC или рекурсивно. При вызове из XBMC нужно представить некий список верхнего уровня (например, список разделов некоего сайта), а при рекурсивном вызове, соответственно, нужно открыть требуемый вложенный список (например, список фильмов в подразделе «Action» раздела «Movies»). Для этого и существует парсер параметров вызова плагина.
Примечание: на самом деле плагин с параметрами может быть вызван не только рекурсивно, но и из другого плагина. При этом используется тот же синтаксис с виртуальным URL, начинающимся с plugin://.
Внимание: если при рекурсивном вызове плагин не будет формировать вложенный список при помощи xbmcplugin.addDirectoryItem, а будет выполнять какие-либо другие операции, используйте isFolder=False. При isFolder=True XBMC всегда ожидает вложенный список, и отсутствие такового приводит к разным глюкам.

35—49: парсер RSS-ленты со списком подкастов выбранного раздела. Функция представляет собой примитивный парсер XML и иллюстрирует бизнес-логику плагина. Вместо нее могут быть использованы сколь угодно сложные парсеры, заточенные для доступа к разделам какого-либо сайта и получения прямых ссылок на размещенные на нем мультимедийные материалы (видео, музыку, фото).

52—64: функция формирования списка групп подкастов сайта cnet.com — по сути, списка виртуальных папок. Рассмотрим ее подробно.

57: здесь для каждого элемента списка мы создаем экземпляр класса xbmcgui.ListItem. Этот класс представляет собой контейнер для хранения свойств некоего элемента списка, представляющего мультимедийный контент. Элемент списка должен иметь, как минимум, текстовую подпись, но для правильного оформления для него желательно указать картинку (эскиз) и обои (фанарт).

59: добавляем фанарт к элементу списка. Как видно, для всех элементов списка используется фанарт плагина, но ничто не мешает назначить каждому элементу собственный фанарт.

61: составляем ссылку для рекурсивного вызова плагина. В качестве аргументов вызова выступают очищенные от лишних символов названия групп подкастов. Соответственно, при выборе соответствующей группы запущенный рекурсивно плагин сформирует список подкастов этой группы.

63: функция добавления элемента в список контента. Именно здесь нам нужен идентификатор плагина, полученный из sys.argv[1]. Напомнию, что этот параметр передается только плагинам-источникам контента. Соответственно, во всех других видах плагинов функцию addDirectoryItem() использовать нельзя. Параметр isFolder=True говорит XBMC, что этот элемент списка — виртуальная папка, т. е. при выборе этого элемента нужно отобразить некий вложенный список, а не открыть эту ссылку в плеере. В параметре url мы добавляем ссылки на виртуальные папки, формируемые плагином, но здесь также можно использовать пути к реальным папкам с мультимедийными файлами на локальном или сетевом диске.

65: добавляем метод сортировки. Дело в том, что по умолчанию элементы списка отображаются в порядке добавления, что не всегда желательно.

67: говорим XBMC, что формирование списка закончено.

69: вызываем функцию переключения вида (представления). Для списков контента, формируемых плагинами, используется стандартный вид текущего скина (как правило, простой список), и если мы хотим для своего плагина использовать вид, отличный от стандартного, то логику придется писать самому, причем для каждого скина индивидуально. Параметр Container.SetViewMode(ХХХ) как раз и представляет собой числовой код нужного вида скина, который можно найти в файлах MyVideoNav.xml, MyMusicNav.xml и MyPics.xml соответствующего скина для видео, музыки и фото соответственно.
К сожалению, переключение вида редко используется в плагинах. Один из немногих реальных примеров использования — плагин Youtube.

72—77: собственно функция переключения вида. Мы получаем текущий скин и переключаемся на нужный вид. Код 500 соответствует виду «Эскизы» стандартного скина Confluence, а код 512 — виду «Инфо-стена» скина «Aeon-Nox». Для всех остальных скинов будет использоваться их стандартный вид.

80—89: функция составления списка подкастов, содержащего ссылки на файлы для проигрывания. Здесь всё почти так же, как в функции feed_list(), за исключением того, что URL элементов списка представляют собой ссылки непосредственно на видеофайлы и параметр isFolder для элемента списка равен False, т. е. это не папка (виртуальная или реальная) а медиафайл, который передается плееру.
Примечание: элемент списка с isFolder=False также может содержать ссылку на рекурсивный вызов плагина, но при таком вызове плагин может делать всё что угодно, кроме формирования списка при помощи xbmcplugin.addDirectoryItem. Например, при выборе такой ссылки может открываться диалог ввода текста для поискового запроса (подробнее о диалогах в следующих статьях). Но xbmcplugin.addDirectoryItem использовать нельзя.

92—119: главная функция плагина. Думаю, здесь всё понятно из комментариев. Я бы только хотел остановиться на паре строк.

101: получаем нужное качество подкастов, выбранное в панели настроек плагина. Любые настройки всегда возвращаются в виде строк, и их нужно приводить к нужному типу, если необходимо.

114: выводим сообщение об ошибке в интерфейсе XBMC. Здесь как раз и используется наша вспомогательная функция _string(), которая извлекает нужные строки из языковых файлов XBMC. Подробнее о языковых файлах ниже.

\resources


Папка \resources — служебная, и в ней содержатся различные дополнительные файлы плагина.

settings.xml


settings.xml — служебный файл, в котором на специальном языке, основанном на XML, описывается панель настроек плагина, которая вызывается при выборе подменю «Настройки». Благодаря такому подходу панель настроек плагина всегда имеет «родной» вид в любом скине.

Содержимое файла:
<?xml version="1.0" encoding="UTF-8"?>
<settings>
  <category label="128">
    <setting id="quality" type="labelenum" label="100500" values="HD|SD" default="HD" />
  </category>
</settings>


В данном случае панель настроек имеет один орган управления — спинбокс (labelenum) для выбора качества показываемых подкастов (HD или SD). Параметры label= содержат текстовые описания элементов панели настроек, и в них может быть как простой текст, так и номера строк из языковых файлов. Для доступа к конкретным настройкам используются текстовые идентификаторы в параметрах id="" (см. строку 101 default.py).
Подробное описание синтаксиса файла settings.xml и доступных элементов управления можно найти в руководстве «XBMC Addon Developers Guide» (английский язык). Это руководство по написанию плагинов несколько устарело, но всё равно содержит много полезной информации, в том числе и об организации панели настроек плагина.
Сами настройки плагинов хранятся в виде файлов XML в подпапке \userdata\addon_data\ папки с пользовательскими настройками XBMC. В Windows это, как правило, %AppData%\XBMC, в Linux — $HOME/.xbmc.

\language


Подпапка \language является служебной. В ней, в подпапках нижнего уровня, имеющих имена, соответствующие английским названиям разных языков, лежат языковые файлы плагина strings.po. В нашем плагине имеются подпапки \English, \Russian и \Ukrainian, т. е. плагин имеет интерфейс на английском, русском и украинском языках. Плагин, имеющий свой интерфейс, должен содержать как минимум папку \English с соответствующим языковым файлом. Впрочем, в некоторых случаях можно обойтись и без своих языковых файлов, но об этом чуть ниже.

Пример русского языкового файла strings.po:
# XBMC Media Center language file
msgid ""
msgstr ""

"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: ru\n"

msgctxt "#100500"
msgid "Quality:"
msgstr "Качество:"

msgctxt "#100501"
msgid "Access error!"
msgstr "Ошибка доступа!"

msgctxt "#100502"
msgid "Failed to retrieve the feed data."
msgstr "Не удалось получить данные с ленты."


Как видно, файл имеет формат .po GNU Gettext. Однако XBMC не использует настоящий Gettext: двуязычные файлы не компилируются в .mo, и для идентификации отдельных строк интерфейса используются номера, указываемые в параметрах msgctxt. Связано это с историческими причинами: ранее XBMC использовал собственный формат языковых файлов на базе XML, однако с переходом на онлайновую систему перевода www.transifex.net было решено перейти на более распространенный и поддерживаемый формат локализации. По-видимому, такой промежуточный вариант с использованием текстовых файлов .po было внедрить проще, чем полностью перейти на Gettext.
Как уже было сказано, строки интерфейса извлекаются методом getLocalizedString() по номеру в msgctxt. При этом используется сквозная нумерация: основной языковый файл XBMC + языковый файл плагина. Желательно, чтобы номера строк основного языкового файла и плагина не повторялись. В принципе, при совпадении номеров в обоих файлах приоритет имеет языковый файл плагина, но, вполне возможно, это является багом, и полагаться на такое поведение нельзя. Поэтому выбирайте для строк интерфейса плагина собственные номера.
Рекомендуемые диапазоны номеров языковых строк можно найти здесь, но эти рекомендации не являются жесткими. Например, в данном примере они не соблюдаются.
Номера строк языковых файлов используются как в коде плагина при создании элементов интерфейса и выводе сообщений, так и в файле с описанием панели настроек settings.xml для отображения подписей к элементам управления.
В принципе, если все нужные строки интерфейса можно найти в основном языковом файле XBMC, то в самом плагине языковые файлы можно не создавать. В таком случае в соответствующих местах будут использоваться номера строк из основного языкового файла.

\lib

В подпапке \lib содержится наш дополнительный питоновский модуль, импортируемый в плагин. Подапка \lib не служебная. Она может называться как угодно, и сами импортируемые модули могут располагаться где угодно: рядом с основным скриптом плагина, в подпапке первого уровня и т. п. Главное, чтобы они были доступны для импорта. Просто, как правило, удобнее, если все дополнительные файлы плагина, как служебные, так и нет, размещались в одном месте.

\thumbnails


В подпапке \thumbnails лежат картинки с логотипами подкастов cnet.com. Как и \lib, эта подпапка не служебная, и картинки могут лежать в любом доступном месте по вашему выбору.

Отладка


Несколько слов об отладке плагинов. При отсутствии консоли для вывода сообщений используется лог XBMC. В него автоматически записываются все непойманные исключения (при этом в интерфейсе XBMC отображается сообщение "Ошибка скрипта ХХХ"). Также в лог можно выводить дополнительную информацию при помощи оператора print и функции xbmc.log().
Лог находится в папке с пользовательскими настройками XBMC.
Кроме того, XBMC поддерживает удаленную отладку при помощи Eclipse + PyDev. Подробнее об этом можно прочитать здесь.

Заключение


На этом разбор структуры относительно простого плагина XBMC завершен. Несмотря на свою простоту, плагин имеет почти все элементы, присущие более сложным плагинам. Желающие могут использовать этот плагин в качестве «скелета» для собственного плагина.
В следующий раз, если будет время и желание, я расскажу о том, как сделать плагин с собственным интерфейсом.

Источники информации


Темы, посвященные разработке плагинов, в официальной Wiki XBMC: http://wiki.xbmc.org/index.php?title=Category:Addon_Development
Документ «XBMC Addon Developers Guide»: yadi.sk/d/NvfFuXYw92paL
Краткая справка по модулям XBMC Python API: mirrors.xbmc.org/docs/python-docs

P. S.


Post factum поправил некоторую информацию. Прошу строго не судить за возможные неточности. Написание плагинов XBMC плохо документировано даже на английском, а на русском информации вообще крохи. Многие вещи приходилось находить по крупицам либо осваивать методом тыка.

Продолжение


Пишем плагин для XBMC с собственным интерфейсом: часть I — теория и простейший пример.
Tags:
Hubs:
+22
Comments 10
Comments Comments 10

Articles