Kodi Tutorial: Create Music Addon

Thursday 27th September 2018 10:34pm

This tutorial will describe how to create a music addon for the popular Kodi media center. The addon we will be creating will recieve streams from the radio station Absolute Radio. Absolute Radio is one of the UK’s Independent National Radio stations. The station is based in London and plays popular rock music broadcasting on medium wave and DAB across the UK (105.8 FM in London and 105.2 FM in the West Midlands). It is also available in other parts of the world via satellite, cable, and on the Internet.

Absolute Radio conveniently offers the following links to their audio streams.

This addon will use these links to stream content from the following stations:

To add a bit of eye candy this addon will monitor the content of the stream searching for the current artist and display an image in the background when found. We will also allow the option for showing a detailed description of the current artist.

There will be a page for changing the default settings like the type and quality of the stream and the option to disable searching for artist information.

When creating addons for Kodi one of the most useful resources at your disposal is Kodi’s Add-0n Developement page

Let’s Begin 🙂

We need to achieve the following tasks:

 

Create Folders

We first need to create a subfolder within the .kodi\addons folder. The name of this folder will be plugin.audio.absolute-radio. This is where we will write the code. Next we need to create a subfolder within this folder called resources. This folder will contain the icon and fanart images along with the default settings. Within the resources folder we will need to create four further folders named language, libmedia and skin. These will contain language options, extra code modules used by the addon, icons and fanart for menu options. The skin folder will contain two custom layouts for displaying the artist image and description screens.

 

Language Settings

We now have only one more folder to create. The folder for the language settings so open the language folder and create a subfolder called resource.language.en_gb. This is where we will store all the text settings used by our addon in the english language along with a unique number. We will refer to this number within the addon whenever we need to use that text so we can translate the text into another language.

Create a file called strings.po and insert the following:

# Kodi Media Center language file
# Addon Name: Absolute Radio
# Addon id: plugin.audio.absolute-radio
# Addon Provider: Phantom Raspberry Blower
msgid ""
msgstr ""
"Project-Id-Version: Kodi Addons\n"
"Report-Msgid-Bugs-To: alanwww1@kodi.org\n"
"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Kodi Translation Team\n"
"Language-Team: English (http://www.transifex.com/projects/p/xbmc-addons/language/en/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: en\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"

# General settings
msgctxt "#30001"
msgid "General"
msgstr ""

msgctxt "#30002"
msgid "Sound Quality"
msgstr ""

msgctxt "#30003"
msgid "Hide Artist Fanart"
msgstr ""

msgctxt "#30004"
msgid "Compression Format"
msgstr ""

msgctxt "#30005"
msgid "Bitrate"
msgstr ""

msgctxt "#30006"
msgid "Account Details"
msgstr ""

msgctxt "#30007"
msgid "Username"
msgstr ""

msgctxt "#30008"
msgid "Password"
msgstr ""

msgctxt "#30009"
msgid "Hide Artist Information"
msgstr ""

# Language strings

msgctxt "#30101"
msgid "Absolute Radio"
msgstr ""

msgctxt "#30102"
msgid "Status"
msgstr ""

msgctxt "#30103"
msgid "Unplayable stream"
msgstr ""

msgctxt "#30104"
msgid "Something wicked happened :("
msgstr ""

msgctxt "#30105"
msgid "Error!"
msgstr ""

msgctxt "#30106"
msgid "Please wait..."
msgstr ""

msgctxt "#30107"
msgid "Process Complete"
msgstr ""

msgctxt "#30108"
msgid "Failed to download audio links"
msgstr ""

msgctxt "#30109"
msgid "Logged into Absolute Radio."
msgstr ""

msgctxt "#30110"
msgid "Login Success :)"
msgstr ""

msgctxt "#30111"
msgid "Failed to log into Absolute Radio."
msgstr ""

msgctxt "#30112"
msgid "Login Failed :("
msgstr ""

msgctxt "#30113"
msgid "Artist Information"
msgstr ""

msgctxt "#30114"
msgid "Unable to download artists info!"
msgstr ""

msgctxt "#30115"
msgid "No artist name showing!"
msgstr ""

msgctxt "#30116"
msgid "No audio stream is playing!"
msgstr ""

msgctxt "#30117"
msgid "Settings"
msgstr ""

msgctxt "#30118"
msgid "It's all about the song, not the video; gigs, not photo shoots; built to last, not flavour of the month; something to say, not something to sell."
msgstr ""

msgctxt "#30119"
msgid "Nothing but the biggest rock classics from the likes of Led Zeppelin, The Who, The Rolling Stones, Queen and more."
msgstr ""

msgctxt "#30120"
msgid "We're going to take you on a nostalgia trip, playing undisputed 1980s classics from Madonna, Duran Duran, Prince, Pet Shop Boys, Michael Jackson, Depeche Mode, U2, The Human League, The Police and more."
msgstr ""

msgctxt "#30121"
msgid "Timeless classics from the likes of The Beatles, Stones, Doors, Jimi Hendrix, The Who, Aretha Franklin, Elvis and the Kinks. The impact of this music is still being felt by the new artists of today."
msgstr ""

msgctxt "#30122"
msgid "The decade of pop, soul, disco and rock from the likes of Rod Stewart, Stevie Wonder, Elton John, Marvin Gaye, Slade, T. Rex, The Jackson 5, Blondie, ABBA, David Bowie and more."
msgstr ""

msgctxt "#30123"
msgid "Undisputed classics stretching from Oasis and Blur, to Primal Scream and the Chemical Brothers. Whether it's Radiohead or Portishead, the Stone Roses or Guns 'n Roses, we'll be playing all the best music you remember from the 90s and none of the duds."
msgstr ""

msgctxt "#30124"
msgid "The best of the noughties, from Coldplay to Keane, Eminem to Elbow, Arctic Monkeys to Amy Winehouse, Scissor Sisters to The Streets and Kings of Leon to The Killers."
msgstr ""

msgctxt "#30125"
msgid "Displays image and information about the artist or band currently streaming."
msgstr ""

msgctxt "#30126"
msgid "Open settings."
msgstr ""

 

Artist Information

We now need to go back to the resources folder and open the lib folder and create a empty file with the name __init__.py allowing the folder to be discovered. Next create another file called artistinfo.py and insert the following:


import urllib2
import json

# Written by: Phantom Raspberry Blower
# Date: 21-02-2017
# Description: Module for downloading information about music artists

FANART_URL = 'http://www.theaudiodb.com/api/v1/json/1/search.php?s='
#FANART_URL = 'https://www.theaudiodb.com/api/195003/json/1/search.php?s='


class ArtistInfo:

    def __init__(self, artist=None):
        self.clear()
        self.__artist_name = artist
        print str(artist)
        if artist is not None:
            self.get_artist_info(artist)
            print str(artist)

    @property
    def artist_id(self):
        return self.__artist_id

    @property
    def artist_name(self):
        return self.__artist_name

    @property
    def artist_aka(self):
        return self.__artist_aka

    @property
    def rec_label(self):
        return self.__rec_label

    @property
    def rec_label_id(self):
        return self.__rec_label_id

    @property
    def year_formed(self):
        return self.__year_formed

    @property
    def year_born(self):
        return self.__year_born

    @property
    def year_died(self):
        return self.__year_died

    @property
    def disbanded(self):
        return self.__disbanded

    @property
    def style(self):
        return self.__style

    @property
    def genre(self):
        return self.__genre

    @property
    def mood(self):
        return self.__mood

    @property
    def website(self):
        return self.__website

    @property
    def facebook(self):
        return self.__facebook

    @property
    def twitter(self):
        return self.__twitter

    @property
    def biography_en(self):
        return self.__biography_en

    @property
    def biography_de(self):
        return self.__biography_de

    @property
    def biography_fr(self):
        return self.__biography_fr

    @property
    def biography_cn(self):
        return self.__biography_cn

    @property
    def biography_it(self):
        return self.__biography_it

    @property
    def biography_jp(self):
        return self.__biography_jp

    @property
    def biography_ru(self):
        return self.__biography_ru

    @property
    def biography_es(self):
        return self.__biography_es

    @property
    def biography_pt(self):
        return self.__biography_pt

    @property
    def biography_se(self):
        return self.__biography_se

    @property
    def biography_nl(self):
        return self.__biography_nl

    @property
    def biography_hu(self):
        return self.__biography_hu

    @property
    def biography_no(self):
        return self.__biography_np

    @property
    def biography_il(self):
        return self.__biography_il

    @property
    def biography_pl(self):
        return self.__biography_pl

    @property
    def gender(self):
        return self.__gender

    @property
    def members(self):
        return self.__members

    @property
    def country(self):
        return self.__country

    @property
    def country_code(self):
        return self.__country_code

    @property
    def artist_thumb(self):
        return self.__artist_thumb

    @property
    def artist_logo(self):
        return self.__artist_logo

    @property
    def fanart(self):
        return self.__fanart

    @property
    def fanart2(self):
        return self.__fanart2

    @property
    def fanart3(self):
        return self.__fanart3

    @property
    def banner(self):
        return self.__banner

    @property
    def music_brainz_id(self):
        return self.__music_brainz_id

    @property
    def last_fm_chart(self):
        return self.__last_fm_chart

    @property
    def locked(self):
        return self.__locked

    def clear(self):
        self.__artist_id = None
        self.__artist_name = None
        self.__artist_aka = None
        self.__rec_label = None
        self.__rec_label_id = None
        self.__year_formed = None
        self.__year_born = None
        self.__year_died = None
        self.__disbanded = None
        self.__style = None
        self.__genre = None
        self.__mood = None
        self.__website = None
        self.__facebook = None
        self.__twitter = None
        self.__biography_en = None
        self.__biography_de = None
        self.__biography_fr = None
        self.__biography_cn = None
        self.__biography_it = None
        self.__biography_jp = None
        self.__biography_ru = None
        self.__biography_es = None
        self.__biography_pt = None
        self.__biography_se = None
        self.__biography_nl = None
        self.__biography_hu = None
        self.__biography_no = None
        self.__biography_il = None
        self.__biography_pl = None
        self.__gender = None
        self.__members = None
        self.__country = None
        self.__country_code = None
        self.__artist_thumb = None
        self.__artist_logo = None
        self.__fanart = None
        self.__fanart2 = None
        self.__fanart3 = None
        self.__banner = None
        self.__fanart = None
        self.__music_brainz_id = None
        self.__last_fm_chart = None
        self.locked = None

    def _common_tasks(self, url):
        """
        Download url and return result
        """
#        req = urllib2.urlopen(url)
        hdr = {'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.64 Safari/537.11',
               'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
               'Accept-Charset': 'ISO-8859-1,utf-8;q=0.7,*;q=0.3',
               'Accept-Encoding': 'none',
               'Accept-Language': 'en-US,en;q=0.8',
               'Connection': 'keep-alive'}
        req = urllib2.Request(url, headers=hdr)
        response = urllib2.urlopen(req)
        json_string = response.read()
        parsed_json = json.loads(json_string)
        return parsed_json

    def _url_encode(self, text):
        """
        Encode html ie. %26 = &
        """
        return (urllib2.quote(text.replace('and', '&'), safe='')
                .replace('%20', '+'))

    def _artist_info(self, artist):
        """
        Parse artist information and return a dictionary of items
        """
        url = (FANART_URL + self._url_encode(artist))
        try:
            parsed_json = self._common_tasks(url)
            self.__artist_id = parsed_json['artists'][0]['idArtist']
            self.__artist_name = parsed_json['artists'][0]['strArtist']
            self.__artist_aka = parsed_json['artists'][0]['strArtistAlternate']
            self.__rec_label = parsed_json['artists'][0]['strLabel']
            self.__rec_label_id = parsed_json['artists'][0]['idLabel']
            self.__year_formed = parsed_json['artists'][0]['intFormedYear']
            self.__year_born = parsed_json['artists'][0]['intBornYear']
            self.__year_died = parsed_json['artists'][0]['intDiedYear']
            self.__disbanded = parsed_json['artists'][0]['strDisbanded']
            self.__style = parsed_json['artists'][0]['strStyle']
            self.__genre = parsed_json['artists'][0]['strGenre']
            self.__mood = parsed_json['artists'][0]['strMood']
            self.__website = parsed_json['artists'][0]['strWebsite']
            self.__facebook = parsed_json['artists'][0]['strFacebook']
            self.__twitter = parsed_json['artists'][0]['strTwitter']
            self.__biography_en = parsed_json['artists'][0]['strBiographyEN']
            if self.__biography_en is not None:
                self.__biography_en = (parsed_json['artists'][0]
                                       ['strBiographyEN'].encode('utf-8'))
            self.__biography_de = parsed_json['artists'][0]['strBiographyDE']
            if self.__biography_de is not None:
                self.__biography_de = (parsed_json['artists'][0]
                                       ['strBiographyDE'].encode('utf-8'))
            self.__biography_fr = parsed_json['artists'][0]['strBiographyFR']
            if self.__biography_fr is not None:
                self.__biography_fr = (parsed_json['artists'][0]
                                       ['strBiographyFR'].encode('utf-8'))
            self.__biography_cn = parsed_json['artists'][0]['strBiographyCN']
            if self.__biography_cn is not None:
                self.__biography_cn = (parsed_json['artists'][0]
                                       ['strBiographyCN'].encode('utf-8'))
            self.__biography_it = parsed_json['artists'][0]['strBiographyIT']
            if self.__biography_it is not None:
                self.__biography_it = (parsed_json['artists'][0]
                                       ['strBiographyIT'].encode('utf-8'))
            self.__biography_jp = parsed_json['artists'][0]['strBiographyJP']
            if self.__biography_jp is not None:
                self.__biography_jp = (parsed_json['artists'][0]
                                       ['strBiographyJP'].encode('utf-8'))
            self.__biography_ru = parsed_json['artists'][0]['strBiographyRU']
            if self.__biography_ru is not None:
                self.__biography_ru = (parsed_json['artists'][0]
                                       ['strBiographyRU'].encode('utf-8'))
            self.__biography_es = parsed_json['artists'][0]['strBiographyES']
            if self.__biography_es is not None:
                self.__biography_es = (parsed_json['artists'][0]
                                       ['strBiographyES'].encode('utf-8'))
            self.__biography_pt = parsed_json['artists'][0]['strBiographyPT']
            if self.__biography_pt is not None:
                self.__biography_pt = (parsed_json['artists'][0]
                                       ['strBiographyPT'].encode('utf-8'))
            self.__biography_se = parsed_json['artists'][0]['strBiographySE']
            if self.__biography_se is not None:
                self.__biography_se = (parsed_json['artists'][0]
                                       ['strBiographySE'].encode('utf-8'))
            self.__biography_nl = parsed_json['artists'][0]['strBiographyNL']
            if self.__biography_nl is not None:
                self.__biography_nl = (parsed_json['artists'][0]
                                       ['strBiographyNL'].encode('utf-8'))
            self.__biography_hu = parsed_json['artists'][0]['strBiographyHU']
            if self.__biography_hu is not None:
                self.__biography_hu = (parsed_json['artists'][0]
                                       ['strBiographyHU'].encode('utf-8'))
            self.__biography_no = parsed_json['artists'][0]['strBiographyNO']
            if self.__biography_no is not None:
                self.__biography_no = (parsed_json['artists'][0]
                                       ['strBiographyNO'].encode('utf-8'))
            self.__biography_il = parsed_json['artists'][0]['strBiographyIL']
            if self.__biography_il is not None:
                self.__biography_il = (parsed_json['artists'][0]
                                       ['strBiographyIL'].encode('utf-8'))
            self.__biography_pl = parsed_json['artists'][0]['strBiographyPL']
            if self.__biography_pl is not None:
                self.__biography_pl = (parsed_json['artists'][0]
                                       ['strBiographyPL'].encode('utf-8'))
            self.__gender = parsed_json['artists'][0]['strGender']
            self.__members = parsed_json['artists'][0]['intMembers']
            self.__country = parsed_json['artists'][0]['strCountry']
            self.__country_code = parsed_json['artists'][0]['strCountryCode']
            self.__artist_thumb = parsed_json['artists'][0]['strArtistThumb']
            self.__artist_logo = parsed_json['artists'][0]['strArtistLogo']
            self.__fanart = parsed_json['artists'][0]['strArtistFanart']
            self.__fanart2 = parsed_json['artists'][0]['strArtistFanart2']
            self.__fanart3 = parsed_json['artists'][0]['strArtistFanart3']
            self.__banner = parsed_json['artists'][0]['strArtistBanner']
            self.__music_brainz_id = (parsed_json['artists'][0]
                                      ['strMusicBrainzID'])
            self.__last_fm_chart = parsed_json['artists'][0]['strLastFMChart']
            self.__locked = parsed_json['artists'][0]['strLocked']
            return {'artist_id': self.__artist_id,
                    'artist': self.__artist_name,
                    'artist_aka': self.__artist_aka,
                    'rec_label': self.__rec_label,
                    'rec_label_id': self.__rec_label_id,
                    'year_formed': self.__year_formed,
                    'year_born': self.__year_born,
                    'year_died': self.__year_died,
                    'disbanded': self.__disbanded,
                    'style': self.__style,
                    'genre': self.__genre,
                    'mood': self.__mood,
                    'website': self.__website,
                    'facebook': self.__facebook,
                    'twitter': self.__twitter,
                    'biography_en': self.__biography_en,
                    'biography_de': self.__biography_de,
                    'biography_fr': self.__biography_fr,
                    'biography_cn': self.__biography_cn,
                    'biography_it': self.__biography_it,
                    'biography_jp': self.__biography_jp,
                    'biography_ru': self.__biography_ru,
                    'biography_es': self.__biography_es,
                    'biography_pt': self.__biography_pt,
                    'biography_se': self.__biography_se,
                    'biography_nl': self.__biography_nl,
                    'biography_hu': self.__biography_hu,
                    'biography_no': self.__biography_no,
                    'biography_il': self.__biography_il,
                    'biography_pl': self.__biography_pl,
                    'gender': self.__gender,
                    'members': self.__members,
                    'country': self.__country,
                    'country_code': self.__country_code,
                    'artist_thumb': self.__artist_thumb,
                    'artist_logo': self.__artist_logo,
                    'fanart': self.__fanart,
                    'fanart2': self.__fanart2,
                    'fanart3': self.__fanart3,
                    'banner': self.__banner,
                    'music_brainz_id': self.__music_brainz_id,
                    'last_fm_chart': self.__last_fm_chart,
                    'locked': self.__locked
                    }
        except:
            return 0

    def get_artist_info(self, artist):
        """
        Return artist information as dictionary. If no result try either
        removing or prefixing the 'the' word.
        """
        self.clear()
        ai = self._artist_info(artist)
        if ai != 0:
            return ai
        else:
            if 'the ' in artist:
                ai = self._artist_info(artist.replace('the ', ''))
            else:
                ai = self._artist_info('the ' + artist)
            if ai == 0:
                if 'junior' in artist:
                  ai = self._artist_info(artist.replace('junior', 'jr.'))
                if ai == 0:
                  if '-' in artist:
                    ai = self._artist_info(artist.replace('-', ' '))
        return ai

The above code takes an artist name as input and returns all the information about that artist including links to images from the website www.theaudiodb.com

 

Images

We now need to go back to the resources folder and open the media folder. This folder contains all the icons and fanart images used by the addon along with any screenshot images of the program once complete. Download the following images and save into this folder.

Icons

Fanart

 

Skins

We now need to go back to the resources folder and open the skin folder. Now create a subfolder called Default, next create another subfolder in the Default folder called 720p. This folder contains the layout for two screens; one shows the image of the current artist the other shows a detailed description of the artist.

Create a new file called plugin-music-visualisation.xml and insert the following:

<?xml version="1.0" encoding="UTF-8"?>
<window>
	<defaultcontrol>-</defaultcontrol>
	<controls>
		<control type="visualisation" id="2">
			<!-- FIX ME Music Visualization needs to have an id of 2 in this window to be able to lock or change preset -->
			<description>visualisation</description>
			<left>0</left>
			<top>0</top>
			<width>1280</width>
			<height>720</height>
		</control>
		<control type="image">
			<description>Fanart Image for Artist</description>
			<left>0</left>
			<top>0</top>
			<width>1280</width>
			<height>720</height>
			<aspectratio>scale</aspectratio>
			<!--<texture background="true">$INFO[Player.Art(fanart)]</texture>-->
			<texture background="true">$INFO[Window(12006).Property(ArtistFanart)]</texture>
			<visible>!String.IsEmpty(Player.Art(fanart)) + !Skin.HasSetting(HideVisualizationFanart)</visible>
			<fadetime>600</fadetime>
		</control>
		<!-- media infos -->
		<control type="group">
			<depth>DepthOSD</depth>
			<animation effect="fade" time="150">VisibleChange</animation>
			<visible>[Player.ShowInfo | Window.IsActive(MusicOSD)] + ![Window.IsVisible(AddonSettings) | Window.IsVisible(SelectDialog) | Window.IsVisible(VisualisationPresetList) | Window.IsVisible(PVROSDChannels) | Window.IsVisible(PVROSDGuide) | Window.IsVisible(PVRRadioRDSInfo) | Window.IsVisible(OSDAudioDSPSettings) | Window.IsVisible(Addon)]</visible>
			<control type="image">
				<left>-20</left>
				<top>-150</top>
				<width>1320</width>
				<height>256</height>
				<texture flipy="true" border="1">HomeNowPlayingBack.png</texture>
			</control>
			<control type="label">
				<description>Partymode Header label</description>
				<left>30</left>
				<top>5</top>
				<width>800</width>
				<height>25</height>
				<align>left</align>
				<aligny>center</aligny>
				<font>font13</font>
				<textcolor>white</textcolor>
				<shadowcolor>black</shadowcolor>
				<label>$LOCALIZE[589]</label>
				<visible>MusicPartyMode.Enabled</visible>
			</control>
			<control type="label">
				<description>Normal Header label</description>
				<left>30</left>
				<top>5</top>
				<width>800</width>
				<height>25</height>
				<align>left</align>
				<aligny>center</aligny>
				<font>font13</font>
				<textcolor>white</textcolor>
				<shadowcolor>black</shadowcolor>
				<label>$INFO[musicplayer.Playlistposition,$LOCALIZE[554]: ]$INFO[musicplayer.Playlistlength, / ]</label>
				<visible>!MusicPartyMode.Enabled</visible>
			</control>
			<control type="label">
				<description>Clock label</description>
				<left>450</left>
				<top>5</top>
				<width>800</width>
				<height>25</height>
				<align>right</align>
				<aligny>center</aligny>
				<font>font13</font>
				<textcolor>white</textcolor>
				<shadowcolor>black</shadowcolor>
				<label>$INFO[System.Time]</label>
				<animation effect="slide" start="0,0" end="-30,0" time="0" condition="Player.Muted">conditional</animation>
				<animation effect="slide" start="0,0" end="-70,0" time="0" condition="system.getbool(input.enablemouse) + Window.IsVisible(MusicOSD)">conditional</animation>
			</control>
			<control type="image">
				<left>-20</left>
				<top>230r</top>
				<width>1320</width>
				<height>230</height>
				<texture border="1">HomeNowPlayingBack.png</texture>
			</control>
			<control type="image">
				<depth>DepthOSDPopout</depth>
				<description>cover image</description>
				<left>20</left>
				<top>250r</top>
				<width>300</width>
				<height>230</height>
				<texture fallback="DefaultAlbumCover.png">$INFO[Player.Art(thumb)]</texture>
				<aspectratio aligny="bottom">keep</aspectratio>
				<bordertexture border="8">ThumbShadow.png</bordertexture>
				<bordersize>8</bordersize>
			</control>
			<control type="group">
				<left>330</left>
				<top>185r</top>
				<control type="label" id="1">
					<description>Heading label</description>
					<left>0</left>
					<top>0</top>
					<width>910</width>
					<height>25</height>
					<align>left</align>
					<font>font13</font>
					<label>$LOCALIZE[31040]</label>
					<textcolor>white</textcolor>
					<shadowcolor>black</shadowcolor>
					<animation effect="slide" start="0,0" end="0,25" time="0" condition="String.IsEmpty(MusicPlayer.Artist) + String.IsEmpty(MusicPlayer.Album)">conditional</animation>
				</control>
				<control type="label" id="1">
					<description>Artist label</description>
					<left>20</left>
					<top>30</top>
					<width>910</width>
					<height>25</height>
					<align>left</align>
					<font>font12</font>
					<label>$INFO[MusicPlayer.Artist]$INFO[MusicPlayer.Album, - ]</label>
					<textcolor>grey2</textcolor>
					<shadowcolor>black</shadowcolor>
				</control>
				<control type="grouplist">
					<left>20</left>
					<top>62</top>
					<width>910</width>
					<height>35</height>
					<itemgap>5</itemgap>
					<orientation>horizontal</orientation>
					<visible>!system.getbool(audiooutput.dspaddonsenabled)</visible>
					<control type="label">
						<width min="10" max="638">auto</width>
						<height>20</height>
						<font>font30</font>
						<align>left</align>
						<aligny>center</aligny>
						<label>$INFO[Player.Title]</label>
						<textcolor>orange</textcolor>
						<scroll>true</scroll>
					</control>
					<control type="image">
						<description>Audio Codec Image</description>
						<width>81</width>
						<height>29</height>
						<texture>$INFO[MusicPlayer.Codec,flagging/audio/,.png]</texture>
						<visible>!Player.ChannelPreviewActive</visible>
					</control>
					<control type="group">
						<description>Rating</description>
						<width>172</width>
						<height>29</height>
						<control type="image">
							<description>rating back</description>
							<left>0</left>
							<top>0</top>
							<width>172</width>
							<height>29</height>
							<texture border="5">flagging/blank.png</texture>
						</control>
						<control type="image">
							<description>User Rating</description>
							<left>2</left>
							<top>5</top>
							<width>168</width>
							<height>21</height>
							<texture fallback="ratings/0.png">$INFO[MusicPlayer.UserRating,ratings/,.png]</texture>
						</control>
					</control>
				</control>

				<control type="grouplist">
					<left>20</left>
					<top>60</top>
					<width>910</width>
					<height>35</height>
					<itemgap>5</itemgap>
					<orientation>horizontal</orientation>
					<visible>system.getbool(audiooutput.dspaddonsenabled)</visible>
					<control type="label">
						<width min="10" max="557">auto</width>
						<height>20</height>
						<font>font30</font>
						<align>left</align>
						<aligny>center</aligny>
						<label>$INFO[Player.Title]</label>
						<textcolor>orange</textcolor>
						<scroll>true</scroll>
					</control>
					<control type="image">
						<description>ADSP Master Mode Image</description>
						<width>81</width>
						<height>29</height>
						<visible>![String.IsEmpty(ADSP.MasterOwnIcon) | Player.ChannelPreviewActive]</visible>
						<texture>$INFO[ADSP.MasterOwnIcon]</texture>
					</control>
					<control type="image">
						<description>Audio Codec Image</description>
						<width>81</width>
						<height>29</height>
						<visible>String.IsEmpty(ADSP.MasterOverrideIcon) + !Player.ChannelPreviewActive</visible>
						<texture>$INFO[MusicPlayer.Codec,flagging/audio/,.png]</texture>
					</control>
					<control type="image">
						<description>ADSP Audio Codec Override Image</description>
						<width>81</width>
						<height>29</height>
						<visible>![String.IsEmpty(ADSP.MasterOwnIcon) | Player.ChannelPreviewActive]</visible>
						<texture>$INFO[ADSP.MasterOverrideIcon]</texture>
					</control>
					<control type="group">
						<description>Rating</description>
						<width>172</width>
						<height>29</height>
						<control type="image">
							<description>rating back</description>
							<left>0</left>
							<top>0</top>
							<width>172</width>
							<height>29</height>
							<texture border="5">flagging/blank.png</texture>
						</control>
						<control type="image">
							<description>User Rating</description>
							<left>2</left>
							<top>5</top>
							<width>168</width>
							<height>21</height>
							<texture fallback="ratings/0.png">$INFO[MusicPlayer.UserRating,ratings/,.png]</texture>
						</control>
					</control>
				</control>
				<control type="label">
					<left>0</left>
					<top>120</top>
					<width>910</width>
					<height>25</height>
					<label>$LOCALIZE[19031]: $INFO[MusicPlayer.offset(1).Artist,, - ]$INFO[MusicPlayer.offset(1).Title]</label>
					<align>center</align>
					<aligny>center</aligny>
					<font>font12</font>
					<textcolor>grey</textcolor>
					<scroll>true</scroll>
					<visible>MusicPlayer.HasNext + !Window.IsVisible(MusicOSD)</visible>
					<animation effect="fade" time="150">VisibleChange</animation>
				</control>
			</control>
			<control type="group">
				<left>330</left>
				<top>95r</top>
				<control type="label">
					<left>0</left>
					<top>0</top>
					<width>100</width>
					<height>40</height>
					<font>font13</font>
					<align>left</align>
					<aligny>center</aligny>
					<label>$INFO[Player.Time(hh:mm:ss)]</label>
				</control>
				<control type="progress">
					<description>Progressbar</description>
					<left>100</left>
					<top>15</top>
					<width>720</width>
					<height>16</height>
					<info>Player.Progress</info>
				</control>
				<control type="label">
					<left>820</left>
					<top>0</top>
					<width>100</width>
					<height>40</height>
					<font>font13</font>
					<align>right</align>
					<aligny>center</aligny>
					<label>$INFO[Player.Duration(hh:mm:ss)]</label>
				</control>
			</control>
		</control>
	</controls>
</window>

Now create another file called plugin-audio-absolute-radio.xml and insert the following:

<?xml version="1.0" encoding="UTF-8"?>
<window>
	<defaultcontrol>61</defaultcontrol>
	<!-- <onload condition="!MusicPlayer.Content(LiveTV)">SetFocus(602)</onload> -->
	<controls>
		<control type="group">
			<depth>DepthDialog-</depth>
			<animation effect="slide" start="1100,0" end="0,0" time="300" tween="quadratic" easing="out">WindowOpen</animation>
			<animation effect="slide" start="0,0" end="1100,0" time="300" tween="quadratic" easing="out">WindowClose</animation>
			<control type="image">
				<left>180</left>
				<top>0</top>
				<width>1120</width>
				<height>720</height>
				<texture border="15,0,0,0" flipx="true">MediaBladeSub.png</texture>
			</control>
			<control type="button">
				<description>Close Window button</description>
				<left>200</left>
				<top>0</top>
				<width>64</width>
				<height>32</height>
				<label>-</label>
				<font>-</font>
				<onclick>PreviousMenu</onclick>
				<texturefocus>DialogCloseButton-focus.png</texturefocus>
				<texturenofocus>DialogCloseButton.png</texturenofocus>
				<onleft>61</onleft>
				<onright>61</onright>
				<onup>61</onup>
				<ondown>61</ondown>
				<visible>system.getbool(input.enablemouse)</visible>
			</control>
			<control type="group">
				<animation effect="fade" delay="300" start="0" end="100" time="150">WindowOpen</animation>
				<animation effect="fade" start="100" end="0" time="150">WindowClose</animation>
				<!-- Heading -->
				<control type="label">
					<label>$INFO[Window(10147).Property(HeadingLabel)]</label>
					<description>header label</description>
					<left>210</left>
					<top>50</top>
					<width>1030</width>
					<height>30</height>
					<font>font24_title</font>
					<align>center</align>
					<aligny>center</aligny>
					<textcolor>white</textcolor>
					<shadowcolor>black</shadowcolor>
				</control>
				<!-- Artist Image -->
				<control type="image">
					<description>artist image</description>
					<left>210</left>
					<top>100</top>
					<width>480</width>
					<height>270</height>
					<bordertexture border="5">button-nofocus.png</bordertexture>
					<bordersize>4</bordersize>
					<texture border="15,0,0,0">$INFO[Window(10147).Property(ArtistImage)]</texture>
					<include>VisibleFadeEffect</include>
				</control>
				<!-- Artist Info -->
				<control type="label">
					<description>artist style label</description>
					<label>Style:</label>
					<left>720</left>
					<top>100</top>
					<width>100</width>
					<height>30</height>
					<font>font13</font>
					<align>right</align>
					<aligny>center</aligny>
					<textcolor>blue</textcolor>
					<selectedcolor>selected</selectedcolor>
				</control>
				<control type="label">
					<description>artist style</description>
					<label>$INFO[Window(10147).Property(ArtistStyle)]</label>
					<left>830</left>
					<top>100</top>
					<width>430</width>
					<height>30</height>
					<font>font13</font>
					<align>left</align>
					<aligny>center</aligny>
					<textcolor>white</textcolor>
					<selectedcolor>white</selectedcolor>
					<info>ListItem.Label2</info>
				</control>
				<control type="label">
					<description>artist genre label</description>
					<label>Genre:</label>
					<left>720</left>
					<top>130</top>
					<width>100</width>
					<height>30</height>
					<font>font13</font>
					<align>right</align>
					<aligny>center</aligny>
					<textcolor>blue</textcolor>
					<selectedcolor>selected</selectedcolor>
					<info>ListItem.Label</info>
				</control>
				<control type="label">
					<description>artist genre</description>
					<label>$INFO[Window(10147).Property(ArtistGenre)]</label>
					<left>830</left>
					<top>130</top>
					<width>430</width>
					<height>30</height>
					<font>font13</font>
					<align>left</align>
					<aligny>center</aligny>
					<textcolor>white</textcolor>
					<selectedcolor>white</selectedcolor>
					<info>ListItem.Label2</info>
				</control>
				<control type="label">
					<description>artist name label</description>
					<label>Name:</label>
					<left>720</left>
					<top>160</top>
					<width>100</width>
					<height>30</height>
					<font>font13</font>
					<align>right</align>
					<aligny>center</aligny>
					<textcolor>blue</textcolor>
					<selectedcolor>selected</selectedcolor>
				</control>
				<control type="label">
					<description>artist name</description>
					<label>$INFO[Window(10147).Property(ArtistName)]</label>
					<left>830</left>
					<top>160</top>
					<width>430</width>
					<height>30</height>
					<font>font13</font>
					<align>left</align>
					<aligny>center</aligny>
					<textcolor>white</textcolor>
					<selectedcolor>white</selectedcolor>
					<info>ListItem.Label2</info>
				</control>
				<control type="label">
					<description>artist formed label</description>
					<label>Formed:</label>
					<left>720</left>
					<top>190</top>
					<width>100</width>
					<height>30</height>
					<font>font13</font>
					<align>right</align>
					<aligny>center</aligny>
					<textcolor>blue</textcolor>
					<selectedcolor>selected</selectedcolor>
				</control>
				<control type="label">
					<description>artist formed</description>
					<label>$INFO[Window(10147).Property(ArtistFormed)]</label>
					<left>830</left>
					<top>190</top>
					<width>430</width>
					<height>30</height>
					<font>font13</font>
					<align>left</align>
					<aligny>center</aligny>
					<textcolor>white</textcolor>
					<selectedcolor>white</selectedcolor>
					<info>ListItem.Label2</info>
				</control>
				<control type="label">
					<description>artist born label</description>
					<label>Born:</label>
					<left>720</left>
					<top>220</top>
					<width>100</width>
					<height>30</height>
					<font>font13</font>
					<align>right</align>
					<aligny>center</aligny>
					<textcolor>blue</textcolor>
					<selectedcolor>selected</selectedcolor>
				</control>
				<control type="label">
					<description>artist born</description>
					<label>$INFO[Window(10147).Property(ArtistBorn)]</label>
					<left>830</left>
					<top>220</top>
					<width>430</width>
					<height>30</height>
					<font>font13</font>
					<align>left</align>
					<aligny>center</aligny>
					<textcolor>white</textcolor>
					<selectedcolor>white</selectedcolor>
					<info>ListItem.Label2</info>
				</control>
				<control type="label">
					<description>artist died label</description>
					<label>Died:</label>
					<left>720</left>
					<top>250</top>
					<width>100</width>
					<height>30</height>
					<font>font13</font>
					<align>right</align>
					<aligny>center</aligny>
					<textcolor>blue</textcolor>
					<selectedcolor>selected</selectedcolor>
				</control>
				<control type="label">
					<description>artist died</description>
					<label>$INFO[Window(10147).Property(ArtistDied)]</label>
					<left>830</left>
					<top>250</top>
					<width>430</width>
					<height>30</height>
					<font>font13</font>
					<align>left</align>
					<aligny>center</aligny>
					<textcolor>white</textcolor>
					<selectedcolor>white</selectedcolor>
					<info>ListItem.Label2</info>
				</control>
				<control type="label">
					<description>artist gender label</description>
					<label>Gender:</label>
					<left>720</left>
					<top>280</top>
					<width>100</width>
					<height>30</height>
					<font>font13</font>
					<align>right</align>
					<aligny>center</aligny>
					<textcolor>blue</textcolor>
					<selectedcolor>selected</selectedcolor>
				</control>
				<control type="label">
					<description>artist gender</description>
					<label>$INFO[Window(10147).Property(ArtistGender)]</label>
					<left>830</left>
					<top>280</top>
					<width>430</width>
					<height>30</height>
					<font>font13</font>
					<align>left</align>
					<aligny>center</aligny>
					<textcolor>white</textcolor>
					<selectedcolor>white</selectedcolor>
					<info>ListItem.Label2</info>
				</control>
				<control type="label">
					<description>artist country label</description>
					<label>Country:</label>
					<left>720</left>
					<top>310</top>
					<width>100</width>
					<height>30</height>
					<font>font13</font>
					<align>right</align>
					<aligny>center</aligny>
					<textcolor>blue</textcolor>
					<selectedcolor>selected</selectedcolor>
				</control>
				<control type="label">
					<description>artist country</description>
					<label>$INFO[Window(10147).Property(ArtistCountry)]</label>
					<left>830</left>
					<top>310</top>
					<width>430</width>
					<height>30</height>
					<font>font13</font>
					<align>left</align>
					<aligny>center</aligny>
					<textcolor>white</textcolor>
					<selectedcolor>white</selectedcolor>
					<info>ListItem.Label2</info>
				</control>
				<control type="label">
					<description>artist website label</description>
					<label>Website:</label>
					<left>720</left>
					<top>340</top>
					<width>100</width>
					<height>30</height>
					<font>font13</font>
					<align>right</align>
					<aligny>center</aligny>
					<textcolor>blue</textcolor>
					<selectedcolor>selected</selectedcolor>
				</control>
				<control type="label">
					<description>artist website</description>
					<label>$INFO[Window(10147).Property(ArtistWebsite)]</label>
					<left>830</left>
					<top>340</top>
					<width>430</width>
					<height>30</height>
					<font>font13</font>
					<align>left</align>
					<aligny>center</aligny>
					<textcolor>white</textcolor>
					<selectedcolor>white</selectedcolor>
					<info>ListItem.Label2</info>
				</control>
				<!-- Seperator -->
				<control type="image">
					<description>seperator</description>
					<left>210</left>
					<top>383</top>
					<width>1030</width>
					<height>4</height>
					<aspectratio>stretch</aspectratio>
					<texture>separator.png</texture>
				</control>
				<control type="group">
					<animation effect="fade" delay="300" start="0" end="100" time="150">WindowOpen</animation>
					<animation effect="fade" start="100" end="0" time="150">WindowClose</animation>
					<left>210</left>
					<top>395</top>
					<width>1100</width>
					<control type="textbox">
						<description>description textarea</description>
						<label>$INFO[Window(10147).Property(Description)]</label>
						<description>description</description>
						<left>0</left>
						<top>0</top>
						<width>1015</width>
						<height>310</height>
						<label>-</label>
						<font>font13</font>
						<align>justify</align>
						<textcolor>white</textcolor>
						<shadowcolor>black</shadowcolor>
						<pagecontrol>61</pagecontrol>
					</control>
					<control type="scrollbar" id="61">
						<left>1030</left>
						<top>0</top>
						<width>25</width>
						<height>310</height>
						<texturesliderbackground border="0,14,0,14">ScrollBarV.png</texturesliderbackground>
						<texturesliderbar border="0,14,0,14">ScrollBarV_bar.png</texturesliderbar>
						<texturesliderbarfocus border="0,14,0,14">ScrollBarV_bar_focus.png</texturesliderbarfocus>
						<textureslidernib>ScrollBarNib.png</textureslidernib>
						<textureslidernibfocus>ScrollBarNib.png</textureslidernibfocus>
						<onleft>61</onleft>
						<onright>61</onright>
						<ondown>61</ondown>
						<onup>61</onup>
						<showonepage>true</showonepage>
						<orientation>vertical</orientation>
					</control>
				</control>
			</control>
		</control>
		<control type="group">
			<depth>DepthDialog-</depth>
			<include>Clock</include>
		</control>
	</controls>
</window>

 

Settings

We now need to go back to the resources folder and create an empty file named __init__.py, next create another file called settings.xml. This file holds the users settings on stream format, sound quality, displaying artist info and loging into Absolute Radio if the user has an account. Insert the following into the settings.xml file:

<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<settings>
    <!--General-->
    <category label="30001">
        <setting type="lsep" label="30002" />
        <setting id="compression_format" type="select" label="30004" values="AAC|MP3|Ogg Vorbis|Ogg FLAC - Lossless|Opus" default="AAC" />
        <setting id="bitrate.aac" type="select" label="Bitrate" values="24kbps|64kbps|128kbps" default="24kbps" visible="eq(-1,AAC)" />
        <setting id="bitrate.mp3" type="select" label="Bitrate" values="32kbps|128kbps" default="32kbps" visible="eq(-2,MP3)" />
        <setting id="bitrate.ogg" type="select" label="Bitrate" values="32kbps|160kbps" default="32kbps" visible="eq(-3,Ogg Vorbis)" />
        <setting id="bitrate.ogg-flac" type="select" label="Bitrate" values="1024kbps" default="1024kbps" visible="eq(-4,Ogg FLAC - Lossless)" />
        <setting id="bitrate.opus" type="select" label="Bitrate" values="24kbps|64kbps|96kbps" default="24kbps" visible="eq(-5,Opus)" />
        <setting type="lsep" label="30113" />
        <setting id="hide_artist_info" type="bool" label="30009" value="false" />
        <setting id="hide_artist_artwork" type="bool" label="30003" value="false" />
        <setting type="lsep" label="30006" />
        <setting id="username" type="text" label="30007" value="" />
        <setting id="password" type="text" label="30008" value="" option="hidden" enable="!eq(-1,'')" />
    </category>
</settings>

 

Icon & Fanart

You may have noticed the text in the above code contains the unique number from the language settings we created ealier. Download the icon and fanart image and save to the resources folder.

 

 

Addon Details

We now need to go back to the plugin.audio.absolute-radio folder and create a new file named addon.xml . This file defines the what the name of the addon is, what dependances are requied by the addon etc. Insert the following:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<addon id="plugin.audio.absolute-radio" name="Absolute Radio" version="1.0.6" provider-name="Phantom Raspberry Blower">
  <requires>
    <import addon="xbmc.python" version="2.25.0"/>
  </requires>
  <extension point="xbmc.python.pluginsource" library="addon.py">
    <provides>audio</provides>
  </extension>
  <extension point="xbmc.addon.metadata">
    <summary>Absolute Radio</summary>
    <description>
      Absolute Radio is one of the UK's Independent National Radio stations. The station is based in London and plays popular rock music broadcasting on medium wave and DAB across the UK (105.8 FM in London and 105.2 FM in the West Midlands). It is also available in other parts of the world via satellite, cable, and on the Internet.
    </description>
    <disclaimer>
      [COLOR red][B]Phantom Raspberry Blower[/B][/COLOR] does not host or distribute any of the content provided by this addon. Does not have any affiliation with the content provider and accepts no responsibility for the included addon.
    </disclaimer>
    <platform>all</platform>
    <language></language>
    <license>GNU GENERAL PUBLIC LICENSE. Version 2, June 1991</license>
    <forum></forum>
    <website></website>
    <assets>
      <icon>resources/icon.png</icon>
      <fanart>resources/fanart.jpg</fanart>
      <screenshot>resources/media/screenshot000.jpg</screenshot>
      <screenshot>resources/media/screenshot001.jpg</screenshot>
      <screenshot>resources/media/screenshot002.jpg</screenshot>
      <screenshot>resources/media/screenshot003.jpg</screenshot>
      <screenshot>resources/media/screenshot004.jpg</screenshot>
      <screenshot>resources/media/screenshot005.jpg</screenshot>
      <screenshot>resources/media/screenshot006.jpg</screenshot>
    </assets>
    <news>Updated theaudiodb.com API</news>
    <email></email>
    <source>https://github.com/PhantomRaspberryBlower/repository.prb-entertainment-pack/tree/master/plugin.video.leicester-community-radio</source>
  </extension>
</addon>

 

Addon Code

We can now start writing the main code for the addon. Create a new file and name it addon.py and insert the following:

#!/bin/python

import xbmc
import xbmcplugin
import xbmcgui
import xbmcaddon
import urllib
import urllib2
import cookielib
import re
import sys
import os

from resources.lib.artistinfo import ArtistInfo

import thread

# Written by: Phantom Raspberry Blower (The PRB)
# Date: 21-02-2017
# Description: Addon for listening to Absolute Radio live broadcasts

# Get addon details
__addon_id__ = 'plugin.audio.absolute-radio'
__addon__ = xbmcaddon.Addon(id=__addon_id__)
__addonname__ = __addon__.getAddonInfo('name')
__icon__ = __addon__.getAddonInfo('icon')
__fanart__ = __addon__.getAddonInfo('fanart')
__author__ = 'Phantom Raspberry Blower'
__url__ = sys.argv[0]
__handle__ = int(sys.argv[1])
__baseurl__ = 'https://absoluteradio.co.uk/listen/links/'
__fanarturl__ = 'http://www.theaudiodb.com/api/v1/json/1/search.php?s='
__cookie_file__ = xbmc.translatePath(os.path.join('special://profile/addon_data/'
                                                  'plugin.audio.absolute-radio',
                                                  'cookies-absolute-radio'))

# Get localized language text
__language__ = __addon__.getLocalizedString
_login_success_title = __language__(30109)
_login_success_msg = __language__(30110)
_login_failed_title = __language__(30111)
_login_failed_msg = __language__(30112)
_artist_info = __language__(30113)
_unable_to_download_artist = __language__(30114)
_no_artist_name_present = __language__(30115)
_no_audio_stream = __language__(30116)
_settings = __language__(30117)
_ar_desc = __language__(30118)
_ar_cr_desc = __language__(30119)
_ar_80s_desc = __language__(30120)
_ar_60s_desc = __language__(30121)
_ar_70s_desc = __language__(30122)
_ar_90s_desc = __language__(30123)
_ar_00s_desc = __language__(30124)
_download_artist_info_desc = __language__(30125)
_settings_desc = __language__(30126)

# Get addon user settings
_compression_format = __addon__.getSetting('compression_format')
_hide_artist_info = __addon__.getSetting('hide_artist_info')
_hide_artist_artwork = __addon__.getSetting('hide_artist_artwork')

# Define local variables
g_default_image = None
image_path = xbmc.translatePath(os.path.join('special://home/addons/',
	                            __addon_id__ + '/resources/media/'))
link_info = {'Absolute Radio':
             {'thumbs': os.path.join(image_path, 
                                     'absolute_radio.png'),
              'fanart': os.path.join(image_path,
                                     'absolute_radio_fanart.jpg'),
              'desc': _ar_desc
              },
             'Absolute Classic Rock':
             {'thumbs': os.path.join(image_path,
                                     'absolute_radio_classic_rock.png'),
              'fanart': os.path.join(image_path,
                                     'absolute_radio_classic_rock'
                                     '_fanart.jpg'),
              'desc': _ar_cr_desc
              },
             'Absolute 80s':
             {'thumbs': os.path.join(image_path,
                                     'absolute_radio_80s.png'),
              'fanart': os.path.join(image_path,
                                     'absolute_radio_80s_fanart.jpg'),
              'desc': _ar_80s_desc
              },
             'Absolute Radio 60s':
             {'thumbs': os.path.join(image_path,
                                     'absolute_radio_60s.png'),
              'fanart': os.path.join(image_path,
                                     'absolute_radio_60s_fanart.jpg'),
              'desc': _ar_60s_desc
              },
             'Absolute Radio 70s':
             {'thumbs': os.path.join(image_path,
                                     'absolute_radio_70s.png'),
              'fanart': os.path.join(image_path,
                                     'absolute_radio_70s_fanart.jpg'),
              'desc': _ar_70s_desc
              },
             'Absolute Radio 90s':
             {'thumbs': os.path.join(image_path,
                                     'absolute_radio_90s.png'),
              'fanart': os.path.join(image_path,
                                     'absolute_radio_90s_fanart.jpg'),
              'desc': _ar_90s_desc
              },
             'Absolute Radio 00s':
             {'thumbs': os.path.join(image_path,
                                     'absolute_radio_00s.png'),
              'fanart': os.path.join(image_path,
                                     'absolute_radio_00s_fanart.jpg'),
              'desc': _ar_00s_desc
              },
             _artist_info:
             {'thumbs': os.path.join(image_path,
                                     'artist_info.png'),
              'fanart': os.path.join(image_path,
                                     'artist_info_fanart.jpg'),
              'desc': _download_artist_info_desc
              },
             _settings:
             {'thumbs': os.path.join(image_path,
                                     'settings.png'),
              'fanart': os.path.join(image_path,
                                     'settings_fanart.jpg'),
              'desc': _settings_desc
              }
             }


def _login_absolute_radio(username, password):
    """
    Login into absolute radio and save the cookie to the cookie jar
    subsequent login's will re-use the cookie.
    """
    cookie_jar = cookielib.LWPCookieJar(__cookie_file__)
    try:
        cookie_jar.load()
    except:
        pass
    cookies = cookie_jar
    if len(cookies) > 1:
        return True
    else:
        url = 'https://absoluteradio.co.uk/_ajax/account-process.php'
        values = {'emailfield': username,
                  'passwordfield': password,
                  'signinbutton': 'signin'}
        data = urllib.urlencode(values)
        opener = urllib2.build_opener(
            urllib2.HTTPRedirectHandler(),
            urllib2.HTTPHandler(debuglevel=0),
            urllib2.HTTPSHandler(debuglevel=0),
            urllib2.HTTPCookieProcessor(cookies))
        response = opener.open(url, data)
        the_page = response.read()
        http_headers = response.info()
        # The login cookies should be contained in the cookies variable
        if len(cookies) > 1:
            cookie_jar.save(ignore_discard=True)
            notification(_login_success_title,
                             _login_success_msg, __icon__, 5000)
            return True
        else:
            notification(_login_failed_title,
                             _login_failed_msg, __icon__, 5000)
            return False


def _get_url(url):
    """
    Download url and remove carriage return
    and tab spaces from page
    """
    req = urllib2.Request(url)
    req.add_header('User-Agent',
                   'Mozilla/5.0 (Windows; '
                   'U; Windows NT 5.1; en-GB; '
                   'rv:1.9.0.3) Gecko/2008092417 Firefox/3.0.3')
    response = urllib2.urlopen(req)
    link = response.read()
    response.close()
    return link


def categories():
    """
    Download categories from absolute radio
    and display a list of the stations
    """
    response = _get_url(__baseurl__)
    response = response.replace('\n', '').replace('\t', '').replace('  ', '')
    headings = re.compile('<h3>(.+?)</h3>').findall(response)
    if len(__addon__.getSetting('username')) > 1:
        _login_absolute_radio(__addon__.getSetting('username'),
                              __addon__.getSetting('password'))
    for item in headings:
        if 'Trial' not in item:
            list_item = item.replace(' stream URLs', '')
            addDir(list_item,
                   __baseurl__,
                   1,
                   link_info[list_item]['thumbs'],
                   link_info[list_item]['fanart'],
                   link_info[list_item]['desc'],
                   isFolder=False)
    if _hide_artist_info != 'true':
        addDir(_artist_info,
               'XBMC.RunPlugin({0}?url=artistinfo&mode=4)',
               4,
               link_info[_artist_info]['thumbs'],
               link_info[_artist_info]['fanart'],
               link_info[_artist_info]['desc'],
               isFolder=False)
    addDir(_settings,
           'XBMC.RunPlugin({0}?url=settings&mode=5)',
           5,
           link_info[_settings]['thumbs'],
           link_info[_settings]['fanart'],
           link_info[_settings]['desc'],
           isFolder=False)


def get_links(name, url, icon, fanart):
    """
    Fetch user's compression format and bitrate settings.
    Download links to the audio stream location.
    """
    response = _get_url(url).replace(' stream URLs', '')
    response = response.replace('\n', '').replace('\t', '').replace('  ', '')
    filter = re.compile('<h3>' +
                        name +
                        '</h3>(.+?)</p>').findall(response)
    regex = '<strong>(.+?)</strong> <span class="display-url">(.+?)</span>'
    data = re.compile(regex).findall(str(filter))
    compression_format = _compression_format
    bitrate = None
    if compression_format == 'AAC':
        bitrate = __addon__.getSetting('bitrate.aac')
        compression_format = 'AAC+'
    elif compression_format == 'MP3':
        bitrate = __addon__.getSetting('bitrate.mp3')
    elif compression_format == 'Ogg Vorbis':
        bitrate = __addon__.getSetting('bitrate.ogg')
    elif compression_format == 'Ogg FLAC - Lossless':
        bitrate = __addon__.getSetting('bitrate.ogg-flac')
    elif compression_format == 'Opus':
        bitrate = __addon__.getSetting('bitrate.opus')
    sound_quality = '%s (%s)' % (compression_format, bitrate)
    for quality, link in data:
        if (quality.replace(':', '').replace('~', '') ==
                sound_quality.replace('- Lossless (1024kbps)', '1Mb')
                .replace('(', '')
                .replace('bps)', '')):
            get_audio(sound_quality, link, icon, fanart)


def get_audio(name, url, icon, fanart):
    """
    Parse the file location and title links from audio stream location.
    """
    response = _get_url(url)
    files = re.compile('File1=(.+?)\n').findall(response)
    titles = re.compile('Title1=(.+?)\n').findall(response)
    for file in files:
        for title in titles:
            play_audio(title, file, icon, fanart)
            while not xbmc.Player().isPlayingAudio():
                xbmc.sleep(3)
            thread.start_new_thread( _show_artist_image,())


def play_audio(name, url, icon, fanart):
    """
    Create a list item to the audio stream and
    start playing the audio stream.
    """
    if xbmc.Player().isPlayingAudio:
        xbmc.Player().stop()
    liz = xbmcgui.ListItem(str(name),
                           iconImage='Icon.png',
                           thumbnailImage=icon)
    # Set a fanart image for the list item.
    liz.setProperty('fanart_image', fanart)
    xbmc.Player().play(url, liz, False)
    xbmc.executebuiltin('Action(Fullscreen)')


def notification(message, title, icon, duration):
    # Show message notification
    dialog = xbmcgui.Dialog()
    dialog.notification(title, message, icon, duration)


def message(message, title):
    # Display message to user
    dialog = xbmcgui.Dialog()
    dialog.ok(title, message)


def show_artist_info():
    if xbmc.Player().isPlayingAudio():
        try:
            my_title = xbmc.Player().getMusicInfoTag().getTitle()
            if len(my_title) > 0:
                items = my_title.split(' - ')
                artist = items[0]
                song = items[1]
                previous = my_title
                if len(song) > 1:
                    show_artist_details(artist)
                else:
                    message(_unable_to_download_artist, _artist_info)
            else:
                message(_no_artist_name_present, _artist_info)
            _show_artist_image()
        except:
            message(_unable_to_download_artist, _artist_info)
    else:
        message(_no_audio_stream, _artist_info)


def show_artist_details(artistname):
    artist_info = ArtistInfo(artistname)
    home = xbmc.translatePath('special://home')
    if xbmc.getInfoLabel('System.ProfileName') != 'Master user':
        you = xbmc.getInfoLabel('System.ProfileName')
    elif (xbmc.getCondVisibility('System.Platform.Windows') is True or
          xbmc.getCondVisibility('System.Platform.OSX') is True):
        if 'Users\\' in home:
            proyou = str(home).split('Users\\')
            preyou = str(proyou[1]).split('\\')
            you = preyou[0]
        else:
            you = 'You'
    else:
        you = 'You'
    window = xbmcgui.WindowXMLDialog('plugin-audio-absolute-radio.xml',
                                     __addon__.getAddonInfo('path'))
    win = xbmcgui.Window(10147)
    win.setProperty('HeadingLabel',
                    artistname)
    # Property can be accessed in the XML using:
    # <label fallback="416">$INFO[Window(10147).Property(HeadingLabel)]</label>
    win.setProperty('ArtistImage',
                    artist_info.fanart)
    win.setProperty('ArtistStyle',
                    artist_info.style)
    win.setProperty('ArtistGenre',
                    artist_info.genre)
    win.setProperty('ArtistName',
                    artist_info.artist_name)
    win.setProperty('ArtistFormed',
                    str(artist_info.year_formed).replace('None', ''))
    win.setProperty('ArtistBorn',
                    str(artist_info.year_born).replace('None', ''))
    win.setProperty('ArtistDied',
                    str(artist_info.year_died).replace('None', ''))
    win.setProperty('ArtistGender',
                    artist_info.gender)
    win.setProperty('ArtistCountry',
                    artist_info.country.replace('None', '') + ' ' +
                    artist_info.country_code.replace('None', ''))
    win.setProperty('ArtistWebsite',
                    artist_info.website)
    win.setProperty('Description',
                    artist_info.biography_en)
    window.doModal()
    del window


def get_current_artist_image():
    """
    Discover artist name, download image and return the image path
    if no image can be found return the default image instead
    """
    global g_default_image
    # Find the current artist name
    artist_name = ''
    if xbmc.Player().isPlayingAudio():
        try:
            my_title = xbmc.Player().getMusicInfoTag().getTitle()
            if len(my_title) > 0:
                items = my_title.split(' - ')
                artist = items[0]
                song = items[1]
                previous = my_title
                if len(song) > 1:
                    artist_name = artist
        except:
            notification(_no_artist_name_present,
                             _artist_info, __icon__, 5000)
    else:
        notification(_no_audio_stream, _artist_info, __icon__, 5000)
    # Find artist image
    if len(artist_name) > 1:
        # Find the artist information
        artist_info = ArtistInfo(artist_name)
        if artist_info != 0:
            try:
                if len(artist_info.fanart) > 1:
                    return artist_info.fanart
                else:
                    return g_default_image
            except:
                return g_default_image
        else:
            return g_default_image
    else:
        return g_default_image


def _show_artist_image():
    global g_default_image
    window = xbmcgui.WindowXMLDialog('plugin-music-visualisation.xml',
                                     __addon__.getAddonInfo('path'))
    win = xbmcgui.Window(12006)
    window.show()
    if xbmc.Player().isPlayingAudio():
        my_title = xbmc.Player().getMusicInfoTag().getTitle()
    previous_title = ''
    # main loop
    while (not xbmc.Monitor().abortRequested() and
           xbmc.Player().isPlayingAudio()):
        if _hide_artist_artwork != 'true':
            my_title = xbmc.Player().getMusicInfoTag().getTitle()
            if my_title != previous_title:
                # check if we are on the music visualization screen
                # do not try and set image for any background media
                if xbmc.getCondVisibility("Player.IsInternetStream"):
                    win.setProperty('ArtistFanart',
                                    get_current_artist_image())
                previous_title = my_title
        else:
            win.setProperty('ArtistFanart', g_default_image)
        xbmc.sleep(1000)
    del window


def get_params():
    """
    Parse the paramters sent to the application
    """
    param = []
    paramstring = sys.argv[2]
    if len(paramstring) >= 2:
        params = sys.argv[2]
        cleanedparams = params.replace('?', '')
        if (params[len(params)-1] == '/'):
            params = params[0:len(params)-2]
        pairsofparams = cleanedparams.split('&')
        param = {}
        for i in range(len(pairsofparams)):
            splitparams = {}
            splitparams = pairsofparams[i].split('=')
            if (len(splitparams)) == 2:
                param[splitparams[0]] = splitparams[1]
    return param


def addDir(name, url, mode, icon, fanart, desc, isFolder=False):
    """
    Display a list of links
    """
    u = (sys.argv[0] + '?url=' + urllib.quote_plus(url) +
         '&mode=' + str(mode) + '&name=' + urllib.quote_plus(name) +
         '&icon=' + str(icon) + '&fanart=' + str(fanart))
    ok = True
    liz = xbmcgui.ListItem(name)

    # Set fanart and thumb images for the list item.
    if not fanart:
        fanart = __fanart__
    if not icon:
        icon = __icon__
    liz.setArt({'fanart': fanart,
                'thumb': icon})

    # Set additional info for the list item.
    liz.setInfo(type='music',
                infoLabels={'title': name,
                            'artist': name,
                            'description': desc,
                            'genre': 'Internet Radio',
                            'year': 2015,
                            'mediatype': 'album'
                            }
                )
    ok = xbmcplugin.addDirectoryItem(handle=__handle__,
                                     url=u,
                                     listitem=liz,
                                     isFolder=isFolder)
    return ok


# Define local variables
params = get_params()
url = None
name = None
mode = None
icon = None
fanart = None

# Parse the url, name, mode, icon and fanart parameters
try:
    url = urllib.unquote_plus(params['url'])
except:
    pass
try:
    name = urllib.unquote_plus(params['name'])
except:
    pass
try:
    mode = int(params['mode'])
except:
    pass
try:
    icon = urllib.unquote_plus(params['icon'])
except:
    pass
try:
    fanart = urllib.unquote_plus(params['fanart'])
except:
    pass

# Route the request based upon the mode number
if mode is None or url is None or len(url) < 1:
    categories()
elif mode == 1:
    g_default_image = fanart
    get_links(name, url, icon, fanart)
elif mode == 2:
    get_audio(name, url, icon, fanart)
elif mode == 3:
    play_audio(name, url, icon, fanart)
elif mode == 4:
    show_artist_info()
elif mode ==5:
    __addon__.openSettings()
    xbmc.executebuiltin("Container.Refresh")


xbmcplugin.endOfDirectory(__handle__)

 

Activate Addon

Shut down and restart Kodi then navigate to System>Add-ons>My Addons>Music Addons and click onto Absolute Radio and Enable. You can now use your new addon in Kodi 🙂

That’s All Folks!!!!

FB Like & Share