diff --git a/addons.xml b/addons.xml index f69b7c8f..fccd8f26 100644 --- a/addons.xml +++ b/addons.xml @@ -1,6 +1,6 @@ - + @@ -8,6 +8,7 @@ + @@ -29,14 +30,16 @@ - + + + [String.Contains(ListItem.Plot,"item=") + ListItem.HasEpg] + [Window.IsVisible(tvguide)|Window.IsVisible(tvsearch)|Window.IsVisible(tvchannels)] - [String.Contains(ListItem.PVRInstanceName, PseudoTV Live) + Window.IsVisible(tvrecordings)] + [String.StartsWith(ListItem.PVRInstanceName, PseudoTV Live) + Window.IsVisible(tvrecordings)] @@ -58,7 +61,12 @@ [!ListItem.IsPlayable + ListItem.IsFolder] + [!String.IsEmpty(ListItem.Label)] + + + [String.Contains(ListItem.Plot,"item=")] + [Window.IsVisible(tvchannels)] + + PseudoTV Live acts like a set-top box for Kodi! diff --git a/addons.xml.md5 b/addons.xml.md5 index 14defb53..0bf11cc4 100644 --- a/addons.xml.md5 +++ b/addons.xml.md5 @@ -1 +1 @@ -0a5cfaff5c3184e4b9a7644a13af146c \ No newline at end of file +afbf1ed9c6544c544ce7b37b56450635 \ No newline at end of file diff --git a/plugin.video.pseudotv.live/addon.xml b/plugin.video.pseudotv.live/addon.xml index dd54ab39..dd19f638 100644 --- a/plugin.video.pseudotv.live/addon.xml +++ b/plugin.video.pseudotv.live/addon.xml @@ -1,5 +1,5 @@ - + @@ -7,6 +7,7 @@ + @@ -28,14 +29,16 @@ - + + + [String.Contains(ListItem.Plot,"item=") + ListItem.HasEpg] + [Window.IsVisible(tvguide)|Window.IsVisible(tvsearch)|Window.IsVisible(tvchannels)] - [String.Contains(ListItem.PVRInstanceName, PseudoTV Live) + Window.IsVisible(tvrecordings)] + [String.StartsWith(ListItem.PVRInstanceName, PseudoTV Live) + Window.IsVisible(tvrecordings)] @@ -56,8 +59,13 @@ [!ListItem.IsPlayable + ListItem.IsFolder] + [!String.IsEmpty(ListItem.Label)] + + + + [String.Contains(ListItem.Plot,"item=")] + [Window.IsVisible(tvchannels)] + PseudoTV Live acts like a set-top box for Kodi! diff --git a/plugin.video.pseudotv.live/changelog.txt b/plugin.video.pseudotv.live/changelog.txt index 232a934d..c30ca76c 100644 --- a/plugin.video.pseudotv.live/changelog.txt +++ b/plugin.video.pseudotv.live/changelog.txt @@ -1,4 +1,14 @@ v.0.5.4 +-Improved PVR Provider meta. + - If you haven't installed Kodi master nightlies, you ought; Many PVR Improvements. +-Moved all context menu under "PseudoTV Live". +-Added Channel Manager to channel list context menu. +-Fixed Autotune prompt not showing. +-Fixed Disable Trakt, Playcount Rollback not triggering. +-Added "Restart" replay prompt. + - When media is in-progress, a button will appear to restart the program from the beginning. + Media will be launched as a singular VOD event. + - Global and Adv. Channel rule to disable/set restart parameters. -Added "Resume Later" recordings option. - When using "Add to Recordings" on currently playing content an option to "Incl. Resume" will be offered. Resume later will start the future playback at the resume position for easy viewing later... diff --git a/plugin.video.pseudotv.live/resources/language/resource.language.en_gb/strings.po b/plugin.video.pseudotv.live/resources/language/resource.language.en_gb/strings.po index cb64c38a..b378ffc1 100644 --- a/plugin.video.pseudotv.live/resources/language/resource.language.en_gb/strings.po +++ b/plugin.video.pseudotv.live/resources/language/resource.language.en_gb/strings.po @@ -633,6 +633,14 @@ msgctxt "#30152" msgid "Resume Later" msgstr "" +msgctxt "#30153" +msgid "Set Replay Percentage" +msgstr "" + +msgctxt "#30154" +msgid "Channel Manager (PseudoTV)" +msgstr "" + # Skin - strings 31000 thru 31999 reserved for skins @@ -1374,6 +1382,11 @@ msgctxt "#32183" msgid "Select server to remove." msgstr "" +msgctxt "#32184" +msgid "Replay Percentage (%s)" +msgstr "" + + # Help - strings 33000 thru 33999 reserved for common strings used in add-ons msgctxt "#33001" @@ -1644,6 +1657,10 @@ msgctxt "#33150" msgid "Parse Library & Build Channels while playing." msgstr "" +msgctxt "#33153" +msgid "[B]Restart[/B] prompt when media progress above % [0% Disabled, 5% Default]." +msgstr "" + msgctxt "#33159" msgid "Force a complete rebuild of your Autotune library." msgstr "" diff --git a/plugin.video.pseudotv.live/resources/lib/channelbug.py b/plugin.video.pseudotv.live/resources/lib/channelbug.py index 175183ac..044b991c 100644 --- a/plugin.video.pseudotv.live/resources/lib/channelbug.py +++ b/plugin.video.pseudotv.live/resources/lib/channelbug.py @@ -22,15 +22,6 @@ from ast import literal_eval from globals import * -# Actions -ACTION_MOVE_LEFT = 1 -ACTION_MOVE_RIGHT = 2 -ACTION_MOVE_UP = 3 -ACTION_MOVE_DOWN = 4 -ACTION_SELECT_ITEM = 7 -ACTION_INVALID = 999 -ACTION_PREVIOUS_MENU = [92,10,110,521,ACTION_SELECT_ITEM] - class ChannelBug(xbmcgui.WindowXML): lastActionTime = time.time() diff --git a/plugin.video.pseudotv.live/resources/lib/constants.py b/plugin.video.pseudotv.live/resources/lib/constants.py index 115464d2..ce8356e7 100644 --- a/plugin.video.pseudotv.live/resources/lib/constants.py +++ b/plugin.video.pseudotv.live/resources/lib/constants.py @@ -35,7 +35,8 @@ LANGUAGE = REAL_SETTINGS.getLocalizedString #constants -OVERLAY_DELAY = 15 #secs +OVERLAY_DELAY = 5 #secs +EPOCH_TIMER = 15 #secs DTFORMAT = '%Y%m%d%H%M%S' DTZFORMAT = '%Y%m%d%H%M%S +%z' DTJSONFORMAT = '%Y-%m-%d %H:%M:%S' @@ -76,7 +77,8 @@ GROUP_TYPES = ['Addon', 'Directory', 'TV', 'Movies', 'Music', 'Other', 'PVR', 'Plugin', 'Radio', 'Smartplaylist', 'UPNP', 'IPTV'] + AUTOTUNE_TYPES WEB_TYPES = ["http", - "ftp"] + "ftp", + "pvr"] VFS_TYPES = ["plugin://", "pvr://", @@ -194,6 +196,15 @@ # https://github.com/xbmc/xbmc/blob/master/xbmc/addons/kodi-dev-kit/include/kodi/c-api/gui/input/action_ids.h +# Actions +ACTION_MOVE_LEFT = 1 +ACTION_MOVE_RIGHT = 2 +ACTION_MOVE_UP = 3 +ACTION_MOVE_DOWN = 4 +ACTION_INVALID = 999 +ACTION_SELECT_ITEM = [7,135] +ACTION_PREVIOUS_MENU = [92,10,110,521,ACTION_SELECT_ITEM] + #rules ##builder RULES_ACTION_CHANNEL_CITEM = 1 diff --git a/plugin.video.pseudotv.live/resources/lib/context_create.py b/plugin.video.pseudotv.live/resources/lib/context_create.py index b5dc6038..cc326135 100644 --- a/plugin.video.pseudotv.live/resources/lib/context_create.py +++ b/plugin.video.pseudotv.live/resources/lib/context_create.py @@ -21,10 +21,16 @@ from manager import Manager class Create: - def __init__(self, sysARG: dict, listitem: xbmcgui.ListItem=xbmcgui.ListItem()): - log('Create: __init__, sysARG = %s'%(sysARG)) - if not listitem.getPath(): return DIALOG.notificationDialog(LANGUAGE(32030)) - if DIALOG.yesnoDialog('Would you like to add:\n[B]%s[/B]\nto the first available %s channel?'%(listitem.getLabel(),ADDON_NAME)): + def __init__(self, sysARG: dict={}, listitem: xbmcgui.ListItem=xbmcgui.ListItem(), fitem: dict={}): + log('Create: __init__, sysARG = %s, fitem = %s\npath = %s'%(sysARG,fitem,listitem.getPath())) + self.sysARG = sysARG + self.fitem = fitem + self.listitem = listitem + + + def add(self): + if not self.listitem.getPath(): return DIALOG.notificationDialog(LANGUAGE(32030)) + if DIALOG.yesnoDialog('Would you like to add:\n[B]%s[/B]\nto the first available %s channel?'%(self.listitem.getLabel(),ADDON_NAME)): if not PROPERTIES.isRunning('MANAGER_RUNNING'): with PROPERTIES.setRunning('MANAGER_RUNNING'), BUILTIN.busy_dialog(), PROPERTIES.suspendActivity(): manager = Manager("%s.manager.xml"%(ADDON_ID), ADDON_PATH, "default", start=False) @@ -32,9 +38,9 @@ def __init__(self, sysARG: dict, listitem: xbmcgui.ListItem=xbmcgui.ListItem()): channelData['type'] = 'Custom' channelData['favorite'] = True channelData['number'] = manager.getFirstAvailChannel() - channelData['radio'] = True if listitem.getPath().startswith('musicdb://') else False - channelData['name'], channelData = manager.validateLabel(cleanLabel(listitem.getLabel()),channelData) - path, channelData = manager.validatePath(unquoteString(listitem.getPath()),channelData,spinner=False) + channelData['radio'] = True if self.listitem.getPath().startswith('musicdb://') else False + channelData['name'], channelData = manager.validateLabel(cleanLabel(self.listitem.getLabel()),channelData) + path, channelData = manager.validatePath(unquoteString(self.listitem.getPath()),channelData,spinner=False) if path is None: return channelData['path'] = [path.strip('/')] channelData['id'] = getChannelID(channelData['name'], channelData['path'], channelData['number']) @@ -46,5 +52,18 @@ def __init__(self, sysARG: dict, listitem: xbmcgui.ListItem=xbmcgui.ListItem()): manager = Manager("%s.manager.xml"%(ADDON_ID), ADDON_PATH, "default", channel=channelData['number']) del manager + + def open(self): + if not PROPERTIES.isRunning('MANAGER_RUNNING'): + with PROPERTIES.setRunning('MANAGER_RUNNING'), BUILTIN.busy_dialog(), PROPERTIES.suspendActivity(): + chnum = self.fitem.get('citem',{}).get('number',1) + if chnum > CHANNEL_LIMIT: chnum = 1 + manager = Manager("%s.manager.xml"%(ADDON_ID), ADDON_PATH, "default", channel=chnum) + del manager + + if __name__ == '__main__': - Create(sys.argv,listitem=sys.listitem) \ No newline at end of file + param = sys.argv[1] + log('Create: __main__, param = %s'%(param)) + if param == 'manage': Create(sys.argv,listitem=sys.listitem,fitem=decodePlot(BUILTIN.getInfoLabel('Plot'))).open() + else: Create(sys.argv,listitem=sys.listitem,fitem=decodePlot(BUILTIN.getInfoLabel('Plot'))).add() \ No newline at end of file diff --git a/plugin.video.pseudotv.live/resources/lib/default.py b/plugin.video.pseudotv.live/resources/lib/default.py index 180f6b1e..c33bd12c 100644 --- a/plugin.video.pseudotv.live/resources/lib/default.py +++ b/plugin.video.pseudotv.live/resources/lib/default.py @@ -29,7 +29,8 @@ def run(sysARG): chid = (params.get("chid",'') or None) vid = decodeString(params.get("vid",'') or None) radio = (params.get("radio",'') or 'False').lower() == "true" - log("Default: run, params = %s"%(params)) + isPlay = bool(SETTINGS.getSettingInt('Playback_Method')) + log("Default: run, params = %s, isPlaylist = %s"%(params,isPlay)) if mode == 'guide': hasAddon(PVR_CLIENT_ID,install=True,enable=True) @@ -39,12 +40,12 @@ def run(sysARG): if hasAddon(PVR_CLIENT_ID,install=True,enable=True): SETTINGS.openSettings() elif chid and not vid: return DIALOG.notificationDialog(LANGUAGE(32166)%(PVR_CLIENT_NAME,SETTINGS.IPTV_SIMPLE_SETTINGS().get('m3uRefreshIntervalMins'))) - elif mode in ['vod','dvr']: threadit(Plugin(sysARG).playVOD)(title,vid) + elif mode in ['vod','dvr']: threadit(Plugin(sysARG).playVOD)(title,vid) elif mode == 'live': - if bool(SETTINGS.getSettingInt('Playback_Method')): threadit(Plugin(sysARG).playPlaylist)(name,chid) - else: threadit(Plugin(sysARG).playLive)(name,chid,vid) - elif mode == 'broadcast': threadit(Plugin(sysARG).playBroadcast)(name,chid,vid) - elif mode == 'radio': threadit(Plugin(sysARG).playRadio)(name,chid,vid) - elif mode == 'tv': threadit(Plugin(sysARG).playTV)(name,chid) + if isPlay: threadit(Plugin(sysARG).playPlaylist)(name,chid) + else: threadit(Plugin(sysARG).playLive)(name,chid,vid) + elif mode == 'broadcast': threadit(Plugin(sysARG).playBroadcast)(name,chid,vid) + elif mode == 'radio': threadit(Plugin(sysARG).playRadio)(name,chid,vid) + elif mode == 'tv': threadit(Plugin(sysARG).playTV)(name,chid) if __name__ == '__main__': run(sys.argv) \ No newline at end of file diff --git a/plugin.video.pseudotv.live/resources/lib/fillers.py b/plugin.video.pseudotv.live/resources/lib/fillers.py index cda37b8a..25e0e6b5 100644 --- a/plugin.video.pseudotv.live/resources/lib/fillers.py +++ b/plugin.video.pseudotv.live/resources/lib/fillers.py @@ -45,8 +45,8 @@ def getAdvertPath(self, id: str='plugin.video.ispot.tv') -> list: try: folder = os.path.join(xbmcaddon.Addon(id).getSetting('Download_Folder'),'resources','').replace('/resources/resources','/resources').replace('\\','/') except: folder = 'special://profile/addon_data/%s/resources/'%(id) self.log('getAdvertPath, folder = %s'%(folder)) - return [folder] - return [] + return folder + return '' def fillSources(self): @@ -95,9 +95,7 @@ def __sortItems(data, stype='folder'): elif path.startswith('plugin://'): return __sortItems(_parseVFS(path),'plugin') elif not path.startswith(tuple(VFS_TYPES)): return __sortItems(_parseLocal(path)) else: return {} - except Exception as e: - self.log("buildSource, failed! %s\n path = %s"%(e,path), xbmc.LOGERROR) - DIALOG.notificationDialog("Error occurred adding fillers, please submit your Kodi debug log for review, Thanks...") #todo remove + except Exception as e: self.log("buildSource, failed! %s\n path = %s"%(e,path), xbmc.LOGERROR) def convertMPAA(self, ompaa): diff --git a/plugin.video.pseudotv.live/resources/lib/globals.py b/plugin.video.pseudotv.live/resources/lib/globals.py index 367e197b..3d836b5d 100644 --- a/plugin.video.pseudotv.live/resources/lib/globals.py +++ b/plugin.video.pseudotv.live/resources/lib/globals.py @@ -23,7 +23,7 @@ import codecs, random import uuid, base64, binascii, hashlib import time, datetime -import heapq +import heapq, requests from threading import Lock, Thread, Event, Timer, BoundedSemaphore from threading import enumerate as thread_enumerate @@ -100,11 +100,9 @@ def unescapeString(text, table=HTML_ESCAPE): def getJSON(file): fle = (FileAccess.open(file, 'r') or '') - try: - data = loadJSON(fle.read()) - except Exception as e: - log('Globals: getJSON failed! %s'%(e), xbmc.LOGERROR) - data = {} + data = {} + try: data = loadJSON(fle.read()) + except Exception as e: log('Globals: getJSON failed! %s'%(e), xbmc.LOGERROR) fle.close() return data @@ -115,10 +113,20 @@ def setJSON(file, data): fle.close() return True -def getURL(url, headers=HEADER): +def getURL(url, data={}, header=HEADER, json_data=False): + try: + r = requests.get(url, params=data ,headers=HEADER.update(header)) + log("Globals: getURL, url = %s, status = %s"%(r.url,r.status_code)) + if json_data: return r.json() + else: return r.text + except Exception as e: pass + +def postURL(url, params={}, header=HEADER, json_data=False): try: - r = urllib.request.Request(url, None, headers) - return urllib.request.urlopen(r).read() + r = requests.post(url, data=params ,headers=HEADER.update(header)) + log("Globals: postURL, url = %s, status = %s"%(r.url,r.status_code)) + if json_data: return r.json() + else: return r.text except Exception as e: pass def setURL(url, file): @@ -129,7 +137,7 @@ def setURL(url, file): fle.close() return FileAccess.exists(file) except Exception as e: - log("saveURL, failed! %s"%e, xbmc.LOGERROR) + log("Globals: saveURL, failed! %s"%e, xbmc.LOGERROR) def diffLSTDICT(old, new): sOLD = set([dumpJSON(e) for e in old]) @@ -293,17 +301,19 @@ def KODI_LIVETV_SETTINGS(): #recommended Kodi LiveTV settings 'pvrmanager.startgroupchannelnumbersfromone':'false'} def togglePVR(state=True, reverse=False, wait=15): - log('globals: togglePVR, state = %s, reverse = %s, wait = %s'%(state,reverse,wait)) - #todo check for open pvr windows, don't toggle when open - if not (BUILTIN.getInfoBool('IsPlayingTv','Pvr') | BUILTIN.getInfoBool('IsPlayingRadio','Pvr') | BUILTIN.getInfoBool('IsPlayingRecording','Pvr')): - isEnabled = BUILTIN.getInfoBool('AddonIsEnabled(%s)'%(PVR_CLIENT_ID),'System') - if (state and isEnabled) or (not state and not isEnabled): return - xbmc.executeJSONRPC('{"jsonrpc":"2.0","method":"Addons.SetAddonEnabled","params":{"addonid":"%s","enabled":%s}, "id": 1}'%(PVR_CLIENT_ID,str(state).lower())) - if reverse: - with BUILTIN.busy_dialog(): - timerit(togglePVR)(wait,[not bool(state)]) - DIALOG.notificationWait('%s: %s'%(PVR_CLIENT_NAME,LANGUAGE(32125)),wait=wait) - else: DIALOG.notificationWait(LANGUAGE(30023)%(PVR_CLIENT_NAME)) + if not PROPERTIES.isRunning('togglePVR'): + with PROPERTIES.setRunning('togglePVR'): + log('globals: togglePVR, state = %s, reverse = %s, wait = %s'%(state,reverse,wait)) + #todo check for open pvr windows, don't toggle when open + if not (BUILTIN.getInfoBool('IsPlayingTv','Pvr') | BUILTIN.getInfoBool('IsPlayingRadio','Pvr') | BUILTIN.getInfoBool('IsPlayingRecording','Pvr')): + isEnabled = BUILTIN.getInfoBool('AddonIsEnabled(%s)'%(PVR_CLIENT_ID),'System') + if (state and isEnabled) or (not state and not isEnabled): return + xbmc.executeJSONRPC('{"jsonrpc":"2.0","method":"Addons.SetAddonEnabled","params":{"addonid":"%s","enabled":%s}, "id": 1}'%(PVR_CLIENT_ID,str(state).lower())) + if reverse: + with BUILTIN.busy_dialog(): + timerit(togglePVR)(wait,[not bool(state)]) + DIALOG.notificationWait('%s: %s'%(PVR_CLIENT_NAME,LANGUAGE(32125)),wait=wait) + else: DIALOG.notificationWait(LANGUAGE(30023)%(PVR_CLIENT_NAME)) def isRadio(item): if item.get('radio',False) or item.get('type','') == "Music Genres": return True @@ -372,7 +382,7 @@ def getIDbyPath(path): except Exception as e: log('Globals: getIDbyPath failed! %s'%(e), xbmc.LOGERROR) return path -def mergeDictLST(dict1,dict2): +def mergeDictLST(dict1={},dict2={}): for k, v in list(dict2.items()): dict1.setdefault(k,[]).extend(v) setDictLST() diff --git a/plugin.video.pseudotv.live/resources/lib/kodi.py b/plugin.video.pseudotv.live/resources/lib/kodi.py index 17b0d2cb..de37e801 100644 --- a/plugin.video.pseudotv.live/resources/lib/kodi.py +++ b/plugin.video.pseudotv.live/resources/lib/kodi.py @@ -299,20 +299,14 @@ def getMYUUID(self): if not uuid: from jsonrpc import JSONRPC jsonRPC = JSONRPC() - uuid = genUUID(seed=jsonRPC.getFriendlyName()) + friendly= jsonRPC.getFriendlyName() + uuid = genUUID(seed=friendly) self.setCacheSetting('MY_UUID',uuid) + self.setCacheSetting('Friendly_Name',friendly) del jsonRPC return uuid - def hasAutotuned(self): - return self.getSettingBool('hasAutotuned') - - - def setAutotuned(self, state=True): - return self.setSettingBool('hasAutotuned',state) - - def IPTV_SIMPLE_SETTINGS(self): #recommended IPTV Simple settings return {'kodi_addon_instance_name' :ADDON_NAME, 'kodi_addon_instance_enabled' :'false', @@ -358,12 +352,12 @@ def setPVRPath(self, path, instance=ADDON_NAME, prompt=False, force=False): 'epgPath' :os.path.join(path,XMLTVFLE), 'genresPathType' :'0', 'genresPath' :os.path.join(path,GENREFLE), - 'kodi_addon_instance_name' : '%s (%s)'%(ADDON_NAME,instance), + 'kodi_addon_instance_name' : '%s - %s'%(ADDON_NAME,instance), 'kodi_addon_instance_enabled':'true'} settings.update(nsettings) self.log('setPVRPath, new settings = %s'%(nsettings)) if self.hasPVRInstance(instance) and not force: - return self.log('setPVRInstance, instance (%s) settings exists.'%(instance)) + return self.log('setPVRPath, instance (%s) settings exists.'%(instance)) else: return self.chkPluginSettings(PVR_CLIENT_ID,settings,instance,prompt) @@ -375,7 +369,7 @@ def setPVRRemote(self, host, instance=ADDON_NAME, prompt=False): 'epgUrl' :'http://%s/%s'%(host,XMLTVFLE), 'genresPathType' :'1', 'genresUrl' :'http://%s/%s'%(host,GENREFLE), - 'kodi_addon_instance_name' : '%s (%s)'%(ADDON_NAME,instance), + 'kodi_addon_instance_name' : '%s - %s'%(ADDON_NAME,instance), 'kodi_addon_instance_enabled':'true'} settings.update(nsettings) self.log('setPVRRemote, new settings = %s'%(nsettings)) @@ -387,7 +381,7 @@ def hasPVRInstance(self, instance=ADDON_NAME): def setPVRInstance(self, instance=ADDON_NAME): - # https://github.com/xbmc/xbmc/pull/23648 + # todo https://github.com/xbmc/xbmc/pull/23648 if not FileAccess.exists(os.path.join(PVR_CLIENT_LOC,'settings.xml')): self.log('setPVRInstance, creating missing default settings.xml') return self.chkPluginSettings(PVR_CLIENT_ID,self.IPTV_SIMPLE_SETTINGS(),False) @@ -677,6 +671,14 @@ def clearTrash(self, instanceID=None): #clear abandoned properties after instanc for prop in tmpDCT.get(instanceID,[]): self.clearEXTProperty(prop) + def hasAutotuned(self): + return self.getEXTProperty('hasAutotuned') + + + def setAutotuned(self, state=True): + return self.setEXTProperty('hasAutotuned',state) + + class ListItems: def log(self, msg, level=xbmc.LOGDEBUG): @@ -1020,7 +1022,7 @@ def progressDialog(self, percent=0, control=None, message='', header=ADDON_NAME) def infoDialog(self, listitem): xbmcgui.Dialog().info(listitem) - + def notificationDialog(self, message, header=ADDON_NAME, sound=False, time=PROMPT_DELAY, icon=COLOR_LOGO): self.log('notificationDialog: %s'%(message)) ## - Builtin Icons: diff --git a/plugin.video.pseudotv.live/resources/lib/m3u.py b/plugin.video.pseudotv.live/resources/lib/m3u.py index a04c80be..2d7906f5 100644 --- a/plugin.video.pseudotv.live/resources/lib/m3u.py +++ b/plugin.video.pseudotv.live/resources/lib/m3u.py @@ -315,7 +315,7 @@ def addStation(self, citem): mitem['label'] = citem['name'] #todo channel manager opt to change channel 'label' leaving 'name' static for channelid purposes. mitem['logo'] = citem['logo'] mitem['realtime'] = False - mitem['provider'] = ADDON_NAME + mitem['provider'] = '%s (%s)'%(ADDON_NAME,SETTINGS.getCacheSetting('Friendly_Name')) mitem['provider-type'] = 'addon' mitem['provider-logo'] = HOST_LOGO diff --git a/plugin.video.pseudotv.live/resources/lib/multiroom.py b/plugin.video.pseudotv.live/resources/lib/multiroom.py index ab62473d..70ae04a6 100644 --- a/plugin.video.pseudotv.live/resources/lib/multiroom.py +++ b/plugin.video.pseudotv.live/resources/lib/multiroom.py @@ -134,11 +134,10 @@ def setDiscovery(self, servers={}): def chkPVRservers(self): changed = False servers = self.getDiscovery() - headers = HEADER.copy() - headers["Content-type"] = "application/vnd.apple.mpegurl" for server in list(servers.values()): - server['online'] = True if getURL('http://%s/%s'%(server.get('host'),M3UFLE),headers) else False + server['online'] = True if getURL('http://%s/%s'%(server.get('host'),M3UFLE),header={'Accept': 'text/plain; charset=utf-8'}) else False self.log('chkPVRservers, %s online = %s'%(server.get('name'),server['online'])) + #todo check version, when old trigger update of server info. if SETTINGS.hasPVRInstance(server.get('name')): if server.get('enabled',False): continue else: FileAccess.delete(os.path.join(PVR_CLIENT_LOC,'instance-settings-%s.xml'%(SETTINGS.gePVRInstance(instance)))) @@ -207,7 +206,6 @@ def run(self): ctl = (6,6) self.delServer() elif param == 'Pair_Announcement': - ctl = (6,7) with PROPERTIES.suspendActivity(): return self.pairAnnouncement(self.getPayload()) return openAddonSettings(ctl) diff --git a/plugin.video.pseudotv.live/resources/lib/overlay.py b/plugin.video.pseudotv.live/resources/lib/overlay.py index aaae92ea..e06ffff4 100644 --- a/plugin.video.pseudotv.live/resources/lib/overlay.py +++ b/plugin.video.pseudotv.live/resources/lib/overlay.py @@ -51,6 +51,42 @@ def onInit(self): self.close() +class Replay(xbmcgui.WindowXMLDialog): + def __init__(self, *args, **kwargs): + xbmcgui.WindowXMLDialog.__init__(self, *args, **kwargs) + self.sysInfo = kwargs.get('sysInfo',{}) + self._closeTimer = Timer(OVERLAY_DELAY, self.onClose) + + + def onInit(self): + log("Replay: onInit") + try: + self._closeTimer.name = "_closeTimer" + self._closeTimer.daemon=True + self._closeTimer.start() + self.setFocusId(40001) + except Exception as e: + log("Replay: onInit, failed! %s\ncitem = %s"%(e,self.sysInfo), xbmc.LOGERROR) + self.onClose() + + + def onAction(self, act): + actionId = act.getId() + log('Replay: onAction: actionId = %s'%(actionId)) + if actionId in ACTION_SELECT_ITEM and self.getFocusId(40001): BUILTIN.executebuiltin('PlayMedia(%s,noresume)'%(self.sysInfo.get('fitem',{}).get('catchup-id'))) + self.onClose() + + + def onClose(self): + log("Replay: onClose") + try: + if self._closeTimer.is_alive(): + self._closeTimer.cancel() + self._closeTimer.join() + except: pass + self.close() + + class Overlay(): controlManager = dict() @@ -77,7 +113,7 @@ def __init__(self, jsonRPC, player=None): #init controls self._channelBug = xbmcgui.ControlImage(self.channelBugX, self.channelBugY, 128, 128, 'None', aspectRatio=2) - self._onNext = xbmcgui.ControlTextBox(abs(self.window_w - self.channelBugX), 952, 1418, 36, 'font12', '0xFFFFFFFF')#todo user sets size & location + self._onNext = xbmcgui.ControlTextBox(480, 810, 1920, 36, 'font12', '0xFFFFFFFF')#todo user sets size & location self._background = xbmcgui.ControlImage(0, 0, self.window_w, self.window_h, 'None', aspectRatio=2, colorDiffuse='black') self._vignette = xbmcgui.ControlImage(self._vinOffsetX, self._vinOffsetY, self.window_w, self.window_h, 'None', aspectRatio=0) @@ -175,9 +211,7 @@ def close(self): self.cancelOnNext() self.cancelChannelBug() self.runActions(RULES_ACTION_OVERLAY_CLOSE, self.player.sysInfo.get('citem',{}), inherited=self) - - for control, visible in list(self.controlManager.items()): - self._removeControl(control) + for control, visible in list(self.controlManager.items()): self._removeControl(control) def cancelOnNext(self): diff --git a/plugin.video.pseudotv.live/resources/lib/plugin.py b/plugin.video.pseudotv.live/resources/lib/plugin.py index b607edaa..37f5e08d 100644 --- a/plugin.video.pseudotv.live/resources/lib/plugin.py +++ b/plugin.video.pseudotv.live/resources/lib/plugin.py @@ -58,8 +58,8 @@ def __init__(self, sysARG=sys.argv): if not self.sysInfo.get('start') and self.sysInfo['fitem']: self.sysInfo['start'] = self.sysInfo['fitem'].get('start') self.sysInfo['stop'] = self.sysInfo['fitem'].get('stop') - - try: self.sysInfo['seek'] = float(self.sysInfo.get('seek',(float(self.sysInfo['now']) - float(self.sysInfo['start'])))) + + try: self.sysInfo['seek'] = float(self.sysInfo.get('seek') or (int(self.sysInfo['now']) - int(self.sysInfo['start']))) except: self.sysInfo['seek'] = -1 try: self.sysInfo["citem"] = self.sysInfo["fitem"].pop('citem') @@ -223,7 +223,7 @@ def _matchJSON(): for result in results: if result.get('label','').lower().startswith(dir.lower()): self.log('getCallback: _matchJSON, found dir = %s'%(result.get('file'))) - response = jsonRPC.getDirectory(param={"directory":result.get('file')},checksum=PROPERTIES.getInstanceID(),expiration=datetime.timedelta(minutes=OVERLAY_DELAY)).get('files',[]) + response = jsonRPC.getDirectory(param={"directory":result.get('file')},checksum=PROPERTIES.getInstanceID(),expiration=datetime.timedelta(minutes=EPOCH_TIMER)).get('files',[]) for item in response: if item.get('label','').lower() == chname.lower() and item.get('uniqueid','') == id: self.log('getCallback: _matchJSON, found file = %s'%(item.get('file'))) @@ -277,7 +277,7 @@ def _parseBroadcast(broadcast={}): pvritem['citem'] = decodePlot(pvritem.get('broadcastnow',{}).get('plot','')).get('citem',{}) if isPlaylist and not radio: pvritem = _extend(pvritem) del jsonRPC - cacheResponse = self.cache.set(cacheName, pvritem, checksum=PROPERTIES.getInstanceID(), expiration=datetime.timedelta(seconds=OVERLAY_DELAY), json_data=True) + cacheResponse = self.cache.set(cacheName, pvritem, checksum=PROPERTIES.getInstanceID(), expiration=datetime.timedelta(seconds=EPOCH_TIMER), json_data=True) return cacheResponse diff --git a/plugin.video.pseudotv.live/resources/lib/rules.py b/plugin.video.pseudotv.live/resources/lib/rules.py index 4e9307d8..1c42b86b 100644 --- a/plugin.video.pseudotv.live/resources/lib/rules.py +++ b/plugin.video.pseudotv.live/resources/lib/rules.py @@ -29,6 +29,7 @@ def __init__(self): SetScreenVingette(), MST3k(), DisableOverlay(), + DisableReplay(), ForceSubtitles(), DisableTrakt(), RollbackPlaycount(), @@ -101,7 +102,7 @@ def allRules(self): #load all rules. return [rule.copy() for rule in tmpruleList] - def runActions(self, action, citem, parameter=None, inherited=None): + def runActions(self, action, citem={}, parameter=None, inherited=None): if inherited is None: inherited = self self.log("runActions, %s action = %s, channel = %s"%(inherited.__class__.__name__,action,citem.get('id'))) rules = (self.ruleItems.get(citem.get('id',{})) or self.loadRules([citem]).get(citem.get('id',{})) or {}) @@ -244,7 +245,7 @@ def onActionPickColor(self, optionindex, colorlist=[], colorfile=""): log("onActionPickColor") value = self.dialog.colorDialog(colorlist, self.optionValues[optionindex], colorfile, self.name) if value: self.optionValues[optionindex] = value - + def onActionTextBox(self, optionindex): log("onActionTextBox") @@ -696,6 +697,45 @@ def runAction(self, actionid, citem, parameter, player): return parameter +class DisableReplay(BaseRule): + def __init__(self): + self.myId = 54 + self.ignore = False + self.exclude = False + self.name = LANGUAGE(30153) + self.description = LANGUAGE(33153) + self.optionLabels = [LANGUAGE(30153)] + self.optionValues = [SETTINGS.getSettingInt('Enable_Replay')] + self.optionDescriptions = [LANGUAGE(33153)] + self.actions = [RULES_ACTION_PLAYER_START,RULES_ACTION_PLAYER_STOP] + self.selectBoxOptions = [list(range(0,100,5))] + self.storedValues = [list() for idx in self.optionValues] + + + def copy(self): + return DisableReplay() + + + def getTitle(self): + return LANGUAGE(32184)%(self.optionValues[0]) + + + def onAction(self, optionindex): + self.onActionSelect(optionindex, self.optionLabels[optionindex]) + return self.optionValues[optionindex] + + + def runAction(self, actionid, citem, parameter, player): + if actionid == RULES_ACTION_PLAYER_START: + self.storedValues[0] = player.enableReplay + player.enableReplay = self.optionValues[0] + + elif actionid == RULES_ACTION_PLAYER_STOP: + player.enableReplay = self.storedValues[0] + self.log("runAction, setting enableReplay = %s"%(player.enableReplay)) + return parameter + + class DurationOptions(BaseRule): def __init__(self): self.myId = 500 diff --git a/plugin.video.pseudotv.live/resources/lib/server.py b/plugin.video.pseudotv.live/resources/lib/server.py index ab5fea89..36edb76b 100644 --- a/plugin.video.pseudotv.live/resources/lib/server.py +++ b/plugin.video.pseudotv.live/resources/lib/server.py @@ -131,7 +131,7 @@ def do_GET(self): elif self.path.lower() == '/%s'%(GENREFLE.lower()): path = GENREFLEPATH content = "text/plain" - + if self.path.lower() == '/%s'%(XMLTVFLE.lower()): self.log('do_GET, sending = %s'%(path)) self._set_headers(content) diff --git a/plugin.video.pseudotv.live/resources/lib/service.py b/plugin.video.pseudotv.live/resources/lib/service.py index 253ea7ab..9a56e17e 100644 --- a/plugin.video.pseudotv.live/resources/lib/service.py +++ b/plugin.video.pseudotv.live/resources/lib/service.py @@ -18,7 +18,7 @@ # # -*- coding: utf-8 -*- from globals import * -from overlay import Overlay, Background +from overlay import Overlay, Background, Replay from rules import RulesList from tasks import Tasks from jsonrpc import JSONRPC @@ -26,9 +26,11 @@ class Player(xbmc.Player): sysInfo = {} myService = None + replay = None background = None isPseudoTV = False lastSubState = False + isIdle = False rules = RulesList() runActions = rules.runActions @@ -38,6 +40,7 @@ def __init__(self, jsonRPC=None): self.jsonRPC = jsonRPC self.disableTrakt = SETTINGS.getSettingBool('Disable_Trakt') #todo adv. rule opt self.rollbackPlaycount = SETTINGS.getSettingBool('Rollback_Watched')#todo adv. rule opt + self.enableReplay = SETTINGS.getSettingInt('Enable_Replay') """ Player() Trigger Order @@ -63,7 +66,7 @@ def onAVChange(self): self.log('onAVChange') if self.isPseudoTV: self.lastSubState = BUILTIN.isSubtitle() - self.myService.monitor.chkIdle() + self.isIdle = self.myService.monitor.chkIdle() def onAVStarted(self): @@ -145,8 +148,8 @@ def getPlayerTime(self): def getPlayerProgress(self): - try: return float(BUILTIN.getInfoLabel('Player.Progress')) - except: return 0.0 + try: return int((self.getTimeLabel()*100)//self.getPlayerTime()) + except: return -1 def getElapsedTime(self): @@ -159,6 +162,7 @@ def getTimeLabel(self, prop: str='TimeRemaining') -> int and float: #prop='EpgEv def setTrakt(self, state: bool=SETTINGS.getSettingBool('Disable_Trakt')): + self.log('setTrakt, state = %s'%(state)) if state: PROPERTIES.setEXTProperty('script.trakt.paused',str(state).lower()) else: PROPERTIES.clearEXTProperty('script.trakt.paused') @@ -176,18 +180,23 @@ def setPlaycount(self, state: bool=SETTINGS.getSettingBool('Rollback_Watched'), def _onPlay(self): - self.log('_onPlay') - self.setPlaycount(self.rollbackPlaycount,self.sysInfo.get('fitem',{})) - self.sysInfo = self.getPlayerSysInfo() #get current sysInfo + oldInfo = self.sysInfo + newInfo = self.getPlayerSysInfo() #get current sysInfo + self.log('_onPlay\nnewInfo = %s\noldInfo = %s'%(newInfo,oldInfo)) + self.setPlaycount(self.rollbackPlaycount,oldInfo.get('fitem',{})) + self.toggleReplay(False) self.toggleBackground(False) - if self.sysInfo.get('citem',{}).get('id') != self.sysInfo.get('citem',{}).get('id',random.random()): #playing new channel - self.runActions(RULES_ACTION_PLAYER_START, self.sysInfo.get('citem,{}'), inherited=self) + if newInfo.get('chid') != oldInfo.get('chid',random.random()): #playing new channel + self.sysInfo = newInfo + self.runActions(RULES_ACTION_PLAYER_START, self.sysInfo.get('citem',{}), inherited=self) self.setTrakt(self.disableTrakt) self.setSubtitles(self.lastSubState) #todo allow rules to set sub preference per channel. + self.toggleReplay() def _onChange(self): self.log('_onChange') + self.toggleReplay(False) self.toggleBackground() self.setTrakt(False) self.setPlaycount(self.rollbackPlaycount,self.sysInfo.get('fitem',{})) @@ -213,14 +222,22 @@ def _onError(self): self.onPlayBackStopped() + def toggleReplay(self, state: bool=True): + self.log('toggleReplay, state = %s, enableReplay = %s'%(state,self.enableReplay)) + if state and bool(self.enableReplay) and not self.isIdle: + progress = self.getPlayerProgress() + if (progress >= self.enableReplay and progress < SETTINGS.getSettingInt('Seek_Threshold')): + self.replay = Replay("%s.replay.xml"%(ADDON_ID), ADDON_PATH, "default", "1080i", sysInfo=self.sysInfo) + self.replay.doModal() + elif hasattr(self.replay, 'close'): self.replay.close() + + def toggleBackground(self, state: bool=True): + self.log('toggleBackground, state = %s'%(state)) if state: - if hasattr(self.background, 'show'): - self.log('toggleBackground, state = %s'%(state)) - self.background.show() + if hasattr(self.background, 'show'): self.background.show() else: if hasattr(self.background, 'close'): - self.log('toggleBackground, state = %s'%(state)) self.background = self.background.close() if self.isPlaying(): BUILTIN.executebuiltin('ReplaceWindow(fullscreenvideo)') self.background = Background("%s.background.xml"%(ADDON_ID), ADDON_PATH, "default", citem=self.sysInfo.get('citem',{})) @@ -240,7 +257,7 @@ def __init__(self, jsonRPC=None): self.jsonRPC = jsonRPC self.pendingSuspend = False self.pendingInterrupt = False - self.sleepTime = (SETTINGS.getSettingInt('Idle_Timer') or 0) + self.sleepTime = (SETTINGS.getSettingInt('Idle_Timer') or 0) self.enableOverlay = (SETTINGS.getSettingBool('Enable_Overlay') or True) @@ -293,12 +310,12 @@ def sleepTimer(self): self.log('sleepTimer') sec = 0 cnx = False - inc = int(100/OVERLAY_DELAY) + inc = int(100/EPOCH_TIMER) playSFX(NOTE_WAV) dia = DIALOG.progressDialog(message=LANGUAGE(30078)) - while not self.abortRequested() and (sec < OVERLAY_DELAY): + while not self.abortRequested() and (sec < EPOCH_TIMER): sec += 1 - msg = '%s\n%s'%(LANGUAGE(32039),LANGUAGE(32040)%((OVERLAY_DELAY-sec))) + msg = '%s\n%s'%(LANGUAGE(32039),LANGUAGE(32040)%((EPOCH_TIMER-sec))) dia = DIALOG.progressDialog((inc*sec),dia, msg) if self.waitForAbort(1.0) or dia is None: cnx = True @@ -384,7 +401,7 @@ def __playing(self) -> bool: def __tasks(self): self.log('__tasks') - self.tasks._chkEpochTimer('chkQueTimer',self.tasks._chkQueTimer,OVERLAY_DELAY) + self.tasks._chkEpochTimer('chkQueTimer',self.tasks._chkQueTimer,EPOCH_TIMER) def __initialize(self): diff --git a/plugin.video.pseudotv.live/resources/lib/tasks.py b/plugin.video.pseudotv.live/resources/lib/tasks.py index 9d67869c..e9c2c39a 100644 --- a/plugin.video.pseudotv.live/resources/lib/tasks.py +++ b/plugin.video.pseudotv.live/resources/lib/tasks.py @@ -96,7 +96,7 @@ def chkPVRBackend(self): if hasAddon(PVR_CLIENT_ID,True,True,True,True): if SETTINGS.hasPVRInstance() == False: with BUILTIN.busy_dialog(): - SETTINGS.setPVRPath(USER_LOC, self.jsonRPC.getFriendlyName()) + SETTINGS.setPVRPath(USER_LOC, validString(self.jsonRPC.getFriendlyName())) def _chkQueTimer(self): @@ -186,7 +186,7 @@ def chkLibrary(self, force=False): complete = library.updateLibrary(force) del library if not complete: self._que(self.chkLibrary,1,True) - elif not SETTINGS.hasAutotuned() and not force: self.runAutoTune() #run autotune for the first time this Kodi/PTVL instance. + elif not PROPERTIES.hasAutotuned() and not force: self.runAutoTune() #run autotune for the first time this Kodi/PTVL instance. except Exception as e: self.log('chkLibrary failed! %s'%(e), xbmc.LOGERROR) @@ -248,7 +248,7 @@ def runAutoTune(self): try: autotune = Autotune(service=self.service) complete = autotune._runTune() - if complete: SETTINGS.setAutotuned() + if complete: PROPERTIES.setAutotuned() del autotune except Exception as e: self.log('runAutoTune failed! %s'%(e), xbmc.LOGERROR) diff --git a/plugin.video.pseudotv.live/resources/lib/utilities.py b/plugin.video.pseudotv.live/resources/lib/utilities.py index 32f67d90..0ede0438 100644 --- a/plugin.video.pseudotv.live/resources/lib/utilities.py +++ b/plugin.video.pseudotv.live/resources/lib/utilities.py @@ -185,7 +185,7 @@ def deleteFiles(self, msg, full: bool=False): for key in keys: if FileAccess.delete(files[key]): DIALOG.notificationDialog(LANGUAGE(32127)%(key.replace(':',''))) if full: - SETTINGS.setAutotuned(False) + PROPERTIES.setAutotuned(False) setPendingRestart() @@ -200,7 +200,7 @@ def run(self): with BUILTIN.busy_dialog(): from jsonrpc import JSONRPC jsonRPC = JSONRPC() - if SETTINGS.setPVRPath(USER_LOC,jsonRPC.getFriendlyName(),prompt=True,force=True): + if SETTINGS.setPVRPath(USER_LOC,validString(jsonRPC.getFriendlyName()),prompt=True,force=True): DIALOG.notificationDialog(LANGUAGE(32152)) else: DIALOG.notificationDialog(LANGUAGE(32165)) del jsonRPC diff --git a/plugin.video.pseudotv.live/resources/settings.xml b/plugin.video.pseudotv.live/resources/settings.xml index fe29302d..a6978a93 100644 --- a/plugin.video.pseudotv.live/resources/settings.xml +++ b/plugin.video.pseudotv.live/resources/settings.xml @@ -92,12 +92,6 @@ - - 3 - false - false - - @@ -501,6 +495,18 @@ + + 1 + 5 + + 0 + 5 + 75 + + + false + + 1 true diff --git a/plugin.video.pseudotv.live/resources/skins/default/1080i/plugin.video.pseudotv.live.replay.xml b/plugin.video.pseudotv.live/resources/skins/default/1080i/plugin.video.pseudotv.live.replay.xml new file mode 100644 index 00000000..cfa1f95b --- /dev/null +++ b/plugin.video.pseudotv.live/resources/skins/default/1080i/plugin.video.pseudotv.live.replay.xml @@ -0,0 +1,49 @@ + + + 0x00000000 + SetProperty(plugin.video.pseudotv.live.OVERLAY_REPLAY,true,10000) + SetProperty(plugin.video.pseudotv.live.OVERLAY_REPLAY,false,10000) + + + 0 + 0 + WindowOpen + WindowClose + + Replay Button + 480 + 270 + 200 + 40 + FFFFFFFF + FF696969 + buttons/ButtonFocus.png + /buttons/ButtonFocus.png + keep + + center + center + false + font32_title + FFFFFFFF + FFFFFFFF + 80FFFFFF + FFFFFFFF + + + false + + + + + + 496 + 274 + 34 + 34 + buttons/back.png + scale + + + + \ No newline at end of file diff --git a/plugin.video.pseudotv.live/resources/skins/default/media/buttons/ButtonFocus.png b/plugin.video.pseudotv.live/resources/skins/default/media/buttons/ButtonFocus.png new file mode 100644 index 00000000..0ccc32a1 Binary files /dev/null and b/plugin.video.pseudotv.live/resources/skins/default/media/buttons/ButtonFocus.png differ diff --git a/plugin.video.pseudotv.live/resources/skins/default/media/buttons/back.png b/plugin.video.pseudotv.live/resources/skins/default/media/buttons/back.png new file mode 100644 index 00000000..248cfb1f Binary files /dev/null and b/plugin.video.pseudotv.live/resources/skins/default/media/buttons/back.png differ diff --git a/zips/plugin.video.pseudotv.live/plugin.video.pseudotv.live-0.5.3g.zip b/zips/plugin.video.pseudotv.live/plugin.video.pseudotv.live-0.5.3g.zip index a67cd2ad..6962196a 100644 Binary files a/zips/plugin.video.pseudotv.live/plugin.video.pseudotv.live-0.5.3g.zip and b/zips/plugin.video.pseudotv.live/plugin.video.pseudotv.live-0.5.3g.zip differ diff --git a/zips/plugin.video.pseudotv.live/plugin.video.pseudotv.live-0.5.3h.zip b/zips/plugin.video.pseudotv.live/plugin.video.pseudotv.live-0.5.3h.zip new file mode 100644 index 00000000..f8c1216e Binary files /dev/null and b/zips/plugin.video.pseudotv.live/plugin.video.pseudotv.live-0.5.3h.zip differ