At the end of this tutorial we're going to have a basic version of the Academic Earth Plugin. This plugin plays videos from http://www.academicearth.org/.
Since this tutorial is meant to cover the usage of kodiswift, we will not be covering HTML scraping. It makes sense to partition your scraping code into a separate module from your addon's core functionality. In this example, we're going to use a scraping library for academic earth that I already have written.
The first step is to create your working directory for your addon. Since this can be repetitive, kodiswift provides a script which will create the necessary files and folders for you. So we'll do just that:
(kodiswift)jon@lenovo tmp $ kodiswift create kodiswift - A micro-framework for creating Kodi plugins. [email protected] -- I'm going to ask you a few questions to get this project started. What is your plugin name? : Academic Earth Tutorial Enter your plugin id. [plugin.video.academicearthtutorial]: Enter parent folder (where to create project) [/tmp]: Enter provider name : Jonathan Beluch (jbel) Projects successfully created in /tmp/plugin.video.academicearthtutorial. Done.
If you cd
into the created directory, you should see the familiar addon
structure, including plugin.py
, addon.xml
, resourcres
directory,
etc.
To make this tutorial go a bit smoother, we're going to use some existing code
which handles the scraping of the Academic Earth website. Download this file
and extract it to resources/lib/
.
$ cd resources/lib $ wget https://github.com/downloads/jbeluch/xbmc-academic-earth/academicearth.tgz $ tar -xvzf academicearth.tgz $ rm academicearth.tgz
We should now have an academicearth
directory in our lib directory.
Since our api library requires the use of BeautifulSoup, we'll need to add this as a depenency to our addon.xml file.
If you open the addon.xml file, you'll notice that kodiswift is already in your dependencies:
<import addon="xbmc.python" version="2.0" />
<import addon="script.module.kodiswift" version="0.0.7" />
We'll add BeautifulSoup right after those lines:
<import addon="script.module.beautifulsoup" version="3.0.8" />
The last step is to install BeautifulSoup locally, so we can run our addon on the command line.:
$ pip install BeautifulSoup
Let's modify the the index function, to look like this:
@plugin.route('/')
def main_menu():
items = [
{'label': 'Show Subjects', 'path': plugin.url_for('show_subjects')}
]
return items
The main_menu
function is going to be our default view. Take note that is
has the route of /
. The first time the addon is launched, there will be no
state information, so the requested URL will match '/'.
If you were to run the plugin now, you'd see an exception about a view not being found. This is because we are specifying a view name of 'show_subjects' but we don't have a view with that name! So let's create a stub for that view.
@plugin.route('/subjects/')
def show_subjects():
pass
So now we have a basic plugin with two views. Keep in mind as we go along, that we can always run the plugin from the command line.:
$ kodiswift run 2>/dev/null ------------------------------------------------------------ # Label Path ------------------------------------------------------------ [0] Subjects (plugin://plugin.video.academicearth/subjects/) ------------------------------------------------------------
Now let's add some logic to our show_subjects
function.
@plugin.route('/subjects/')
def show_subjects():
api = AcademicEarth()
subjects = api.get_subjects()
items = [{
'label': subject.name,
'path': plugin.url_for('show_subject_info', url=subject.url),
} for subject in subjects]
sorted_items = sorted(items, key=lambda item: item['label'])
return sorted_items
You can see that we are going to be using our Academic Earth api module here.
So we need to import the class before we instantiate it: from
resources.lib.academicearth.api import AcademicEarth
.
The call to get_subjects
returns a list of Subject objects with various
attributes that we can access.
So our code simply loops over the subjects and creates a dictionary for each
subject. These simple dictionaries will be converted by kodiswift into proper
list items and then displayed by Kodi. The two mandatory keys are label
,
which is the text to display for the item, and path
, which is the URL to
follow when the item is selected.
Here, if the user selects a subject list item, we want to send them to the
show_subject_info
function. Notice we are also passing a keyword argument
to the url_for
method. This is the main way that we can pass information
between successive invocations of the addon. By default, Kodi addons are
stateless, each time a user clicks on an item the addon is executed, it does
some work and then exits. To keep track of what the user was doing, we need to
encode the information in the url. kodiswift handles the url encoding as long
as you pass the arguments to url_for.
The last lines of code in our view simply sort the list of dictionaries based on the label and then return the list.
The last step we need to take before running our addon is to stub out the
show_subject_info
view.
@plugin.route('/subjects/<url>/')
def show_subject_info(url):
pass
Note that since we are passing a url argument to url_for
, we need to
ensure our view can handle the argument. This involves creating a placeholder
in the url, <url>
and then ensuring our view takes a single argument,
url
. kodiswift will attempt to match incoming URLs against the list of
routes. If it finds a match, it will convert any instances of <var_name>
to
variables and then call the view with those variables. See :ref:`routing` for
more detailed information about routing.
Now let's run our plugin in interactive mode (for the sake of brevity I've replaces a lot of entries in the example output with ...
):
$ kodiswift run interactive 2>/dev/null ------------------------------------------------------------ # Label Path ------------------------------------------------------------ [0] Subjects (plugin://plugin.video.academicearth/subjects/) ------------------------------------------------------------ Choose an item or "q" to quit: 0 ---------------------------------------------------------------------------------------------------------------------------------------------------------- # Label Path ---------------------------------------------------------------------------------------------------------------------------------------------------------- [ 0] .. (plugin://plugin.video.academicearth/) [ 1] ACT (plugin://plugin.video.academicearth/subjects/http%3A%2F%2Fwww.academicearth.org%2Fsubjects%2Fact/) [ 2] Accounting (plugin://plugin.video.academicearth/subjects/http%3A%2F%2Fwww.academicearth.org%2Fsubjects%2Faccounting/) [ 3] Algebra (plugin://plugin.video.academicearth/subjects/http%3A%2F%2Fwww.academicearth.org%2Fsubjects%2Falgebra/) [ 4] Anthropology (plugin://plugin.video.academicearth/subjects/http%3A%2F%2Fwww.academicearth.org%2Fsubjects%2Fanthropology/) [ 5] Applied CompSci (plugin://plugin.video.academicearth/subjects/http%3A%2F%2Fwww.academicearth.org%2Fsubjects%2Fapplied-computer-science/) [ 6] Architecture (plugin://plugin.video.academicearth/subjects/http%3A%2F%2Fwww.academicearth.org%2Fsubjects%2Farchitecture/) ... [67] Visualization & Graphics (plugin://plugin.video.academicearth/subjects/http%3A%2F%2Fwww.academicearth.org%2Fsubjects%2Fvisualization-graphics/) ---------------------------------------------------------------------------------------------------------------------------------------------------------- Choose an item or "q" to quit:
The first output we see is our main menu. Then we are prompted for an item to select (only 1 available in this case). When we select Subjects, we are then routed to our show_subjects view.
Let's add some logic to our show_subject_info
view:
@plugin.route('/subjects/<url>/')
def show_subject_info(url):
subject = Subject.from_url(url)
courses = [{
'label': course.name,
'path': plugin.url_for('show_course_info', url=course.url),
} for course in subject.courses]
lectures = [{
'label': 'Lecture: %s' % lecture.name,
'path': plugin.url_for('play_lecture', url=lecture.url),
'is_playable': True,
} for lecture in subject.lectures]
by_label = itemgetter('label')
items = sorted(courses, key=by_label) + sorted(lectures, key=by_label)
return items
Most of this should look very similar to our code for show subjects. This time
however, we have two different types of Academic Earth content to handle,
courses and lectures. We want courses to route to show_course_info
, which
will list all of the lectures for the course. Lectures, however, are simply
videos, so we want these list items to play a video when the user selects one.
We are going to route lectures to play_lecture
.
A new concept in this view is the is_playable
item. By default, list items
in kodiswift are not playable. This means that Kodi expects the list item to
point back to an addon and will not attempt to play a video (or audio) for the
given URL. When you are finally ready for Kodi to play a video, a special flag
must be set. kodiswift handles this for you, all you need to do is remember to
set the is_playable
flag to True.
There is another new concept in this view as well. Typically, if you tell Kodi that a URL is playable, you will pass a direct URL to a resource such as an mp4 file. In this case, we have to do more scraping in order to figure out the URL for the particular video the user selects. So our playable URL actually calls back into our addon, which will then make use of plugin.set_resolved_url().
Let's add the following code to complete our addon:
@plugin.route('/courses/<url>/')
def show_course_info(url):
course = Course.from_url(url)
lectures = [{
'label': 'Lecture: %s' % lecture.name,
'path': plugin.url_for('play_lecture', url=lecture.url),
'is_playable': True,
} for lecture in course.lectures]
return sorted(lectures, key=itemgetter('label'))
@plugin.route('/lectures/<url>/')
def play_lecture(url):
lecture = Lecture.from_url(url)
url = 'plugin://plugin.video.youtube/?action=play_video&videoid=%s' % lecture.youtube_id
plugin.log.info('Playing url: %s' % url)
plugin.set_resolved_url(url)
The show_course_info
view should look pretty familiar at this point. We are
just listing the lectures for the given course url.
The play_lecture
view introduces some new concepts however. Remember that
we told Kodi that our lecture items were playable. Since we gave a URL which
pointed to our addon, we now have to use plugin.set_resolved_url(url)
. This
communicates to Kodi, that this is the real url that we want to play.
We are introducing one more layer of indirection here however. Since all of the content on Academic Earth is hosted on youtube, our addon would normally require lots of extra code just to parse URLs out of youtube. However, the youtube addon conveniently does all of that! So, we will actually set the playable URL to point to the youtube plugin, which will then provide Kodi with the actual playable URL. Sounds a bit complicated, but it makes addons much simpler in the end. Our addon simply deals with parsing the Academic Earth website, and leaves anything youtube specific to the youtube addon.
The last step is now to add youtube as a dependency for our addon. Let's edit the addon.xml again and add youtube:
<import addon="plugin.video.youtube" version="3.1.0" />
We're finished! You should be able to navigate your addon using the command line. You should also be able to test your addon directly in Kodi. I personally like to use symlinks to test my addons. On linux, you could do something like this:
$ cd ~/.xbmc/addons $ ln -s ~/Code/plugin.video.academicearthtutorial
Note that you'll also have to install the kodiswift Kodi distribution. The easiest way is to install one of the addons listed on the :ref:`poweredby` page. Since they all require kodiswift as a dependency, it will automatically be installed. The other option is to download the newest released version from this page and unzip it in your addons directory.