The branch, frodo has been updated
via da3544a2b3ffd6dc20aba536499597a84ebb6d5f (commit)
via b485d7be79860ca0bfb3ec3d67970822b7199fa4 (commit)
via f76b6e2728f664604bef7423a752d1ef5c432829 (commit)
from 076fa9b16e46d30026b9737c07d3de3c08392e16 (commit)
- Log -----------------------------------------------------------------
http://xbmc.git.sourceforge.net/git/gitweb.cgi?p=xbmc/plugins;a=commit;h=da3544a2b3ffd6dc20aba536499597a84ebb6d5f
commit da3544a2b3ffd6dc20aba536499597a84ebb6d5f
Author: beenje <[email protected]>
Date: Tue Dec 31 15:09:30 2013 +0100
[plugin.audio.di.fm] updated to version 3.0.0
diff --git a/plugin.audio.di.fm/addon.xml b/plugin.audio.di.fm/addon.xml
index fd939f6..49aadba 100644
--- a/plugin.audio.di.fm/addon.xml
+++ b/plugin.audio.di.fm/addon.xml
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<addon id="plugin.audio.di.fm"
name="Digitally Imported"
- version="2.1.2"
+ version="3.0.0"
provider-name="qualisoft.dk - Tim C. Steinmetz">
<requires>
<import addon="xbmc.python" version="2.1.0"/>
diff --git a/plugin.audio.di.fm/changelog.txt b/plugin.audio.di.fm/changelog.txt
index 117259b..ee33635 100644
--- a/plugin.audio.di.fm/changelog.txt
+++ b/plugin.audio.di.fm/changelog.txt
@@ -2,6 +2,14 @@
* Fixed/improved feature
- Removed feature
+
+31 December 2013 v3.0.0
++ Rewrote whole plugin
++ Threading of playlists parsing to speed up first run and refreshing of
playlists - 8 threads
+* Now fully support the new di.fm design
+- Check-in with stats are gone for now
+
+
20 May 2013 v2.1.2
* Fixed problems related to new Digitally Imported webpage
Thanks to Vidar Waagbø for the regex patch
diff --git a/plugin.audio.di.fm/config.ini b/plugin.audio.di.fm/config.ini
index 397e01c..f75518e 100644
--- a/plugin.audio.di.fm/config.ini
+++ b/plugin.audio.di.fm/config.ini
@@ -1,21 +1,25 @@
# DI.fm XBMC config
[plugin]
id = plugin.audio.di.fm
-date = 20. May 2013
+date = 31. December 2013
checkinkey = a57ab7ceada3fefeaa70a7136ab05f9af5ebac82
-# Will get stripped from the favorite channels name
-playlistStripName = Digitally Imported -
+
+[cache]
+cachePremiumConfig = cachePremiumConfig.json
+cacheChannels = cacheChannels.json
[urls]
-baseUrl = http://www.di.fm
-loginUrl = http://www.di.fm/login
-listenkeyUrl = http://www.di.fm/member/listen_key
-publicStreamsJson40k = http://listen.di.fm/public2
-premiumStreamsJson40k = http://listen.di.fm/premium_low/
-premiumStreamsJson64k = http://listen.di.fm/premium_medium/
-premiumStreamsJson128k = http://listen.di.fm/premium/
-premiumStreamsJson256k = http://listen.di.fm/premium_high/
-favoritesStreamJson40k = http://listen.di.fm/premium_low/favorites.pls
-favoritesStreamJson64k = http://listen.di.fm/premium_medium/favorites.pls
-favoritesStreamJson128k = http://listen.di.fm/premium/favorites.pls
-favoritesStreamJson256k = http://listen.di.fm/premium_high/favorites.pls
\ No newline at end of file
+frontpage = http://www.di.fm
+login = http://www.di.fm/login
+listenkey = http://www.di.fm/member/listen_key
+
+[streams]
+public = http://listen.di.fm/public2
+premium40k = http://listen.di.fm/premium_low/
+premium64k = http://listen.di.fm/premium_medium/
+premium128k = http://listen.di.fm/premium/
+premium256k = http://listen.di.fm/premium_high/
+favorites40k = http://listen.di.fm/premium_low/favorites.pls
+favorites64k = http://listen.di.fm/premium_medium/favorites.pls
+favorites128k = http://listen.di.fm/premium/favorites.pls
+favorites256k = http://listen.di.fm/premium_high/favorites.pls
\ No newline at end of file
diff --git a/plugin.audio.di.fm/default.py b/plugin.audio.di.fm/default.py
index 3862400..90ed3af 100644
--- a/plugin.audio.di.fm/default.py
+++ b/plugin.audio.di.fm/default.py
@@ -1,3 +1,5 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
########################################
# Digitally Imported XBMC plugin
# by Tim C. 'Bitcrusher' Steinmetz
@@ -22,20 +24,24 @@
#
import os
+import pickle
import sys
import re
-import urllib
-import urllib2
import string
+import urllib
+
import xbmc
import xbmcgui
import xbmcplugin
import xbmcaddon
-import pickle
import time
from xml.dom import minidom
from httpcomm import HTTPComm
from ConfigParser import SafeConfigParser
+import json
+from random import randrange
+import Queue
+import threading
# Import JSON - compatible with Python<v2.6
try:
@@ -43,378 +49,376 @@ try:
except ImportError:
import simplejson as json
+
# Config parser
-parser = SafeConfigParser()
-parser.read( os.path.dirname(__file__) + "/config.ini" )
+pluginConfig = SafeConfigParser()
+pluginConfig.read(os.path.dirname(__file__) + "/config.ini")
+
# Various constants used throughout the script
HANDLE = int(sys.argv[1])
-ADDON = xbmcaddon.Addon(id=parser.get('plugin', 'id'))
+ADDON = xbmcaddon.Addon(id=pluginConfig.get('plugin', 'id'))
# Plugin constants
-__plugin__ = ADDON.getAddonInfo('name')
-__author__ = "Tim C. Steinmetz"
-__url__ = "http://qualisoft.dk/"
-__platform__ = "xbmc media center, [LINUX, OS X, WIN32]"
-__date__ = parser.get('plugin', 'date')
-__version__ = ADDON.getAddonInfo('version')
+__plugin__ = ADDON.getAddonInfo('name')
+__author__ = "Tim C. Steinmetz"
+__url__ = "http://qualisoft.dk/"
+__platform__ = "xbmc media center, [LINUX, OS X, WIN32]"
+__date__ = pluginConfig.get('plugin', 'date')
+__version__ = ADDON.getAddonInfo('version')
+
+
+"""
+ Thread class used for scraping individual playlists for each channel
+"""
+class scraperThread(threading.Thread):
+ def __init__(self, musicObj, channel, channelMeta, channelCount):
+ threading.Thread.__init__(self)
+ self.musicObj = musicObj
+ self.channel = channel
+ self.channelMeta = channelMeta
+ self.channelCount = channelCount
+
+ def run(self):
+ self.musicObj.addChannel(self.channel, self.channelMeta,
self.channelCount)
+
class musicAddonXbmc:
- _addonProfilePath = xbmc.translatePath( ADDON.getAddonInfo('profile')
).decode('utf-8') # Dir where plugin settings and cache will be stored
-
- _cacheStreams = _addonProfilePath + "cache_streamlist.dat"
- _cacheListenkey = _addonProfilePath + "cache_listenkey.dat"
- _checkinFile = _addonProfilePath + "cache_lastcheckin.dat"
-
- _baseUrl = parser.get('urls', 'baseUrl')
- _loginUrl = parser.get('urls', 'loginUrl')
- _listenkeyUrl = parser.get('urls', 'listenkeyUrl')
-
- _publicStreamsJson40k = parser.get('urls', 'publicStreamsJson40k')
# Public AAC 40k/sec AAC+ JSON url
- _premiumStreamsJson40k = parser.get('urls', 'premiumStreamsJson40k')
# AAC 40k/sec AAC+ JSON url
- _premiumStreamsJson64k = parser.get('urls', 'premiumStreamsJson64k')
# AAC 64k/sec AAC+ JSON url
- _premiumStreamsJson128k = parser.get('urls', 'premiumStreamsJson128k')
# AAC 128k/sec AAC+ JSON url
- _premiumStreamsJson256k = parser.get('urls', 'premiumStreamsJson256k')
# MP3 256k/sec AAC+ JSON url
- _favoritesStreamJson40k = parser.get('urls', 'favoritesStreamJson40k')
# Favorites AAC 40k/sec AAC+ playlist url
- _favoritesStreamJson64k = parser.get('urls', 'favoritesStreamJson64k')
# Favorites AAC 64k/sec AAC+ playlist url
- _favoritesStreamJson128k= parser.get('urls', 'favoritesStreamJson128k')
# Favorites AAC 128k/sec AAC+ playlist url
- _favoritesStreamJson256k= parser.get('urls', 'favoritesStreamJson256k')
# Favorites MP3 256k/sec AAC+ playlist url
-
- _httpComm = HTTPComm() # Init CURL thingy
- _frontpageHtml = ""
-
- _newChannels = 0
- _bitrate = 40
- _streamMimeType = 'audio/aac'
-
- def __init__( self ) :
- # If stats is allowed and its been at least 24 hours since last
checkin
- if (ADDON.getSetting('allowstats') == "true") and
(self.checkFileTime(self._checkinFile, self._addonProfilePath, 86400) == True) :
- open(self._checkinFile, "w")
-
- account = 'public'
- if ADDON.getSetting('username') != "" :
- account = 'premium'
-
- xbmc.log( 'Submitting stats', xbmc.LOGNOTICE )
-
self._httpComm.get('http://stats.qualisoft.dk/?plugin='+
ADDON.getAddonInfo('id') + '&version=' + __version__ + '&account=' + account +
'&key=' + parser.get('plugin', 'checkinkey'))
-
- xbmc.log( "[PLUGIN] %s v%s (%s)" % ( __plugin__, __version__,
__date__ ), xbmc.LOGNOTICE )
-
-
- # Let's get some tunes!
- def start( self ) :
- jsonList = [] # list that data from the JSON will be put in
- streamList = [] # the final list of channels, with small custom
additions
-
- # Check if cachefile has expired
- if ADDON.getSetting("forceupdate") == "true" or ((int(
ADDON.getSetting("cacheexpire") ) * 60) != 0 and
self.checkFileTime(self._cacheStreams, self._addonProfilePath, (int(
ADDON.getSetting("cacheexpire") ) * 60)) == True) :
- listenkey = "" # will contain the premium listenkey
-
- if ADDON.getSetting('username') != "" and
ADDON.getSetting("usefavorites") == 'false' : # if username is set and not
using favorites
- xbmc.log( "Going for Premium streams",
xbmc.LOGNOTICE )
-
- # Checks if forceupdate is set and if the
listenkey cachefile exists
- if ADDON.getSetting("forceupdate") == "true" or
not os.path.exists(self._cacheListenkey) :
- listenkey = self.getListenkey()
- pickle.dump( listenkey,
open(self._cacheListenkey, "w"), protocol=0 ) # saves listenkey for further use
- else :
- listenkey = pickle.load(
open(self._cacheListenkey, "r") )
-
- if ADDON.getSetting('bitrate') == '0' :
- self._bitrate = 40
- jsonList = self.getJSONChannelList(
self._premiumStreamsJson40k )
- streamList =
self.customizeStreamListAddMenuitem( jsonList, listenkey )
- elif ADDON.getSetting('bitrate') == '1' :
- self._bitrate = 64
- jsonList = self.getJSONChannelList(
self._premiumStreamsJson64k )
- streamList =
self.customizeStreamListAddMenuitem( jsonList, listenkey )
- elif ADDON.getSetting('bitrate') == '2' :
- self._bitrate = 128
- jsonList = self.getJSONChannelList(
self._premiumStreamsJson128k )
- streamList =
self.customizeStreamListAddMenuitem( jsonList, listenkey )
- else :
- self._bitrate = 256
- self._streamMimeType = 'audio/mpeg'
- jsonList = self.getJSONChannelList(
self._premiumStreamsJson256k )
- streamList =
self.customizeStreamListAddMenuitem( jsonList, listenkey )
-
- xbmc.log( "Bitrate set to " + str(
self._bitrate ), xbmc.LOGNOTICE )
-
-
- elif ADDON.getSetting('username') != "" and
ADDON.getSetting("usefavorites") == 'true' : # if username is set and wants to
use favorites
- xbmc.log( "Going for Premium favorite streams",
xbmc.LOGNOTICE )
- listenkey = self.getListenkey()
- if ADDON.getSetting('bitrate') == '0' :
- self._bitrate = 40
- streamList =
self.getFavoriteStreamsList( self._favoritesStreamJson40k + "?" + listenkey )
- elif ADDON.getSetting('bitrate') == '1' :
- self._bitrate = 64
- streamList =
self.getFavoriteStreamsList( self._favoritesStreamJson64k + "?" + listenkey )
- elif ADDON.getSetting('bitrate') == '2' :
- self._bitrate = 128
- streamList =
self.getFavoriteStreamsList( self._favoritesStreamJson128k + "?" + listenkey )
- else :
- self._bitrate = 256
- self._streamMimeType = 'audio/mpeg'
- streamList =
self.getFavoriteStreamsList( self._favoritesStreamJson256k + "?" + listenkey )
-
- xbmc.log( "Bitrate set to " +
str(self._bitrate), xbmc.LOGNOTICE )
-
- for channel in streamList :
- self.addItem( channel['name'],
channel['playlist'], channel["description"], channel['bitrate'],
self._addonProfilePath + "art_" + channel['key'] + ".png", channel['isNew'] )
-
- else :
- xbmc.log( "Going for Public streams",
xbmc.LOGNOTICE )
- jsonList = self.getJSONChannelList(
self._publicStreamsJson40k )
- streamList =
self.customizeStreamListAddMenuitem(jsonList, "") # sending a blank string as
listenkey
-
- # save streams to cachefile
- pickle.dump( streamList, open(self._cacheStreams, "w"),
protocol=0 )
-
- if (self._newChannels > 0) : # Yay! New channels found
- xbmc.log( ADDON.getLocalizedString(30130) + " "
+ ADDON.getLocalizedString(30131) + str(self._newChannels) +
ADDON.getLocalizedString(30132) + " " + ADDON.getLocalizedString(30133) + " " +
ADDON.getLocalizedString(30134), xbmc.LOGNOTICE )
- xbmcgui.Dialog().ok(
ADDON.getLocalizedString(30130), ADDON.getLocalizedString(30131) +
str(self._newChannels) + ADDON.getLocalizedString(30132),
ADDON.getLocalizedString(30133),ADDON.getLocalizedString(30134) )
-
- else :
- xbmc.log( "Using cached streams", xbmc.LOGNOTICE )
- streamList = pickle.load( open(self._cacheStreams, "r")
)
-
- # Add streams to GUI
- for channel in streamList :
- self.addItem( channel['name'].encode('utf-8'),
channel['playlist'], channel["description"], channel['bitrate'],
self._addonProfilePath + "art_" + channel['key'] + ".png", channel['isNew'] )
-
- # If streams should be sorted A-Z
- if ADDON.getSetting('sortaz') == "true" :
- xbmcplugin.addSortMethod( HANDLE,
sortMethod=xbmcplugin.SORT_METHOD_LABEL )
-
- # End of channel list
- xbmcplugin.endOfDirectory( HANDLE, succeeded=True )
-
- # Resets the 'Force refresh' setting
- ADDON.setSetting( id="forceupdate", value="false" )
-
- return True
-
-
- """return list - False if it fails
- Gets the favorites playlist and returns the streams as a list
- Also every channel is added to the GUI from here, as the progress
indication
- in the GUI would not reflect that something is actually happening till
the very end
- """
- def customizeStreamListAddMenuitem( self, list, listenkey ) :
- # Precompiling regexes
- streamurl_re = re.compile('File\d+=([^\n]*)', re.I) # streams
in .pls file
-
- streamList = []
-
- # Will add list elements to a new list, with a few additions
- for channel in list :
- channel['key'] = self.makeChannelIconname(
channel['name'] ) # customize the key that is used to find channelart
- channel['isNew'] = False # is used to highlight when
it's a new channel
- channelArt = "art_" + channel['key'] + ".png"
- channel['bitrate'] = self._bitrate
- channel["description"] =
channel["description"].encode('utf-8')
-
- if ADDON.getSetting('username') != "" : # append
listenkey to playlist url if username is set
- channel['playlist'] = self.getFirstStream(
channel['playlist'] + "?" + listenkey, streamurl_re )
- else :
- channel['playlist'] = self.getFirstStream(
channel['playlist'], streamurl_re )
-
- if (not os.path.isfile(self._addonProfilePath +
channelArt)) : # if channelart is not in cache
- xbmc.log( "Channelart for " +
channel['name'].encode("ascii","ignore") + " not found in cache at " +
self._addonProfilePath + channelArt, xbmc.LOGNOTICE )
- self.getChannelArt( channel['id'], "art_" +
channel['key'] )
- channel['isNew'] = True
- self._newChannels = self._newChannels + 1
-
- streamList.append( channel )
-
- # I'd have prefeered it if I didn't have to add
menuitem from within this method
- # but I have to, too give the user some visual feedback
that stuff is happening
- self.addItem( channel['name'].encode('utf-8'),
channel['playlist'], channel["description"], self._bitrate,
self._addonProfilePath + "art_" + channel['key'] + ".png", channel['isNew'] )
-
- return streamList # returns the channellist so it can be saved
to cache
-
-
- """return bool
- Will check if channelart/icon is present in cache - if not, try to
download
- """
- def getChannelArt( self, channelId, channelKey ) :
- channelArt_re = re.compile('data-channel-id="' + str(channelId)
+'">(?:[\n\s]*)<a(?:[^>]*)>(?:[\n\s]*)<img(?:[^>]*)src="([^"]*)"', re.I)
-
- try :
- if (self._frontpageHtml == "") : # If frontpage html
has not already been downloaded, do it
- self._frontpageHtml = self._httpComm.get(
self._baseUrl )
- channelartDict = channelArt_re.findall(
self._frontpageHtml )
-
- # Return false if no channel art is found
- if len(channelartDict) < 1:
- return False
-
- # Add HTTP to // syntax links
- if not "http" in channelartDict[0]:
- channelartDict[0] = "http:" + channelartDict[0]
-
- # Will download and save the channelart to the cache
- self._httpComm.getImage( channelartDict[0],
self._addonProfilePath + channelKey + ".png" )
- return True
-
- except Exception :
- sys.exc_clear() # Clears all exceptions so the script
will continue to run
- xbmcgui.Dialog().ok( ADDON.getLocalizedString(30160),
ADDON.getLocalizedString(30161), ADDON.getLocalizedString(30162) +
channelartDict[0] )
- xbmc.log( ADDON.getLocalizedString(30160) + " " +
ADDON.getLocalizedString(30161) + channelKey + " " +
ADDON.getLocalizedString(30162)+ channelartDict[0], xbmc.LOGERROR )
- return False
-
- return True
-
-
- """return String
- Extracts the premium listenkey from the listenkey page html
- """
- def getListenkey( self ) :
- listenkey_re = re.compile('Key is:<br
/>[^<]*<strong>([\w\d]*)<', re.DOTALL)
-
- try :
- logindata = urllib.urlencode({
'member_session[username]': ADDON.getSetting('username'),
-
'member_session[password]': ADDON.getSetting('password') })
-
- self._httpComm.post( self._loginUrl, logindata ) # logs
in so the listenkey page is accessible
-
- listenkeyHtml = self._httpComm.get( self._listenkeyUrl)
- listenkeyDict = listenkey_re.findall( listenkeyHtml )
-
- xbmc.log( "Found listenkey", xbmc.LOGNOTICE )
- return listenkeyDict[0]
-
- except Exception :
- sys.exc_clear() # Clears all exceptions so the script
will continue to run
- xbmcgui.Dialog().ok( ADDON.getLocalizedString(30100),
ADDON.getLocalizedString(30101), ADDON.getLocalizedString(30102) )
- xbmc.log( ADDON.getLocalizedString(30100) + " " +
ADDON.getLocalizedString(30101) + " " + ADDON.getLocalizedString(30102),
xbmc.LOGERROR )
- return False
-
- return False
-
-
- """return list - False if it fails
- Will get a HTML page containing JSON data, decode it and return
- """
- def getJSONChannelList( self, url ) :
- try :
- jsonData = self._httpComm.get( url )
- jsonData = json.loads(jsonData)
- except Exception : # Show error message in XBMC GUI if failing
to parse JSON
- sys.exc_clear() # Clears all exceptions so the script
will continue to run
- xbmcgui.Dialog().ok( ADDON.getLocalizedString(30100),
ADDON.getLocalizedString(30101), ADDON.getLocalizedString(30102) )
- xbmc.log( ADDON.getLocalizedString(30100) + " " +
ADDON.getLocalizedString(30101) + " " + ADDON.getLocalizedString(30102),
xbmc.LOGERROR )
- return False
-
- return jsonData
-
-
- """return list - False if it fails
- Gets the favorites playlist and returns the streams as a list
- """
- def getFavoriteStreamsList( self, url ) :
- try :
- favoritesPlaylist = self._httpComm.get( url ) #
favorites .pls playlist in plaintext
- favoritesList = [] # list that will contain streamlist
-
- streamurl_re = re.compile( 'File\d+=([^\n]*)', re.I
) # first stream in .pls file
- channeltitle_re = re.compile( 'Title\d+=([^\n]*)', re.I
)
-
- streamTitles = channeltitle_re.findall(
favoritesPlaylist )
- streamUrls = streamurl_re.findall(
favoritesPlaylist )
-
- if len(streamUrls) == len( streamTitles ) : # only
continue if the count of urls and titles are equal
-
- for i in range(len(streamUrls)) :
- listitem = {}
- listitem['playlist'] = streamUrls[i]
- listitem['name'] =
streamTitles[i].replace( parser.get('plugin', 'playlistStripName') + " ", "" )
# favorite stream titles has some "fluff" text it that is removed
- listitem['key'] =
self.makeChannelIconname( listitem['name'] )
- listitem['isNew'] = False
- listitem['bitrate'] = self._bitrate
- listitem['description'] = ""
- favoritesList.append( listitem )
-
- else :
- return False
-
- return favoritesList
-
- except Exception : # Show error message in XBMC GUI if failing
to parse JSON
- #sys.exc_clear() # Clears all exceptions so the script
will continue to run
- xbmcgui.Dialog().ok( ADDON.getLocalizedString(30120),
ADDON.getLocalizedString(30111), url )
- xbmc.log( ADDON.getLocalizedString(30120) + " " +
ADDON.getLocalizedString(30111) + " " + url, xbmc.LOGERROR )
- return False
-
- return favoritesList
-
-
- """return string
- Will take a channelname, lowercase it and remove spaces, dashes and
other special characters
- The string returned is normally used as part of the filename for the
channelart
- """
- def makeChannelIconname( self, channelname ) :
- iconreplacement_re = re.compile('[^a-z0-9]', re.I) # regex that
hits everything but a-z and 0-9
- iconname = string.lower(iconreplacement_re.sub( '',
channelname) )
- return iconname
-
-
- """return bool
- Simply adds a music item to the XBMC GUI
- """
- # Adds item to XBMC itemlist
- def addItem( self, channelTitle, streamUrl, streamDescription,
streamBitrate, icon, isNewChannel ) :
-
- if isNewChannel == True : # tart it up a bit if it's a new
channel
- li = xbmcgui.ListItem(label="[COLOR FF007EFF]" +
channelTitle + "[/COLOR]",thumbnailImage=icon)
- xbmc.log( "New channel found: " + channelTitle,
xbmc.LOGERROR )
- else :
- li = xbmcgui.ListItem(label=channelTitle,
thumbnailImage=icon)
-
- li.setProperty("mimetype", self._streamMimeType)
- li.setInfo( type="Music", infoLabels={ "label": channelTitle,
"Genre": channelTitle, "Comment": streamDescription, "Size": (streamBitrate *
1024) })
- li.setProperty("IsPlayable", "true")
- li.setProperty("IsLive", "true")
-
- xbmcplugin.addDirectoryItem(handle=HANDLE, url=streamUrl,
listitem=li, isFolder=False)
-
- return True
-
-
- """return string
- Gets the first stream from a playlist
- """
- def getFirstStream( self, playlistUrl, regex ) :
- plsData = self._httpComm.get( playlistUrl )
-
- streamurls = regex.findall(plsData)
-
- return streamurls[0]
-
-
- """return bool
- Checks if a file is older than x seconds
- """
- def checkFileTime( self, tmpfile, cachedir, timesince ) :
- if not os.path.exists( cachedir ) :
- os.makedirs( cachedir )
- return False
-
- # If file exists, check timestamp
- if os.path.exists( tmpfile ) :
- if os.path.getmtime( tmpfile ) > ( time.time() -
timesince ) :
- xbmc.log( 'It has not been ' + str(
timesince/60 ) + ' minutes since ' + tmpfile + ' was last updated',
xbmc.LOGNOTICE )
- return False
- else :
- xbmc.log( 'The cachefile ' + tmpfile + ' + has
expired', xbmc.LOGNOTICE )
- return True
- # If file does not exist, return true so the file will be
created by scraping the page
- else :
- xbmc.log( 'The cachefile ' + tmpfile + ' does not
exist', xbmc.LOGNOTICE )
- return True
-
-
+ # settings used for multithreading
+ threadMax = 8
+ workQueue = Queue.Queue()
+
+ # Resolves path to where plugin settings and cache will be stored
+ addonProfilePath =
xbmc.translatePath(ADDON.getAddonInfo('profile')).decode('utf-8')
+
+ # Init CURL thingy
+ curler = HTTPComm()
+
+ # regex used to find all streams in a .pls
+ re_playlistStreams = re.compile("File\d=([^\n]+)\s*Title\d=([^\n]+)", re.M
| re.I)
+
+ #dictBitrate = {1: 40, 2: 64, 3: 128, 4: 256}
+ dictBitrate = [40, 64, 128, 256]
+
+ # list of channels used when caching and retrieving from cache
+ channelsList = []
+
+ # unique listenkey if a premium member
+ listenKey = ''
+
+ # stores how many new channels/channelart was found
+ newChannels = 0
+
+ def __init__(self):
+ # If stats is allowed and its been at least 24 hours since last checkin
+ #if (ADDON.getSetting('allowstats') == "true") and (
+ # self.checkFileTime(self._checkinFile, self._addonProfilePath,
86400) == True):
+ # open(self._checkinFile, "w")
+ #
+ # account = 'public'
+ # if ADDON.getSetting('username') != "":
+ # account = 'premium'
+ #
+ # xbmc.log('Submitting stats', xbmc.LOGNOTICE)
+ # self._httpComm.get('http://stats.qualisoft.dk/?plugin=' +
ADDON.getAddonInfo(
+ # 'id') + '&version=' + __version__ + '&account=' + account +
'&key=' + parser.get('plugin',
+ #
'checkinkey'))
+ #
+ xbmc.log("[PLUGIN] %s v%s (%s)" % ( __plugin__, __version__, __date__
), xbmc.LOGNOTICE)
+
+
+ """
+ Let's get some tunes!
+ """
+ def run(self):
+ threads = []
+
+ # check if cache has expired
+ if not ADDON.getSetting("forceupdate") == "true"\
+ and not int(ADDON.getSetting("cacheexpire_days")) == 0\
+ and self.checkFileTime(pluginConfig.get('cache', 'cacheChannels'),
+ ADDON.getSetting("cacheexpire_days")):
+ ADDON.setSetting(id="forceupdate", value="true")
+
+ if ADDON.getSetting("forceupdate") == "true":
+ channels = []
+ html = ""
+
+ # if username is set, try to login and go for premium channels
+ if ADDON.getSetting('username') != "":
+ loginData = urllib.urlencode({'member_session[username]':
ADDON.getSetting('username'),
+ 'member_session[password]':
ADDON.getSetting('password')})
+
+ # post login info and get frontpage html
+ html = self.curler.request(pluginConfig.get('urls', 'login'),
'post', loginData)
+
+ # if we could not reach di.fm at all
+ if not bool(html):
+ xbmc.log('di.fm could not be reached', xbmc.LOGWARNING)
+ xbmcgui.Dialog().ok(ADDON.getLocalizedString(30100),
+ ADDON.getLocalizedString(30101),
+ ADDON.getLocalizedString(30102),
+ ADDON.getLocalizedString(30103))
+ xbmcplugin.endOfDirectory(HANDLE, succeeded=True)
+ return True
+
+ channels =
json.loads(self.curler.request(pluginConfig.get('streams', 'premium%sk' %
self.dictBitrate[int(ADDON.getSetting('bitrate'))]), 'get'))
+
+ premiumConfig = self.getPremiumConfig(html)
+
+ # if premiumConfig['listenKey'] is blank, we did not log in
correctly
+ if premiumConfig['listenKey'] == '':
+ xbmc.log('Login did not succeed', xbmc.LOGWARNING)
+ xbmcgui.Dialog().ok(ADDON.getLocalizedString(30170),
+ ADDON.getLocalizedString(30171),
+ ADDON.getLocalizedString(30172))
+ xbmcplugin.endOfDirectory(HANDLE, succeeded=True)
+ return True
+
+ # if we should get the favorites or all channels
+ if ADDON.getSetting("usefavorites") == 'true':
+ channels = self.getFavoriteChannels(html, channels)
+ if len(channels) == 0:
+ xbmcgui.Dialog().ok(ADDON.getLocalizedString(30180),
+ ADDON.getLocalizedString(30181),
+ ADDON.getLocalizedString(30182))
+ xbmcplugin.endOfDirectory(HANDLE, succeeded=True)
+ return True
+
+ # add listenkey to playlist urls
+ for channel in channels:
+ channel['playlist'] = '%s?%s' % (channel['playlist'],
premiumConfig['listenKey'])
+
+ # go for free/public channels
+ else:
+ html = self.curler.request(pluginConfig.get('urls',
'frontpage'), 'get')
+
+ # if we could not reach di.fm at all
+ if not html:
+ xbmc.log('di.fm could not be reached', xbmc.LOGWARNING)
+ xbmcgui.Dialog().ok(ADDON.getLocalizedString(30100),
+ ADDON.getLocalizedString(30101),
+ ADDON.getLocalizedString(30102),
+ ADDON.getLocalizedString(30103))
+ xbmcplugin.endOfDirectory(HANDLE, succeeded=True)
+ return True
+
+ channels =
json.loads(self.curler.request(pluginConfig.get('streams', 'public'), 'get'))
+
+ re_channelData =
re.compile("NS\('AudioAddict'\).Channels\s*=\s*([^;]+);", re.M | re.I)
+ channelMeta = json.loads(re_channelData.findall(html)[0])
+
+ # put each playlist in a worker queue for threading
+ for channel in channels:
+ self.workQueue.put(channel)
+
+ # starts 8 threads to download streams for each channel
+ while not self.workQueue.empty():
+ xbmc.log('Worker queue size is %s' %
(str(self.workQueue.qsize())), xbmc.LOGNOTICE)
+
+ if threading.activeCount() < self.threadMax and not
self.workQueue.empty():
+ threads.append(scraperThread(self, self.workQueue.get(),
channelMeta, len(channels)))
+ threads[-1].start()
+ else:
+ time.sleep(0.1)
+
+ # wait for all threads to finish before continuing
+ for t in threads:
+ t.join()
+
+ # Saves channels to cache and reset the "force update" flag
+ if len(channels) > 0:
+ pickle.dump(self.channelsList, open("%s/%s" %
(self.addonProfilePath, pluginConfig.get('cache', 'cacheChannels')), "w"),
protocol=0)
+ ADDON.setSetting(id="forceupdate", value="false")
+
+ # else load channels from cache file
+ else:
+ self.channelsList = pickle.load(open(("%s/%s" %
(self.addonProfilePath, pluginConfig.get('cache', 'cacheChannels'))), "r"))
+
+ for channel in self.channelsList:
+ self.addItem(channel['name'],
+ channel['streamUrl'],
+ channel['description'],
+ channel['bitrate'],
+ channel['asset'],
+ channel['isNew'],
+ len(self.channelsList))
+
+ # Should channels be sorted A-Z?
+ if ADDON.getSetting('sortaz') == "true":
+ xbmcplugin.addSortMethod(HANDLE,
sortMethod=xbmcplugin.SORT_METHOD_LABEL)
+
+ # tell XMBC there are no more items to list
+ xbmcplugin.endOfDirectory(HANDLE, succeeded=True)
+
+ if self.newChannels > 0:
+ xbmcgui.Dialog().ok(ADDON.getLocalizedString(30130),
+ ADDON.getLocalizedString(30131) +
str(self.newChannels) + ADDON.getLocalizedString(30132),
+ ADDON.getLocalizedString(30133),
+ ADDON.getLocalizedString(30134))
+
+ return True
+
+
+ """
+ Parses a playlist (if needed) and calls the function that adds it to the
GUI
+ """
+ def addChannel(self, channel, channelMeta, channelCount):
+ # set to true if new channelart is downloaded
+ isNew = 0
+
+ if not os.path.exists(self.addonProfilePath + str(channel['id']) +
'.png'):
+ isNew = 1
+ self.newChannels += 1
+ self.getChannelAsset(str(channel['id']),
channelMeta[str(channel['id'])]['asset_url'])
+
+ if ADDON.getSetting('randomstream') == "true":
+ playlist = self.curler.request(channel['playlist'] +
self.listenKey, 'get')
+
+ playlistStreams = self.re_playlistStreams.findall(playlist)
+
+ # gets a random stream from the channels playlist
+ streamUrl = playlistStreams[randrange(len(playlistStreams))][0]
+ else:
+ streamUrl = channel['playlist'] + self.listenKey
+
+ bitrate = 40
+
+ if ADDON.getSetting('username') != "":
+ bitrate = self.dictBitrate[int(ADDON.getSetting('bitrate'))]
+
+ # stores channel in list to be cached later
+ co = {'name': channel['name'],
+ 'streamUrl': streamUrl,
+ 'description': channel['description'],
+ 'bitrate': bitrate,
+ 'asset': self.addonProfilePath + str(channel['id']) + '.png',
+ 'isNew': isNew}
+ self.channelsList.append(dict(co))
+
+ self.addItem(channel['name'],
+ streamUrl,
+ channel['description'],
+ bitrate,
+ self.addonProfilePath + str(channel['id']) + '.png',
+ isNew,
+ channelCount)
+
+
+ """ Returns a bool
+ Adds item to the XBMC GUI
+ """
+ def addItem(self, channelTitle, streamUrl, streamDescription, bitrate,
icon, isNewChannel, totalItems):
+ # tart it up a bit if it's a new channel
+ if isNewChannel:
+ li = xbmcgui.ListItem(label="[COLOR FF007EFF]" + channelTitle +
"[/COLOR]", thumbnailImage=icon)
+ xbmc.log("New channel found: " + channelTitle, xbmc.LOGERROR)
+ else:
+ li = xbmcgui.ListItem(label=channelTitle, thumbnailImage=icon)
+
+ # 256kb/sec is MP3
+ if bitrate == 256 and ADDON.getSetting('username') != "":
+ li.setProperty("mimetype", 'audio/mpeg')
+ else:
+ li.setProperty("mimetype", 'audio/aac')
+
+ li.setInfo(type="Music", infoLabels={"Genre": channelTitle,
+ "Comment": streamDescription,
+ "Size": bitrate * 1024})
+ li.setProperty("IsPlayable", "true")
+
+ xbmcplugin.addDirectoryItem(handle=HANDLE, url=streamUrl, listitem=li,
isFolder=False, totalItems=totalItems)
+
+ return True
+
+
+ """ Returns a bool
+ Will check if channel asset/art is present in cache - if not, try to
download
+ """
+ def getChannelAsset(self, channelId, assetUrl):
+ try:
+ data = self.curler.request(assetUrl, 'get')
+ filepath = self.addonProfilePath + channelId + '.png'
+ open(filepath, 'wb').write(data)
+ xbmc.log('Found new channel art for with ID %s' % str(channelId),
xbmc.LOGINFO)
+ except Exception:
+ sys.exc_clear() # Clears all exceptions so the script will
continue to run
+ xbmcgui.Dialog().ok(ADDON.getLocalizedString(30160),
ADDON.getLocalizedString(30161),
+ ADDON.getLocalizedString(30162) + channelId)
+ xbmc.log(ADDON.getLocalizedString(30160) + " " +
ADDON.getLocalizedString(
+ 30161) + str(channelId) + " " +
ADDON.getLocalizedString(30162) + str(channelId), xbmc.LOGERROR)
+ return False
+
+ return True
+
+
+ """ Return a list containing a dictonary
+ Filters out all channels that are not in the favorites list
+ """
+ def getFavoriteChannels(self, html, allChannels):
+ channels = []
+ re_favoriteData =
re.compile("NS\('AudioAddict.API.Config.member'\).Favorites\s*=\s*([^;]+);",
re.M | re.I)
+ m = re_favoriteData.findall(html)
+
+ # if favorites list is empty, return empty list
+ if m[0] == '[]':
+ return channels
+ else:
+ favorites = json.loads(re_favoriteData.findall(html)[0])
+
+ # sort favorites after user selected positions
+ favorites = sorted(favorites, key=lambda k: k['position'])
+
+ for fav in favorites:
+ for channel in allChannels:
+ if fav['channel_id'] == channel['id']:
+ channels.append(dict(channel))
+
+ return channels
+
+
+ """ Return a list containing a dictonary or false
+ Returns the logged in users premium config
+ """
+ def getPremiumConfig(self, html):
+ try:
+ if ADDON.getSetting("forceupdate") == "true":
+ re_config =
re.compile("NS\('AudioAddict.API'\).Config\s*=\s*([^;]+);", re.M | re.I)
+ pickle.dump(re_config.findall(html)[0], open("%s/%s" %
(self.addonProfilePath, pluginConfig.get('cache', 'cachePremiumConfig')), "w"),
protocol=0)
+ premiumConfig = json.loads(re_config.findall(html)[0])
+ else:
+ premiumConfig = json.loads(pickle.load(open(("%s/%s" %
(self.addonProfilePath, pluginConfig.get('cache', 'cachePremiumConfig'))),
"r")))
+ return premiumConfig
+ except Exception:
+ sys.exc_clear() # Clears all exceptions so the script will
continue to run
+ return False
+
+
+ """ Return a bool
+ Checks if a file is older than x days
+ """
+ def checkFileTime(self, filename, days):
+ if not os.path.exists(self.addonProfilePath):
+ os.makedirs(self.addonProfilePath)
+ return False
+
+ daysInSecs = int(days)*60*60*24
+
+ file = "%s%s" % (self.addonProfilePath, filename)
+
+ # If file exists, check timestamp
+ if os.path.exists(file):
+ if os.path.getmtime(file) > (time.time() - daysInSecs):
+ xbmc.log('It has not been %s days since %s was last updated' %
(days, file), xbmc.LOGNOTICE)
+ return False
+ else:
+ xbmc.log('The cache file %s + has expired' % file,
xbmc.LOGNOTICE)
+ return True
+ # If file does not exist, return true so the file will be created by
scraping the page
+ else:
+ xbmc.log('The cache file %s does not exist' % file, xbmc.LOGNOTICE)
+ return True
+
+
MusicAddonInstance = musicAddonXbmc()
-MusicAddonInstance.start()
+MusicAddonInstance.run()
diff --git a/plugin.audio.di.fm/httpcomm.py b/plugin.audio.di.fm/httpcomm.py
index e7d0b05..94e99dd 100644
--- a/plugin.audio.di.fm/httpcomm.py
+++ b/plugin.audio.di.fm/httpcomm.py
@@ -6,100 +6,53 @@ import gzip
import StringIO
import cookielib
import socket
-import urllib2
-
-class HTTPComm :
- # Login
-
- _cj = cookielib.CookieJar()
-
- def post( self, url, postdata ) :
- timeout = 10
- socket.setdefaulttimeout(timeout)
-
-
- # create an opener
- opener =
urllib2.build_opener(urllib2.HTTPCookieProcessor(self._cj))
-
- # Add useragent, sites don't like to interact with scripts
- opener.addheaders = [
- ('User-Agent', 'Mozilla/5.0 (X11; U; Linux i686; en-US;
rv:1.9.2.8) Gecko/20100723 Ubuntu/10.04 (lucid) Firefox/3.6.8'),
- ('Accept',
'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'),
- ('Accept-Language', 'en-gb,en;q=0.5'),
- ('Accept-Encoding', 'gzip,deflate'),
- ('Accept-Charset', 'ISO-8859-1,utf-8;q=0.7,*;q=0.7'),
- ('Keep-Alive', '115'),
- ('Connection', 'keep-alive'),
- ('Cache-Control', 'max-age=0'),
- ]
-
- resp = opener.open(url, postdata)
-
- # Compressed (gzip) response...
- if resp.headers.get( "content-encoding" ) == "gzip":
- htmlGzippedData = resp.read()
- stringIO = StringIO.StringIO( htmlGzippedData )
- gzipper = gzip.GzipFile( fileobj = stringIO )
- htmlData = gzipper.read()
- else :
- htmlData = resp.read()
-
- resp.close()
-
- # Return html
- return htmlData
-
- # GET
- def get( self, url ):
- timeout = 10
- socket.setdefaulttimeout(timeout)
-
- #create an opener
- opener =
urllib2.build_opener(urllib2.HTTPCookieProcessor(self._cj))
- #Add useragent, sites don't like to interact with scripts
- opener.addheaders = [
- ('User-Agent', 'Mozilla/5.0 (X11; U; Linux i686; en-US;
rv:1.9.2.8) Gecko/20100723 Ubuntu/10.04 (lucid) Firefox/3.6.8'),
- ('Accept',
'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'),
- ('Accept-Language', 'en-gb,en;q=0.5'),
- ('Accept-Encoding', 'gzip,deflate'),
- ('Accept-Charset', 'ISO-8859-1,utf-8;q=0.7,*;q=0.7'),
- ('Keep-Alive', '115'),
- ('Connection', 'keep-alive'),
- ('Cache-Control', 'max-age=0'),
- ]
- try :
- resp = opener.open(url)
-
- # Compressed (gzip) response...
- if resp.headers.get( "content-encoding" ) == "gzip":
- htmlGzippedData = resp.read()
- stringIO = StringIO.StringIO(
htmlGzippedData )
- gzipper = gzip.GzipFile( fileobj =
stringIO )
- htmlData = gzipper.read()
- else :
- htmlData = resp.read()
-
- resp.close()
-
- # Return html
- return htmlData
- except Exception:
- return False
-
- def getImage( self, url, path ) :
- timeout = 10
- socket.setdefaulttimeout(timeout)
-
- try :
- # Set useragent, sites don't like to interact with
scripts
- headers = { 'User-Agent':'Mozilla/5.0 (X11; U; Linux
i686; en-US; rv:1.9.2.8) Gecko/20100723 Ubuntu/10.04 (lucid)
Firefox/3.6.8','Accept':'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8','Accept-Language':'en-us,en;q=0.5','Accept-Charset':'ISO-8859-1,utf-8;q=0.7,*;q=0.7'}
- req = urllib2.Request(url=url, headers=headers)
- f = urllib2.urlopen(req)
- imagedata = f.read() # Downloads imagedata
- open(path, 'wb').write(imagedata)
- # Return true
- return True
- except Exception:
- return False
+class HTTPComm:
+ cj = None
+ curlinstance = None
+
+ def __init__(self):
+ self.cj = cookielib.CookieJar()
+ self.curlinstance =
urllib2.build_opener(urllib2.HTTPCookieProcessor(self.cj))
+
+ def request(self, url, mode, postdata=None):
+ timeout = 10
+ socket.setdefaulttimeout(timeout)
+
+ # Add useragent and tries to look like a real browser, as the site
doesn't like to interact with scripts
+ self.curlinstance.addheaders = [
+ ('User-Agent',
+ 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:26.0) Gecko/20100101
Firefox/26.0'),
+ ('Accept',
'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'),
+ ('Accept-Language', 'en-gb,en;q=0.5'),
+ ('Accept-Encoding', 'gzip,deflate'),
+ ('Accept-Charset', 'ISO-8859-1,utf-8;q=0.7,*;q=0.7'),
+ ('Keep-Alive', '115'),
+ ('Connection', 'keep-alive'),
+ ('Cache-Control', 'max-age=0'),
+ ]
+
+ try:
+ if mode=='get':
+ resp = self.curlinstance.open(url)
+ elif mode is 'post' and postdata:
+ resp = self.curlinstance.open(url, postdata)
+ else:
+ return False
+
+ # decompress if gzipped response...
+ if resp.headers.get("content-encoding") == "gzip":
+ htmlGzippedData = resp.read()
+ stringIO = StringIO.StringIO(htmlGzippedData)
+ gzipper = gzip.GzipFile(fileobj=stringIO)
+ data = gzipper.read()
+ else:
+ data = resp.read()
+
+ resp.close()
+
+ # Return response data
+ return data
+ except Exception:
+ return False
diff --git a/plugin.audio.di.fm/resources/language/English/strings.xml
b/plugin.audio.di.fm/resources/language/English/strings.xml
index 237d247..d24aff2 100644
--- a/plugin.audio.di.fm/resources/language/English/strings.xml
+++ b/plugin.audio.di.fm/resources/language/English/strings.xml
@@ -1,24 +1,30 @@
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<strings>
- <!-- The strings 30000 to 30999 is reserved for plug-in paramters -->
+ <!-- The strings 30000 to 30999 is reserved for plug-in parameters -->
<!--GENERAL STRINGS -->
- <string id="30001">Stream source</string>
+
+ <string id="30014">General</string>
+ <string id="30011">Sort A-Z (also affects 'My Favorites')</string>
+ <string id="30030">Use first stream for each channel (starts playing
faster)</string>
+ <string id="30012">Stream cache expiration in days (0 = never)</string>
<string id="30010">Force cache refresh (use when changing
settings)</string>
+ <string id="30031">Download random stream from each channel (faster after
caching)</string>
+
+ <string id="30001">Stream source</string>
+
<string id="30002">Premium</string>
<string id="30003">E-mail/username</string>
<string id="30004">Password</string>
<string id="30005">Use 'My favorites' playlist</string>
<string id="30013">Stream bitrate</string>
+
<string id="30006">About</string>
<string id="30007">Version</string>
<string id="30008">Author</string>
<string id="30009">Homepage</string>
- <string id="30011">Sort A-Z (also affects 'My Favorites')</string>
- <string id="30012">Stream cache expiration in minutes (0 =
never)</string>
- <string id="30014">General</string>
- <string id="30015">Get HQ streams with DI Premium at
http://www.di.fm</string>
- <string id="30016">Version: 2.1.2</string>
+ <string id="30015">Get HQ 256kb/sec streams with DI Premium at
http://www.di.fm</string>
+ <string id="30016">Version: 3.0.0</string>
<string id="30017">Author: Tim C. 'Bitcrusher' Steinmetz</string>
<string id="30018">Homepage: http://qualisoft.dk</string>
<string id="30019">E-mail: [email protected]</string>
@@ -33,15 +39,14 @@
<string id="30027">Accounttype (public/premium), plugin version,
country.</string>
<string id="30028">I know it's a bit of a cheap trick, having it on by
default ;)</string>
<string id="30029">-Tim</string>
-
- <string id="30030">Use first stream from each channel (Starts playing
faster)</string>
<string id="30100">Connection error</string>
<string id="30101">Could not connect to di.fm</string>
<string id="30102">Check your internet connection</string>
+ <string id="30103">and the website in your browser</string>
<string id="30110">No streams found</string>
- <string id="30111">Maybe your DI Premium membership expired</string>
+ <string id="30111">Maybe your DI Premium membership expired?</string>
<string id="30112">or the plugin has broken (the HTML layout
changed)</string>
<string id="30120">Connection timed out</string>
@@ -51,7 +56,7 @@
<string id="30131">There was found </string>
<string id="30132"> new piece(s) of channelart</string>
<string id="30133">meaning there could be new channels.</string>
- <string id="30134">The new channels are highlighted in blue</string>
+ <string id="30134">New channels are highlighted in blue</string>
<string id="30140">Cachefiles are missing</string>
<string id="30141">At least one of the cachefiles is missing</string>
@@ -68,4 +73,13 @@
<string id="30161">There was a problem downloading channelart for:
</string>
<string id="30162">From URL: </string>
+ <string id="30170">Login failed</string>
+ <string id="30171">Invalid Username or Password</string>
+ <string id="30172">Has your account perhaps expired?</string>
+
+ <string id="30180">No favorites found</string>
+ <string id="30181">Login to di.fm in your browser and go to 'Manage
favorites'</string>
+ <string id="30182">or uncheck 'Use my favorites' from this plugins
settings</string>
+
+
</strings>
diff --git a/plugin.audio.di.fm/resources/settings.xml
b/plugin.audio.di.fm/resources/settings.xml
index e392fff..4f47d2f 100644
--- a/plugin.audio.di.fm/resources/settings.xml
+++ b/plugin.audio.di.fm/resources/settings.xml
@@ -2,35 +2,25 @@
<settings>
<category label="30014">
<setting id="sortaz" type="bool" label="30011" default="true" />
- <setting id="cacheexpire" type="number" label="30012"
default="720" />
- <setting id="forceupdate" type="bool" label="30010"
default="false" />
+ <setting id="randomstream" type="bool" label="30031"
default="true" />
+ <setting id="cacheexpire_days" type="number" label="30012" default="7"
/>
+ <setting id="forceupdate" type="bool" label="30010" default="false" />
<setting id="getpremium" type="lsep" label="30015" />
- <setting type="lsep" label="30017" />
- <setting type="lsep" label="30018" />
- <setting type="lsep" label="30019" />
- <setting type="lsep" label="30016" />
</category>
<category label="30002">
<setting id="username" type="text" label="30003" />
<setting id="password" type="text" option="hidden"
label="30004" />
- <setting id="bitrate" type="enum" label="30013"
- values="40|64|128|256" default="3" />
+ <setting id="bitrate" type="enum" label="30013"
values="40|64|128|256" default="4" />
<setting id="usefavorites" type="bool" label="30005"
default="false" />
<setting id="forceupdate" type="bool" label="30010"
default="false" />
+ <setting id="getpremium" type="lsep" label="30015" />
+ </category>
+
+ <category label="30006">
+ <setting id="getpremium" type="lsep" label="30015" />
<setting type="lsep" label="30017" />
<setting type="lsep" label="30018" />
<setting type="lsep" label="30019" />
<setting type="lsep" label="30016" />
</category>
- <category label="30020">
- <setting id="allowstats" type="bool" label="30021"
default="true" />
- <setting type="lsep" label="30022" />
- <setting type="lsep" label="30023" />
- <setting type="lsep" label="30024" />
- <setting type="lsep" label="30025" />
- <setting type="lsep" label="30026" />
- <setting type="lsep" label="30027" />
- <setting type="lsep" label="30028" />
- <setting type="lsep" label="30029" />
- </category>
-</settings>
+</settings>
http://xbmc.git.sourceforge.net/git/gitweb.cgi?p=xbmc/plugins;a=commit;h=b485d7be79860ca0bfb3ec3d67970822b7199fa4
http://xbmc.git.sourceforge.net/git/gitweb.cgi?p=xbmc/plugins;a=commit;h=f76b6e2728f664604bef7423a752d1ef5c432829
-----------------------------------------------------------------------
Summary of changes:
plugin.audio.di.fm/addon.xml | 2 +-
plugin.audio.di.fm/changelog.txt | 8 +
plugin.audio.di.fm/config.ini | 34 +-
plugin.audio.di.fm/default.py | 742 ++++++++++----------
plugin.audio.di.fm/httpcomm.py | 143 ++---
.../resources/language/English/strings.xml | 36 +-
plugin.audio.di.fm/resources/settings.xml | 30 +-
.../LICENSE.txt | 0
plugin.video.sbview/addon.xml | 32 +
plugin.video.sbview/default.py | 343 +++++++++
plugin.video.sbview/icon.png | Bin 0 -> 27410 bytes
.../resources/__init__.py | 0
.../resources/language/english/strings.xml | 15 +
.../resources/lib}/__init__.py | 0
plugin.video.sbview/resources/settings.xml | 9 +
plugin.video.udacity/LICENSE.txt | 19 +
plugin.video.udacity/README.md | 24 +
plugin.video.udacity/addon.py | 183 +++++
plugin.video.udacity/addon.xml | 25 +
plugin.video.udacity/changelog.txt | 33 +
plugin.video.udacity/fanart.jpg | Bin 0 -> 142922 bytes
plugin.video.udacity/icon.png | Bin 0 -> 46073 bytes
.../resources/__init__.py | 0
.../resources/language/English/strings.xml | 15 +
.../resources/lib}/__init__.py | 0
plugin.video.udacity/resources/lib/controls.py | 146 ++++
plugin.video.udacity/resources/lib/udacity.py | 226 ++++++
plugin.video.udacity/resources/media/blank.png | Bin 0 -> 3630 bytes
plugin.video.udacity/resources/settings.xml | 8 +
plugin.video.udacity/resources/tests/test_addon.py | 41 ++
30 files changed, 1603 insertions(+), 511 deletions(-)
copy {plugin.audio.dradio => plugin.video.sbview}/LICENSE.txt (100%)
create mode 100644 plugin.video.sbview/addon.xml
create mode 100644 plugin.video.sbview/default.py
create mode 100644 plugin.video.sbview/icon.png
copy {plugin.image.ilpiolatracker =>
plugin.video.sbview}/resources/__init__.py (100%)
create mode 100644 plugin.video.sbview/resources/language/english/strings.xml
copy {plugin.image.ilpiolatracker/resources =>
plugin.video.sbview/resources/lib}/__init__.py (100%)
create mode 100644 plugin.video.sbview/resources/settings.xml
create mode 100644 plugin.video.udacity/LICENSE.txt
create mode 100644 plugin.video.udacity/README.md
create mode 100644 plugin.video.udacity/addon.py
create mode 100644 plugin.video.udacity/addon.xml
create mode 100644 plugin.video.udacity/changelog.txt
create mode 100755 plugin.video.udacity/fanart.jpg
create mode 100755 plugin.video.udacity/icon.png
copy {plugin.audio.jambmc => plugin.video.udacity}/resources/__init__.py (100%)
create mode 100644 plugin.video.udacity/resources/language/English/strings.xml
copy {plugin.audio.jambmc/resources =>
plugin.video.udacity/resources/lib}/__init__.py (100%)
create mode 100644 plugin.video.udacity/resources/lib/controls.py
create mode 100644 plugin.video.udacity/resources/lib/udacity.py
create mode 100644 plugin.video.udacity/resources/media/blank.png
create mode 100644 plugin.video.udacity/resources/settings.xml
create mode 100644 plugin.video.udacity/resources/tests/test_addon.py
hooks/post-receive
--
Plugins
------------------------------------------------------------------------------
Rapidly troubleshoot problems before they affect your business. Most IT
organizations don't have a clear picture of how application performance
affects their revenue. With AppDynamics, you get 100% visibility into your
Java,.NET, & PHP application. Start your 15-day FREE TRIAL of AppDynamics Pro!
http://pubads.g.doubleclick.net/gampad/clk?id=84349831&iu=/4140/ostg.clktrk
_______________________________________________
Xbmc-addons mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/xbmc-addons