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

244 lines
13 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 functools import reduce
from difflib import SequenceMatcher
from seasonal import Seasonal
LOCAL_FOLDERS = [LOGO_LOC, IMAGE_LOC]
MUSIC_RESOURCE = ["resource.images.musicgenreicons.text"]
GENRE_RESOURCE = ["resource.images.moviegenreicons.transparent"]
STUDIO_RESOURCE = ["resource.images.studios.white"]
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 Resources:
queuePool = {}
def __init__(self, service=None):
if service is None: service = Service()
self.service = service
self.jsonRPC = service.jsonRPC
self.cache = service.jsonRPC.cache
self.baseURL = service.jsonRPC.buildWebBase()
self.remoteHost = PROPERTIES.getRemoteHost()
def log(self, msg, level=xbmc.LOGDEBUG):
return log('%s: %s'%(self.__class__.__name__,msg),level)
def getLogo(self, citem: dict, fallback=LOGO, auto=False) -> str:
if citem.get('name') == LANGUAGE(32002): logo = Seasonal().getHoliday().get('logo') #seasonal
else: logo = self.getLocalLogo(citem.get('name')) #local
if not logo: logo = self.getCachedLogo(citem) #cache
if not logo and auto: logo = self.getLogoResources(citem) #resources
if not logo and auto: logo = self.getTVShowLogo(citem.get('name')) #tvshow
if not logo: logo = (fallback or LOGO) #fallback
self.log('getLogo, name = %s, logo = %s, auto = %s'%(citem.get('name'), logo, auto))
return logo
@cacheit(expiration=datetime.timedelta(days=MAX_GUIDEDAYS), json_data=True)
def selectLogo(self, citem: dict) -> list:
logos = []
logos.extend(self.getLocalLogo(citem.get('name'),select=True))
logos.extend(self.getLogoResources(citem, select=True))
logos.extend(self.getTVShowLogo(citem.get('name'), select=True))
self.log('selectLogo, chname = %s, logos = %s'%(citem.get('name'), len(logos)))
return list([_f for _f in logos if _f])
def queueLOGO(self, param):
params = self.queuePool.setdefault('params',[])
params.append(param)
self.queuePool['params'] = setDictLST(params)
self.log("queueLOGO, saving = %s, param = %s"%(len(self.queuePool['params']),param))
timerit(SETTINGS.setCacheSetting)(5.0,['queueLOGO', self.queuePool, ADDON_VERSION, True])
def getCachedLogo(self, citem, select=False):
cacheFuncs = [{'name':'getLogoResources.%s.%s'%(getMD5(citem.get('name')),select), 'args':(citem,select) ,'checksum':getMD5('|'.join([SETTINGS.getAddonDetails(id).get('version',ADDON_VERSION) for id in self.getResources(citem)]))},
{'name':'getTVShowLogo.%s.%s'%(getMD5(citem.get('name')),select) , 'args':(citem.get('name'), select),'checksum':ADDON_VERSION}]
for cacheItem in cacheFuncs:
cacheResponse = self.cache.get(cacheItem.get('name',''),cacheItem.get('checksum',ADDON_VERSION))
if cacheResponse:
self.log('getCachedLogo, chname = %s, type = %s, logo = %s'%(citem.get('name'), citem.get('type'), cacheResponse))
return cacheResponse
else: self.queueLOGO(cacheItem)
def getLocalLogo(self, chname: str, select: bool=False) -> list:
logos = []
for path in LOCAL_FOLDERS:
for ext in IMG_EXTS:
if FileAccess.exists(os.path.join(path,'%s%s'%(chname,ext))):
self.log('getLocalLogo, found %s'%(os.path.join(path,'%s%s'%(chname,ext))))
if select: logos.append(os.path.join(path,'%s%s'%(chname,ext)))
else: return os.path.join(path,'%s%s'%(chname,ext))
if select: return logos
def fillLogoResource(self, id):
results = {}
response = self.jsonRPC.walkListDirectory(os.path.join('special://home/addons/%s/resources'%id), exts=IMG_EXTS, checksum=SETTINGS.getAddonDetails(id).get('version',ADDON_VERSION), expiration=datetime.timedelta(days=28))
for path, images in list(response.items()):
for image in images:
name, ext = os.path.splitext(image)
results[name] = '%s/%s'%(path,image)
return results
def getResources(self, citem={}):
resources = SETTINGS.getSetting('Resource_Logos').split('|').copy()
if citem.get('type') in ["TV Genres","Movie Genres"]: resources.extend(GENRE_RESOURCE)
elif citem.get('type') in ["TV Networks","Movie Studios"]: resources.extend(STUDIO_RESOURCE)
elif citem.get('type') in ["Music Genres","Radio"] or isRadio(citem): resources.extend(MUSIC_RESOURCE)
else: resources.extend(GENRE_RESOURCE + STUDIO_RESOURCE)
self.log('getResources, type = %s, resources = %s'%(citem.get('type'),resources))
return resources
def getLogoResources(self, citem: dict, select: bool=False) -> dict and None:
self.log('getLogoResources, chname = %s, type = %s, select = %s'%(citem.get('name'), citem.get('type'),select))
logos = []
resources = self.getResources(citem)
cacheName = 'getLogoResources.%s.%s'%(getMD5(citem.get('name')),select)
cacheResponse = self.cache.get(cacheName, checksum=getMD5('|'.join([SETTINGS.getAddonDetails(id).get('version',ADDON_VERSION) for id in resources])))
if not cacheResponse:
for id in list(dict.fromkeys(resources)):
if not hasAddon(id):
self.log('getLogoResources, missing %s'%(id))
continue
else:
results = self.fillLogoResource(id)
self.log('getLogoResources, checking %s, results = %s'%(id,len(results)))
for name, logo in list(results.items()):
if self.matchName(citem.get('name'), name, auto=select):
self.log('getLogoResources, found %s'%(logo))
if select: logos.append(logo)
else: return self.cache.set(cacheName, logo, checksum=getMD5('|'.join(resources)), expiration=datetime.timedelta(days=MAX_GUIDEDAYS))
if select: return self.cache.set(cacheName, logos, checksum=getMD5('|'.join(resources)), expiration=datetime.timedelta(days=MAX_GUIDEDAYS))
return cacheResponse
def getTVShowLogo(self, chname: str, select: bool=False) -> dict and None:
self.log('getTVShowLogo, chname = %s, select = %s'%(chname,select))
logos = []
items = self.jsonRPC.getTVshows()
cacheName = 'getTVShowLogo.%s.%s'%(getMD5(chname),select)
cacheResponse = self.cache.get(cacheName)
if not cacheResponse:
for item in items:
if self.matchName(chname, item.get('title',''), auto=select):
keys = ['clearlogo','logo','logos','clearart','icon']
for key in keys:
logo = item.get('art',{}).get(key,'').replace('image://DefaultFolder.png/','').rstrip('/')
if logo:
self.log('getTVShowLogo, found %s'%(logo))
if select: logos.append(logo)
else: return self.cache.set(cacheName, logo, expiration=datetime.timedelta(days=MAX_GUIDEDAYS))
if select: return self.cache.set(cacheName, logos, expiration=datetime.timedelta(days=MAX_GUIDEDAYS))
return cacheResponse
#todo refactor this mess, proper pattern matching...
def matchName(self, chname: str, name: str, type: str='Custom', auto: bool=False) -> bool and None: #todo auto setting SETTINGS.getSettingBool('')
chnames = list(set([chname, splitYear(chname)[0], stripRegion(chname), getChannelSuffix(chname, type), cleanChannelSuffix(chname, type), chname.replace('and', '&'), chname.replace('&','and'), slugify(chname), validString(chname)]))
renames = list(set([name, splitYear(name)[0], stripRegion(name), slugify(name), validString(name)]))
for chname in chnames:
if not chname: continue
elif auto: return SequenceMatcher(None, chname.lower(), name.lower()).ratio() >= .75
elif chname.lower() == name.lower(): return True
for rename in renames:
if not rename: continue
elif chname.lower() == rename.lower(): return True
return False
def buildWebImage(self, image: str) -> str:
#convert any local images to url via local server and/or kodi web server.
if image.startswith(LOGO_LOC) and self.remoteHost:
image = 'http://%s/images/%s'%(self.remoteHost,quoteString(os.path.split(image)[1]))
elif image.startswith(('image://','image%3A')) and self.baseURL and not ('smb' in image or 'nfs' in image or 'http' in image):
image = '%s/image/%s'%(self.baseURL,quoteString(image))
self.log('buildWebImage, returning image = %s'%(image))
return image
def isMono(self, file: str) -> bool:
if file.startswith('resource://') and (bool(set([match in file.lower() for match in ['transparent','white','mono']]))): return True
elif hasAddon('script.module.pil'):
try:
from PIL import Image, ImageStat
file = unquoteString(file.replace('resource://','special://home/addons/').replace('image://','')).replace('\\','/')
mono = reduce(lambda x, y: x and y < 0.005, ImageStat.Stat(Image.open(FileAccess.open(file.encode('utf-8').strip(),'r'),mode='r')).var, True)
self.log('isMono, mono = %s, file = %s'%(mono,file))
return mono
except Exception as e: self.log("isMono, failed! %s\nfile = %s"%(e,file), xbmc.LOGWARNING)
return False
def generate_placeholder(self, text, background_image_path=FileAccess.translatePath(os.path.join(MEDIA_LOC,'blank.png')), output_path=TEMP_LOC, font_path=FileAccess.translatePath(os.path.join('special://skin','fonts','NotoSans-Regular.ttf')), font_size=30, text_color=(255, 255, 255)):
"""
Generates a placeholder image with text on a background image.
Args:
text: The text to display on the placeholder.
background_image_path: Path to the background image.
output_path: Path to save the generated placeholder image.
font_path: Path to the font file (optional).
font_size: Font size for the text (optional).
text_color: Color of the text (optional).
"""
if hasAddon('script.module.pil'):
from PIL import Image, ImageDraw, ImageFont
# Open the background image
background_image = Image.open(background_image_path)
# Create a drawing object
draw = ImageDraw.Draw(background_image)
# Choose a font
font = ImageFont.truetype(font_path, font_size)
# Calculate text size
text_width, text_height = draw.textsize(text, font)
# Calculate text position for centering
x = (background_image.width - text_width) // 2
y = (background_image.height - text_height) // 2
# Draw the text on the image
draw.text((x, y), text, font=font, fill=text_color)
# Save the image
file_name = os.path.join(output_path,'%s.png'%(text))
fle = FileAccess.open(file_name,'wb')
background_image.save(fle,'png')
fle.close()
# Example usage
# generate_placeholder("Product Image", "background.jpg", "placeholder.jpg")