Sunday, June 2, 2013

Add on Settings and Localization

Introduction

We cover add on settings and localization at the same time, because the two topics are related. It is not possible to define a settings menu without localizing it. We will modify the France 24 add on that was developed earlier to support multiple stream languages, as France 24 streams live in English and Arabic in addition to French. We will give the user the choice of auto playing the France 24 stream in the selected language, or of selecting the desired language every time the add on is started.

Resources

To support settings and localization, we will need to add a resources folder to our plugin. In the resources folder we will need to create a languages folder, containing at least a folder named English and additional folders for each language supported. In our case, we will add French.
Inside each language folder, we will add a strings.xml file, and we will add a settings.xml to the resources folder. Once done, it should look like this:
In the strings.xml files, we define a mapping with an ID, which must be greater than 30000, and a string in the appropriate language:
/resources/English/strings.xml:
<?xml version="1.0" encoding="utf-8" standalone="yes"?>

<strings>
    <string id="30001">Live Stream</string>
    <string id="30002">Language</string>
    <string id="30003">French</string>
    <string id="30004">English</string>
    <string id="30005">Arabic</string>
    <string id="30006">Ask</string>
    <string id="30007">Change plugin settings...</string>
</strings>
/resources/French/strings.xml:
<?xml version="1.0" encoding="utf-8" standalone="yes"?>

<strings>
    <string id="30001">Flux en direct</string>
    <string id="30002">Langue</string>
    <string id="30003">Fran&#e7;ais</string>
    <string id="30004">Anglais</string>
    <string id="30005">Arabe</string>
    <string id="30006">Demander</string>
    <string id="30007">Modifier les paramètres de l'extension...</string>
</strings>
We are now ready to define our add-on settings. Our add-on will have just one setting, a rotary selector with four choices, one for each language and one captioned 'Ask' in English, which will cause the add on to display a menu:
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<settings>

  <category label="30001">
    <setting id="language" type="labelenum" label="30002" lvalues="30003|30004|30005|30006"  />
  </category>
</settings>

Note that all of the strings in the settings menu are localized, using the ids previously defined in strings.xml. Other types of controls that can be added to a settings dialog are defined here. Even though we have not written any Python code yet, our settings dialog is now available, by right clicking on the add-on icon and selecting Add-on settings:

Using Settings and Localized Strings

We modify default.py to check for the existence of the language setting. If it is not there, we display the settings dialog to have the user select the desired language. If the user has still not made a choice (by pressing Cancel on the settings dialog), we set the language to Ask. If the language is Ask, we display a menu to select the language, otherwise we auto play the stream in the desired language:
addon = xbmcaddon.Addon('plugin.video.france24.fr')

params = util.parseParameters()
if 'language' in params:
    play(params['language'])
else:
    language = xbmcplugin.getSetting(int(sys.argv[1]), "language")
    if language == '':
        addon.openSettings()
        language = xbmcplugin.getSetting(int(sys.argv[1]), "language")
        if language == '':
            language = "Ask"
    if language == "Ask":
        buildMenu()
    else:
        play(language)
We can also localize the strings in our menu. First, we get a reference to a function that will localize our string, which we place in a variable named localize:
addon = xbmcaddon.Addon('plugin.video.france24.fr')
localize = addon.getLocalizedString

We then use the localize function as needed to convert an id to a localized string:
def buildMenu():
    for language in streams:
        util.addMenuItem(localize(streams[language]['id']), util.makeLink({'language':language}), '', addon.getAddonInfo('icon'))
    util.endListing()

The full code to default.py is shown below and the full plugin can be downloaded here:
import xbmcaddon, util
import xbmcplugin, sys

def buildMenu():
    for language in streams:
        util.addMenuItem(localize(streams[language]['id']), util.makeLink({'language':language}), '', addon.getAddonInfo('icon'))
    util.endListing()

def play(language):
    util.playMedia(addon.getAddonInfo('name'), addon.getAddonInfo('icon'), streams[language]['stream'])

streams = {
        'French':
            {
             'stream': 'rtmp://stream2.france24.yacast.net:80/france24_live/fr playpath=f24_livefr app=france24_live/fr', \
             'id': 30003
            },
        'English':
            {
             'stream': 'rtmp://stream2.france24.yacast.net:80/france24_live/en playpath=f24_liveen app=france24_live/en', \
             'id': 30004
            },
        'Arabic':
            {
             'stream': 'rtmp://stream2.france24.yacast.net:80/france24_live/frda playpath=f24_livefrda app=france24_live/frda', \
             'id': 30005
            }
        }

addon = xbmcaddon.Addon('plugin.video.france24.fr')
localize = addon.getLocalizedString
params = util.parseParameters()
if 'language' in params:
    play(params['language'])
else:
    language = xbmcplugin.getSetting(int(sys.argv[1]), "language")
    if language == '':
        addon.openSettings()
        language = xbmcplugin.getSetting(int(sys.argv[1]), "language")
        if language == '':
            language = "Ask"
    if language == "Ask":
        buildMenu()
    else:
        play(language)

Documentation and References

http://wiki.xbmc.org/index.php?title=Addon_Settings

Scraping web pages and building menus

Introduction

This plugin will display the news from Canal Algerie, located at the following page: http://www.entv.dz/tvfr/video/index.php, The last six broadcasts are available in two languages, so our menu will list 12 videos, with a thumbnail, date and language. This site was chosen because the HTML generated is very regular and very easy to parse, and thus makes for a good example.

Getting Started

We start as we start every plugin, by creating a directory for our plugin, in this case 'plugin.video.canalalgerie', creating the icon file icon.png, ,a blank default.py and a new addon.xml, possibly copied from a previous project and modified accordingly:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<addon id="plugin.video.canalalgerie" name="Canal Algerie"
 version="1.0.0" provider-name="Abed BenBrahim">
 <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">Canal Algerie Journal Télévisé</summary>
  <description lang="en">Canal Algerie Journal Télévisé
  </description>
  <platform>all</platform>
  <language>en</language>
  <email>abenbrahim@comcast.net</email>
 </extension>
</addon>

Next we will look at source code for the web page to be scraped (http://www.entv.dz/tvfr/video/index.php). The section of interest is shown below (reformatted for clarity):

<div style="display: none;" id="tab5" class="tab_content">
 <table width="100%" border="0">
  <tr>
   <td align="left"><a href="index.php?t=JT20H_01-06-2013"
    target="_self"> <img src="../images/img_vid.jpg" width="90"
     height="60" /> </a>Samedi 01 juin</td>
   <td align="left"><a href="index.php?t=JT20H_31-05-2013"
    target="_self"> <img src="../images/img_vid.jpg" width="90"
     height="60" /> </a>Vendredi 31 mai</td>
   <td align="left"><a href="index.php?t=JT20H_30-05-2013"
    target="_self"> <img src="../images/img_vid.jpg" width="90"
     height="60" /> </a>Jeudi 30 mai</td>
  </tr>
  <tr>
   <td align="left"><a href="index.php?t=JT20H_29-05-2013"
    target="_self"> <img src="images/JT20H_29-05-2013.jpg"
     width="90" height="60" /> </a>Mercredi 29 mai</td>
   <td align="left"><a href="index.php?t=JT20H_28-05-2013"
    target="_self"> <img src="images/JT20H_28-05-2013.jpg"
     width="90" height="60" /> </a>Mardi 28 mai</td>
   <td align="left"><a href="index.php?t=JT20H_27-05-2013"
    target="_self"> <img src="images/JT20H_27-05-2013.jpg"
     width="90" height="60" /> </a>Lundi 27 mai</td>
  </tr>
 </table>
</div>
<div style="display: none;" id="tab6" class="tab_content">
 <table width="100%" border="0">
  <tr>
   <td align="left"><a href="index.php?t=JT19H_01-06-2013"
    target="_self"> <img src="../images/img_vid.jpg" width="90"
     height="60" /> </a>Samedi 01 juin</td>
   <td align="left"><a href="index.php?t=JT19H_31-05-2013"
    target="_self"> <img src="../images/img_vid.jpg" width="90"
     height="60" /> </a>Vendredi 31 mai</td>
   <td align="left"><a href="index.php?t=JT19H_30-05-2013"
    target="_self"> <img src="../images/img_vid.jpg" width="90"
     height="60" /> </a>Jeudi 30 mai</td>
  </tr>
  <tr>
   <td align="left"><a href="index.php?t=JT19H_29-05-2013"
    target="_self"> <img src="images/JT19H_29-05-2013.jpg"
     width="90" height="60" /> </a>Mercredi 29 mai</td>
   <td align="left"><a href="index.php?t=JT19H_28-05-2013"
    target="_self"> <img src="images/JT19H_28-05-2013.jpg"
     width="90" height="60" /> </a>Mardi 28 mai</td>
   <td align="left"><a href="index.php?t=JT19H_27-05-2013"
    target="_self"> <img src="images/JT19H_27-05-2013.jpg"
     width="90" height="60" /> </a>Lundi 27 mai</td>
  </tr>
 </table>
</div>

We notice that every news program is listed between <td align="left"> and </td> tags, and there are no other td tags on the page. Between the td tags, we find a link to a page containing the video, a thumbnail image and the  date/ We will infer the language from the link, if it contains 19H it is French, if it contains 20H, it is in Arabic.

Developing a Strategy

When our plugin starts with no parameters we will access the website page, scrape it to build a menu containing the 12 items. Each item will contain a link back to our plugin, with a parameter named 'play' and additional parameters specifying the video information, When our plugin is accessed with the play parameter present, we will load the video page and scrape it to extract the video link, and will play the video as demonstrated in the previous post.

Handling Parameters

Clearly, we first need to parse the parameters passed to our plugin: We will add a function called parseParameters to our util.py, since we will need this for most of our future plugins:
def parseParameters(inputString=sys.argv[2]):
    """Parses a parameter string starting at the first ? found in inputString
    
    Argument:
    inputString: the string to be parsed, sys.argv[2] by default
    
    Returns a dictionary with parameter names as keys and parameter values as values
    """
    parameters = {}
    p1 = inputString.find('?')
    if p1 >= 0:
        splitParameters = inputString[p1 + 1:].split('&')
        for nameValuePair in splitParameters:
            if (len(nameValuePair) > 0):
                pair = nameValuePair.split('=')
                key = pair[0]
                value = urllib.unquote(urllib.unquote_plus(pair[1])).decode('utf-8')
                parameters[key] = value
    return parameters
Our default.py, with playVideo and buildMenu stubbed out, now looks like this;
import util

def playVideo(params):
    pass

def buildMenu():
    pass

parameters = util.parseParameters()
if 'play' in parameters:
    playVideo(parameters)
else:
    buildMenu()

Accessing a web page

To build the menu, we need to access a web page and parse it. We will use the urllib2 library to access a URL (see Documentation and References below). This is the best choice if we only need to access one URL per invocation of the plugin, if we need to access multiple URLs on the same site, there are better choices that will be documented in future posts.
The code to open a URL and get the content is shown below:
import util, urllib2

def playVideo(pageUrl):
    pass

def buildMenu():
    url = WEB_PAGE_BASE + 'index.php'
    response = urllib2.urlopen(url)
    if response and response.getcode() == 200:
        content = response.read()
        print content
    else:
        util.showError(ADDON_ID, 'Could not open URL %s to create menu' % (url))


WEB_PAGE_BASE = 'http://www.entv.dz/tvfr/video/'
ADDON_ID = 'plugin.video.canalalgerie'

parameters = util.parseParameters()
if 'play' in parameters:
    playVideo(parameters['play'])
else:
    buildMenu()

Note that we print the content (line 11) , so that we can start debugging the plugin and ensure that we are on the right track. We should remove the print statements once we finish development, or what is printed will be added to the xbmc log at log level NOTICE.


    
def notify(addonId, message, timeShown=5000):
    """Displays a notification to the user
    
    Parameters:
    addonId: the current addon id
    message: the message to be shown
    timeShown: the length of time for which the notification will be shown, in milliseconds, 5 seconds by default
    """
    addon = xbmcaddon.Addon(addonId)
    xbmc.executebuiltin('Notification(%s, %s, %d, %s)' % (addon.getAddonInfo('name'), message, timeShown, addon.getAddonInfo('icon')))
This code includes error handling code in case the web page cannot be accessed, with a new procedure showError defined in util.py:
    
def showError(addonId, errorMessage):
    """
    Shows an error to the user and logs it
    
    Parameters:
    addonId: the current addon id
    message: the message to be shown
    """
    notify(addonId, errorMessage)
    xbmc.log(errorMessage, xbmc.LOGERROR)


Testing the add-on in the IDE

In order to test the plugin in the IDE, we need to add the xbmc stubs to the Python path, as described in a previous post. We also nend to define two of the three parameters:
  • the plugin URI, in this case plugin://plugin.video.canalalgerie. You  cannot set this, as this will be set to the path of your python file when you run in the IDE.
  • A plugin id, any integer will do, we use 0
  • the parameter string, even if there are no parameters, we will at least get a question mark
So for example, to test the plugin's buildMenu procedure, you would set the parameters to '0 ?'
Once this is done, we should be able to run the plugin and if everything went well, we should see the source fpr the web page in the IDE's console window.

Parsing the page

There are many libraries to handle the problem of parsing HTML, which may not be well formed. Libraries such as BeautifulSoup and ElementTree are very powerful, but for our simple needs, something we write ourselves will be faster and less resource intensive if we do not need the power and flexibility these libraries offer. We therefore define extractAll in util.py, a function that finds all occurrences of text between a starting token and ending token and returns an array of all such occurences:
def extractAll(text, startText, endText):
    """
    Extract all occurences of string within text that start with startText and end with endText
    
    Parameters:
    text: the text to be parsed
    startText: the starting tokem
    endText: the ending token
    
    Returns an array containing all occurences found, with tabs and newlines removed and leading whitespace removed
    """
    result = []
    start = 0
    pos = text.find(startText, start)
    while pos != -1:
        start = pos + startText.__len__()
        end = text.find(endText, start)
        result.append(text[start:end].replace('\n', '').replace('\t', '').lstrip())
        pos = text.find(startText, end)
    return result

Our buildMenu procedure in default.py now looks like this
def buildMenu():
    url = WEB_PAGE_BASE + 'index.php'
    response = urllib2.urlopen(url)
    if response and response.getcode() == 200:
        content = response.read()
        videos=util.extractAll(content, '<td align="left">', '/td>')
        for video in videos:
            print video
    else:
        util.showError(ADDON_ID, 'Could not open URL %s to create menu' % (url))

When we run the plugin, we should now see: 12 lines containing the extracted text.
We are now ready to extract the video title, the thumbnail image link and the video page link, the last step before displaying tthe menu. To extract a single substring, we define the extract function in util.py:
def extract(text, startText, endText):
    """
    Extract the first occurence of a string within text that start with startText and end with endText
    
    Parameters:
    text: the text to be parsed
    startText: the starting tokem
    endText: the ending token
    
    Returns the string found between startText and endText, or None if the startText or endText is not found
    """
    start=text.find(startText,0)
    if start!=-1:
        start=start+startText.__len__()
        end=text.find(endText,start+1)
        if end!=-1:
            return text[start:end]
    return None
The three pieces of information are extracted in our buildMenu procedure:
def buildMenu():
    url = WEB_PAGE_BASE + 'index.php'
    response = urllib2.urlopen(url)
    if response and response.getcode() == 200:
        content = response.read()
        videos=util.extractAll(content, '', '/td>')
        for video in videos:
            params={'play':1}
            params['video']=WEB_PAGE_BASE+util.extract(video, 'a href="', '\"')
            params['image']=WEB_PAGE_BASE+util.extract(video,'img src="','\"')
            params['title']=util.extract(video,'</a>','<')+' (%s)'%('Français' if '19H' in params['video'] else 'Arabe')
            print 'Title: ',params['title'],'\tVideo page:', params['video'],'\tImage: ', params['image']
    else:
        util.showError(ADDON_ID, 'Could not open URL %s to create menu' % (url))


Note that we store the extracted values in a dictionary, as this will be handy when we build a link back to the plugin to play the video. Also note that in order to include non ASCII characters (the c cedilla in Français), we need to use Unicode. This is not a problem because the parseParameters and the makeLink (described in the next section) that we defined in util.py fully support Unicode, unlike a lot of routines you will find in other existing plugins.

Building the menu

In order to build the menu, we define three helper functions, makeLink to create links back to our plugin,  addMenuItem to add a menu item to the xbmc menu and endListing to signal the end of the menu.

def makeLink(params, baseUrl=sys.argv[0]):
    """
    Build a link with the specified base URL and parameters
    
    Parameters:
    params: the params to be added to the URL
    BaseURL: the base URL, sys.argv[0] by default
    """
        return baseUrl + '?' +urllib.urlencode(dict([k.encode('utf-8'),unicode(v).encode('utf-8')] for k,v in params.items()))


def addMenuItem(caption, link, icon=None, thumbnail=None, folder=False):
    """
    Add a menu item to the xbmc GUI
    
    Parameters:
    caption: the caption for the menu item
    icon: the icon for the menu item, displayed if the thumbnail is not accessible
    thumbail: the thumbnail for the menu item
    link: the link for the menu item
    folder: True if the menu item is a folder, false if it is a terminal menu item
    
    Returns True if the item is successfully added, False otherwise
    """
    listItem = xbmcgui.ListItem(unicode(caption), iconImage=icon, thumbnailImage=thumbnail)
    listItem.setInfo(type="Video", infoLabels={ "Title": caption })
    return xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=link, listitem=listItem, isFolder=folder)

def endListing():
    """
    Signals the end of the menu listing
    """
    xbmcplugin.endOfDirectory(int(sys.argv[1]))
The buildMenu procedure now looks like this:
def buildMenu():
    url = WEB_PAGE_BASE + 'index.php'
    response = urllib2.urlopen(url)
    if response and response.getcode() == 200:
        content = response.read()
        videos = util.extractAll(content, '', '/td>')
        for video in videos:
            params = {'play':1}
            params['video'] = WEB_PAGE_BASE + util.extract(video, 'a href="', '\"')
            params['image'] = WEB_PAGE_BASE + util.extract(video, 'img src="', '\"')
            params['title'] = util.extract(video, '</a>', '<') + ' (%s)' % ('Français' if '19H' in params['video'] else 'Arabe')
            link = util.makeLink(params)
            util.addMenuItem(params['title'], link, 'DefaultVideo.png', params['image'], False)
        util.endListing()
    else:
        util.showError(ADDON_ID, 'Could not open URL %s to create menu' % (url))

This yields a menu that looks like this:

Playing the video

The code for the playVideo procedure is shown below without comment, since all concepts used have already been discussed:
def playVideo(params):
    response = urllib2.urlopen(params['video'])
    if response and response.getcode() == 200:
        content = response.read()
        videoLink = util.extract(content, 'flashvars.File = "', '"')
        util.playMedia(params['title'], params['image'], videoLink, 'Video')
    else:
        util.showError(ADDON_ID, 'Could not open URL %s to get video information' % (params['video']))



The plugin including full source code can be downloaded here.

Documentation and References


Our First Plugin


Introduction

In this plugin, we will simply play a  live stream video, more specifically France24 in French. I have found some plugins which have France24 in English, but not in French, but in any case, I wanted to access this stream as a top level plugin, and not buried in multiple levels of menu.

Getting Started

We start by creating the folder 'plugin.video.france24.fr' in the addons folder.We then add the three required files, icon.png, created from a logo found from a Google image search, a blank default.py, and an addon.xml with the following content:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<addon id="plugin.video.france24.fr"
       name="France24"
       version="1.0.0" 
       provider-name="Abed BenBrahim">
  <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="fr">Les Emissions de France24</summary>
    <description lang="fr">Les Emissions de France24</description>
    <platform>all</platform>
 <language>fr</language>
 <email>abenbrahim@comcast.net</email>
  </extension>
</addon>


If xbmc is running, it needs to be restarted so that the new plugin can be detected. It will not be necessary to restart xbmc when we edit our plugin source, as the code is interpreted and the script file is reopened every time the script is run. We should now have an icon named France24, with the the image of the specified icon. Clicking on the icon does nothing since we do not have any code in default.py.

The code 

In our first version, we will simply tell the player to play the stream.
import xbmc

link='rtmp://stream2.france24.yacast.net:80/france24_live/fr playpath=f24_livefr app=france24_live/fr'
xbmc.Player().play(item=link)

If you click the France24 icon under Video|Addons, the stream will now play. But you will notice that the caption display's fr and that a default icon is shown. This is not what we want, rather we want something that looks like this:


The play method of xbmc Player takes three parameters, all optional:

  • item: the item to play, either a local file, a network stream or a playlist
  • listitem: information about the item to be played. This is where we can specify the icon and the title.
  • windowed: whether to play the file full screen (value False, the default) or windowed (value True).

The following code creates a list item:

li = xbmcgui.ListItem(label=title, iconImage=icon, thumbnailImage=icon, path=link)
li.setInfo(type='Video', infoLabels={ "Title": title })
The first line defines the title, icon, thumbnail and link of the file to be played. The second line defines detailed information about the media, using info labels. Many info labels can be defined (a full list is here), but for our purposes, the title will suffice.

Now, we need to specify the title and icon. We could hard code the title and icon path, but in the case of the icon, that would not be very portable (it would only work on one machine). We therefore need to get information about the plugin to find out the plugin path:
addon = xbmcaddon.Addon('plugin.video.france24.fr')
title = addon.getAddonInfo('name')
icon = addon.getAddonInfo('icon')
We create an addon object with our addon's id to retrieve some information about our add-on. We will use the same object later when we deal with add-on settings and localization. Here we retrieve the add-on name and the full path to the icon. More information coud be queried, which you will find in the reference section below, in the API documentation for xbmcaddon.

So now our full code looks like this:
import xbmc, xbmcgui, xbmcaddon

addon = xbmcaddon.Addon('plugin.video.france24.fr')
title = addon.getAddonInfo('name')
icon = addon.getAddonInfo('icon')
link = 'rtmp://stream2.france24.yacast.net:80/france24_live/fr playpath=f24_livefr app=france24_live/fr'

li = xbmcgui.ListItem(label=title, iconImage=icon, thumbnailImage=icon, path=link)
li.setInfo(type='Video', infoLabels={ "Title": title })
li.setProperty('IsPlayable', 'true')

xbmc.Player().play(item=link, listitem=li)
We could stop right here and call it a day, however, there is code in thia add-on we could reuse in future projects, so we want to extract it to a separate file, util.py, which will expand on future projects, and avoid reinventing the wheel for every new plugin. We will create a function named playMedia in our new util.py script:
import xbmcgui, xbmc

def playMedia(title, thumbnail, link, mediaType='Video') :
    """Plays a video

    Arguments:
    title: the title to be displayed
    thumbnail: the thumnail to be used as an icon and thumbnail
    link: the link to the media to be played
    mediaType: the type of media to play, defaults to Video. Known values are Video, Pictures, Music and Programs
    """
    li = xbmcgui.ListItem(label=title, iconImage=thumbnail, thumbnailImage=thumbnail, path=link)
    li.setInfo(type=mediaType, infoLabels={ "Title": title })
    xbmc.Player().play(item=link, listitem=li)
and addon.py now looks like this:
import xbmcaddon, util

addon = xbmcaddon.Addon('plugin.video.france24.fr')

util.playMedia(addon.getAddonInfo('name'), addon.getAddonInfo('icon'), 
               'rtmp://stream2.france24.yacast.net:80/france24_live/fr playpath=f24_livefr app=france24_live/fr')

The complete plugin is available here

More information and references

Saturday, June 1, 2013

Structure and Lifecycle of an xbmc add-on

Introduction

We will start by writing video addons. Video add-ons expose programming available on the web, through a set of menus which display sub-menus or launch a video.

Add-on Lifecycle

An add-on is a standalone Python program, which is launched whenever a user clicks on your addon's icon. The following arguments are set when the program is launched:
  • sys.argv[0]: the executable path to your icon (in xbmc, it will be a string starting with plugin:// followed by the plugin name, for example plugin://plugin.video.1channel/
  • sys.argv[1]: your plugin handle, which is used in xbmc api calls to identify your plugin.
  • sys.argv[2]: a parameter string, starting with a question mark, followed by parameter name/value pairs, with the parameter name separated from the value by an equal sign, and the name/value pair separated by an ampersand, for example ?category=News&date=20130519. Note that if the parameter names or values contain the reserved characters ? or &, they should be URL encoded.
You will notice that the first and third arguments put together form a web like URL. Also note that there are no parameters defined when your plugin is first invoked.
So let's say we define a plugin named 'plugin.video.myplugin'. It is invoked as plugin://plugin.video.myplugin. We see that no parameters are defined so we diplay the top level menu.Lets say we need to define a menu listing some categories. When we define the menu, we provide a link for each menu item with a URL pointing back to our plugin, with a parameter defining each category, for example plugin://plugin.video.myplugin?category=1, plugin://plugin.video.myplugin?category=2, etc..
Now if a user clicks on category 1, our plugin will be invoked with sys.argv[2] set to ?category=1. When our plugin sees this, it will  build the menu for that category, with either  a link back to our plugin to display additional sub-menus or play a file, or a direct link to a file to be played.

Add-on Structure

Add-ons are located in the xbmc add-on OS dependent directory, for example %APPDATA%\XBMC\addons for Windows 7 and $HOME/xbmc for Linux. A video add-on will be located in a sub-directory with a name starting with plugin.video., and containing the following files at a minimum::
  • A Pyhton file containing the code for your plugin. Any name can be used but default.py seems to used by convention.
  • An icon for your add-on, as a PNG file named icon.png, 256 by 256 pixels.
  • An XML file describing your plugin, named addon.xml, further described below.
Other files could be included, especially if you plan to submit your plugin to the official xbmc repository, and are described in the references section below. What I have listed is the minimum needed to get a reasonable plugin. .

addon.xml file structure 

The following is a sample addon.xml file:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<addon id="plugin.video.myplugin"
       name="My first Plugin"
       version="1.0.0"
       provider-name="My Name">
  <requires>
 <import addon="xbmc.python" version="2.1.0"/>
  </requires>
  <extension point="xbmc.python.pluginsource" library="default.py">
        <provides>video</provides>
  </extension>
  <extension point="xbmc.addon.metadata">
    <summary lang="en">This is my first plugin.</summary>
    <description lang="en">This is a longer description of my first plugin.</description>
    <platform>all</platform>
 <language>en</language>
 <disclaimer> report any issues with this addon to someone@gmail.com</disclaimer>
  </extension>
</addon>

You will need to change the following information for your plugin:

  • The plugin id (line 2), which should match the folder name
  • The plugin name (line 3), which will be displayed in the xbmc gui
  • The plugin version (line 4)
  • The plugin provider(line 5), that's your name, although many people use an alias
  • The requires section can be left as is, and as our plugins get more advanced, we will add additional required components
  • The entry point Python script file name, if not named default.py, should be changed on line 9.
  • The title, description and disclaimer should be specified on lines 13,14 and 17 respectively.
That's it for now, the next post will implement a simple plugin.

More Information and References



Getting Started with xmbc Plugin Development

Introduction 

This blog was started by an xbmc neophyte to bring together information on how to write plugins for xbmc. It is presented from the perspective of a total beginner (which I am), with less than eight hours of Python experience, but with dozens of years of programming experience. While there is some information scattered about on how to write a plugin, most of the information is vague, scattered about in various places and written by someone who is more experienced and therefore leave out details which appear trivial but are critical to a proper understanding of the topic.Constructive criticism is welcome, with the aim of improving the content on this site. Also if someone has something substantive to contribute, please consider a guest post on this blog.

Assumptions

This bog is written with some  assumptions. For example, it is assumed that you already know a programming language, and know or can learn Python. Any experienced programmer should be able to pick up Python in a few minutes or hours,and  there are a number of excellent resources on the web to teach you Python. The aim of this blog is not to teach Python, but rather to document the APIs that are used to create a plugin. Likewise, it is assumed that you know how to use development tools, such as an IDE, and have some familiarity with debugging. Since a plugin is most often a web page scraper, it is also assumed that you understand HTML, and for some sites , JavaScript,

What you need

You will need to install Python, version 2.X (not 3!). I use version 2.7, any version between 2.4 and 2.7 will do. While you do not need an IDE, it really helps a lot. If you already have Eclipse installed (or even if you do not), Pydev is a good choice.
You will also need some API stub files. These files are not part of your plugins, but are referenced during development to provide documentation, code completion and allow scripts to be interpreted while running outside of the xbmc environment. I use the stubs found at https://github.com/Tenzer/xbmcstubs. Place these files anywhere on your system, and reference them in every project as part of the Python path:


We are now have everything needed to get started, so without further ado, let's write our first plugin