Updated kodi settings on Lenovo

This commit is contained in:
2026-03-22 22:28:43 +01:00
parent 725dfa7157
commit 32b5a81da6
10925 changed files with 575678 additions and 5511 deletions

View File

@@ -0,0 +1,668 @@
# Copyright (C) 2024 Lunatixz
#
#
# This file is part of PseudoTV Live.
#
# PseudoTV Live 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 3 of the License, or
# (at your option) any later version.
#
# PseudoTV Live 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 PseudoTV Live. If not, see <http://www.gnu.org/licenses/>.
# -*- coding: utf-8 -*-
from globals import *
from videoparser import VideoParser
class Service:
player = PLAYER()
monitor = MONITOR()
def _interrupt(self) -> bool:
return PROPERTIES.isPendingInterrupt()
def _suspend(self) -> bool:
return PROPERTIES.isPendingSuspend()
class JSONRPC:
def __init__(self, service=None):
if service is None: service = Service()
self.service = service
self.cache = SETTINGS.cacheDB
self.videoParser = VideoParser()
def log(self, msg, level=xbmc.LOGDEBUG):
return log('%s: %s' % (self.__class__.__name__, msg), level)
def sendJSON(self, param, timeout=-1):
command = param
command["jsonrpc"] = "2.0"
command["id"] = ADDON_ID
self.log('sendJSON, timeout = %s, command = %s'%(timeout,dumpJSON(command)))
if timeout > 0: response = loadJSON((killit(BUILTIN.executeJSONRPC)(timeout,dumpJSON(command))) or {'error':{'message':'JSONRPC timed out!'}})
else: response = loadJSON(BUILTIN.executeJSONRPC(dumpJSON(command)))
if response.get('error'):
self.log('sendJSON, failed! error = %s\n%s'%(dumpJSON(response.get('error')),command), xbmc.LOGWARNING)
response.setdefault('result',{})['error'] = response.pop('error')
#throttle calls, low power devices suffer segfault during rpc flood
self.service.monitor.waitForAbort(float(SETTINGS.getSettingInt('RPC_Delay')/1000))
return response
def queueJSON(self, param):
queuePool = (SETTINGS.getCacheSetting('queueJSON', json_data=True) or {})
params = queuePool.setdefault('params',[])
params.append(param)
queuePool['params'] = sorted(setDictLST(params), key=lambda d: d.get('params',{}).get('setting',''))
queuePool['params'] = sorted(setDictLST(params), key=lambda d: d.get('params',{}).get('playcount',0))
queuePool['params'].reverse() #prioritize setsetting,playcount rollback over duration amendments.
self.log("queueJSON, saving = %s\n%s"%(len(queuePool['params']),param))
SETTINGS.setCacheSetting('queueJSON', queuePool, json_data=True)
def cacheJSON(self, param, life=datetime.timedelta(minutes=15), checksum=ADDON_VERSION, timeout=-1):
cacheName = 'cacheJSON.%s'%(getMD5(dumpJSON(param)))
cacheResponse = self.cache.get(cacheName, checksum=checksum, json_data=True)
if not cacheResponse:
cacheResponse = self.sendJSON(param, timeout)
if cacheResponse.get('result',{}): self.cache.set(cacheName, cacheResponse, checksum=checksum, expiration=life, json_data=True)
return cacheResponse
def walkFileDirectory(self, path, media='video', depth=5, chkDuration=False, retItem=False, checksum=ADDON_VERSION, expiration=datetime.timedelta(minutes=15)):
walk = dict()
self.log('walkFileDirectory, walking %s, depth = %s'%(path,depth))
items = self.getDirectory({"directory":path,"media":media},True,checksum,expiration).get('files',[])
for idx, item in enumerate(items):
if item.get('filetype') == 'file':
if chkDuration:
item['duration'] = self.getDuration(item.get('file'),item, accurate=bool(SETTINGS.getSettingInt('Duration_Type')))
if item['duration'] == 0: continue
walk.setdefault(path,[]).append(item if retItem else item.get('file'))
elif item.get('filetype') == 'directory' and depth > 0:
depth -= 1
walk.update(self.walkFileDirectory(item.get('file'), media, depth, chkDuration, retItem, checksum, expiration))
return walk
def walkListDirectory(self, path, exts='', depth=5, chkDuration=False, appendPath=False, checksum=ADDON_VERSION, expiration=datetime.timedelta(minutes=15)):
def _chkfile(path, f):
if exts and not f.lower().endswith(tuple(exts)): return
if chkDuration:
dur = self.getDuration(os.path.join(path,f), accurate=bool(SETTINGS.getSettingInt('Duration_Type')))
if dur == 0: return
return {True:os.path.join(path,f).replace('\\','/'),False:f}[appendPath]
def _parseXBT(resource):
self.log('walkListDirectory, parsing XBT = %s'%(resource))
walk.setdefault(resource,[]).extend(self.getListDirectory(resource,checksum,expiration)[1])
return walk
walk = dict()
path = path.replace('\\','/')
subs, files = self.getListDirectory(path,checksum,expiration)
if len(files) > 0 and TEXTURES in files: return _parseXBT(re.sub('/resources','',path).replace('special://home/addons/','resource://'))
nfiles = [_f for _f in [_chkfile(path, file) for file in files] if _f]
self.log('walkListDirectory, walking %s, found = %s, appended = %s, depth = %s, ext = %s'%(path,len(files),len(nfiles),depth,exts))
walk.setdefault(path,[]).extend(nfiles)
for sub in subs:
if depth == 0: break
depth -= 1
walk.update(self.walkListDirectory(os.path.join(path,sub), exts, depth, chkDuration, appendPath, checksum, expiration))
return walk
def getListDirectory(self, path, checksum=ADDON_VERSION, expiration=datetime.timedelta(minutes=15)):
cacheName = 'getListDirectory.%s'%(getMD5(path))
results = self.cache.get(cacheName, checksum)
if not results:
try:
results = self.cache.set(cacheName, FileAccess.listdir(path), checksum, expiration)
self.log('getListDirectory path = %s, checksum = %s'%(path, checksum))
except Exception as e:
self.log("getListDirectory, failed! %s\npath = %s"%(e,path), xbmc.LOGERROR)
results = [],[]
self.log('getListDirectory return dirs = %s, files = %s\n%s'%(len(results[0]), len(results[1]),path))
return results
def getIntrospect(self, id):
param = {"method":"JSONRPC.Introspect","params":{"filter":{"id":id,"type":"method"}}}
return self.cacheJSON(param,datetime.timedelta(days=28),BUILTIN.getInfoLabel('BuildVersion','System')).get('result',{})
def getEnums(self, id, type='', key='enums'):
self.log('getEnums id = %s, type = %s, key = %s' % (id, type, key))
param = {"method":"JSONRPC.Introspect","params":{"getmetadata":True,"filterbytransport":True,"filter":{"getreferences":False,"id":id,"type":"type"}}}
json_response = self.cacheJSON(param,datetime.timedelta(days=28),BUILTIN.getInfoLabel('BuildVersion','System')).get('result',{}).get('types',{}).get(id,{})
return (json_response.get('properties',{}).get(type,{}).get(key) or json_response.get(type,{}).get(key) or json_response.get(key,[]))
def notifyAll(self, message, data, sender=ADDON_ID):
param = {"method":"JSONRPC.NotifyAll","params":{"sender":sender,"message":message,"data":[data]}}
return self.sendJSON(param).get('result') == 'OK'
def playerOpen(self, params={}):
param = {"method":"Player.Open","params":params}
return self.sendJSON(param).get('result') == 'OK'
def getSetting(self, category, section, cache=False):
param = {"method":"Settings.GetSettings","params":{"filter":{"category":category,"section":section}}}
if cache: return self.cacheJSON(param).get('result',{}).get('settings',[])
else: return self.sendJSON(param).get('result', {}).get('settings',[])
def getSettingValue(self, key, default='', cache=False):
param = {"method":"Settings.GetSettingValue","params":{"setting":key}}
if cache: return (self.cacheJSON(param).get('result',{}).get('value') or default)
else: return (self.sendJSON(param).get('result',{}).get('value') or default)
def setSettingValue(self, key, value, queue=False):
param = {"method":"Settings.SetSettingValue","params":{"setting":key,"value":value}}
if queue: self.queueJSON(param)
else: self.sendJSON(param)
def getSources(self, media='video', cache=True):
param = {"method":"Files.GetSources","params":{"media":media}}
if cache: return self.cacheJSON(param).get('result', {}).get('sources', [])
else: return self.sendJSON(param).get('result', {}).get('sources', [])
def getAddonDetails(self, addonid=ADDON_ID, cache=True):
param = {"method":"Addons.GetAddonDetails","params":{"addonid":addonid,"properties":self.getEnums("Addon.Fields", type='items')}}
if cache: return self.cacheJSON(param).get('result', {}).get('addon', {})
else: return self.sendJSON(param).get('result', {}).get('addon', {})
def getAddons(self, param={"content":"video","enabled":True,"installed":True}, cache=True):
param["properties"] = self.getEnums("Addon.Fields", type='items')
param = {"method":"Addons.GetAddons","params":param}
if cache: return self.cacheJSON(param).get('result', {}).get('addons', [])
else: return self.sendJSON(param).get('result', {}).get('addons', [])
def getSongs(self, cache=True):
param = {"method":"AudioLibrary.GetSongs","params":{"properties":self.getEnums("Audio.Fields.Song", type='items')}}
if cache: return self.cacheJSON(param).get('result', {}).get('songs', [])
else: return self.sendJSON(param).get('result', {}).get('songs', [])
def getArtists(self, cache=True):
param = {"method":"AudioLibrary.GetArtists","params":{"properties":self.getEnums("Audio.Fields.Artist", type='items')}}
if cache: return self.cacheJSON(param).get('result', {}).get('artists', [])
else: return self.sendJSON(param).get('result', {}).get('artists', [])
def getAlbums(self, cache=True):
param = {"method":"AudioLibrary.GetAlbums","params":{"properties":self.getEnums("Audio.Fields.Album", type='items')}}
if cache: return self.cacheJSON(param).get('result', {}).get('albums', [])
else: return self.sendJSON(param).get('result', {}).get('albums', [])
def getEpisodes(self, cache=True):
param = {"method":"VideoLibrary.GetEpisodes","params":{"properties":self.getEnums("Video.Fields.Episode", type='items')}}
if cache: return self.cacheJSON(param).get('result', {}).get('episodes', [])
else: return self.sendJSON(param).get('result', {}).get('episodes', [])
def getTVshows(self, cache=True):
param = {"method":"VideoLibrary.GetTVShows","params":{"properties":self.getEnums("Video.Fields.TVShow", type='items')}}
if cache: return self.cacheJSON(param).get('result', {}).get('tvshows', [])
else: return self.sendJSON(param).get('result', {}).get('tvshows', [])
def getMovies(self, cache=True):
param = {"method":"VideoLibrary.GetMovies","params":{"properties":self.getEnums("Video.Fields.Movie", type='items')}}
if cache: return self.cacheJSON(param).get('result', {}).get('movies', [])
else: return self.sendJSON(param).get('result', {}).get('movies', [])
def getVideoGenres(self, type="movie", cache=True): #type = "movie"/"tvshow"
param = {"method":"VideoLibrary.GetGenres","params":{"type":type,"properties":self.getEnums("Library.Fields.Genre", type='items')}}
if cache: return self.cacheJSON(param).get('result', {}).get('genres', [])
else: return self.sendJSON(param).get('result', {}).get('genres', [])
def getMusicGenres(self, cache=True):
param = {"method":"AudioLibrary.GetGenres","params":{"properties":self.getEnums("Library.Fields.Genre", type='items')}}
if cache: return self.cacheJSON(param).get('result', {}).get('genres', [])
else: return self.sendJSON(param).get('result', {}).get('genres', [])
def getDirectory(self, param={}, cache=True, checksum=ADDON_VERSION, expiration=datetime.timedelta(minutes=15), timeout=-1):
param["properties"] = self.getEnums("List.Fields.Files", type='items')
param = {"method":"Files.GetDirectory","params":param}
if cache: return self.cacheJSON(param, expiration, checksum, timeout).get('result', {})
else: return self.sendJSON(param, timeout).get('result', {})
def getLibrary(self, method, param={}, cache=True):
param = {"method":method,"params":param}
if cache: return self.cacheJSON(param).get('result', {})
else: return self.sendJSON(param).get('result', {})
def getStreamDetails(self, path, media='video'):
if isStack(path): path = splitStacks(path)[0]
param = {"method":"Files.GetFileDetails","params":{"file":path,"media":media,"properties":["streamdetails"]}}
return self.cacheJSON(param, life=datetime.timedelta(days=MAX_GUIDEDAYS), checksum=getMD5(path)).get('result',{}).get('filedetails',{}).get('streamdetails',{})
def getFileDetails(self, file, media='video', properties=["duration","runtime"]):
return self.cacheJSON({"method":"Files.GetFileDetails","params":{"file":file,"media":media,"properties":properties}})
def getViewMode(self):
default = {"nonlinearstretch":False,"pixelratio":1,"verticalshift":0,"viewmode":"custom","zoom": 1.0}
return self.cacheJSON({"method":"Player.GetViewMode","params":{}},datetime.timedelta(seconds=FIFTEEN)).get('result',default)
def setViewMode(self, params={}):
return self.sendJSON({"method":"Player.SetViewMode","params":params})
def getPlayerItem(self, playlist=False):
self.log('getPlayerItem, playlist = %s' % (playlist))
if playlist: param = {"method":"Playlist.GetItems","params":{"playlistid":self.getActivePlaylist(),"properties":self.getEnums("List.Fields.All", type='items')}}
else: param = {"method":"Player.GetItem" ,"params":{"playerid":self.getActivePlayer() ,"properties":self.getEnums("List.Fields.All", type='items')}}
result = self.sendJSON(param).get('result', {})
return (result.get('item', {}) or result.get('items', []))
def getPVRChannels(self, radio=False):
param = {"method":"PVR.GetChannels","params":{"channelgroupid":{True:'allradio',False:'alltv'}[radio],"properties":self.getEnums("PVR.Fields.Channel", type='items')}}
return self.sendJSON(param).get('result', {}).get('channels', [])
def getPVRChannelsDetails(self, id):
param = {"method":"PVR.GetChannelDetails","params":{"channelid":id,"properties":self.getEnums("PVR.Fields.Channel", type='items')}}
return self.sendJSON(param).get('result', {}).get('channels', [])
def getPVRBroadcasts(self, id):
param = {"method":"PVR.GetBroadcasts","params":{"channelid":id,"properties":self.getEnums("PVR.Fields.Broadcast", type='items')}}
return self.sendJSON(param).get('result', {}).get('broadcasts', [])
def getPVRBroadcastDetails(self, id):
param = {"method":"PVR.GetBroadcastDetails","params":{"broadcastid":id,"properties":self.getEnums("PVR.Fields.Broadcast", type='items')}}
return self.sendJSON(param).get('result', {}).get('broadcastdetails', [])
def getPVRRecordings(self, media='video', cache=True):
param = {"method":"Files.GetDirectory","params":{"directory":"pvr://recordings/tv/active/","media":media,"properties":self.getEnums("List.Fields.Files", type='items')}}
if cache: return self.cacheJSON(param).get('result', {}).get('files', [])
else: return self.sendJSON(param).get('result', {}).get('files', [])
def getPVRSearches(self, media='video', cache=True):
param = {"method":"Files.GetDirectory","params":{"directory":"pvr://search/tv/savedsearches/","media":media,"properties":self.getEnums("List.Fields.Files", type='items')}}
if cache: return self.cacheJSON(param).get('result', {}).get('files', [])
else: return self.sendJSON(param).get('result', {}).get('files', [])
def getPVRSearchItems(self, id, media='video', cache=True):
param = {"method":"Files.GetDirectory","params":{"directory":f"pvr://search/tv/savedsearches/{id}/","media":media,"properties":self.getEnums("List.Fields.Files", type='items')}}
if cache: return self.cacheJSON(param).get('result', {}).get('files', [])
else: return self.sendJSON(param).get('result', {}).get('files', [])
def getSmartPlaylists(self, type='video', cache=True):
param = {"method":"Files.GetDirectory","params":{"directory":f"special://profile/playlists/{type}/","media":"video","properties":self.getEnums("List.Fields.Files", type='items')}}
if cache: return self.cacheJSON(param).get('result', {}).get('files', [])
else: return self.sendJSON(param).get('result', {}).get('files', [])
def getInfoLabel(self, key, cache=False):
param = {"method":"XBMC.GetInfoLabels","params":{"labels":[key]}}
if cache: return self.cacheJSON(param).get('result', {}).get(key)
else: return self.sendJSON(param).get('result', {}).get(key)
def getInfoBool(self, key, cache=False):
param = {"method":"XBMC.GetInfoBooleans","params":{"booleans":[key]}}
if cache: return self.cacheJSON(param).get('result', {}).get(key)
else: return self.sendJSON(param).get('result', {}).get(key)
def _setRuntime(self, item={}, runtime=0, save=SETTINGS.getSettingBool('Store_Duration')): #set runtime collected by player, accurate meta.
self.cache.set('getRuntime.%s'%(getMD5(item.get('file'))), runtime, checksum=getMD5(item.get('file')), expiration=datetime.timedelta(days=28), json_data=False)
if not item.get('file','plugin://').startswith(tuple(VFS_TYPES)) and save and runtime > 0: self.queDuration(item, runtime=runtime)
def _getRuntime(self, item={}): #get runtime collected by player, else less accurate provider meta
runtime = self.cache.get('getRuntime.%s'%(getMD5(item.get('file'))), checksum=getMD5(item.get('file')), json_data=False)
return (runtime or item.get('resume',{}).get('total') or item.get('runtime') or item.get('duration') or (item.get('streamdetails',{}).get('video',[]) or [{}])[0].get('duration') or 0)
def _setDuration(self, path, item={}, duration=0, save=SETTINGS.getSettingBool('Store_Duration')):#set VideoParser cache
self.cache.set('getDuration.%s'%(getMD5(path)), duration, checksum=getMD5(path), expiration=datetime.timedelta(days=28), json_data=False)
if save and item: self.queDuration(item, duration)
return duration
def _getDuration(self, path): #get VideoParser cache
return (self.cache.get('getDuration.%s'%(getMD5(path)), checksum=getMD5(path), json_data=False) or 0)
def getDuration(self, path, item={}, accurate=bool(SETTINGS.getSettingInt('Duration_Type')), save=SETTINGS.getSettingBool('Store_Duration')):
self.log("getDuration, accurate = %s, path = %s, save = %s" % (accurate, path, save))
if not item: item = {'file':path}
runtime = self._getRuntime(item)
if runtime == 0 or accurate:
duration = 0
if isStack(path):# handle "stacked" videos
for file in splitStacks(path): duration += self.__parseDuration(runtime, file)
else: duration = self.__parseDuration(runtime, path, item, save)
if duration > 0: runtime = duration
self.log("getDuration, returning path = %s, runtime = %s" % (path, runtime))
return runtime
def getTotRuntime(self, items=[]):
total = sum([self._getRuntime(item) for item in items])
self.log("getTotRuntime, items = %s, total = %s" % (len(items), total))
return total
def getTotDuration(self, items=[]):
total = sum([self.getDuration(item.get('file'),item) for item in items])
self.log("getTotDuration, items = %s, total = %s" % (len(items), total))
return total
def __parseDuration(self, runtime, path, item={}, save=SETTINGS.getSettingBool('Store_Duration')):
self.log("__parseDuration, runtime = %s, path = %s, save = %s" % (runtime, path, save))
duration = self.videoParser.getVideoLength(path.replace("\\\\", "\\"), item, self)
if not path.startswith(tuple(VFS_TYPES)):
## duration diff. safe guard, how different are the two values? if > 45% don't save to Kodi.
rundiff = int(percentDiff(runtime, duration))
runsafe = False
if (rundiff <= 45 and rundiff > 0) or (rundiff == 100 and (duration == 0 or runtime == 0)) or (rundiff == 0 and (duration > 0 and runtime > 0)) or (duration > runtime): runsafe = True
self.log("__parseDuration, path = %s, runtime = %s, duration = %s, difference = %s%%, safe = %s" % (path, runtime, duration, rundiff, runsafe))
## save parsed duration to Kodi database, if enabled.
if runsafe:
runtime = duration
if save and not path.startswith(tuple(VFS_TYPES)): self.queDuration(item, duration)
else: runtime = duration
self.log("__parseDuration, returning runtime = %s" % (runtime))
return runtime
def queDuration(self, item={}, duration=0, runtime=0):
mtypes = {'video' : {},
'movie' : {"method":"VideoLibrary.SetMovieDetails" ,"params":{"movieid" :item.get('id',-1) ,"runtime": runtime,"resume": {"position": item.get('position',0.0),"total": duration}}},
'movies' : {"method":"VideoLibrary.SetMovieDetails" ,"params":{"movieid" :item.get('movieid',-1) ,"runtime": runtime,"resume": {"position": item.get('position',0.0),"total": duration}}},
'episode' : {"method":"VideoLibrary.SetEpisodeDetails" ,"params":{"episodeid" :item.get('id',-1) ,"runtime": runtime,"resume": {"position": item.get('position',0.0),"total": duration}}},
'episodes' : {"method":"VideoLibrary.SetEpisodeDetails" ,"params":{"episodeid" :item.get('episodeid',-1) ,"runtime": runtime,"resume": {"position": item.get('position',0.0),"total": duration}}},
'musicvideo' : {"method":"VideoLibrary.SetMusicVideoDetails","params":{"musicvideoid":item.get('id',-1) ,"runtime": runtime,"resume": {"position": item.get('position',0.0),"total": duration}}},
'musicvideos': {"method":"VideoLibrary.SetMusicVideoDetails","params":{"musicvideoid":item.get('musicvideoid',-1) ,"runtime": runtime,"resume": {"position": item.get('position',0.0),"total": duration}}},
'song' : {"method":"AudioLibrary.SetSongDetails" ,"params":{"songid" :item.get('id',-1) ,"runtime": runtime,"resume": {"position": item.get('position',0.0),"total": duration}}},
'songs' : {"method":"AudioLibrary.SetSongDetails" ,"params":{"songid" :item.get('songid',-1) ,"runtime": runtime,"resume": {"position": item.get('position',0.0),"total": duration}}}}
try:
mtype = mtypes.get(item.get('type'))
if mtype.get('params'):
if duration == 0: mtype['params'].pop('resume') #save file duration meta
elif runtime == 0: mtype['params'].pop('runtime') #save player runtime meta
id = (item.get('id') or item.get('movieid') or item.get('episodeid') or item.get('musicvideoid') or item.get('songid'))
self.log('[%s] queDuration, media = %s, duration = %s, runtime = %s'%(id,item['type'],duration,runtime))
self.queueJSON(mtype['params'])
except Exception as e: self.log("queDuration, failed! %s\nmtype = %s\nitem = %s"%(e,mtype,item), xbmc.LOGERROR)
def quePlaycount(self, item, save=SETTINGS.getSettingBool('Rollback_Watched')):
param = {'video' : {},
'movie' : {"method":"VideoLibrary.SetMovieDetails" ,"params":{"movieid" :item.get('id',-1) ,"playcount": item.get('playcount',0),"resume": {"position": item.get('position',0.0),"total": item.get('total',0.0)}}},
'movies' : {"method":"VideoLibrary.SetMovieDetails" ,"params":{"movieid" :item.get('movieid',-1) ,"playcount": item.get('playcount',0),"resume": {"position": item.get('position',0.0),"total": item.get('total',0.0)}}},
'episode' : {"method":"VideoLibrary.SetEpisodeDetails" ,"params":{"episodeid" :item.get('id',-1) ,"playcount": item.get('playcount',0),"resume": {"position": item.get('position',0.0),"total": item.get('total',0.0)}}},
'episodes' : {"method":"VideoLibrary.SetEpisodeDetails" ,"params":{"episodeid" :item.get('episodeid',-1) ,"playcount": item.get('playcount',0),"resume": {"position": item.get('position',0.0),"total": item.get('total',0.0)}}},
'musicvideo' : {"method":"VideoLibrary.SetMusicVideoDetails","params":{"musicvideoid":item.get('id',-1) ,"playcount": item.get('playcount',0),"resume": {"position": item.get('position',0.0),"total": item.get('total',0.0)}}},
'musicvideos': {"method":"VideoLibrary.SetMusicVideoDetails","params":{"musicvideoid":item.get('musicvideoid',-1) ,"playcount": item.get('playcount',0),"resume": {"position": item.get('position',0.0),"total": item.get('total',0.0)}}},
'song' : {"method":"AudioLibrary.SetSongDetails" ,"params":{"songid" :item.get('id',-1) ,"playcount": item.get('playcount',0),"resume": {"position": item.get('position',0.0),"total": item.get('total',0.0)}}},
'songs' : {"method":"AudioLibrary.SetSongDetails" ,"params":{"songid" :item.get('songid',-1) ,"playcount": item.get('playcount',0),"resume": {"position": item.get('position',0.0),"total": item.get('total',0.0)}}}}
if not item.get('file','plugin://').startswith(tuple(VFS_TYPES)):
try:
params = param.get(item.get('type'))
self.log('quePlaycount, params = %s'%(params.get('params',{})))
self.queueJSON(params)
except: pass
def requestList(self, citem, path, media='video', page=SETTINGS.getSettingInt('Page_Limit'), sort={}, limits={}, query={}):
# {"method": "VideoLibrary.GetEpisodes",
# "params": {
# "properties": ["title"],
# "sort": {"ignorearticle": true,
# "method": "label",
# "order": "ascending",
# "useartistsortname": true},
# "limits": {"end": 0, "start": 0},
# "filter": {"and": [{"field": "title", "operator": "contains", "value": "Star Wars"}]}}}
##################################
# {"method": "Files.GetDirectory",
# "params": {
# "directory": "videodb://tvshows/studios/-1/-1/-1/",
# "media": "video",
# "properties": ["title"],
# "sort": {"ignorearticle": true,
# "method": "label",
# "order": "ascending",
# "useartistsortname": true},
# "limits": {"end": 25, "start": 0}}}
param = {}
if query: #library query
getDirectory = False
param['filter'] = query.get('filter',{})
param["properties"] = self.getEnums(query['enum'], type='items')
else: #vfs path
getDirectory = True
param["media"] = media
param["directory"] = escapeDirJSON(path)
param["properties"] = self.getEnums("List.Fields.Files", type='items')
self.log("requestList, id: %s, getDirectory = %s, media = %s, limit = %s, sort = %s, query = %s, limits = %s\npath = %s"%(citem['id'],getDirectory,media,page,sort,query,limits,path))
if limits.get('end',-1) == -1: #-1 unlimited pagination, replace with autoPagination.
limits = self.autoPagination(citem['id'], path, query) #get
self.log('[%s] requestList, autoPagination limits = %s'%(citem['id'],limits))
if limits.get('total',0) > page and sort.get("method","") == "random":
limits = self.randomPagination(page,limits)
self.log('[%s] requestList, generating random limits = %s'%(citem['id'],limits))
param["limits"] = {}
param["limits"]["start"] = 0 if limits.get('end', 0) == -1 else limits.get('end', 0)
param["limits"]["end"] = abs(limits.get('end', 0) + page)
param["sort"] = sort
self.log('[%s] requestList, page = %s\nparam = %s'%(citem['id'], page, param))
if getDirectory:
results = self.getDirectory(param,timeout=float(SETTINGS.getSettingInt('RPC_Timer')*60))
if 'filedetails' in results: key = 'filedetails'
else: key = 'files'
else:
results = self.getLibrary(query['method'],param, cache=False)
key = query.get('key',list(results.keys())[0])
items, limits, errors = results.get(key,[]), results.get('limits',param["limits"]), results.get('error',{})
if (limits.get('end',0) >= limits.get('total',0) or limits.get('start',0) >= limits.get('total',0)):
# restart page to 0, exceeding boundaries.
self.log('[%s] requestList, resetting limits to 0'%(citem['id']))
limits = {"end": 0, "start": 0, "total": limits.get('total',0)}
if len(items) == 0 and limits.get('total',0) > 0:
# retry last request with fresh limits when no items are returned.
self.log("[%s] requestList, trying again with start limits at 0"%(citem['id']))
return self.requestList(citem, path, media, page, sort, {"end": 0, "start": 0, "total": limits.get('total',0)}, query)
else:
self.autoPagination(citem['id'], path, query, limits) #set
self.log("[%s] requestList, return items = %s" % (citem['id'], len(items)))
return items, limits, errors
def resetPagination(self, id, path, query={}, limits={"end": 0, "start": 0, "total":0}):
return self.autoPagination(id, path, query, limits)
def autoPagination(self, id, path, query={}, limits={}):
if not limits: return (self.cache.get('autoPagination.%s.%s.%s'%(id,getMD5(path),getMD5(dumpJSON(query))), checksum=id, json_data=True) or {"end": 0, "start": 0, "total":0})
else: return self.cache.set('autoPagination.%s.%s.%s'%(id,getMD5(path),getMD5(dumpJSON(query))), limits, checksum=id, expiration=datetime.timedelta(days=28), json_data=True)
def randomPagination(self, page=SETTINGS.getSettingInt('Page_Limit'), limits={}, start=0):
if limits.get('total',0) > page: start = random.randrange(0, (limits.get('total',0)-page), page)
return {"end": start, "start": start, "total":limits.get('total',0)}
@cacheit(checksum=PROPERTIES.getInstanceID())
def buildWebBase(self, local=False):
port = 80
username = 'kodi'
password = ''
secure = False
enabled = True
settings = self.getSetting('control','services')
for setting in settings:
if setting.get('id','').lower() == 'services.webserver' and not setting.get('value'):
enabled = False
DIALOG.notificationDialog(LANGUAGE(32131))
break
if setting.get('id','').lower() == 'services.webserverusername': username = setting.get('value')
elif setting.get('id','').lower() == 'services.webserverport': port = setting.get('value')
elif setting.get('id','').lower() == 'services.webserverpassword': password = setting.get('value')
elif setting.get('id','').lower() == 'services.webserverssl' and setting.get('value'): secure = True
username = '{0}:{1}@'.format(username, password) if username and password else ''
protocol = 'https' if secure else 'http'
if local: ip = 'localhost'
else: ip = SETTINGS.getIP()
webURL = '{0}://{1}{2}:{3}'.format(protocol,username,ip, port)
self.log("buildWebBase; returning %s"%(webURL))
return webURL
def padItems(self, files, page=SETTINGS.getSettingInt('Page_Limit')):
# Balance media limits, by filling with duplicates to meet min. pagination.
self.log("padItems; files In = %s"%(len(files)))
if len(files) < page:
iters = cycle(files)
while not self.service.monitor.abortRequested() and (len(files) < page and len(files) > 0):
item = next(iters).copy()
if self.service.monitor.waitForAbort(0.0001): break
elif self.getDuration(item.get('file'),item) == 0:
try: files.pop(files.index(item))
except: break
else: files.append(item)
self.log("padItems; files Out = %s"%(len(files)))
return files
def inputFriendlyName(self):
friendly = self.getSettingValue("services.devicename")
self.log("inputFriendlyName, name = %s"%(friendly))
if not friendly or friendly.lower() == 'kodi':
with PROPERTIES.interruptActivity():
if DIALOG.okDialog(LANGUAGE(32132)%(friendly)):
friendly = DIALOG.inputDialog(LANGUAGE(30122), friendly)
if not friendly or friendly.lower() == 'kodi':
return self.inputFriendlyName()
else:
self.setSettingValue("services.devicename",friendly,queue=False)
self.log('inputFriendlyName, setting device name = %s'%(friendly))
return friendly
def getCallback(self, sysInfo={}):
self.log('[%s] getCallback, mode = %s, radio = %s, isPlaylist = %s'%(sysInfo.get('chid'),sysInfo.get('mode'),sysInfo.get('radio',False),sysInfo.get('isPlaylist',False)))
def _matchJSON():#requires 'pvr://' json whitelisting
results = self.getDirectory(param={"directory":"pvr://channels/{dir}/".format(dir={'True':'radio','False':'tv'}[str(sysInfo.get('radio',False))])}, cache=False).get('files',[])
for dir in [ADDON_NAME,'All channels']: #todo "All channels" may not work with non-English translations!
for result in results:
if result.get('label','').lower().startswith(dir.lower()):
self.log('getCallback: _matchJSON, found dir = %s'%(result.get('file')))
channels = self.getDirectory(param={"directory":result.get('file')},checksum=PROPERTIES.getInstanceID(),expiration=datetime.timedelta(minutes=FIFTEEN)).get('files',[])
for item in channels:
if item.get('label','').lower() == sysInfo.get('name','').lower() and decodePlot(item.get('plot','')).get('citem',{}).get('id') == sysInfo.get('chid'):
self.log('[%s] getCallback: _matchJSON, found file = %s'%(sysInfo.get('chid'),item.get('file')))
return item.get('file')
if sysInfo.get('mode','').lower() == 'live' and sysInfo.get('chpath'):
callback = sysInfo.get('chpath')
elif sysInfo.get('isPlaylist'):
callback = sysInfo.get('citem',{}).get('url')
elif sysInfo.get('mode','').lower() == 'vod' and sysInfo.get('nitem',{}).get('file'):
callback = sysInfo.get('nitem',{}).get('file')
else:
callback = sysInfo.get('callback','')
if not callback: callback = _matchJSON()
self.log('getCallback: returning callback = %s'%(callback))
return callback# or (('%s%s'%(self.sysARG[0],self.sysARG[2])).split('%s&'%(slugify(ADDON_NAME))))[0])
def matchChannel(self, chname: str, id: str, radio: bool=False, extend=True):
self.log('[%s] matchChannel, chname = %s, radio = %s'%(id,chname,radio))
def __match():
channels = self.getPVRChannels(radio)
for channel in channels:
if channel.get('label','').lower() == chname.lower():
for key in ['broadcastnow', 'broadcastnext']:
if decodePlot(channel.get(key,{}).get('plot','')).get('citem',{}).get('id') == id:
channel['broadcastnext'] = [channel.get('broadcastnext',{})]
self.log('[%s] matchChannel: __match, found pvritem = %s'%(id,channel))
return channel
def __extend(pvritem: dict={}) -> dict:
channelItem = {}
def _parseBroadcast(broadcast={}):
if broadcast.get('progresspercentage',0) == 100:
channelItem.setdefault('broadcastpast',[]).append(broadcast)
elif broadcast.get('progresspercentage',0) > 0 and broadcast.get('progresspercentage',100) < 100:
channelItem['broadcastnow'] = broadcast
elif broadcast.get('progresspercentage',0) == 0 and broadcast.get('progresspercentage',100) < 100:
channelItem.setdefault('broadcastnext',[]).append(broadcast)
broadcasts = self.getPVRBroadcasts(pvritem.get('channelid',{}))
[_parseBroadcast(broadcast) for broadcast in broadcasts]
pvritem['broadcastnext'] = channelItem.get('broadcastnext',pvritem['broadcastnext'])
self.log('matchChannel: __extend, broadcastnext = %s entries'%(len(pvritem['broadcastnext'])))
return pvritem
cacheName = 'matchChannel.%s'%(getMD5('%s.%s.%s.%s'%(chname,id,radio,extend)))
cacheResponse = (self.cache.get(cacheName, checksum=PROPERTIES.getInstanceID(), json_data=True) or {})
if not cacheResponse:
pvrItem = __match()
if pvrItem:
if extend: pvrItem = __extend(pvrItem)
cacheResponse = self.cache.set(cacheName, pvrItem, checksum=PROPERTIES.getInstanceID(), expiration=datetime.timedelta(seconds=FIFTEEN), json_data=True)
else: return {}
return cacheResponse
def getNextItem(self, citem={}, nitem={}): #return next broadcast ignoring fillers
if not nitem: nitem = decodePlot(BUILTIN.getInfoLabel('NextPlot','VideoPlayer'))
nextitems = sorted(self.matchChannel(citem.get('name',''), citem.get('id',''), citem.get('radio',False)).get('broadcastnext',[]), key=itemgetter('starttime'))
for nextitem in nextitems:
if not isFiller(nextitem): return decodePlot(nextitem.get('plot',''))
return nitem
def toggleShowLog(self, state=False):
self.log('toggleShowLog, state = %s'%(state))
if SETTINGS.getSettingBool('Enable_PVR_RELOAD'): #check that users allow alternations to kodi.
opState = not bool(state)
if self.getSettingValue("debug.showloginfo") == opState:
self.setSettingValue("debug.showloginfo",state,queue=False)