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:
- Absolute Radio
- Absolute Radio Classic Rock
- Absolute Radio 60’s
- Absolute Radio 70’s
- Absolute Radio 80’s
- Absolute Radio 90’s
- Absolute Radio 00’s
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
- Language Settings
- Artist Information Module
- Images
- Skins
- Settings
- Icon & Fanart
- Addon Details
- Addon Code
- Activate Addon
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, lib, media 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!!!!