XBMC Crash Course Version 1 ( Beta )

Admit that you have always wanted to know how to develop a XBMC Plugins but never had the time to learn. Here I will walk you through and at the end of the course we will have created a fully functional XBMC Plugin!

Day 1

Our objective

We will create a Xbmc plugin fully configurable and more flexible. It will possible to stream all video searched by user or by a custom search pattern. Let`s begin with a Hello World course style.

Install Latest XBMC

sudo apt-get install python-software-properties pkg-config
sudo apt-get install software-properties-common
sudo add-apt-repository ppa:team-xbmc/ppa
sudo apt-get update
sudo apt-get install xbmc

Plugin/Addon minimal structure

The first thing to do is to create a plugin folder named plugin.video.helloworld in ~/.xbmc/addons directory.

On Android the folder is: /sdcard/Android/data/org.xbmc.xbmc/files/.xbmc/addons.

In this folder create the following file:

  • Create a folder than contain the nane of plugings ex: plugin.video.resources –>requied
  • An XML file named addon.xml describing the plugin:
<?xml version='1.0' encoding='UTF-8' standalone='yes'?>
<addon id="plugin.video.helloworld" name="HelloWorld" provider-name="MyName" version="1.0.0">
  <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 HelloWorld plugin.</summary>
    <description lang="en">This is a longer description of my first HelloWorld plugin.</description>
    <platform>all</platform>
 <language>en</language>
 <disclaimer> Report any issues with this addon to myname@gmail.com</disclaimer>
  </extension>
</addon>
  • An PNG file named icon.png, 256 by 256 pixels, created on the fly by Gimp –>requied
  • A Pyhton file containing the code : default.py –>requied
import xbmc
 
link='http://rkpisanu.altervista.org/wdtv/Hello.flv'
xbmc.Player().play(item=link)

Setting Python IDE

Now you can install Eclipse IDE with PyDev plugin and XbmcStubs to verify syntax error.

Ubuntu 14.04 LTS tested:

sudo apt-get install idle

sudo apt-get remove --purge openjdk-* icedtea-* icedtea6-* oracle-java6-*
sudo add-apt-repository ppa:webupd8team/java
sudo apt-get update
sudo apt-get install oracle-java7-installer
java -version


Download last Eclipse Standard:

https://www.eclipse.org/downloads/download.php?file=/technology/epp/downloads/release/luna/R/eclipse-standard-luna-R-linux-gtk-x86_64.tar.gz

tar xvfz ./Downloads/eclipse-standard-luna-R-linux-gtk-x86_64.tar.gz 
cd eclipse
./eclipse

Eclipse --> Help --> Install New Software --> (http://pydev.org/updates doesnt work ) http://update-production-pydev.s3.amazonaws.com/pydev/updates/site.xml
--> Add --> PyDev --> PyDev --> next --> finish

Eclipse --> Window --> Preferences --> PyDev --> Interpreters 
--> Python Interpreter --> Quick Auto-Config --> Apply

Restart Eclipse

https://github.com/twinther/xbmcstubs Download as zip
cd
unzip ./Downloads/xbmcstubs-master.zip

Eclipse --> File --> New --> Project... --> PyDev Project --> plugin.video.helloworld --> Finish
Eclipse --> File --> New File --> default.py 

import xbmc
 
link='http://rkpisanu.altervista.org/wdtv/Hello.flv'
xbmc.Player().play(item=link)

Eclipse --> Project --> Properties --> PyDev - PYTHONPATH 
        --> External Libreries --> Add Source Folder 
        --> and select xbmcstubs-master folder --> OK

For manual run with console:
export PYTHONPATH=$PYTHONPATH:/home/asdf/xbmcstubs-master/
python default.py

Restart Eclipse

sudo apt-get install python-beautifulsoup
sudo apt-get install python-pip
sudo pip install simplejson

Debug
Go to line link...
[CTRL]+F10 Add Breakpoint
Run --> Debug as --> Python Run

Command Line Debug in PyDev IDE:
sys.argv.extend(['1', '?mode=PlayListSMode&url=thenewboston'])


Zip Addon:
zip -r yt.zip plugin.video.yt

Create Desktop Icon:

cd ~/Desktop
vi Eclipse.desktop 

#!/usr/bin/env xdg-open
[Desktop Entry]
Version=1.0
Type=Application
Terminal=false
Icon[en_US]=/home/asdf/eclipse/icon.xpm
Name[en_US]=Eclipse
Exec=/home/asdf/eclipse/eclipse
Name=Eclipse
Icon=/home/asdf/eclipse/icon.xpm

Day 2

Debugging at Runtime

Now it is possible to discover python syntax error in IDE. And for plugin logic error ? Logging is the key.

import xbmc
 
def log(channel,msg):
    xbmc.log(  str( "==>HelloWorldPlugin: " +  msg ),level=channel )
 
link='http://rkpisanu.altervista.org/wdtv/Hello.flv'
log(xbmc.LOGDEBUG,"Playing link: " +link)
xbmc.Player().play(item=link)

Turn debugging on: Inside of XBMC go into Settings → System → Debugging and turn on Enable debug logging. You will see some weird text pop up on the screen.

Debug Log file: ~/.xbmc/temp/xbmc.log

Filter plugin log:

grep HelloWorldPlugin xbmc.log

XBMC Plugin Runtime Parameter

XBMC pass to plugin 3 runtime parameters:

  • sys.argv[0] A sort of url of plugin, in this case plugin://plugin.video.helloworld/
  • sys.argv[1] Plugin Handle, internal XBMC istance id of the plugin
  • sys.argv[2] A sort of url encoded parameters

I'll try to get values:

import xbmc,sys
 
def log(channel,msg):
    xbmc.log(  str( "==>HelloWorldPlugin: " +  msg ),level=channel )
 
 
plugin_url = sys.argv[0]
plugin_handle = int(sys.argv[1])
plugin_url_params = sys.argv[2]
 
log(xbmc.LOGDEBUG,"plugin_url = " + plugin_url)
log(xbmc.LOGDEBUG,"plugin_handle = " + str(plugin_handle))
log(xbmc.LOGDEBUG,"plugin_url_params = " + plugin_url_params)
 
link='http://rkpisanu.altervista.org/wdtv/Hello.flv'
log(xbmc.LOGDEBUG,"Playing link: " +link)
xbmc.Player().play(item=link)

Log:

18:35:23 T:140384873203456   DEBUG: ==>HelloWorldPlugin: plugin_url = plugin://plugin.video.helloworld/
18:35:23 T:140384873203456   DEBUG: ==>HelloWorldPlugin: plugin_handle = 0
18:35:23 T:140384873203456   DEBUG: ==>HelloWorldPlugin: plugin_url_params =
18:35:23 T:140384873203456   DEBUG: ==>HelloWorldPlugin: Playing link: http://rkpisanu.altervista.org/wdtv/Hello.flv 

If sys.argv[2] is empty we are in root url.

XBMC Plugin Flow with Folder

Now it is possible to show a simple forder, but nothing appened. In Debug Mode it is possible to discover the flow: XBMC call plugin with parameters, the first time parameter is null while the second time parameter is the string built by plugin itself. Next step is to parse parameters.

import xbmc,xbmcgui,xbmcplugin,urllib,sys
 
def log(channel,msg):
    xbmc.log(  str( "==>HelloWorldPlugin: " +  msg ),level=channel )
 
def addDir(name,url,mode,iconimage):
        u=sys.argv[0]+"?url="+urllib.quote_plus(url)+"&mode="+str(mode)
        ok=True
        liz=xbmcgui.ListItem(name, iconImage="DefaultFolder.png", thumbnailImage=iconimage)
        liz.setInfo( type="Video", infoLabels={ "Title": name } )
        ok=xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]),url=u,listitem=liz,isFolder=True)
        return ok
 
log(xbmc.LOGDEBUG,"plugin_url = " + sys.argv[0])
log(xbmc.LOGDEBUG,"plugin_handle = " + sys.argv[1])
log(xbmc.LOGDEBUG,"plugin_url_params = " + sys.argv[2])
 
 
addDir("HelloWorldFolder","","HelloWorldFolderMode","")
xbmcplugin.endOfDirectory(int(sys.argv[1]))
First call:
10:09:17 T:140629394319104   DEBUG: ==>HelloWorldPlugin: plugin_url = plugin://plugin.video.helloworld/
10:09:17 T:140629394319104   DEBUG: ==>HelloWorldPlugin: plugin_handle = 0
10:09:17 T:140629394319104   DEBUG: ==>HelloWorldPlugin: plugin_url_params =

Second call:
10:09:18 T:140629394319104   DEBUG: ==>HelloWorldPlugin: plugin_url = plugin://plugin.video.helloworld/
10:09:18 T:140629394319104   DEBUG: ==>HelloWorldPlugin: plugin_handle = 0
10:09:18 T:140629394319104   DEBUG: ==>HelloWorldPlugin: plugin_url_params = ?mode=HelloWorldFolderMode&url

Day 3

Parsing Parameters

import xbmc,xbmcgui,xbmcplugin,urllib,sys
 
def log(channel,msg):
    xbmc.log(  str( "==>HelloWorldPlugin: " +  msg ),level=channel )
 
def addDir(name,url,mode,iconimage):
        u=sys.argv[0]+"?url="+urllib.quote_plus(url)+"&mode="+str(mode)
        ok=True
        liz=xbmcgui.ListItem(name, iconImage="DefaultFolder.png", thumbnailImage=iconimage)
        liz.setInfo( type="Video", infoLabels={ "Title": name } )
        ok=xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]),url=u,listitem=liz,isFolder=True)
        return ok
 
def index():    
    addDir("HelloWorldFolder","","HelloWorldFolderMode","")
    xbmcplugin.endOfDirectory(int(sys.argv[1]))
 
def parameters_string_to_dict(parameters):
    ''' Convert parameters encoded in a URL to a dict. '''
    paramDict = {}
    if parameters:
        paramPairs = parameters[1:].split("&")
        for paramsPair in paramPairs:
            paramSplits = paramsPair.split('=')
            if (len(paramSplits)) == 2:
                paramDict[paramSplits[0]] = paramSplits[1]
    return paramDict
 
'''========== Main Routune =========='''
 
'''=== Split Parameters ==='''
params=parameters_string_to_dict(sys.argv[2])
mode=params.get('mode')
url=params.get('url')
if type(url)==type(str()):
    url=urllib.unquote_plus(url)
 
'''=== Logging ==='''
log(xbmc.LOGDEBUG,"plugin_url = " + sys.argv[0])
log(xbmc.LOGDEBUG,"plugin_handle = " + sys.argv[1])
log(xbmc.LOGDEBUG,"plugin_url_params = " + sys.argv[2])
if not mode:
    log(xbmc.LOGDEBUG,"mode = ")
else:     
    log(xbmc.LOGDEBUG,"mode = " + mode)
if not url:
    log(xbmc.LOGDEBUG,"url = ")
else:     
    log(xbmc.LOGDEBUG,"url = " + url)
 
 
'''=== Call the function indexed by mode param ==='''
if not mode:
    # Call Root Index
    index()
First call:
12:11:46 T:140253066729216   DEBUG: ==>HelloWorldPlugin: plugin_url = plugin://plugin.video.helloworld/
12:11:46 T:140253066729216   DEBUG: ==>HelloWorldPlugin: plugin_handle = 0
12:11:46 T:140253066729216   DEBUG: ==>HelloWorldPlugin: plugin_url_params =
12:11:46 T:140253066729216   DEBUG: ==>HelloWorldPlugin: mode =
12:11:46 T:140253066729216   DEBUG: ==>HelloWorldPlugin: url =

Second call:
12:11:48 T:140253066729216   DEBUG: ==>HelloWorldPlugin: plugin_url = plugin://plugin.video.helloworld/
12:11:48 T:140253066729216   DEBUG: ==>HelloWorldPlugin: plugin_handle = 0
12:11:48 T:140253066729216   DEBUG: ==>HelloWorldPlugin: plugin_url_params = ?mode=HelloWorldFolderMode&url
12:11:48 T:140253066729216   DEBUG: ==>HelloWorldPlugin: mode = HelloWorldFolderMode
12:11:48 T:140253066729216   DEBUG: ==>HelloWorldPlugin: url =

Folder Clicked

import xbmc,xbmcgui,xbmcplugin,urllib,sys
 
def log(channel,msg):
    xbmc.log(  str( "==>HelloWorldPlugin: " +  msg ),level=channel )
 
def addDir(name,url,mode,iconimage):
        u=sys.argv[0]+"?url="+urllib.quote_plus(url)+"&mode="+str(mode)
        ok=True
        liz=xbmcgui.ListItem(name, iconImage="DefaultFolder.png", thumbnailImage=iconimage)
        liz.setInfo( type="Video", infoLabels={ "Title": name } )
        ok=xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]),url=u,listitem=liz,isFolder=True)
        return ok
 
def addlink(name,url):
        ok=True
        liz=xbmcgui.ListItem(name, iconImage="DefaultVideo.png", thumbnailImage="DefaultVideo.png")
        liz.setInfo( type="Video", infoLabels={ "Title": name } )
        ok=xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]),url=url,listitem=liz,isFolder=False)
        return ok        
 
def index():    
    addDir("HelloWorldFolder","","HelloWorldFolderMode","")
    xbmcplugin.endOfDirectory(int(sys.argv[1]))
 
def HelloWorldFolderClicked():    
    addlink("HelloWorldVideo","http://rkpisanu.altervista.org/wdtv/Hello.flv")
    xbmcplugin.endOfDirectory(int(sys.argv[1]))
 
 
def parameters_string_to_dict(parameters):
    ''' Convert parameters encoded in a URL to a dict. '''
    paramDict = {}
    if parameters:
        paramPairs = parameters[1:].split("&")
        for paramsPair in paramPairs:
            paramSplits = paramsPair.split('=')
            if (len(paramSplits)) == 2:
                paramDict[paramSplits[0]] = paramSplits[1]
    return paramDict
 
'''========== Main Routune =========='''
 
'''=== Split Parameters ==='''
params=parameters_string_to_dict(sys.argv[2])
mode=params.get('mode')
url=params.get('url')
if type(url)==type(str()):
    url=urllib.unquote_plus(url)
 
'''=== Logging ==='''
log(xbmc.LOGDEBUG,"plugin_url = " + sys.argv[0])
log(xbmc.LOGDEBUG,"plugin_handle = " + sys.argv[1])
log(xbmc.LOGDEBUG,"plugin_url_params = " + sys.argv[2])
if not mode:
    log(xbmc.LOGDEBUG,"mode = ")
else:     
    log(xbmc.LOGDEBUG,"mode = " + mode)
if not url:
    log(xbmc.LOGDEBUG,"url = ")
else:     
    log(xbmc.LOGDEBUG,"url = " + url)
 
 
'''=== Call the function indexed by mode param ==='''
if mode == 'HelloWorldFolderMode':
    HelloWorldFolderClicked()
else:
    # Call Root Index    
    index()
First call:
12:48:22 T:139749830416128   DEBUG: ==>HelloWorldPlugin: plugin_url = plugin://plugin.video.helloworld/
12:48:22 T:139749830416128   DEBUG: ==>HelloWorldPlugin: plugin_handle = 0
12:48:22 T:139749830416128   DEBUG: ==>HelloWorldPlugin: plugin_url_params =
12:48:22 T:139749830416128   DEBUG: ==>HelloWorldPlugin: mode =
12:48:22 T:139749830416128   DEBUG: ==>HelloWorldPlugin: url =

Second call:
12:48:24 T:139749830416128   DEBUG: ==>HelloWorldPlugin: plugin_url = plugin://plugin.video.helloworld/
12:48:24 T:139749830416128   DEBUG: ==>HelloWorldPlugin: plugin_handle = 0
12:48:24 T:139749830416128   DEBUG: ==>HelloWorldPlugin: plugin_url_params = ?mode=HelloWorldFolderMode&url
12:48:24 T:139749830416128   DEBUG: ==>HelloWorldPlugin: mode = HelloWorldFolderMode
12:48:24 T:139749830416128   DEBUG: ==>HelloWorldPlugin: url =

Third call:
12:48:42 T:139749830416128   DEBUG: ==>HelloWorldPlugin: plugin_url = plugin://plugin.video.helloworld/
12:48:42 T:139749830416128   DEBUG: ==>HelloWorldPlugin: plugin_handle = 0
12:48:42 T:139749830416128   DEBUG: ==>HelloWorldPlugin: plugin_url_params = ?mode=HelloWorldFolderMode&url
12:48:42 T:139749830416128   DEBUG: ==>HelloWorldPlugin: mode = HelloWorldFolderMode
12:48:42 T:139749830416128   DEBUG: ==>HelloWorldPlugin: url =
12:48:42 T:139750888552320   ERROR: Control 50 in window 10025 has been asked to focus, but it can't
12:48:42 T:139749830416128    INFO: Scriptresult: Success
12:48:42 T:139750888552320   DEBUG: WaitOnScriptResult- plugin returned successfully
12:48:42 T:139749830416128    INFO: Python script stopped
12:48:42 T:139749830416128   DEBUG: Thread XBPyThread 139749830416128 terminating
12:48:42 T:139749830416128  NOTICE: Thread Background Loader start, auto delete: false
12:48:42 T:139749830416128   DEBUG: Thread Background Loader 139749830416128 terminating
12:48:42 T:139750888552320   DEBUG: waiting for python thread 5 (/home/asdf/.xbmc/addons/plugin.video.helloworld/default.py) to stop
12:48:42 T:139750888552320   DEBUG: python thread 5 (/home/asdf/.xbmc/addons/plugin.video.helloworld/default.py) destructed
12:48:42 T:139750367291136   DEBUG: DoWork - Saving file state for video item http://rkpisanu.altervista.org/wdtv/Hello.flv

Play Youtube Video From ID

import xbmc,xbmcgui,xbmcplugin,urllib,sys
 
def log(channel,msg):
    xbmc.log(  str( "==>HelloWorldPlugin: " +  msg ),level=channel )
 
def addDir(name,url,mode,iconimage):
        u=sys.argv[0]+"?url="+urllib.quote_plus(url)+"&mode="+str(mode)
        ok=True
        liz=xbmcgui.ListItem(name, iconImage="DefaultFolder.png", thumbnailImage=iconimage)
        liz.setInfo( type="Video", infoLabels={ "Title": name } )
        ok=xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]),url=u,listitem=liz,isFolder=True)
        return ok
 
def addDirYT(name,url,mode,iconimage):
        u=sys.argv[0]+"?url="+urllib.quote_plus(url)+"&mode="+str(mode)
        ok=True
        liz=xbmcgui.ListItem(name, iconImage="DefaultFolder.png", thumbnailImage=iconimage)
        liz.setInfo( type="Video", infoLabels={ "Title": name } )
        ok=xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]),url=u,listitem=liz,isFolder=True)
        return ok
 
def addlink(name,url):
        ok=True
        liz=xbmcgui.ListItem(name, iconImage="DefaultVideo.png", thumbnailImage="DefaultVideo.png")
        liz.setInfo( type="Video", infoLabels={ "Title": name } )
        ok=xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]),url=url,listitem=liz,isFolder=False)
        return ok
 
def PlayVideoYT(yt_id):
        log(xbmc.LOGDEBUG,"def PlayVideoYT(" + yt_id + "):")
        ok=True
        url = "plugin://plugin.video.youtube/?path=/root/video&action=play_video&videoid=" + yt_id
        xbmc.Player(xbmc.PLAYER_CORE_MPLAYER).play(url)
        return ok              
 
def index():    
    addDir("HelloWorldFolder","","HelloWorldFolderMode","")
    addDirYT("HelloWorldFolderYT","A-Bw_cqPeiA","PlayVideoYTMode","")
    xbmcplugin.endOfDirectory(int(sys.argv[1]))
 
def HelloWorldFolderClicked():    
    addlink("HelloWorldVideo","http://rkpisanu.altervista.org/wdtv/Hello.flv")
    xbmcplugin.endOfDirectory(int(sys.argv[1]))
 
 
def parameters_string_to_dict(parameters):
    ''' Convert parameters encoded in a URL to a dict. '''
    paramDict = {}
    if parameters:
        paramPairs = parameters[1:].split("&")
        for paramsPair in paramPairs:
            paramSplits = paramsPair.split('=')
            if (len(paramSplits)) == 2:
                paramDict[paramSplits[0]] = paramSplits[1]
    return paramDict
 
'''========== Main Routune =========='''
 
'''=== Split Parameters ==='''
params=parameters_string_to_dict(sys.argv[2])
mode=params.get('mode')
url=params.get('url')
if type(url)==type(str()):
    url=urllib.unquote_plus(url)
 
'''=== Logging ==='''
log(xbmc.LOGDEBUG,"plugin_url = " + sys.argv[0])
log(xbmc.LOGDEBUG,"plugin_handle = " + sys.argv[1])
log(xbmc.LOGDEBUG,"plugin_url_params = " + sys.argv[2])
if not mode:
    log(xbmc.LOGDEBUG,"mode = ")
else:     
    log(xbmc.LOGDEBUG,"mode = " + mode)
if not url:
    log(xbmc.LOGDEBUG,"url = ")
else:     
    log(xbmc.LOGDEBUG,"url = " + url)
 
 
'''=== Call the function indexed by mode param ==='''
if mode == 'HelloWorldFolderMode':
    HelloWorldFolderClicked()
elif mode == 'PlayVideoYTMode':
    PlayVideoYT(url)
else:
    # Call Root Index    
    index()

My Addon

xbmc/xbmc_crash_course.txt · Last modified: 2015/02/24 10:02 by rkpisanu
www.chimeric.de Creative Commons License Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0





Mail