This post is the second part of my series on XBMC plugin development. In Part 1 — Documentation and tools I discussed the documentation and tools necessary to get started. If you haven’t read that, I suggest to do so.
In this installment I will go over the steps of importing a simple example project into PyDev, and walk you through the actual Python code.
Getting the source
You can get the source code from GitHub, or you can simply download this ZIPped Eclipse PyDev project. Either way, the first step is to put the files in the addons folder of your XBMC installation. The location of this folder depends on your operating system:
- Linux: ~/.xbmc/addons
- Mac OS X: ~/Library/Application Support/XBMC/addons
- Windows: C:\Program Files\XBMC\addons
Using Git
Creating a new folder called plugin.audio.tutorial, and run git checkout from there. The repository location is: git://github.com/zstorok/xbmc-soundcloud-plugin.git.
Using the ZIP file
If you are using the ZIP file, you can simply extract it in the addons folder, since it already includes the plugin.audio.tutorial folder.
Testing with XBMC
Although the location of the addon folders should be unambiguous, it’s best to make sure and check if XBMC actually finds the newly added plugin. Launch XBMC and go to Music | Music Add-ons to see if the plugin is picked up by XBMC. You should see an XBMC Plugin Tutorial item in the list, just like in the screenshot below. Go ahead and explore the tutorial plugin to explore what it does.
Importing into Eclipse
The next step is to get the code into Eclipse. Go to File | New… | PyDev project and give the new project a name. Uncheck the Use default checkbox under Project contents, and browse for the plugin.audio.tutorial folder in your addons folder. Set Grammar Version to 2.4 and Interpreter to Default, and also make sure to uncheck the Create default ‘src’ folder… checkbox. See the screenshot below for how it should look on Mac OS X; other platforms will only differ in the directory location. Click Finish to accept all the other defaults.
Next, right-click on the newly created project and select Properties. Go to the PyDev – PYTHONPATH property page and add the project folder as a source folder, like it is shown in the screenshot below.
Exploring the files
Now you’re all set to begin working with the code. The newly created PyDev project contains the following files:
- addon.xml (the plugin metadata descriptor)
- default.py (and the actual Python code)
- icon.png (plugin icon, 256×256 PNG)
addon.xml
This XML file contains various bits of metadata about the addon, for example: name, version, author, dependencies, etc. The XBMC Wiki goes into the syntax and purpose of this file in detail.
default.py
This is where the meat is: the actual Python code. I will go over the main sections of it in detail, and I suggest keeping the XBMC Python module documentation handy to quickly look up the relevant API classes and functions.
One thing to keep in mind is, that the code is run by XBMC every time you enter a directory in your plugin. As you will see, the usual practice to pass information (subdirectory location, query parameters, etc.) between subsequent invocations is to encode them as query strings in the list item URLs. On each invocation, the URL is provided as a command line argument in sys.argv, which is then parsed to obtain the parameter names and values. This will make a lot more sense once you see it in action, so let’s dive into the code.
First we import the three XBMC modules, sys for handling command line arguments and urllib for encoding and decoding parameters in URLs.
import sys import xbmc, xbmcgui, xbmcplugin import urllib
Next we initialize some constants, you will see how these are used a bit later.
# plugin modes # plugin modes MODE_FIRST = 10 MODE_SECOND = 20 # parameter keys PARAMETER_KEY_MODE = "mode" # menu item names FIRST_SUBMENU = "First Submenu" SECOND_SUBMENU = "Second Submenu" # plugin handle handle = int(sys.argv[1])
Next up are two utility functions: the first is for converting URL parameters to a Python dict, the second one is for adding an item to the directory listing in XBMC. The latter contains the first truly XBMC specific code. First it creates a ListItem, then calls xbmcplugin.addDirectoryItem(…) to add it to the current directory listing. Also, it encodes various parameters we want to pass on to the next invocation of our plugin script into the directory item’s URL. Remember, default.py is run each time you select a list item in the UI, and we need to pass on some information about where we are in the menu structure.
# utility functions 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 def addDirectoryItem(name, isFolder=True, parameters={}): ''' Add a list item to the XBMC UI.''' li = xbmcgui.ListItem(name) url = sys.argv[0] + '?' + urllib.urlencode(parameters) return xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=url, listitem=li, isFolder=isFolder)
Now we are getting to the part where the root menu and the two submenus are created. These functions make use of the addDirectoryItem(…) function defined above.
The show_root_menu() function adds two folder items, and specifies the mode associated with the item via the parameters argument. Since it’s a dict, you could put any kind of information here, but to keep it simple, this example only sets the mode. After the two items are added, it calls xbmcplugin.endOfDirectory(…) to indicate, that we don’t want to add any more items to the list.
# UI builder functions def show_root_menu(): ''' Show the plugin root menu. ''' addDirectoryItem(name=FIRST_SUBMENU, parameters={ PARAMETER_KEY_MODE: MODE_FIRST }, isFolder=True) addDirectoryItem(name=SECOND_SUBMENU, parameters={ PARAMETER_KEY_MODE: MODE_SECOND }, isFolder=True) xbmcplugin.endOfDirectory(handle=handle, succeeded=True)
The show_first_submenu() and show_second_submenu() functions simply create and insert a few dummy items into the submenus. Note that these are not folder items, so in a real plugin these would be the actual media files (images, audio and video clips). Of course you can create a menu structure of any depth, but in this example I kept it as simple as possible.
def show_first_submenu(): ''' Show first submenu. ''' for i in range(0, 5): name = "%s Item %d" % (FIRST_SUBMENU, i) addDirectoryItem(name, isFolder=False) xbmcplugin.endOfDirectory(handle=handle, succeeded=True) def show_second_submenu(): ''' Show second submenu. ''' for i in range(0, 10): name = "%s Item %d" % (SECOND_SUBMENU, i) addDirectoryItem(name, isFolder=False) xbmcplugin.endOfDirectory(handle=handle, succeeded=True)
The last part of the code is the most important: this is where the parameters encoded in the URL are parsed to decide which function to invoke to display the expected menu.
If sys.argv[2] is unspecified it means that we are at the top level, so the root menu should be displayed. The actual submenus have a mode associated with them, which is encoded into their item URLs when they are added to the root menu.
If you want to follow what is happening when running a plugin, you can take a look at the XBMC log. In this example, the mode is printed into the log at each invocation.
# parameter values params = parameters_string_to_dict(sys.argv[2]) mode = int(params.get(PARAMETER_KEY_MODE, "0")) print "##########################################################" print("Mode: %s" % mode) print "##########################################################" # Depending on the mode, call the appropriate function to build the UI. if not sys.argv[2]: # new start ok = show_root_menu() elif mode == MODE_FIRST: ok = show_first_submenu() elif mode == MODE_SECOND: ok = show_second_submenu()
This is all that’s required to have a simple, executable XBMC plugin. Of course currently it’s not very useful, since the items are not tied to actual media files. In the next part I will show you how to connect to the SoundCloud API and stream music directly in XBMC.



Hi! Enjoying the SoundCloud plugin, but notice that it does not scrobble my “now playing” track information to Last.fm. I know there is some proof-of-concept code available for last.fm in python, do you know if this is how you would do the last.fm submission, or is there a way to hook into the XBMC internal last.fm API functions?
=darwin
Thanks for your feedback, I’m glad you like it. I haven’t considered Last.fm scrobbling at all when developing this plugin, so I can’t comment on that right now. If I recall correctly, Last.fm’s API is a fairly simple REST web service, so it should be possible to use it with any programming language.
I will take a look into the XBMC scrobbling mechanism, maybe I’m just not doing something right and XBMC would pick up the track plays when I fix it.
FWIW, the addon recently stopped working after an update.
=darwin
Thanks for the feedback, I fixed the track playback bug and released version 0.1.4. The updated version should be available in the official XBMC add-ons repository in a matter of hours.
Hi Zsolt, I think you should talk to the guys over at the XBMC forums about replacing their current plugin development tutorial with yours.
I’m not sure if you’ve seen theirs, but in a word, it’s garbage. It’s more focussed on teaching bad Python rather than how to actually develop a plugin.
Your’s is much more to the point and a lot easier to follow. Definitely a lot better than what they’ve got to offer.
Thanks Nathan, very kind of you. I’m already on the XBMC add-on dev mailing list, so I will mention these blog posts to them and see if they like them.
I have actually seen the XBMC tutorial on their site, and found it somewhat confusing, so I ended up doing a lot of research and hacking. Once I got to a level where things were kind of working, I realized that I could save others the trouble by summarizing what I learned.
I appreciate the great tutorial. I’ll give you a shout out when I finish mine via the README. Great work.
Thanks, glad you found it useful.
hi Zsolt
I can’t import the “xbmc, xbmcgui, xbmcplugin”.
please let me know, where is it?
Hi Zsolt,
Thank you for the excellent tutorial!
Any estimate when will you post the remaining parts?
Thanks
Dusan
I’ve been very busy with other stuff, so I can’t promise anything. In the meantime you can take a look at the source of my XBMC Soundcloud plugin. Also, I will update that plugin in the near future to be compatible with the upcoming XBMC release, and the changes will eventually make it into these blog posts as well.
It is expected that these modules will not be found during development, since they are only available when running inside XBMC. In short: don’t worry about these errors, just start writing your plugin and run it in XBMC.
hi zsolt…
I really appreciate your work, you have helped me a lot with my project.
but i would like to ask you something…
does your plugin work fine in the xbox console or there are any constraint in the python code for some versions of XBMC?
Thanks for the great tutorial… This is definitely the best I’ve come across. The next best thing is peeking into other projects. Thanks for the hard work