python

xbmcswift2 — микро-фреймворк для написания плагинов к Kodi (XBMC)

  • пятница, 28 ноября 2014 г. в 02:10:30
http://habrahabr.ru/post/243249/

Вступление


Это, так сказать, «бонусная» статья в моей серии статей о плагинах к медиацентру Kodi (XBMC). Прежде всего, необходимо отметить, что, начиная с версии 14.0, популярный медиацентр меняет название с XBMC на Kodi. О причинах смены названия можно почитать на официальном сайте и форуме, и для нашей статьи они не принципиальны. Однако дальше в статье будет использоваться новое название — Kodi.

Предыдущие статьи

Подробная анатомия простого плагина для XBMC
Пишем плагин для XBMC с собственным интерфейсом: часть I — теория и простейший пример
Пишем плагин для XBMC с собственным интерфейсом: часть II — диалоги и украшателства
Пишем плагин для XBMC с собственным интерфейсом: часть III — API и микро-фреймворк

xbmcswift2


В первой статье — «Подробная анатомия простого плагина для XBMC» — было рассказано о базовых принципах работы плагинов-источников контента, т. е. плагинов, которые позволяют смотреть видео и слушать музыку с различных онлайновых ресурсов. Этих базовых принципов два:
  1. Каждый элемент виртуального каталога (ссылка на подраздел или файл для проигрывания) представляет собой объект класса xbmcgui.listItem, содержащий всю информацию об элементе виртуального каталога. При создании списка элементов мы последовательно создаем объекты xbmcgui.listItem, задаем их свойства (эскиз, фанарт, ссылка, дополнительная информация) и «скармливаем» эти элементы функции xbmcplugin.addDirectoryItem.
  2. Для создания многоуровневых каталогов плагин рекурсивно вызывает сам себя, передавая параметры в виде URL-encoded строки через элемент списка sys.argv[2]. При этом нам нужно декодировать эти параметры и вызвать соответствующую часть кода, чтобы, например, сформировать каталог нижнего уровня (подраздел) или воспроизвести видео по ссылке. Т. е. нам нужно организовать маршрутизацию таких рекурсивных вызовов.

В результате на разработчика плагина возлагается лишняя работа, не связанная непосредственно с получением информации, организацией контента и его воспроизведением.

При создании xbmcswift2 разработчик, очевидно, вдохновлялся популярными микро-фреймворками Python для веб-разработки — Flask и Bottle. Механизм маршрутизации рекурсивных вызовов явно позаимствован у этих фреймворков — пути вызовов и передача параметров реализованы через декораторы функций.
Кроме этого, в xbmcswift2 унифицирована структура элементов виртуальных каталогов. Теперь отдельный элемент представляет собой питоновский словарь со всеми необходимыми свойствами в виде пар ключ—значение. Эти элементы объединяются в список, и для отображения списка элементов в интерфейсе Kodi функция, «украшенная» декоратором маршрута, должна вернуть этот список. Дальнейшую обработку списка и декодирование словарей, описывающих элементы этого списка, берет на себя xbmcswift2.
Помимо упрощения создания списков контента и маршрутизации рекурсивных вызовов xbmcswift2 предлагает такие приятные возможности, как кэширование объектов, возвращаемых функциями и методами, а также постоянное хранилище для хранения состояния объектов между рекурсивными вызовами. xbmcswift2 также позволяет выполнять отладку кода плагина в консоли, без использования Kodi.

Чтобы проиллюстрировать использование xbmcswift2, я возьму плагин из статьи «Подробная анатомия простого плагина для XBMC» и перепишу под этот фреймворк. Для простоты новый плагин не будет выводить никаких сообщений на экран, поэтому языковые файлы в нем отсутствуют.

Код плагина
# -*- coding: utf-8 -*-
# Name:        plugin.video.cnet
# Author:      Roman V.M.
# Created:     02.02.2014
# Licence:     GPL v.3: http://www.gnu.org/copyleft/gpl.html

# Импортируем стандартные модули
import sys
import os
import urllib2
import xml.dom.minidom
# Импортируем xbmcswift2
from xbmcswift2 import Plugin

# Создаем объект plugin
plugin = Plugin()
# Получаем путь к плагину
addon_path = plugin.addon.getAddonInfo('path').decode('utf-8')
# Комбинируем путь к значкам
thumbpath = os.path.join(addon_path, 'resources', 'thumbnails')
# Комбинируем путь к фанарту
fanart = os.path.join(addon_path, 'fanart.jpg')
# Импортируем собственный модуль
sys.path.append(os.path.join(addon_path, 'resources', 'lib'))
from feeds import FEEDS


# Кэшируем объект, возвращаемый функцией (список), на 30 мин.
@plugin.cached(30)
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:
        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


# Корневой путь
@plugin.route('/')
def feed_index():
    feed_list = []
    for i in range(len(FEEDS)):
        # Прописываем свойства каждого раздела.
        # Текстовая подпись:
        feed = {'label': FEEDS[i]['label'],
                # Эскиз:
                'thumbnail': os.path.join(thumbpath, FEEDS[i]['thumb']),
                # Дополнительные свойства объекта (фонарт):
                'properties': {'fanart_image': fanart},
                # Комбинируем путь к объекту.
                'path': plugin.url_for('podcast_index', feed=str(i))}
        feed_list.append(feed)
    # Возвращаем список разделов подкастов с дополнительными свойствами:
    # метод сортировки списка и режим отображения ("Эскизы" в скине Confluence).
    return plugin.finish(feed_list, sort_methods=['label'], view_mode=500)


# Список подкастов в разделе
@plugin.route('/feeds/<feed>')
def podcast_index(feed):
    feedNo = int(feed)
    quality = plugin.addon.getSetting('quality')
    # Получаем список подкастов в данном разделе.
    podcasts = rss_parser(url=FEEDS[feedNo][quality])
    thumb = os.path.join(thumbpath, FEEDS[feedNo]['thumb'])
    podcast_list = []
    for podcast in podcasts:
        item = {'label': podcast[0],
                'thumbnail': thumb,
                'properties': {'fanart_image': fanart},
                'path': plugin.url_for('play_podcast', url=podcast[1]),
                # Указываем, что данный объект не содержит вложенных объектов (видео для воспроизведения).
                'is_playable': True}
        podcast_list.append(item)
    # Возвращаем список подкастов без дополнительных ствойств
    return podcast_list


@plugin.route('/play/<url>')
def play_podcast(url):
    # Отдаем команду Kodi воспроизвести видео по ссылке.
    plugin.set_resolved_url(url)


if __name__ == '__main__':
    # Запускаем плагин.
    plugin.run()



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

30: декоратор @plugin.cached() используется для кэширования объектов, возвращаемых функциями или методами. Таким декоратором рекомендуется «украшать» функции, получающие содержимое с веб-сайтов, чтобы не создавать излишнюю нагрузку на эти сайты. Срок кэширования в минутах задается в качестве параметра декоратора.
49: декоратор @plugin.route() используется для маршрутизации вызовов плагина. Плагин в обязательном порядке должен содержать как минимум корневой маршрут ('/').
55—61: свойства элемента списка задаются в виде достаточно простого и понятного словаря.
61: метод url_for() формирует правильный путь для рекурсивного вызова плагина, чтобы этот путь мог быть декодирован xbmcswift2. В качестве первого параметра используется имя вызываемой функции в виде строки, а дополнительная информация передается через именованные параметры. В качестве параметров можно использовать только простые строки. Соответственно, все другие типы данных должны быть приведены к строкам. Символы, отличные от ASCII, можно передавать в виде URL-encoded последовательности (например, «Вася» > «%D0%92%D0%B0%D1%81%D1%8F » или в кодировке base64.
65: метод finish() используется для передачи дополнительных параметров отображения списка контента (помимо самого списка). Если никаких дополнительных параметров возвращать не нужно, можно вернуть сам список, не используя метод finish().
69,70: вызываем функцию, которая формирует список подкастов. В качестве параметра передаем номер раздела в списке (точнее, tuple) FEEDS.
83: указываем, что этот элемент не содержит вложенных элементов (в данном случае элемент списка — файл для проигрывания). По умолчанию этот параметр равен False, поэтому в предыдущей функции он опущен.
89—90: здесь для отправки файла в основной код Kodi на проигрывание используется специальная функция play_podcast(), которая, в свою очередь, вызывает метод set_resolved_url(). Этот метод представляет собой «обертку» xbmcswift2 вокруг стандартной функции Kodi Python API xbmcplugin.setResolvedUrl(). Использование xbmcplugin.setResolvedUrl() плохо документировано, но именно такой метод является предпочтительным для запуска проигрывания мультимедийных файлов. Безусловно, можно использовать прямые ссылки на эти файлы (и в примере из статьи «Подробная анатомия простого плагина для XBMC» был использован простой вариант с прямыми ссылками), но при использовании прямых ссылок имеются нежелательные побочные эффекты. Например, при формировании списка файлов Kodi старается прочитать метаданные этих файлов, что при большом количестве элементов списка и медленном соединении приводит к тому, что список формируется очень долго. Кроме того, при использовании прямых ссылок не поддерживаются автозакладки и отметки просмотренного. Причина последнего непонятна (по всей видимости, баг.) Однако в случае использования xbmcplugin.setResolvedUrl() и его аналога из xbmcswift2 — set_resolved_url() — этих побочных эффектов не наблюдается.

Заключение


Микрофреймврок xbmcswift2 доступен в официальном репозитории Kodi, и при создании плагина на его основе микро-фреймворк нужно указать в качестве зависимости в файле метаданных плагина Kodi — addon.xml. Подробнее об этом см. в предыдущих статьях и в официальной Вики.
Готовый демонстрационный плагин на базе xbmcswift2 можно загрузить отсюда.

Надеюсь, информация из этих статей поможет вам в написании полезных плагинов для Kodi. Как показывает практика, самая сложная задача при написании плагина — это вытащить ссылки на видео или музыку с того или иного сайта, а уже организовать информацию и ссылки в плагине значительно проще.

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


Официальная документация xbmcswift2.