xbmcswift2 - micro-framework for writing plugins for Kodi (XBMC)

  • Tutorial

Introduction


This, so to speak, is a “bonus” article in my series of articles about plugins for the Kodi Media Center (XBMC). First of all, it should be noted that, starting with version 14.0, the popular media center changes its name from XBMC to Kodi. You can read about the reasons for the name change on the official website and forum, and they are not important for our article. However, later in the article a new name will be used - Kodi.

Previous Articles

Detailed anatomy of a simple plug-in for XBMC
We write a plug-in for XBMC with our own interface: part I - theory and a simple example
We write a plug-in for XBMC with our own interface: part II - dialogs and decorations
We write a plug-in for XBMC with our own interface: part III - API and micro- framework

xbmcswift2


The first article - “Detailed anatomy of a simple plug-in for XBMC” - talked about the basic principles of operation of content source plug-ins, that is, plug-ins that allow you to watch videos and listen to music from various online resources. There are two of these basic principles:
  1. Each element of the virtual directory (a link to a subsection or file to play) is an object of class xbmcgui.listItem containing all the information about the element of the virtual directory. When creating a list of elements, we sequentially create xbmcgui.listItem objects , set their properties (thumbnail, fanart, link, additional information) and “feed” these elements to the xbmcplugin.addDirectoryItem function .
  2. To create multilevel directories, the plugin recursively calls itself, passing parameters in the form of URL-encoded strings through the list element sys.argv [2]. At the same time, we need to decode these parameters and call the corresponding part of the code, for example, to form a lower level directory (subsection) or play the video by reference. That is, we need to organize the routing of such recursive calls.

As a result, the plug-in developer is assigned unnecessary work that is not directly related to obtaining information, organizing content, and reproducing it.

When creating xbmcswift2, the developer was obviously inspired by the popular Python micro-frameworks for web development - Flask and Bottle. The recursive call routing mechanism is clearly borrowed from these frameworks - call paths and parameter passing are implemented through function decorators.
In addition, xbmcswift2 unifies the structure of virtual directory entries. Now a separate element is a Python dictionary with all the necessary properties in the form of key – value pairs. These elements are combined into a list, and to display the list of elements in the Kodi interface, the function “decorated” with the route decorator should return this list. Further processing of the list and decoding of dictionaries describing the elements of this list is undertaken by xbmcswift2.
In addition to simplifying the creation of content lists and routing recursive calls, xbmcswift2 offers such nice features as caching objects returned by functions and methods, as well as persistent storage for storing the state of objects between recursive calls. xbmcswift2 also allows you to debug plug-in code in the console, without using Kodi.

To illustrate the use of xbmcswift2, I’ll take the plugin from the article “Detailed anatomy of a simple plug-in for XBMC” and rewrite it under this framework. For simplicity, the new plugin will not display any messages on the screen, so there are no language files in it.

Plugin code
# -*- 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('<![CDATA[', '').replace(']]>', '')
            link = link.toxml().replace('', '').replace('', '')
            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/')
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/')
def play_podcast(url):
    # Отдаем команду Kodi воспроизвести видео по ссылке.
    plugin.set_resolved_url(url)
if __name__ == '__main__':
    # Запускаем плагин.
    plugin.run()



Now, as always, line-by-line parsing. To display line numbers, use a text editor with the appropriate function, for example Notepad ++. I miss the obvious things and what is clear from the comments.

30: the @ plugin.cached () decorator is used to cache objects returned by functions or methods. Such a decorator is recommended to "decorate" the functions that receive content from websites, so as not to create an unnecessary load on these sites. The caching time in minutes is set as a decorator parameter.
49: the @ plugin.route () decorator is used to route plugin calls. The plugin must contain at least the root route ('/').
55–61: the properties of a list item are set in the form of a fairly simple and understandable dictionary.
61: the url_for () method forms the correct path for the recursive call of the plugin so that this path can be decoded by xbmcswift2. The name of the called function as a string is used as the first parameter, and additional information is passed through named parameters. Only simple strings can be used as parameters. Accordingly, all other data types must be cast to strings. Non-ASCII characters can be transmitted in the form of URL-encoded sequences (for example, "Vasya"> "% D0% 92% D0% B0% D1% 81% D1% 8F" or base64 encoded.
65: finish () methodused to transmit additional parameters for displaying the content list (in addition to the list itself). If you do not need to return any additional parameters, you can return the list itself without using the finish () method .
69.70: call the function that forms the list of podcasts. As a parameter, we pass the number of the section in the FEEDS list (more precisely, tuple).
83: indicate that this element does not contain nested elements (in this case, the list element is a file to play). By default, this parameter is False, so it is omitted in the previous function.
89–90: here, to send the file to the Kodi main code for playback, we use the special function play_podcast () , which, in turn, calls the set_resolved_url () method. This method is a “wrapper” of xbmcswift2 around the standard Kodi Python API function xbmcplugin.setResolvedUrl () . Using xbmcplugin.setResolvedUrl () is poorly documented, but it is this method that is preferable to start playing multimedia files. Of course, you can use direct links to these files (and in the example from the article "Detailed anatomy of a simple plug-in for XBMC"a simple option with direct links was used), but when using direct links there are undesirable side effects. For example, when creating a list of files, Kodi tries to read the metadata of these files, which with a large number of list items and a slow connection leads to the list being formed for a very long time. In addition, when using direct links, auto-bookmarks and marks of the viewed are not supported. The reason for the latter is unclear (apparently, a bug.) However, in the case of using xbmcplugin.setResolvedUrl () and its analogue from xbmcswift2 - set_resolved_url ()  - these side effects are not observed.

Conclusion


The microframework xbmcswift2 is available in the official Kodi repository, and when creating a plug-in based on it, the micro-framework must be specified as a dependency in the Kodi plug-in metadata file - addon.xml. See previous articles and the official Wiki for more on this.
A ready-made demo plugin based on xbmcswift2 can be downloaded from here .

I hope the information in these articles helps you write useful plugins for Kodi. As practice shows, the most difficult task when writing a plugin is to get links to videos or music from a particular site, and organizing information and links in the plugin is much simpler.

Sources of information


Official xbmcswift2 documentation .

Also popular now: