User Tools

Site Tools


doc:appunti:software:kodi_addon

This is an old revision of the document!


How to write a Kodi Addon

Here are some notes I wrote while writing the Kodi plugin script.picture.photo-frame.

What I needed was a slideshow plugin witout fancy transition effects (no transition effects at all!), but with the ability to select only some pictures from a directory and the ability to crop a selected portion of the images, without the need to actually modify the original ones. The need arose because I shot my photos using a 4:3 digital camera, but I wish to show them on a 16:9 TV screen. Black borders are unacceptable to me. Automatic zoom/crop is not an option because I want the control on it; also keeping a copy of each original photo is not an acceptable option.

I also opened this forum thread about adding this functionality to Kodi.

The idea was to start from a playlist file, which contains the list of images to show, along with geometry data to crop each image. Something like this:

IMG_6602.JPG|4000x2250+0+332
IMG_6605.JPG|2971x1671+796+628
IMG_6606.JPG|4000x2250+0+442
IMG_6610.JPG|3810x2143+90+387

My starting point was the Hello World Addon, but I had to solve several problems, so here are my notes.

Add-on types

First of all we need to understand the differences between a script add-on and a plugin add-on. As explained in the About Add-ons page:

Unlike Scripts (which can basically perform any action), Plugins do not really provide new functionality to Kodi, instead what they do is provide a directory listing (for instance, like your movie library) to Kodi. Another difference is that scripts can create their own gui (skin) while plugins can't. Plugin listings are presented in the current skin.

So it is clear that I want a script add-on, because I want full control on how each picture is displayed, I don't want to rely on the standard Kodi's picture viewer.

Kodi add-ons are mainly developed in Python language. Kodi provides its own version of Python, so it does not need to rely on the underlaying operating system. Kodi 17 comes with Python 2.7, Kodi version 19 will include Python 3.

addon.xml and addon.py

The add-on is composed mainly by two files: addon.xml and addon.py. The first one defines how the add-on is integrated in Kodi, so it is read on Kodi startup. The second file is actually the Python program run on add-on activation. You can modifiy it while Kodi is running and start it again to see the effects.

There is a dedicated page on the wiki about addon.xml.

Let's see some parts of the addon.xml file:

<addon>
    <extension point="xbmc.python.script" library="addon.py">
        <provides>executable</provides>
    </extension>
</addon>

To be considered a script add-on, we have declared it at the extension point xbmc.python.script. The library attribute defines the name of the Python script to be executed, addon.py in our case.

The addon.py in this study case is used also to create the user interface. In fact we use the Python classes xbmcgui.Window() and xbmcgui.ControlImage() to creat all we need. Another approach is to use the xbmcgui.WindowXML() class, where you provide an XML file where the user interface is defined.

Install add-on from a local zip file

The target directory for add-on installation is /home/kodi/.kodi/addons/, where each add-on have its directory. But it is not sufficient to unzip the archive there, you have to use the add-on installation procedure of Kodi. The add-on must be packed into a zip file, containing the add-on directory at the top level. Then use the following:

Main Menu ⇒ Add-ons ⇒ Search (Add-on browser) ⇒ Cancel ⇒ Install from zip file

Disable the Screen Saver

Our add-on script will perform a slideshow, so we have to disable the Kodi screensaver before starting it. We also need to re-enable the same screensaver before exiting the add-on code. We will use Kodi RPC to get the screensaver status and to set it. Passing an empty string means disabling the screensaver.

Here it is an example using the xbmc.executeJSONRPC() function and json:

import json
import xbmcaddon
import xbmcgui
 
def getScreensaver():
    command = {
        'jsonrpc': '2.0', 'id': 0, 'method': 'Settings.getSettingValue',
        'params': { 'setting': 'screensaver.mode' }
    }
    json_rpc = json.loads(xbmc.executeJSONRPC(json.dumps(command)))
    try:
        result = json_rpc['result']['value']
    except:
        result = None
    return result
 
def setScreensaver(mode):
    command = {
        'jsonrpc': '2.0', 'id': 0, 'method': 'Settings.setSettingValue',
        'params': { 'setting': 'screensaver.mode', 'value': mode}
    }
    json_rpc = json.loads(xbmc.executeJSONRPC(json.dumps(command)))
    try:
        result = json_rpc['result']
    except:
        result = False
    return result
 
 
saved_screensaver = getScreensaver()
setScreensaver('')
# ...
setScreensaver(saved_screensaver)

Run the Script using the Context Menu

The Context Menu is activated in Kodi by pressing the C key or the right mouse button. It is possibile to add an item to the Context Menu to launch our add-on. Just add an <extension> section into the addon.xml file:

<addon>
    <extension point="kodi.context.item">
        <menu id="kodi.core.main">
            <item library="addon.py">
                <label>View in Photo Frame</label>
                <visible>ListItem.IsFolder + Container.Content(images)</visible>
            </item>
        </menu>
    </extension>
</addon>

The <visible> tag allows to specify when the menu item will be visible. Here we specified two conditions, so that the menu will be available on folders and over containers containing images, which specifically means an m3u playlists. The two conditions should be both true (the plus sign means a logical AND), so it seems that a playlist file is considered as with IsFolder = True. See the wiki about Conditional visibility.

Inside the add-on Python code you can retrieve the item that was active when the Context Menu was selected:

contextmenu_item = xbmc.getInfoLabel('ListItem.FilenameAndPath')

Using settings.xml

Adding a settings page to an add-on is as simple as writing an XML file. Create the file resources/settings.xml and follow the syntax described into the Add-on settings page. Once deployed, you can access the settings page from Main menuAdd-onsAdd-on Context MenuSettings. Kodi take cares of displaying the settings page and storing the user preferences.

Here it is an example of settings.xml which allow to set two values, identified by the id attribute:

<settings>
    <category label="Window Size">
        <setting label="Width"  type="labelenum" id="WindowWidth"  values="720|1280|1920"    option="int" default="1280"/>
        <setting label="Height" type="labelenum" id="WindowHeight" values="480|576|720|1080" option="int" default="720"/>
    </category>
</settings>

From the Python add-on code, it will be possible to access the setting values using the getSetting() function. E.g.:

img_w = int(ADDON.getSetting('WindowWidth'))

Localization

Localization of a Kodi add-on takes place in several files. First of all we can localize the Information item of the Context Menu, by just adding the relevant part into the addon.xml file:

<addon>
    <extension point="xbmc.addon.metadata">
        <summary lang="en">Photo Frame Slideshow</summary>
        <summary lang="it">Presentazione Photo Frame</summary>
    </extension>
</addon>

Besides the <summary> tag, you can localize also <description>, etc. The supported language codes are the alpha-2 ISO-639 codes (two letters), and they are listed into the ISO-639:1988 Kodi Wiki page.

Then you will need to localize text strings used into Python code and into XML files (e.g. addon.xml, settings.xml, etc.). For each language you intend to support, you have to prepare a file:

  • resources/language/resource.language.en_gb/strings.po
  • resources/language/resource.language.it_it/strings.po

NOTICE: Kodi documentation says to use here the alpha-4 ISO-639 codes (four letters) as directory name suffix.

Every file begins with a preamble:

# Kodi Media Center language file
# Addon Name: Photo Frame
# Addon id: script.picture.photo-frame
# Addon Provider: niccolo@rigacci.org
msgid ""
msgstr ""
"Project-Id-Version: XBMC-Addons\n"
"Report-Msgid-Bugs-To: niccolo@rigacci.org\n"
"POT-Creation-Date: 2019-09-11 15:40+0200\n"
"PO-Revision-Date: 2019-09-12 11:56+0200\n"
"Last-Translator: Niccolo Rigacci <niccolo@rigacci.org>\n"
"Language-Team: LANGUAGE\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: it_IT\n"
"Plural-Forms: nplurals=2; plural=(n != 1)\n"

then the file contains every string to be localized along with its translation. Here it is an excerpt of the Italian file:

msgctxt "#32001"
msgid "View in Photo Frame"
msgstr "Presentazione Photo Frame"

msgctxt "#32007"
msgid "Auto Play"
msgstr "Avanzamento automatico"

msgctxt "#32013"
msgid "Bad geometry for image \"%s\""
msgstr "Geometria errata per l'immagine \"%s\""

Notice that msgid is the original, untranslated English text. The msgstr is the localized version and msgctxt is an unique ID for each string.

If a localization is missing, the british english version will be used by default, so the file for the en_gb regional must exists. That file contains only the original text, not the translation:

msgctxt "#32001"
msgid "View in Photo Frame"
msgstr ""

msgctxt "#32007"
msgid "Auto Play"
msgstr ""

msgctxt "#32013"
msgid "Bad geometry for image \"%s\""
msgstr ""

Beware of this:

  • For scripts add-ons, according to the page Language support, we should use the range 32000 thru 32999 for string IDs.
  • If a string contains a double quote, it must be escaped with a backslash. Notice that C syntax is applied, so backslash is used as the escape character.
  • For better reading a string can be split over several lines. Just close the string with a double quote and start a new line with a double quote too. No newline characters will be added automatically, you have to include the sequence \n explicitly.
  • For more info see PO-Files.

Finally we have to change the Python code, substituting the strings with a function call and the string ID:

ADDON = xbmcaddon.Addon()
__localize__ = ADDON.getLocalizedString
 
heading = __localize__(32004)
message = __localize__(32005)
xbmcgui.Dialog().notification(heading, message, xbmcgui.NOTIFICATION_WARNING)

Where __localize__ is simply an handy name that we will use to call the xbmcaddon.Addon().getLocalizedString() function.

We can use the string IDs also in <label> tags, label attributes, etc. in XML files. Here an example from a settings.xml:

<settings>
    <category label="32016">
        <setting label="32017" type="labelenum" id="WindowWidth"  ... />
        <setting label="32018" type="labelenum" id="WindowHeight" ... />
    </category>
</settings>

Here an example from addon.xml:

<addon>
    <extension point="kodi.context.item">
        <menu id="kodi.core.main">
            <item library="addon.py">
                <label>32001</label>
            </item>
        </menu>
    </extension>
</addon>

Change Regional Settings

Regional default for Kodi is English, which means British English (en_gb). Other English variants are labeled e.g. English (US), which requires the resource.language.en_us add-on.

When you change regional settings in Kodi (Settings ⇒ …) - if the choosen set is not already installed - the system tries to download the appropriate archive. In our test case (Kodi 17.6 on the Raspberry Pi, Debian Stretch 9.3) the download URL is something like this:

http://mirrors.kodi.tv/addons/krypton/resource.language.en_us/resource.language.fr_fr-3.0.6.zip.md5

Unfortunately the above md5 file does not exists, so the installation of the requested resource fails. You have to download the zip file manually and follow the procedure to install an add-on from a zip file. The installation will create the directory /home/kodi/.kodi/addons/resource.language.fr_fr/.

Web References

doc/appunti/software/kodi_addon.1568363126.txt.gz · Last modified: 2019/09/13 10:25 by niccolo