Files
DevOps/Kodi/Lenovo/addons/plugin.video.pseudotv.live/resources/lib/library.py

508 lines
25 KiB
Python

# 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 predefined import Predefined
from resources import Resources
from channels import Channels
#constants
REG_KEY = 'PseudoTV_Recommended.%s'
class Service:
from jsonrpc import JSONRPC
player = PLAYER()
monitor = MONITOR()
jsonRPC = JSONRPC()
def _interrupt(self) -> bool:
return PROPERTIES.isPendingInterrupt()
def _suspend(self) -> bool:
return PROPERTIES.isPendingSuspend()
class Library:
def __init__(self, service=None):
if service is None: service = Service()
self.service = service
self.jsonRPC = service.jsonRPC
self.cache = service.jsonRPC.cache
self.predefined = Predefined()
self.channels = Channels()
self.resources = Resources(service=self.service)
self.pCount = 0
self.pDialog = None
self.pMSG = ''
self.pHeader = ''
self.libraryDATA = getJSON(LIBRARYFLE_DEFAULT)
self.libraryTEMP = self.libraryDATA['library'].pop('Item')
self.libraryDATA.update(self._load())
def log(self, msg, level=xbmc.LOGDEBUG):
return log('%s: %s'%(self.__class__.__name__,msg),level)
def _load(self, file=LIBRARYFLEPATH):
return getJSON(file)
def _save(self, file=LIBRARYFLEPATH):
self.libraryDATA['uuid'] = SETTINGS.getMYUUID()
return setJSON(file, self.libraryDATA)
def getLibrary(self, type=None):
self.log('getLibrary, type = %s'%(type))
if type is None: return self.libraryDATA.get('library',{})
else: return self.libraryDATA.get('library',{}).get(type,[])
def enableByName(self, type, names=[]):
self.log('enableByName, type = %s, names = %s'%(type, names))
items = self.getLibrary(type)
for name in names:
for item in items:
if name.lower() == item.get('name','').lower(): item['enabled'] = True
else: item['enabled'] = False
return self.setLibrary(type, items)
def setLibrary(self, type, items=[]):
self.log('setLibrary, type = %s, items = %s'%(type,len(items)))
self.libraryDATA['library'][type] = items
enabled = self.getEnabled(type, items)
PROPERTIES.setEXTPropertyBool('%s.has.%s'%(ADDON_ID,slugify(type)),len(items) > 0)
PROPERTIES.setEXTPropertyBool('%s.has.%s.enabled'%(ADDON_ID,slugify(type)),len(enabled) > 0)
SETTINGS.setSetting('Select_%s'%(slugify(type)),'[COLOR=orange][B]%s[/COLOR][/B]/[COLOR=dimgray]%s[/COLOR]'%(len(enabled),len(items)))
return self._save()
def getEnabled(self, type, items=None):
if items is None: items = self.getLibrary(type)
return [item for item in items if item.get('enabled',False)]
def updateLibrary(self, force: bool=False) -> bool:
def __funcs():
return {
"Playlists" :{'func':self.getPlaylists ,'life':datetime.timedelta(minutes=FIFTEEN)},
"TV Networks" :{'func':self.getNetworks ,'life':datetime.timedelta(days=MAX_GUIDEDAYS)},
"TV Shows" :{'func':self.getTVShows ,'life':datetime.timedelta(hours=MAX_GUIDEDAYS)},
"TV Genres" :{'func':self.getTVGenres ,'life':datetime.timedelta(days=MAX_GUIDEDAYS)},
"Movie Genres" :{'func':self.getMovieGenres ,'life':datetime.timedelta(days=MAX_GUIDEDAYS)},
"Movie Studios":{'func':self.getMovieStudios,'life':datetime.timedelta(days=MAX_GUIDEDAYS)},
"Mixed Genres" :{'func':self.getMixedGenres ,'life':datetime.timedelta(days=MAX_GUIDEDAYS)},
"Mixed" :{'func':self.getMixed ,'life':datetime.timedelta(minutes=FIFTEEN)},
"Recommended" :{'func':self.getRecommend ,'life':datetime.timedelta(hours=MAX_GUIDEDAYS)},
"Services" :{'func':self.getServices ,'life':datetime.timedelta(hours=MAX_GUIDEDAYS)},
"Music Genres" :{'func':self.getMusicGenres ,'life':datetime.timedelta(days=MAX_GUIDEDAYS)}
}
def __fill(type, func):
try: items = func()
except Exception as e:
self.log("__fill, %s failed! %s"%(type,e), xbmc.LOGERROR)
items = []
self.log('__fill, returning %s (%s)'%(type,len(items)))
return items
def __update(type, items, existing=[]):
if not existing: existing = self.channels.getType(type)
self.log('__update, type = %s, items = %s, existing = %s'%(type,len(items),len(existing)))
for item in items:
if not item.get('enabled',False):
for eitem in existing:
if getChannelSuffix(item.get('name'), type).lower() == eitem.get('name','').lower():
if eitem['logo'] not in [LOGO,COLOR_LOGO] and item['logo'] in [LOGO,COLOR_LOGO]: item['logo'] = eitem['logo']
item['enabled'] = True
break
item['logo'] = self.resources.getLogo(item,item.get('logo',LOGO)) #update logo
entry = self.libraryTEMP.copy()
entry.update(item)
yield entry
if force: #clear library cache.
with BUILTIN.busy_dialog():
for label, params in list(__funcs().items()):
DIALOG.notificationDialog(LANGUAGE(30070)%(label),time=5)
self.cache.clear("%s.%s"%(self.__class__.__name__,params['func'].__name__),wait=5)
complete = True
types = list(__funcs().keys())
for idx, type in enumerate(types):
self.pMSG = type
self.pCount = int(idx*100//len(types))
self.pHeader = '%s, %s %s'%(ADDON_NAME,LANGUAGE(32028),LANGUAGE(32041))
self.pDialog = DIALOG.progressBGDialog(header=self.pHeader)
if (self.service._interrupt() or self.service._suspend()) and PROPERTIES.hasFirstRun():
self.log("updateLibrary, _interrupt")
complete = False
self.pDialog = DIALOG.progressBGDialog(self.pCount, self.pDialog, '%s: %s'%(LANGUAGE(32144),LANGUAGE(32213)), self.pHeader)
break
self.pDialog = DIALOG.progressBGDialog(self.pCount, self.pDialog, self.pMSG, self.pHeader)
cacheResponse = self.cache.get("%s.%s"%(self.__class__.__name__,__funcs()[type]['func'].__name__))
if not cacheResponse:
self.pHeader = '%s, %s %s'%(ADDON_NAME,LANGUAGE(32022),LANGUAGE(32041))
cacheResponse = self.cache.set("%s.%s"%(self.__class__.__name__,__funcs()[type]['func'].__name__), __fill(type, __funcs()[type]['func']), expiration=__funcs()[type]['life'])
if complete:
self.setLibrary(type, list(__update(type,cacheResponse,self.getEnabled(type))))
self.log("updateLibrary, type = %s, saved items = %s"%(type,len(cacheResponse)))
self.pDialog = DIALOG.progressBGDialog(100, self.pDialog, header='%s, %s %s'%(ADDON_NAME,LANGUAGE(32041),LANGUAGE(32025)))
self.log('updateLibrary, force = %s, complete = %s'%(force, complete))
return complete
def resetLibrary(self, ATtypes=AUTOTUNE_TYPES):
self.log('resetLibrary')
for ATtype in ATtypes:
items = self.getLibrary(ATtype)
for item in items:
item['enabled'] = False #disable everything before selecting new items.
self.setLibrary(ATtype, items)
def updateProgress(self, percent, message, header):
if self.pDialog: self.pDialog = DIALOG.progressBGDialog(percent, self.pDialog, message=message, header=header)
def getNetworks(self):
return self.getTVInfo().get('studios',[])
def getTVGenres(self):
return self.getTVInfo().get('genres',[])
def getTVShows(self):
return self.getTVInfo().get('shows',[])
def getMovieStudios(self):
return self.getMovieInfo().get('studios',[])
def getMovieGenres(self):
return self.getMovieInfo().get('genres',[])
def getMusicGenres(self):
return self.getMusicInfo().get('genres',[])
def getMixedGenres(self):
MixedGenreList = []
tvGenres = self.getTVGenres()
movieGenres = self.getMovieGenres()
for tv in [tv for tv in tvGenres for movie in movieGenres if tv.get('name','').lower() == movie.get('name','').lower()]:
MixedGenreList.append({'name':tv.get('name'),'type':"Mixed Genres",'path':self.predefined.createGenreMixedPlaylist(tv.get('name')),'logo':tv.get('logo'),'rules':{"800":{"values":{"0":tv.get('name')}}}})
self.log('getMixedGenres, genres = %s' % (len(MixedGenreList)))
return sorted(MixedGenreList,key=itemgetter('name'))
def getMixed(self):
MixedList = []
MixedList.append({'name':LANGUAGE(32001), 'type':"Mixed",'path':self.predefined.createMixedRecent() ,'logo':self.resources.getLogo({'name':LANGUAGE(32001),'type':"Mixed"})}) #"Recently Added"
MixedList.append({'name':LANGUAGE(32002), 'type':"Mixed",'path':self.predefined.createSeasonal() ,'logo':self.resources.getLogo({'name':LANGUAGE(32002),'type':"Mixed"}),'rules':{"800":{"values":{"0":LANGUAGE(32002)}}}}) #"Seasonal"
MixedList.extend(self.getPVRRecordings())#"PVR Recordings"
MixedList.extend(self.getPVRSearches()) #"PVR Searches"
self.log('getMixed, mixed = %s' % (len(MixedList)))
return sorted(MixedList,key=itemgetter('name'))
def getPVRRecordings(self):
recordList = []
json_response = self.jsonRPC.getPVRRecordings()
paths = [item.get('file') for idx, item in enumerate(json_response) if item.get('label','').endswith('(%s)'%(ADDON_NAME))]
if len(paths) > 0: recordList.append({'name':LANGUAGE(32003),'type':"Mixed",'path':[paths],'logo':self.resources.getLogo({'name':LANGUAGE(32003),'type':"Mixed"})})
self.log('getPVRRecordings, recordings = %s' % (len(recordList)))
return sorted(recordList,key=itemgetter('name'))
def getPVRSearches(self):
searchList = []
json_response = self.jsonRPC.getPVRSearches()
for idx, item in enumerate(json_response):
if not item.get('file'): continue
searchList.append({'name':"%s (%s)"%(item.get('label',LANGUAGE(32241)),LANGUAGE(32241)),'type':"Mixed",'path':[item.get('file')],'logo':self.resources.getLogo({'name':item.get('label',LANGUAGE(32241)),'type':"Mixed"})})
self.log('getPVRSearches, searches = %s' % (len(searchList)))
return sorted(searchList,key=itemgetter('name'))
def getPlaylists(self):
PlayList = []
for type in ['video','mixed','music']:
self.updateProgress(self.pCount,'%s: %s'%(self.pMSG,LANGUAGE(32140)),self.pHeader)
results = self.jsonRPC.getSmartPlaylists(type)
for idx, result in enumerate(results):
self.updateProgress(self.pCount,'%s (%s): %s%%'%(self.pMSG,type.title(),int((idx)*100//len(results))),self.pHeader)
if not result.get('label'): continue
logo = result.get('thumbnail')
if not logo: logo = self.resources.getLogo({'name':result.get('label',''),'type':"Custom"})
PlayList.append({'name':result.get('label'),'type':"%s Playlist"%(type.title()),'path':[result.get('file')],'logo':logo})
self.log('getPlaylists, PlayList = %s' % (len(PlayList)))
PlayList = sorted(PlayList,key=itemgetter('name'))
PlayList = sorted(PlayList,key=itemgetter('type'))
return PlayList
@cacheit()
def getTVInfo(self, sortbycount=True):
self.log('getTVInfo')
if BUILTIN.hasTV():
NetworkList = Counter()
ShowGenreList = Counter()
TVShows = Counter()
self.updateProgress(self.pCount,'%s: %s'%(self.pMSG,LANGUAGE(32140)),self.pHeader)
json_response = self.jsonRPC.getTVshows()
for idx, info in enumerate(json_response):
self.updateProgress(self.pCount,'%s: %s%%'%(self.pMSG,int((idx)*100//len(json_response))),self.pHeader)
if not info.get('label'): continue
TVShows.update({json.dumps({'name': info.get('label'), 'type':"TV Shows", 'path': self.predefined.createShowPlaylist(info.get('label')), 'logo': info.get('art', {}).get('clearlogo', ''),'rules':{"800":{"values":{"0":info.get('label')}}}}): info.get('episode', 0)})
NetworkList.update([studio for studio in info.get('studio', [])])
ShowGenreList.update([genre for genre in info.get('genre', [])])
if sortbycount:
TVShows = [json.loads(x[0]) for x in sorted(TVShows.most_common(250))]
NetworkList = [x[0] for x in sorted(NetworkList.most_common(50))]
ShowGenreList = [x[0] for x in sorted(ShowGenreList.most_common(25))]
else:
TVShows = (sorted(map(json.loads, list(TVShows.keys())), key=itemgetter('name')))
del TVShows[250:]
NetworkList = (sorted(set(list(NetworkList.keys()))))
del NetworkList[250:]
ShowGenreList = (sorted(set(list(ShowGenreList.keys()))))
#search resources for studio/genre logos
nNetworkList = []
for idx, network in enumerate(NetworkList):
self.updateProgress(self.pCount,'%s: %s%%'%(self.pMSG,int((idx)*100//len(NetworkList))),self.pHeader)
nNetworkList.append({'name':network, 'type':"TV Networks", 'path': self.predefined.createNetworkPlaylist(network),'logo':self.resources.getLogo({'name':network,'type':"TV Networks"}),'rules':{"800":{"values":{"0":network}}}})
NetworkList = nNetworkList
nShowGenreList = []
for idx, tvgenre in enumerate(ShowGenreList):
self.updateProgress(self.pCount,'%s: %s%%'%(self.pMSG,int((idx)*100//len(ShowGenreList))),self.pHeader)
nShowGenreList.append({'name':tvgenre, 'type':"TV Genres" , 'path': self.predefined.createTVGenrePlaylist(tvgenre),'logo':self.resources.getLogo({'name':tvgenre,'type':"TV Genres"}),'rules':{"800":{"values":{"0":tvgenre}}}})
ShowGenreList = nShowGenreList
else: NetworkList = ShowGenreList = TVShows = []
self.log('getTVInfo, networks = %s, genres = %s, shows = %s' % (len(NetworkList), len(ShowGenreList), len(TVShows)))
return {'studios':NetworkList,'genres':ShowGenreList,'shows':TVShows}
@cacheit()
def getMovieInfo(self, sortbycount=True):
self.log('getMovieInfo')
if BUILTIN.hasMovie():
StudioList = Counter()
MovieGenreList = Counter()
self.updateProgress(self.pCount,'%s: %s'%(self.pMSG,LANGUAGE(32140)),self.pHeader)
json_response = self.jsonRPC.getMovies() #we can't parse for genres directly from Kodi json ie.getGenres; because we need the weight of each genre to prioritize list.
for idx, info in enumerate(json_response):
StudioList.update([studio for studio in info.get('studio', [])])
MovieGenreList.update([genre for genre in info.get('genre', [])])
if sortbycount:
StudioList = [x[0] for x in sorted(StudioList.most_common(25))]
MovieGenreList = [x[0] for x in sorted(MovieGenreList.most_common(25))]
else:
StudioList = (sorted(set(list(StudioList.keys()))))
del StudioList[250:]
MovieGenreList = (sorted(set(list(MovieGenreList.keys()))))
#search resources for studio/genre logos
nStudioList = []
for idx, studio in enumerate(StudioList):
self.updateProgress(self.pCount,'%s: %s%%'%(self.pMSG,int((idx)*100//len(StudioList))),self.pHeader)
nStudioList.append({'name':studio, 'type':"Movie Studios", 'path': self.predefined.createStudioPlaylist(studio) ,'logo':self.resources.getLogo({'name':studio,'type':"Movie Studios"}),'rules':{"800":{"values":{"0":studio}}}})
StudioList = nStudioList
nMovieGenreList = []
for idx, genre in enumerate(MovieGenreList):
self.updateProgress(self.pCount,'%s: %s%%'%(self.pMSG,int((idx)*100//len(MovieGenreList))),self.pHeader)
nMovieGenreList.append({'name':genre, 'type':"Movie Genres" , 'path': self.predefined.createMovieGenrePlaylist(genre) ,'logo':self.resources.getLogo({'name':genre,'type':"Movie Genres"}) ,'rules':{"800":{"values":{"0":genre}}}})
MovieGenreList = nMovieGenreList
else: StudioList = MovieGenreList = []
self.log('getMovieInfo, studios = %s, genres = %s' % (len(StudioList), len(MovieGenreList)))
return {'studios':StudioList,'genres':MovieGenreList}
@cacheit()
def getMusicInfo(self, sortbycount=True):
self.log('getMusicInfo')
if BUILTIN.hasMusic():
MusicGenreList = Counter()
self.updateProgress(self.pCount,'%s: %s'%(self.pMSG,LANGUAGE(32140)),self.pHeader)
json_response = self.jsonRPC.getMusicGenres()
for idx, info in enumerate(json_response):
MusicGenreList.update([genre.strip() for genre in info.get('label','').split(';')])
if sortbycount:
MusicGenreList = [x[0] for x in sorted(MusicGenreList.most_common(50))]
else:
MusicGenreList = (sorted(set(list(MusicGenreList.keys()))))
del MusicGenreList[250:]
MusicGenreList = (sorted(set(list(MusicGenreList.keys()))))
#search resources for studio/genre logos
nMusicGenreList = []
for idx, genre in enumerate(MusicGenreList):
self.updateProgress(self.pCount,'%s: %s%%'%(self.pMSG,int((idx)*100//len(MusicGenreList))),self.pHeader)
nMusicGenreList.append({'name':genre, 'type':"Music Genres", 'path': self.predefined.createMusicGenrePlaylist(genre),'logo':self.resources.getLogo({'name':genre,'type':"Music Genres"})})
MusicGenreList = nMusicGenreList
else: MusicGenreList = []
self.log('getMusicInfo, found genres = %s' % (len(MusicGenreList)))
return {'genres':MusicGenreList}
def getRecommend(self):
self.log('getRecommend')
PluginList = []
WhiteList = self.getWhiteList()
AddonsList = self.searchRecommended()
for addonid, item in list(AddonsList.items()):
if addonid not in WhiteList: continue
items = item.get('data',{}).get('vod',[])
items.extend(item.get('data',{}).get('live',[]))
for vod in items:
path = vod.get('path')
if not isinstance(path,list): path = [path]
PluginList.append({'id':item['meta'].get('name'), 'name':vod.get('name'), 'type':"Recommended", 'path': path, 'logo':vod.get('icon',item['meta'].get('thumbnail'))})
self.log('getRecommend, found (%s) vod items.' % (len(PluginList)))
PluginList = sorted(PluginList,key=itemgetter('name'))
PluginList = sorted(PluginList,key=itemgetter('id'))
return PluginList
def getRecommendInfo(self, addonid):
self.log('getRecommendInfo, addonid = %s'%(addonid))
return self.searchRecommended().get(addonid,{})
def searchRecommended(self):
return {} #todo
# def _search(addonid):
# cacheName = 'searchRecommended.%s'%(getMD5(addonid))
# addonMeta = SETTINGS.getAddonDetails(addonid)
# payload = PROPERTIES.getEXTProperty(REG_KEY%(addonid))
# if not payload: #startup services may not be broadcasting beacon; use last cached beacon instead.
# payload = self.cache.get(cacheName, checksum=addonMeta.get('version',ADDON_VERSION), json_data=True)
# else:
# payload = loadJSON(payload)
# self.cache.set(cacheName, payload, checksum=addonMeta.get('version',ADDON_VERSION), expiration=datetime.timedelta(days=MAX_GUIDEDAYS), json_data=True)
# if payload:
# self.log('searchRecommended, found addonid = %s, payload = %s'%(addonid,payload))
# return addonid,{"data":payload,"meta":addonMeta}
# addonList = sorted(list(set([_f for _f in [addon.get('addonid') for addon in list([k for k in self.jsonRPC.getAddons() if k.get('addonid','') not in self.getBlackList()])] if _f])))
# return dict([_f for _f in [_search(addonid) for addonid in addonList] if _f])
def getServices(self):
self.log('getServices')
return []
def getWhiteList(self):
#whitelist - prompt shown, added to import list and/or manager dropdown.
return self.libraryDATA.get('whitelist',[])
def setWhiteList(self, data=[]):
self.libraryDATA['whitelist'] = sorted(set(data))
return self._save()
def getBlackList(self):
#blacklist - plugin ignored for the life of the list.
return self.libraryDATA.get('blacklist',[])
def setBlackList(self, data=[]):
self.libraryDATA['blacklist'] = sorted(set(data))
return self._save()
def addWhiteList(self, addonid):
self.log('addWhiteList, addonid = %s'%(addonid))
whiteList = self.getWhiteList()
whiteList.append(addonid)
whiteList = sorted(set(whiteList))
if len(whiteList) > 0: PROPERTIES.setEXTPropertyBool('%s.has.WhiteList'%(ADDON_ID),len(whiteList) > 0)
return self.setWhiteList(whiteList)
def addBlackList(self, addonid):
self.log('addBlackList, addonid = %s'%(addonid))
blackList = self.getBlackList()
blackList.append(addonid)
blackList = sorted(set(blackList))
return self.setBlackList(blackList)
def clearBlackList(self):
return self.setBlackList()
def importPrompt(self):
addonList = self.searchRecommended()
ignoreList = self.getWhiteList()
ignoreList.extend(self.getBlackList()) #filter addons previously parsed.
addonNames = sorted(list(set([_f for _f in [item.get('meta',{}).get('name') for addonid, item in list(addonList.items()) if not addonid in ignoreList] if _f])))
self.log('importPrompt, addonNames = %s'%(len(addonNames)))
try:
if len(addonNames) > 1:
retval = DIALOG.yesnoDialog('%s'%(LANGUAGE(32055)%(ADDON_NAME,', '.join(addonNames))), customlabel=LANGUAGE(32056))
self.log('importPrompt, prompt retval = %s'%(retval))
if retval == 1: raise Exception('Single Entry')
elif retval == 2:
for addonid, item in list(addonList.items()):
if item.get('meta',{}).get('name') in addonNames:
self.addWhiteList(addonid)
else: raise Exception('Single Entry')
except Exception as e:
self.log('importPrompt, %s'%(e))
for addonid, item in list(addonList.items()):
if item.get('meta',{}).get('name') in addonNames:
if not DIALOG.yesnoDialog('%s'%(LANGUAGE(32055)%(ADDON_NAME,item['meta'].get('name','')))):
self.addBlackList(addonid)
else:
self.addWhiteList(addonid)
PROPERTIES.setEXTPropertyBool('%s.has.WhiteList'%(ADDON_ID),len(self.getWhiteList()) > 0)
PROPERTIES.setEXTPropertyBool('%s.has.BlackList'%(ADDON_ID),len(self.getBlackList()) > 0)
SETTINGS.setSetting('Clear_BlackList','|'.join(self.getBlackList()))