# # Copyright (C) 2014-2015 # Sean Poyser (seanpoyser@gmail.com) # # This Program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2, or (at your option) # any later version. # # This Program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with XBMC; see the file COPYING. If not, write to # the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. # http://www.gnu.org/copyleft/gpl.html # import xbmc import xbmcaddon import xbmcgui import os import re import datetime import sfile def GetXBMCVersion(): #xbmc.executeJSONRPC('{ "jsonrpc": "2.0", "method": "Application.GetProperties", "params": {"properties": ["version", "name"]}, "id": 1 }') version = xbmcaddon.Addon('xbmc.addon').getAddonInfo('version') version = version.split('.') return int(version[0]), int(version[1]) #major, minor eg, 13.9.902 ADDONID = 'plugin.program.stv.favourites' ADDON = xbmcaddon.Addon(ADDONID) HOME = ADDON.getAddonInfo('path') _FOLDER = 400 ROOT = ADDON.getSetting('FOLDER') if not ROOT: ROOT = 'special://profile/addon_data/plugin.program.stv.favourites/' SHOWXBMC = ADDON.getSetting('SHOWXBMC') == 'true' INHERIT = ADDON.getSetting('INHERIT') == 'true' ALPHA_SORT = ADDON.getSetting('ALPHA_SORT') == 'true' LABEL_NUMERIC = ADDON.getSetting('LABEL_NUMERIC') == 'true' LABEL_NUMERIC_QL = False #ADDON.getSetting('LABEL_NUMERIC_QL') == 'true' PROFILE = os.path.join(ROOT, 'diamond Favourites') VERSION = ADDON.getAddonInfo('version') ICON = os.path.join(HOME, 'icon.png') FANART = os.path.join(HOME, 'fanart.jpg') SEARCH = os.path.join(HOME, 'resources', 'media', 'search.png') GETTEXT = ADDON.getLocalizedString TITLE = ADDON.getAddonInfo('name') SF_RUNNING = 'SFRUNNING' MAX_SIZE = 100 XBMCHOME = os.path.join('special://home', 'addons') DISPLAYNAME = 'Kodi' NUMBER_SEP = ' | ' PLAYABLE = xbmc.getSupportedMedia('video') + '|' + xbmc.getSupportedMedia('music') PLAYABLE = PLAYABLE.replace('|.zip', '') PLAYABLE = PLAYABLE.split('|') PLAYMEDIA_MODE = 1 ACTIVATEWINDOW_MODE = 2 RUNPLUGIN_MODE = 3 ACTION_MODE = 4 HOMESPECIAL = 'special://home/' HOMEFULL = xbmc.translatePath(HOMESPECIAL) DEBUG = ADDON.getSetting('DEBUG') == 'true' KEYMAP_HOT = 'diamond_favourites_hot.xml' KEYMAP_MENU = 'diamond_favourites_menu.xml' MAJOR, MINOR = GetXBMCVersion() FRODO = (MAJOR == 12) and (MINOR < 9) GOTHAM = (MAJOR == 13) or (MAJOR == 12 and MINOR == 9) HELIX = (MAJOR == 14) or (MAJOR == 13 and MINOR == 9) ISENGARD = (MAJOR == 15) or (MAJOR == 14 and MINOR == 9) KRYPTON = (MAJOR == 17) or (MAJOR == 16 and MINOR == 9) FILENAME = 'favourites.xml' FOLDERCFG = 'folder.cfg' def Log(text, force=False): log(text, force) def log(text, force=False): try: output = '%s V%s : %s' % (TITLE, VERSION, str(text)) if DEBUG or force: xbmc.log(output) else: xbmc.log(output, xbmc.LOGDEBUG) except: pass def outputDict(params, title=None): if not title: title = '' log('outputDict %s' % title) if params == None: log('params == None') else: for key in params: log('%s\t: %s' % (key, params[key])) log('__________________________________________________________') def DialogOK(line1, line2='', line3=''): d = xbmcgui.Dialog() d.ok(TITLE + ' - ' + VERSION, line1, line2 , line3) def DialogYesNo(line1, line2='', line3='', noLabel=None, yesLabel=None): d = xbmcgui.Dialog() if noLabel == None or yesLabel == None: return d.yesno(TITLE + ' - ' + VERSION, line1, line2 , line3) == True else: return d.yesno(TITLE + ' - ' + VERSION, line1, line2 , line3, noLabel, yesLabel) == True def Progress(title, line1 = '', line2 = '', line3 = ''): dp = xbmcgui.DialogProgress() dp.create(title, line1, line2, line3) dp.update(0) return dp def generateMD5(text): if not text: return '' try: import hashlib return hashlib.md5(text.lower()).hexdigest() except: pass try: import md5 return md5.new(text).hexdigest() except: pass return '0' def LaunchSF(): xbmc.executebuiltin('ActivateWindow(videos,plugin://%s)' % ADDONID) def CheckVersion(): try: prev = ADDON.getSetting('VERSION') curr = VERSION if prev == curr: return verifydiamondSearch() VerifySettings() VerifyZipFiles() src = os.path.join(ROOT, 'cache') dst = os.path.join(ROOT, 'C') sfile.rename(src, dst) ADDON.setSetting('VERSION', curr) if prev == '0.0.0' or prev == '1.0.0': sfile.makedirs(PROFILE) #call showChangeLog like this to workaround bug in openElec script = os.path.join(HOME, 'showChangelog.py') cmd = 'AlarmClock(%s,RunScript(%s),%d,True)' % ('changelog', script, 0) xbmc.executebuiltin(cmd) except: pass def VerifyZipFiles(): #cleanup corrupt zip files sfile.remove(os.path.join('special://userdata', '_sf_temp.zip')) sfile.remove(os.path.join('special://userdata', 'SF_Temp')) def VerifySettings(): #patch any settings that have changed types or values if ADDON.getSetting('DISABLEMOVIEVIEW') == 'true': ADDON.setSetting('DISABLEMOVIEVIEW', 'false') ADDON.setSetting('CONTENTTYPE', '') def safeCall(func): try: func() except Exception, e: log('Failed in call to %s - %s' % (func.__name__, str(e))) def verifydiamondSearch(): old = os.path.join(ROOT, 'Search') dst = os.path.join(ROOT, 'S') sfile.rename(old, dst) try: sfile.makedirs(dst) except: pass src = os.path.join(HOME, 'resources', 'search', FILENAME) dst = os.path.join(dst, FILENAME) if not sfile.exists(dst): sfile.copy(src, dst) try: #patch any changes xml = sfile.read(dst) xml = xml.replace('is/?action=movies_search&', 'is/?action=movieSearch&') xml = xml.replace('is/?action=people_movies&', 'is/?action=moviePerson&') xml = xml.replace('is/?action=shows_search&', 'is/?action=tvSearch&') xml = xml.replace('is/?action=people_shows&', 'is/?action=tvPerson&') f = sfile.file(dst, 'w') f.write(xml) f.close() except: pass import favourite new = favourite.getFavourites(src, validate=False) for item in new: fave, index, nFaves = favourite.findFave(dst, item[2]) if index < 0: favourite.addFave(dst, item) def UpdateKeymaps(): if ADDON.getSetting('HOTKEY') != GETTEXT(30111): #i.e. not programmable DeleteKeymap(KEYMAP_HOT) DeleteKeymap(KEYMAP_MENU) VerifyKeymaps() def DeleteKeymap(map): path = os.path.join('special://profile/keymaps', map) DeleteFile(path) def DeleteFile(path): tries = 5 while sfile.exists(path) and tries > 0: tries -= 1 try: sfile.remove(path) except: xbmc.sleep(500) def verifyLocation(): #if still set to default location reset, to workaround #Android bug in browse folder dialog location = ADDON.getSetting('FOLDER') profile = 'special://profile/addon_data/plugin.program.stv.favourites/' userdata = 'special://userdata/addon_data/plugin.program.stv.favourites/' if (location == profile) or (location == userdata): ADDON.setSetting('FOLDER', '') def verifyPlugins(): folder = os.path.join(ROOT, 'Plugins') if sfile.exists(folder): return try: sfile.makedirs(folder) except: pass def VerifyKeymaps(): reload = False scriptPath = ADDON.getAddonInfo('profile') scriptPath = os.path.join(scriptPath, 'captureLauncher.py') if not sfile.exists(scriptPath): DeleteKeymap(KEYMAP_MENU) #ensure gets updated to launcher version src = os.path.join(HOME, 'captureLauncher.py') sfile.copy(src, scriptPath) if VerifyKeymapHot(): reload = True if VerifyKeymapMenu(): reload = True if not reload: return xbmc.sleep(1000) xbmc.executebuiltin('Action(reloadkeymaps)') def VerifyKeymapHot(): if ADDON.getSetting('HOTKEY') == GETTEXT(30111): #i.e. programmable return False dest = os.path.join('special://profile/keymaps', KEYMAP_HOT) if sfile.exists(dest): return False key = ADDON.getSetting('HOTKEY') valid = [] for i in range(30028, 30040): valid.append(GETTEXT(i)) valid.append(GETTEXT(30058)) includeKey = key in valid if not includeKey: DeleteKeymap(KEYMAP_HOT) return True if isATV(): DialogOK(GETTEXT(30118), GETTEXT(30119)) return False return WriteKeymap(key.lower(), key.lower()) def WriteKeymap(start, end): filename = KEYMAP_HOT cmd = 'XBMC.RunScript(special://home/addons/plugin.program.stv.favourites/hot.py)' dest = os.path.join('special://profile/keymaps', filename) cmd = '<%s>%s' % (start, cmd, end) f = sfile.file(dest, 'w') f.write(cmd) f.close() xbmc.sleep(1000) tries = 4 while not sfile.exists(dest) and tries > 0: tries -= 1 f = sfile.file(dest, 'w') f.write(cmd) f.close() xbmc.sleep(1000) return True def VerifyKeymapMenu(): context = ADDON.getSetting('CONTEXT') == 'true' if not context: DeleteKeymap(KEYMAP_MENU) return True keymap = 'special://profile/keymaps' dst = os.path.join(keymap, KEYMAP_MENU) if sfile.exists(dst): return False src = os.path.join(HOME, 'resources', 'keymaps', KEYMAP_MENU) sfile.makedirs(keymap) sfile.copy(src, dst) return True def verifyPlayMedia(cmd): return True def verifyPlugin(cmd): try: plugin = re.compile('plugin://(.+?)/').search(cmd.replace('?', '/')).group(1) return xbmc.getCondVisibility('System.HasAddon(%s)' % plugin) == 1 except: pass return True def verifyScript(cmd): try: script = cmd.split('(', 1)[1].split(',', 1)[0].replace(')', '').replace('"', '') script = script.split('/', 1)[0] if xbmc.getCondVisibility('System.HasAddon(%s)' % script) == 1: return True file = cmd.split('(', 1)[1].split(',', 1)[0].replace(')', '').replace('"', '') if sfile.exists(file): return True return False except: pass return True def isATV(): return xbmc.getCondVisibility('System.Platform.ATV2') == 1 def GetSFFolder(text): startFolder = '' if ADDON.getSetting('MENU_PREV_LOCN') == 'true': startFolder = xbmcgui.Window(10000).getProperty('SF_ADDTOSF_FOLDER') if len(startFolder) == 0 or not sfile.exists(startFolder): startFolder = None folder = GetFolder(text, startFolder) if not folder: return None xbmcgui.Window(10000).setProperty('SF_ADDTOSF_FOLDER', folder) return folder def GetFolder(title, start=None): if start: default = start else: default = ROOT sfile.makedirs(PROFILE) folder = xbmcgui.Dialog().browse(3, title, 'files', '', False, False, default) if folder == default: if (not start): return None return folder def GetText(title, text='', hidden=False, allowEmpty=False): if text == None: text = '' kb = xbmc.Keyboard(text.strip(), title) kb.setHiddenInput(hidden) kb.doModal() if not kb.isConfirmed(): return None text = kb.getText().strip() if (len(text) < 1) and (not allowEmpty): return None return text html_escape_table = { "&": "&", '"': """, "'": "'", ">": ">", "<": "<", } def escape(text): return str(''.join(html_escape_table.get(c,c) for c in text)) def unescape(text): text = text.replace('&', '&') text = text.replace('"', '"') text = text.replace(''', '\'') text = text.replace('>', '>') text = text.replace('<', '<') return text def fix(text): ret = '' for ch in text: if ord(ch) < 128: ret += ch return ret.strip() def Clean(name): import re name = re.sub('\([0-9)]*\)', '', name) items = name.split(']') name = '' for item in items: if len(item) == 0: continue item += ']' item = re.sub('\[[^)]*\]', '', item) if len(item) > 0: name += item name = name.replace('[', '') name = name.replace(']', '') name = name.strip() while True: length = len(name) name = name.replace(' ', ' ') if length == len(name): break return name.strip() def CleanForSort(text): text = text[0] text = text.lower() text = Clean(text) return text def fileSystemSafe(text): if not text: return None text = re.sub('[:\\\\/*?\<>|"]+', '', text) text = text.strip() if len(text) < 1: return None return text def findAddon(item): try: try: addon = re.compile('"(.+?)"').search(item).group(1) except: addon = item addon = addon.replace('plugin://', '') addon = addon.replace('script://', '') addon = addon.replace('/', '') addon = addon.split('?', 1)[0] if xbmc.getCondVisibility('System.HasAddon(%s)' % addon) == 0: addon = None except: addon = None return addon def getSettingsLabel(addon): label = xbmcaddon.Addon(addon).getAddonInfo('name') label = fix(label) label = label.strip() try: if len(label) > 0: return GETTEXT(30094) % label except: pass return GETTEXT(30094) % GETTEXT(30217) #logic for setting focus inspired by lambda def openSettings(addonID, focus=None): if not focus: return xbmcaddon.Addon(addonID).openSettings() try: xbmc.executebuiltin('Addon.OpenSettings(%s)' % addonID) value1, value2 = str(focus).split('.') if FRODO: page = int(value1) + 200 item = int(value2) + 100 else: page = int(value1) + 100 item = int(value2) + 200 xbmc.executebuiltin('SetFocus(%d)' % page) xbmc.executebuiltin('SetFocus(%d)' % item) except: return #Remove Tags method from #http://stackoverflow.com/questions/9662346/python-code-to-remove-html-tags-from-a-string TAG_RE = re.compile('<.*?>') def RemoveTags(html): return TAG_RE.sub('', html) def showBusy(): busy = None try: import xbmcgui busy = xbmcgui.WindowXMLDialog('DialogBusy.xml', '') busy.show() try: busy.getControl(10).setVisible(False) except: pass except: busy = None return busy def showText(heading, text, waitForClose=False): id = 10147 xbmc.executebuiltin('ActivateWindow(%d)' % id) xbmc.sleep(100) win = xbmcgui.Window(id) retry = 50 while (retry > 0): try: xbmc.sleep(10) win.getControl(1).setLabel(heading) win.getControl(5).setText(text) retry = 0 except: retry -= 1 if waitForClose: while xbmc.getCondVisibility('Window.IsVisible(%d)' % id) == 1: xbmc.sleep(50) def showChangelog(addonID=None): try: if addonID: _ADDON = xbmcaddon.Addon(addonID) else: _ADDON = xbmcaddon.Addon(ADDONID) path = os.path.join(_ADDON.getAddonInfo('path'), 'changelog.txt') text = sfile.read(path) title = '%s - %s' % (xbmc.getLocalizedString(24054), _ADDON.getAddonInfo('name')) showText(title, text) except: pass def getAllPlayableFiles(folder): files = {} _getAllPlayableFiles(folder, files) return files def _getAllPlayableFiles(folder, theFiles): current, dirs, files = sfile.walk(folder) for dir in dirs: path = os.path.join(current, dir) _getAllPlayableFiles(path, theFiles) for file in files: path = os.path.join(current, file) if isPlayable(path): size = sfile.size(path) theFiles[path] = [path, size] def isFilePlayable(path): try: return ('.' + sfile.getextension(path) in PLAYABLE) except: return False def isPlayable(path): if not sfile.exists(path): return False if sfile.isfile(path): playable = isFilePlayable(path) return playable current, dirs, files = sfile.walk(path) for file in files: if isPlayable(os.path.join(current, file)): return True for dir in dirs: if isPlayable(os.path.join(current, dir)): return True return False def parseFolder(folder, subfolders=True): items = [] current, dirs, files = sfile.walk(folder) if subfolders: for dir in dirs: path = os.path.join(current, dir) if isPlayable(path): items.append([dir, path, False]) for file in files: path = os.path.join(current, file) if isPlayable(path): items.append([file, path, True]) return items def getPrefix(index): index += 1 prefix = str(index) + NUMBER_SEP if index < 10: prefix = '0' + prefix return prefix, index ELEMENTS = ['[b]', '[/b]', '[i]', '[/i]', '[/color]'] def isFormatElement(element): element = element.lower() if element.startswith('[color '): return True if element in ELEMENTS: return True return False def addPrefixToLabel(index, label, addPrefix=None): if addPrefix == None: addPrefix = LABEL_NUMERIC if not addPrefix: return label, index prefix, index = getPrefix(index) locn = -1 SEARCHING = 0 INELEMENT = 1 BODY = 2 mode = SEARCHING element = '' for c in label: locn += 1 if mode == SEARCHING: if c is '[': mode = INELEMENT element = c else: mode = BODY elif mode == INELEMENT: element += c if c is ']': if not isFormatElement(element): locn -= len(element) - 1 break element = '' mode = SEARCHING if mode == BODY: break label = label[:locn] + prefix + label[locn:] return label, index def playItems(items, id=-1): if items == None or len(items) < 1: return pl = xbmc.PlayList(xbmc.PLAYLIST_VIDEO) pl.clear() resolved = False for item in items: title = item[0] url = item[1] if len(item) > 2: iconImage = item[2] else: iconImage = ICON liz = xbmcgui.ListItem(title, iconImage=iconImage, thumbnailImage=iconImage) liz.setInfo(type='Video', infoLabels={'Title':title}) pl.add(url, liz) if id >= 0 and (not resolved): import xbmcplugin resolved = True xbmcplugin.setResolvedUrl(id, True, liz) if id == -1: xbmc.Player().play(pl) def inWidget(): return True return validWidget() < maxWidget() def convertToHome(text): if text.startswith(HOMEFULL): text = text.replace(HOMEFULL, HOMESPECIAL) return text def getCurrentWindowId(): winID = xbmcgui.getCurrentWindowId() tries = 10 while winID == 10000 and tries > 0: xbmc.sleep(100) tries -= 1 winID = xbmcgui.getCurrentWindowId() return winID if winID != 10000 else 10025 def getFolderThumb(path, isXBMC=False): import parameters cfg = os.path.join(path, FOLDERCFG) cfg = parameters.getParams(cfg) thumb = parameters.getParam('ICON', cfg) fanart = parameters.getParam('FANART', cfg) if thumb and fanart: return thumb, fanart if isXBMC: thumb = thumb if (thumb != None) else 'DefaultFolder.png' fanart = fanart if (fanart != None) else FANART return thumb, fanart if not INHERIT: thumb = thumb if (thumb != None) else ICON fanart = fanart if (fanart != None) else FANART return thumb, fanart import locking if not locking.unlocked(path): thumb = thumb if (thumb != None) else ICON fanart = fanart if (fanart != None) else FANART return thumb, fanart import favourite faves = favourite.getFavourites(os.path.join(path, FILENAME), 1) if len(faves) < 1: thumb = thumb if (thumb != None) else ICON fanart = fanart if (fanart != None) else FANART return thumb, fanart tFave = faves[0][1] fFave = favourite.getFanart(faves[0][2]) thumb = thumb if (thumb != None) else tFave fanart = fanart if (fanart != None) else fFave fanart = fanart if (fanart and len(fanart) > 0) else FANART return thumb, fanart def getViewType(): #logic to obtain viewtype inspired by lambda path = 'special://skin/' addon = os.path.join(path, 'addon.xml') xml = sfile.read(addon).replace('\n','').replace('\t','') try: src = re.compile('defaultresolution="(.+?)"').findall(xml)[0] except: src = re.compile('(.+?)').findall(view)[0].split(',') for v in view: v = int(v) if v not in views: views.append(v) except: pass count = 1 for view in views: label = xbmc.getInfoLabel('Control.GetLabel(%d)' % view) if label: return view return 0 def get_params(path): import urllib params = {} path = path.split('?', 1)[-1] pairs = path.split('&') for pair in pairs: split = pair.split('=') if len(split) > 1: params[split[0]] = urllib.unquote_plus(split[1]) return params def convertDictToURL(dict): import urllib text = '' for label in dict: value = dict[label] try: value = str(value) except Exception, e: try: value = value.encode('utf-8') except Exception, e: value = '' if len(value) == 0: continue if len(text) > 0: text += '&' text += '%s=%s' % (label, urllib.quote_plus(value.replace('\n', '\\n'))) return text def convertURLToDict(url): if not url: return {} import urllib dict = {} url = urllib.unquote_plus(url) url = get_params(url) for label in url: value = url[label] if label=='castandrole': try: value = eval(value) except: value = '' if len(value) == 0: continue dict[label] = value return dict def setKodiSetting(setting, value): import json as simplejson setting = '"%s"' % setting if isinstance(value, list): text = '' for item in value: text += '"%s",' % str(item) text = text[:-1] text = '[%s]' % text value = text elif isinstance(value, bool): value = 'true' if value else 'false' elif not isinstance(value, int): value = '"%s"' % value query = '{"jsonrpc":"2.0", "method":"Settings.SetSettingValue","params":{"setting":%s,"value":%s}, "id":1}' % (setting, value) Log(query) response = xbmc.executeJSONRPC(query) Log(response) def getKodiSetting(setting): import json as simplejson try: setting = '"%s"' % setting query = '{"jsonrpc":"2.0", "method":"Settings.GetSettingValue","params":{"setting":%s}, "id":1}' % (setting) Log(query) response = xbmc.executeJSONRPC(query) Log(response) response = simplejson.loads(response) if response.has_key('result'): if response['result'].has_key('value'): return response ['result']['value'] except: pass return None def changeSkin(skin): skin = 'skin.%s' % skin if getKodiSetting('lookandfeel.skin') == skin: return installed = xbmc.getCondVisibility('System.HasAddon(%s)' % skin) == 1 if not installed: count = 10 * 10 #10 seconds xbmc.executebuiltin('UpdateLocalAddons') while not installed and count > 0: count -= 1 xbmc.sleep(100) installed = xbmc.getCondVisibility('System.HasAddon(%s)' % skin) == 1 if not installed: return setKodiSetting('lookandfeel.skin', skin) while xbmc.getCondVisibility('Window.IsActive(yesnodialog)') == 0: xbmc.sleep(10) cmd = 'Control.Message(11,click)' xbmc.executebuiltin(cmd) if __name__ == '__main__': pass