Tommy Winther

twenty-first century code monkey

XBMC addon tutorial

Jeg er blevet foreslået at lave en XBMC addon skole, så lad os give det et forsøg 🙂 Jeg skraber kun overfladen af hvad der er muligt med XBMC addons, så kom endelig med spørgsmål og input, hvis du ønsker jeg går i dybden med noget.

Jeg tager udgangspunkt i den VideoVideo HD addon jeg har lavet til videovideo.dk. Du kan finde koden på github.com.

Livscyklus

VideoVideo HD addon’en er en video addon, hvilke gør at den findes i XBMC under video addons. Det er også muligt at lave andre typer af addons, bl.a. deciderede programmer, fx som den Movie Quiz jeg har lavet. Sådanne addons opfører sig anderledes end den video addon jeg beskriver her.

En video addon kaldes meget lig et HTTP kald på internettet. Dvs. for hver side der vises bliver addon’en kald med en række parametre. En side i dette tilfælde er en liste af undermapper og/eller video klip. De parametre addon’en kalde med har typisk også samme format som på en HTTP url, men det er dog ikke noget krav, da du selv har ansvaret for at parse informationerne ud igen.

Når der navigeres tilbage blive addon’en ikke kaldt, her har XBMC cachet informationer, lidt ligesom din browser gør på nettet.

Struktur

En addon her et unikt ID som også går igen i strukturen – for VideoVideo HD er ID’et plugin.video.videovideo.dk. Det betyder alle filerne ligger i en mappe med samme navn, som du også kan se på github. I addon mappen er der nogle filer som skal være der:

  • addon.xml – information om addon’en, så som navn, beskrivelse men også evt. ekstra script moduler (dependencies)
  • LICENSE.txt – licens oplysninger for addon’en, typisk GPLv2 eller lign.
  • changelog.txt – beskrivelse af ændringer mellem de enkelte version af addon’en.
  • icon.png – addon’ens ikon, skal være i PNG format.

Ud over disse filer skal der også være selve Python koden, så addon’en kan udføre noget.

Addon.xml

<?xml version="1.0" encoding="UTF-8"?>
<addon
        id="plugin.video.videovideo.dk"
        version="1.1.2"
        name="videovideo.dk HD"
        provider-name="twinther [tommy@winther.nu]">
    <requires>
        <import addon="xbmc.python" version="1.0"/>
        <import addon="script.module.simplejson" version="2.0.10"/>
    </requires>
    <extension point="xbmc.python.pluginsource" library="addon.py">
        <provides>video</provides>
    </extension>
    <extension point="xbmc.addon.metadata">
        <summary lang="en">720p HD video from newz Media ApS</summary>
        <summary lang="da">720p HD video fra newz Media ApS</summary>
        <description lang="en">videovideo.dk is overall video platform for all sites belonging to newz Media ApS, including filmz.dk and railgun.newz.dk</description>
        <description lang="da">videovideo.dk er overordnet videoplatform for alle sites tilhørende newz Media ApS, heriblandt filmz.dk og railgun.newz.dk</description>
        <license>GPL 2.0</license>
        <platform>all</platform>
    </extension>
</addon>

Addon.xml indeholder grundlæggende information om din addon, så som id, navn, version, mv. (Linje 2-6).

XML’en beskriver også hvilken Python fil XBMC skal kalde for at eksekvere filen. (Linje 11)

Der er også information om addon’ens navn, beskrivelse, mv. Det er muligt at oversætte disse felter vha. lang=”da” og lign. (Linje 15-18). Hvis du vælger ikke at oversætte din addon, kan du undlade lang attributten i disse tags.

VideoVideo HD addon’en bruger JSON da det er dette format videovideo.dk leverer information i. Derfor har vi brug for et ekstra script modul da Python ikke har indbygget support for dette. (Linje 9)

Python koden

import sys
import urllib2
import simplejson

import xbmcaddon
import xbmcgui
import xbmcplugin

class VideoVideoHD(object):
    ADDON = xbmcaddon.Addon(id = 'plugin.video.videovideo.dk')
    INDEX_URL = 'http://videovideo.dk/index/json/'
    SHOWS_URL = 'http://videovideo.dk/shows/json/'

    def showOverview(self):
        shows = simplejson.loads(self.downloadUrl(self.SHOWS_URL))

        if self.ADDON.getSetting('show.teasers') == 'true':
            teasers = simplejson.loads(self.downloadUrl(self.INDEX_URL))
        else:
            teasers = None

        for show in shows:
            item = xbmcgui.ListItem(show['title'], iconImage = show['image'])
            item.setInfo(type = 'video', infoLabels = {
                'title' : show['title'],
                'plot' : show['description']
            })
            item.setProperty('Fanart_Image', show['imagefull'])
            url = PATH + '?' + show['url']
            xbmcplugin.addDirectoryItem(HANDLE, url, item, True)

        if teasers is not None:
            for teaser in teasers:
                item = xbmcgui.ListItem(teaser['headline'], iconImage = teaser['image'])
                item.setInfo(type = 'video', infoLabels = {
                    'title' : teaser['headline'],
                    'plot' : teaser['text'],
                    'duration' : teaser['episode']['duration']
                })
                item.setProperty('Fanart_Image', teaser['episode']['imagefull'])
                url = teaser['episode']['distributions']['720']
                xbmcplugin.addDirectoryItem(HANDLE, url, item, False)

        xbmcplugin.endOfDirectory(HANDLE)

    def showShow(self, url):
        episodes = simplejson.loads(self.downloadUrl(url))
        for episode in episodes:
            item = xbmcgui.ListItem(episode['title'], iconImage = episode['image'])

            day = episode['timestamp'][8:10]
            month = episode['timestamp'][5:7]
            year = episode['timestamp'][0:4]

            date = '%s.%s.%s' % (day, month, year)
            aired = '%s-%s-%s' % (year, month, day)

            infoLabels = {
                'title' : episode['title'],
                'plot' : episode['shownotes'],
                'date' : date,
                'aired' : aired,
                'year' : int(year),
                'duration' : episode['duration']
            }
            item.setInfo('video', infoLabels)
            item.setProperty('Fanart_Image', episode['imagefull'])
            url = episode['distributions']['720']
            xbmcplugin.addDirectoryItem(HANDLE, url, item, False)

        xbmcplugin.addSortMethod(HANDLE, xbmcplugin.SORT_METHOD_DATE)
        xbmcplugin.endOfDirectory(HANDLE)

    def downloadUrl(self, url):
        """
        Retrieves and returns the contents of the provided url.
        Errors are not handled.

        @param self: VideoVideoHD instance
        @param url: the url to retrieve
        @return: the contents of the url
        """
        u = urllib2.urlopen(url)
        response = u.read()
        u.close()

        return response

if __name__ == '__main__':
    vvd = VideoVideoHD()

    # XBMC provides three values in the sys.argv array, such as:
    # ['plugin://plugin.video.videovideo.dk/', '0', '']
    # Copy the values to meaningful variables
    PATH = sys.argv[0]
    HANDLE = int(sys.argv[1])
    PARAMS = sys.argv[2]

    if PARAMS != '':
        vvd.showShow(PARAMS[1:]) # remove ?
    else:
        vvd.showOverview()

For VideoVideo HD addon’en er al Python koden gemt i en enkelt fil, da der ikke kræves vældig meget logik. Det er selvfølgelige muligt at splittet koden uden i flere filer præcis som Python tillader.

Input

For denne addon har jeg valgt at oprette en class der indeholder al logikken, men det er også frit for udvikleren. Det vigtige stumper i forhold til integrationen i XBMC er linje 95-97, hvor vi behandler input parametre fra XBMC, samt fx linje 34-44, hvor vi sender indhold tilbage til XBMC.

  • PATH er XBMC’s plugin:// sti til denne addon. Det skal bruges hvis addon’en skal kalde sig selv, fx når der skal navigeres til en undermappe eller lign.
  • HANDLE er dette addon kalds ID, som skal bruges når vi sender indehold tilbage til XBMC .
  • PARAMS er en streng der indeholder de parametre vi sendte fra et tidligere kald, fx. ved navigation til undermappe.

I dette tilfælde er logikken simpel. Hvis der ingen parametre er vist vi oversigten (Overview) eller parse vi en URL ud af PARAMS og bruger den til at vise et Show.

Output

For at få data tilbage til XBMC benytter vi de indbyggede xbmcplugin og xbmcgui Python moduler. For hvert videoklip der skal vises på en side opretter vi et ListItem med en titel og et ikon. Vi sætter også en række oplysninger vha. setInfo(). Disse oplysninger kan bl.a. vises i liste- og infovisning i XBMC afhængig af skin.

Når vores ListItem er klar tilføjer vi det til XBMC ved at kalde addDirectoryItem(). URL’en der gives med kan enten være tilbage til addon’en (linje 29) eller direkte til et videoklip (linje 41).

Tilsidst når alle ListItem er tilføjet giver vi XBMC besked på at vi er færdige ved at kalde endOfDirectory(). Dette er typisk det sidste vi gør i et kald til vores addon.

Addon indstillinger og internationalization

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<settings>
	<category label="30000">
        <setting id="show.teasers" label="30001" type="bool" default="true" />
	</category>
</settings>

I nogle tilfælde er det praktisk at kunne give brugeren mulighed for at tilpasse sin addon vha. indstillinger. Det kunne fx være for at gemme oplysninger om login til den webside videoklippene afspilles fra, oplysninger om streaming hastighed eller lign. I dette tilfælde har jeg lavet en simpel indstillinger der bestemmer om teasers videoklip skal vises eller ej. Det er muligt at have en række forskellige indstillinges typer, hvor brugeren kan indtaste tal, tekst, vælge mellem flere foruddefinerede værdier, sandt/falsk, etc.

Opsætning af tilgængelige indstillinger gemmes i filen settings.xml som gemmes i resources mappen som en undermappen til addonens mappe.

Internationalization

I XBMC er det muligt at vælge hvilke menusprog man ønsker at bruge og dette valg har også indflydelse på addons. For at oversætte tekster oprettes en strings.xml under /resources/language/<sprog>/ – hvor <sprog> fx en Danish, English eller lign.

<?xml version="1.0" encoding="utf-8"?>
<strings>
    <string id="30000">Generelt</string>
    <string id="30001">Vis teasers nedenunder shows</string>
</strings>

Indstillingerne ovenfor er oversat vha. af XML’en herover. Dvs. label=”30000″ i indstillinger referer til string id=”30000″ herover og gør i praksis det at indstillings kategorien får teksten Generelt.

Det er også muligt at hente oversættelses teksterne i Python kode ved at kalde getLocalizedString() på et Addon objekt fra xbmcaddon Python modulet.

Afslutning

Dette var en grundlæggende introduktion til video addons i XBMC. Savner du mere information, uddybning eller har du bare en kommentar, så er du velkommen til at skrive herunder. Til sidst er der et par links til eksterne ressourcer, som jeg jævnligt bruger mens jeg arbejder på min addons.

Eksterne links

10 Comments

  1. Hej Tommy

    Det var rigtig nyttig info, tak skal du have. Det gør det noget nemmere at komme i gang for os andre.

    Jeg har også stor fornøjelse af dine addons 🙂

    Mvh Poul Anker

  2. Hi Tommy, I just found out you wrote the very nice danish live tv-plugin. I’m a student of media informatics at HS Osnabrück. I’m trying to create an xbmc-livestreaming-plugin too, but there’s one big problem we have at the moment and I thought perhaps you could give me a hint 😉 We’re streaming content over hls ( http://en.wikipedia.org/wiki/HTTP_Live_Streaming ) … stream is working nearly fine, audio/video okay, but… the stream in xbmc stops after a couple of seconds or minutes (varying, sometimes when the “bar” has loaded completely). The source is okay, the connection too. It has to be a problem with xbmc. It will be great if you could help me here. Perhaps you could write me a Mail for more details?

    Best Regards, Dominik

  3. Hi Dominik,
    Yeah, I also have various degrees of success with HLS streaming. Sometimes it works and sometimes it don’t. I don’t think the support in XBMC is complete.
    You might have more success if you compile the master branch of XBMC from github – they recently upgraded the ffmpeg libraries, so it might have better support for HLS.
    Br.
    Tommy

  4. Hej tommy
    Kan du lave en video om lave addons til xbmc

  5. Hi Tommy,

    our approach at the moment is to compile from sources with the pull request 107 from ctpspiff. It works like a charm 🙂 What’s not working is getting in contact to the xbmc team (we try it “the official way”, working as students for a online live tv provider with more than 10 million users 😉 ) – after 2 months, not a single response, mail, forum, irc, nothing :/

    But recently we ran into another little problem and I hope you could help us. We got our streams working and everything, but the “user” (as we’re using scrum in our project), while streaming, don’t want the progress bar from the controls to move (sorry, I hope you understand what I mean). We looked into your plugin and saw you’d set a property called “IsLive”. We didn’t find any documentation about this property and when we added it to our plugin code, it didn’t do anything.
    Perhaps we’re overlooking something? Would be glad to hear from you 🙂

    Greetings,
    Dominik

  6. Hi Dominik,
    The “IsLive” is info for the rtmp:// playback, so you can ignore that.
    I think what you want is to hide the progressbar that displays how far along the videoclip has played. I don’t think that is possible, perhaps you can do it by modifing the skin..
    Br.
    Tommy

  7. hej tommy når man skal lave en addon så nå jeg til default.py og der er tal fra 1 til 106,
    så min spørgsmål er hvor må lave om på teksten og hvad skal man skrive, tak
    Svar

  8. Tak for gennemgangen! Den er lysår bedre end hvad jeg fandt inde på XMBC’s egen wiki – det er jo slet ikke svært at lave plug-ins, når bare man får den rigtige introduktion.

  9. Findes der ikke en ikke-flinker-neutral, bredt instruktiv manual derude? Og hvis nogen vedligeholder et addon med i hundrevis af links, må der være nogle genveje til vedligeholdelse?

  10. Jeg forstår simpelthen ikke hvad det er du skriver!
    Hvis du kan programmere er der ikke så meget hokus pokus i at lave en addon. Jeg har opsummeret de grundlæggende trin.
    Kan du ikke programmere skal du nok starte med et “Hello World” eksempel.. Google er din ven.
    Mvh.
    Tommy

Skriv et svar

Your email address will not be published.

*

© 2017 Tommy Winther

Theme by Anders NorenUp ↑