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

209 lines
12 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 resources import Resources
#Ratings - resource only, Movie Type only any channel type
#Bumpers - plugin, path only, tv type, tv network, custom channel type
#Adverts - plugin, path only, tv type, any tv channel type
#Trailers - plug, path only, movie type, any movie channel.
class Fillers:
def __init__(self, builder, citem={}):
self.builder = builder
self.bctTypes = builder.bctTypes
self.runActions = builder.runActions
self.jsonRPC = builder.jsonRPC
self.cache = builder.jsonRPC.cache
self.citem = citem
self.resources = Resources(service=builder.service)
self.log('[%s] __init__, bctTypes = %s'%(self.citem.get('id'),builder.bctTypes))
self.fillSources(citem, builder.bctTypes)
def log(self, msg, level=xbmc.LOGDEBUG):
return log('%s: %s'%(self.__class__.__name__,msg),level)
def fillSources(self, citem={}, bctTypes={}):
for ftype, values in list(bctTypes.items()):
if values.get('enabled',False):
self.builder.updateProgress(self.builder.pCount,message='%s %s'%(LANGUAGE(30014),ftype.title()),header='%s, %s'%(ADDON_NAME,self.builder.pMSG))
if values.get('incKODI',False): values["items"] = mergeDictLST(values.get('items',{}), self.builder.getTrailers())
for id in values["sources"].get("ids",[]):
values['items'] = mergeDictLST(values.get('items',{}),self.buildSource(ftype,id)) #parse resource packs
for path in values["sources"].get("paths",[]):
values['items'] = mergeDictLST(values.get('items',{}),self.buildSource(ftype,path)) #parse vfs paths
values['items'] = lstSetDictLst(values['items'])
self.log('[%s] fillSources, type = %s, items = %s'%(self.citem.get('id'),ftype,len(values['items'])))
@cacheit(expiration=datetime.timedelta(minutes=30),json_data=False)
def buildSource(self, ftype, path=''):
self.log('[%s] buildSource, type = %s, path = %s'%(self.citem.get('id'),ftype, path))
def _parseResource(id):
if hasAddon(id, install=True): return self.jsonRPC.walkListDirectory(os.path.join('special://home/addons/%s'%id,'resources'),exts=VIDEO_EXTS,depth=CHANNEL_LIMIT,checksum=SETTINGS.getAddonDetails(id).get('version',ADDON_VERSION),expiration=datetime.timedelta(days=MAX_GUIDEDAYS))
def _parseVFS(path):
if path.startswith('plugin://'):
if not hasAddon(path, install=True): return {}
return self.jsonRPC.walkFileDirectory(path, depth=CHANNEL_LIMIT, chkDuration=True, retItem=True)
def _parseLocal(path):
if FileAccess.exists(path): return self.jsonRPC.walkListDirectory(path,exts=VIDEO_EXTS,depth=CHANNEL_LIMIT,chkDuration=True)
def __sortItems(data, stype='folder'):
tmpDCT = {}
if data:
for path, files in list(data.items()):
if stype == 'file': key = file.split('.')[0].lower()
elif stype == 'folder': key = (os.path.basename(os.path.normpath(path)).replace('\\','/').strip('/').split('/')[-1:][0]).lower()
for file in files:
if isinstance(file,dict): [tmpDCT.setdefault(key.lower(),[]).append(file) for key in (file.get('genre',[]) or ['resources'])]
else:
dur = self.jsonRPC.getDuration(os.path.join(path,file), accurate=True)
if dur > 0: tmpDCT.setdefault(key.lower(),[]).append({'file':os.path.join(path,file),'duration':dur,'label':'%s - %s'%(path.strip('/').split('/')[-1:][0],file.split('.')[0])})
self.log('[%s] buildSource, __sortItems: stype = %s, items = %s'%(self.citem.get('id'),stype,len(tmpDCT)))
return tmpDCT
try:
if path.startswith('resource.'): return __sortItems(_parseResource(path))
elif path.startswith(tuple(VFS_TYPES+DB_TYPES)): return __sortItems(_parseVFS(path))
else: return __sortItems(_parseLocal(path))
except Exception as e: self.log("[%s] buildSource, failed! %s\n path = %s"%(self.citem.get('id'),e,path), xbmc.LOGERROR)
return {}
def convertMPAA(self, ompaa):
try:
ompaa = ompaa.upper()
mpaa = re.compile(":(.*?)/", re.IGNORECASE).search(ompaa).group(1).strip()
except: return ompaa
mpaa = mpaa.replace('TV-Y','G').replace('TV-Y7','G').replace('TV-G','G').replace('NA','NR').replace('TV-PG','PG').replace('TV-14','PG-13').replace('TV-MA','R')
return mpaa
#todo always add a bumper for pseudo/kodi (based on build ver.)
# resource.videos.bumpers.kodi
# resource.videos.bumpers.pseudotv
def getSingle(self, type, keys=['resources'], chance=False):
items = [random.choice(tmpLST) for key in keys if (tmpLST := self.bctTypes.get(type, {}).get('items', {}).get(key.lower(), []))]
if not items and chance:
items.extend(self.getSingle(type))
self.log('[%s] getSingle, type = %s, keys = %s, chance = %s, returning = %s' % (self.citem.get('id'),type, keys, chance, len(items)))
return setDictLST(items)
def getMulti(self, type, keys=['resources'], count=1, chance=False):
items = []
tmpLST = []
for key in keys:
tmpLST.extend(self.bctTypes.get(type, {}).get('items', {}).get(key.lower(), []))
if len(tmpLST) >= count:
items = random.sample(tmpLST, count)
elif tmpLST:
items = setDictLST(random.choices(tmpLST, k=count))
if len(items) < count and chance:
items.extend(self.getMulti(type, count=(count - len(items))))
self.log('[%s] getMulti, type = %s, keys = %s, count = %s, chance = %s, returning = %s' % (self.citem.get('id'),type, keys, count, chance, len(items)))
return setDictLST(items)
def injectBCTs(self, fileList):
nfileList = []
for idx, fileItem in enumerate(fileList):
if not fileItem: continue
else:
runtime = fileItem.get('duration',0)
if runtime == 0: continue
chtype = self.citem.get('type','')
chname = self.citem.get('name','')
fitem = fileItem.copy()
dbtype = fileItem.get('type','')
fmpaa = (self.convertMPAA(fileItem.get('mpaa')) or 'NR')
fcodec = (fileItem.get('streamdetails',{}).get('audio') or [{}])[0].get('codec','')
fgenre = (fileItem.get('genre') or self.citem.get('group') or '')
if isinstance(fgenre,list) and len(fgenre) > 0: fgenre = fgenre[0]
#pre roll - bumpers/ratings
if dbtype.startswith(tuple(MOVIE_TYPES)):
ftype = 'ratings'
preKeys = [fmpaa, fcodec]
elif dbtype.startswith(tuple(TV_TYPES)):
ftype = 'bumpers'
preKeys = [chname, fgenre]
else:
ftype = None
if ftype:
preFileList = []
if self.bctTypes[ftype].get('enabled',False) and chtype not in IGNORE_CHTYPE:
preFileList.extend(self.getSingle(ftype, preKeys, chanceBool(self.bctTypes[ftype].get('chance',0))))
for i, item in enumerate(setDictLST(preFileList)):
if (item.get('duration') or 0) > 0:
runtime += item.get('duration')
self.log('[%s] injectBCTs, adding pre-roll %s - %s'%(self.citem.get('id'),item.get('duration'),item.get('file')))
self.builder.updateProgress(self.builder.pCount,message='Filling Pre-Rolls %s%%'%(int(i*100//len(preFileList))),header='%s, %s'%(ADDON_NAME,self.builder.pMSG))
item.update({'title':'Pre-Roll','episodetitle':item.get('label'),'genre':['Pre-Roll'],'plot':item.get('plot',item.get('file')),'path':item.get('file')})
nfileList.append(self.builder.buildCells(self.citem,item.get('duration'),entries=1,info=item)[0])
# original media
nfileList.append(fileItem)
self.log('[%s] injectBCTs, adding media %s - %s'%(self.citem.get('id'),fileItem.get('duration'),fileItem.get('file')))
# post roll - adverts/trailers
postFileList = []
for ftype in ['adverts','trailers']:
postIgnoreTypes = {'adverts':IGNORE_CHTYPE + MOVIE_CHTYPE,'trailers':IGNORE_CHTYPE}[ftype]
postFillRuntime = diffRuntime(runtime) if self.bctTypes[ftype]['auto'] else MIN_EPG_DURATION
if self.bctTypes[ftype].get('enabled',False) and chtype not in postIgnoreTypes:
postFileList.extend(self.getMulti(ftype, [chname, fgenre], self.bctTypes[ftype]['max'] if self.bctTypes[ftype]['auto'] else self.bctTypes[ftype]['min'], chanceBool(self.bctTypes[ftype].get('chance',0))))
postAuto = (self.bctTypes['adverts']['auto'] | self.bctTypes['trailers']['auto'])
postCounter = 0
if len(postFileList) > 0:
i = 0
postFileList = randomShuffle(postFileList)
self.log('[%s] injectBCTs, post-roll current runtime %s, available runtime %s, available content %s'%(self.citem.get('id'),runtime, postFillRuntime,len(postFileList)))
while not self.builder.service.monitor.abortRequested() and postFillRuntime > 0 and len(postFileList) > 0:
if self.builder.service.monitor.waitForAbort(0.0001): break
else:
i += 1
item = postFileList.pop(0)
if (item.get('duration') or 0) == 0: continue
elif postAuto and postCounter >= len(postFileList):
self.log('[%s] injectBCTs, unused post roll runtime %s %s/%s'%(self.citem.get('id'),postFillRuntime,postCounter,len(postFileList)))
break
elif postFillRuntime >= item.get('duration'):
postFillRuntime -= item.get('duration')
self.log('[%s] injectBCTs, adding post-roll %s - %s'%(self.citem.get('id'),item.get('duration'),item.get('file')))
self.builder.updateProgress(self.builder.pCount,message='Filling Post-Rolls %s%%'%(int(i*100//len(postFileList))),header='%s, %s'%(ADDON_NAME,self.builder.pMSG))
item.update({'title':'Post-Roll','episodetitle':item.get('label'),'genre':['Post-Roll'],'plot':item.get('plot',item.get('file')),'path':item.get('file')})
nfileList.append(self.builder.buildCells(self.citem,item.get('duration'),entries=1,info=item)[0])
elif postFillRuntime < item.get('duration'):
postFileList.append(item)
postCounter += 1
self.log('[%s] injectBCTs, finished'%(self.citem.get('id')))
return nfileList