Updated kodi settings on Lenovo
This commit is contained in:
@@ -0,0 +1,509 @@
|
||||
# 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 -*-
|
||||
|
||||
import os, sys, re, json, struct, errno, zlib
|
||||
import shutil, subprocess, io, platform
|
||||
import codecs, random
|
||||
import uuid, base64, binascii, hashlib
|
||||
import time, datetime, calendar
|
||||
import heapq, requests, pyqrcode
|
||||
import xml.sax.saxutils
|
||||
|
||||
from six.moves import urllib
|
||||
from io import StringIO, BytesIO
|
||||
from threading import Lock, Thread, Event, Timer, BoundedSemaphore
|
||||
from threading import enumerate as thread_enumerate
|
||||
from xml.dom.minidom import parse, parseString, Document
|
||||
from xml.etree.ElementTree import ElementTree, Element, SubElement, XMLParser, fromstringlist, fromstring, tostring
|
||||
from xml.etree.ElementTree import parse as ETparse
|
||||
from typing import Dict, List, Union, Optional
|
||||
|
||||
from variables import *
|
||||
from kodi_six import xbmc, xbmcaddon, xbmcplugin, xbmcgui, xbmcvfs
|
||||
from contextlib import contextmanager, closing
|
||||
from socket import gethostbyname, gethostname
|
||||
from itertools import cycle, chain, zip_longest, islice
|
||||
from xml.sax.saxutils import escape, unescape
|
||||
from operator import itemgetter
|
||||
|
||||
from logger import *
|
||||
from cache import Cache, cacheit
|
||||
from pool import killit, timeit, poolit, executeit, timerit, threadit
|
||||
from kodi import *
|
||||
from fileaccess import FileAccess, FileLock
|
||||
from collections import defaultdict, Counter, OrderedDict
|
||||
from six.moves import urllib
|
||||
from math import ceil, floor
|
||||
from infotagger.listitem import ListItemInfoTag
|
||||
from requests.adapters import HTTPAdapter, Retry
|
||||
|
||||
DIALOG = Dialog()
|
||||
PROPERTIES = DIALOG.properties
|
||||
SETTINGS = DIALOG.settings
|
||||
LISTITEMS = DIALOG.listitems
|
||||
BUILTIN = DIALOG.builtin
|
||||
|
||||
def slugify(s, lowercase=False):
|
||||
if lowercase: s = s.lower()
|
||||
s = s.strip()
|
||||
s = re.sub(r'[^\w\s-]', '', s)
|
||||
s = re.sub(r'[\s_-]+', '_', s)
|
||||
s = re.sub(r'^-+|-+$', '', s)
|
||||
return s
|
||||
|
||||
def validString(s):
|
||||
return "".join(x for x in s if (x.isalnum() or x not in '\\/:*?"<>|'))
|
||||
|
||||
def stripNumber(s):
|
||||
return re.sub(r'\d+','',s)
|
||||
|
||||
def stripRegion(s):
|
||||
match = re.compile(r'(.*) \((.*)\)', re.IGNORECASE).search(s)
|
||||
try: return match.group(1)
|
||||
except: return s
|
||||
|
||||
def chanceBool(percent=25):
|
||||
return random.randrange(100) <= percent
|
||||
|
||||
def decodePlot(text: str = '') -> dict:
|
||||
plot = re.search(r'\[COLOR item=\"(.+?)\"]\[/COLOR]', text)
|
||||
if plot: return loadJSON(decodeString(plot.group(1)))
|
||||
return {}
|
||||
|
||||
def encodePlot(plot, text):
|
||||
return '%s [COLOR item="%s"][/COLOR]'%(plot,encodeString(dumpJSON(text)))
|
||||
|
||||
def escapeString(text, table=HTML_ESCAPE):
|
||||
return escape(text,table)
|
||||
|
||||
def unescapeString(text, table=HTML_ESCAPE):
|
||||
return unescape(text,{v:k for k, v in list(table.items())})
|
||||
|
||||
def getJSON(file):
|
||||
data = {}
|
||||
try:
|
||||
fle = FileAccess.open(file,'r')
|
||||
data = loadJSON(fle.read())
|
||||
except Exception as e: log('Globals: getJSON failed! %s\nfile = %s'%(e,file), xbmc.LOGERROR)
|
||||
fle.close()
|
||||
return data
|
||||
|
||||
def setJSON(file, data):
|
||||
with FileLock():
|
||||
fle = FileAccess.open(file, 'w')
|
||||
fle.write(dumpJSON(data, idnt=4, sortkey=False))
|
||||
fle.close()
|
||||
return True
|
||||
|
||||
def requestURL(url, params={}, payload={}, header=HEADER, timeout=FIFTEEN, json_data=False, cache=None, checksum=ADDON_VERSION, life=datetime.timedelta(minutes=15)):
|
||||
def __error(json_data):
|
||||
return {} if json_data else ""
|
||||
|
||||
def __getCache(key,json_data,cache,checksum):
|
||||
return (cache.get('requestURL.%s'%(key), checksum, json_data) or __error(json_data))
|
||||
|
||||
def __setCache(key,results,json_data,cache,checksum,life):
|
||||
return cache.set('requestURL.%s'%(key), results, checksum, life, json_data)
|
||||
|
||||
complete = False
|
||||
cacheKey = '.'.join([url,dumpJSON(params),dumpJSON(payload),dumpJSON(header)])
|
||||
session = requests.Session()
|
||||
retries = Retry(total=3, backoff_factor=1, status_forcelist=[500, 502, 503, 504])
|
||||
adapter = HTTPAdapter(max_retries=retries)
|
||||
session.mount("http://", adapter)
|
||||
session.mount("https://", adapter)
|
||||
try:
|
||||
headers = HEADER.copy()
|
||||
headers.update(header)
|
||||
if payload: response = session.post(url, data=dumpJSON(payload), headers=headers, timeout=timeout)
|
||||
else: response = session.get(url, params=params, headers=headers, timeout=timeout)
|
||||
response.raise_for_status() # Raise an exception for HTTP errors
|
||||
log("Globals: requestURL, url = %s, status = %s"%(url,response.status_code))
|
||||
complete = True
|
||||
|
||||
if json_data: results = response.json()
|
||||
else: results = response.content
|
||||
if results and cache: return __setCache(cacheKey,results,json_data,cache,checksum,life)
|
||||
else: return results
|
||||
|
||||
except requests.exceptions.ConnectionError as e:
|
||||
log("Globals: requestURL, failed! Error connecting to the server: %s"%('Returning cache' if cache else 'No Response'))
|
||||
return __getCache(cacheKey,json_data,cache,checksum) if cache else __error(json_data)
|
||||
|
||||
except requests.exceptions.HTTPError as e:
|
||||
log("Globals: requestURL, failed! HTTP error occurred: %s"%('Returning cache' if cache else 'No Response'))
|
||||
return __getCache(cacheKey,json_data,cache,checksum) if cache else __error(json_data)
|
||||
|
||||
except Exception as e:
|
||||
log("Globals: requestURL, failed! An error occurred: %s"%(e), xbmc.LOGERROR)
|
||||
return __error(json_data)
|
||||
|
||||
finally:
|
||||
if not complete and payload:
|
||||
queueURL({"url":url, "params":params, "payload":payload, "header":header, "timeout":timeout, "json_data":json_data, "cache":cache, "checksum":checksum, "life":life}) #retry post
|
||||
|
||||
def queueURL(param):
|
||||
queuePool = (SETTINGS.getCacheSetting('queueURL', json_data=True) or {})
|
||||
params = queuePool.setdefault('params',[])
|
||||
params.append(param)
|
||||
queuePool['params'] = setDictLST(params)
|
||||
log("Globals: queueURL, saving = %s\n%s"%(len(queuePool['params']),param))
|
||||
SETTINGS.setCacheSetting('queueURL', queuePool, json_data=True)
|
||||
|
||||
def setURL(url, file):
|
||||
try:
|
||||
contents = requestURL(url)
|
||||
fle = FileAccess.open(file, 'w')
|
||||
fle.write(contents)
|
||||
fle.close()
|
||||
return FileAccess.exists(file)
|
||||
except Exception as e: log('Globals: setURL failed! %s\nurl = %s'%(e,url), xbmc.LOGERROR)
|
||||
|
||||
def diffLSTDICT(old, new):
|
||||
set1 = {dumpJSON(d, sortkey=True) for d in old}
|
||||
set2 = {dumpJSON(d, sortkey=True) for d in new}
|
||||
return {"added": [loadJSON(s) for s in set2 - set1], "removed": [loadJSON(s) for s in set1 - set2]}
|
||||
|
||||
def getChannelID(name, path, number):
|
||||
if isinstance(path, list): path = '|'.join(path)
|
||||
tmpid = '%s.%s.%s.%s'%(number, name, hashlib.md5(path.encode(DEFAULT_ENCODING)),SETTINGS.getMYUUID())
|
||||
return '%s@%s'%((binascii.hexlify(tmpid.encode(DEFAULT_ENCODING))[:32]).decode(DEFAULT_ENCODING),slugify(ADDON_NAME))
|
||||
|
||||
def getRecordID(name, path, number):
|
||||
if isinstance(path, list): path = '|'.join(path)
|
||||
tmpid = '%s.%s.%s.%s'%(number, name, hashlib.md5(path.encode(DEFAULT_ENCODING)),SETTINGS.getMYUUID())
|
||||
return '%s@%s'%((binascii.hexlify(tmpid.encode(DEFAULT_ENCODING))[:16]).decode(DEFAULT_ENCODING),slugify(ADDON_NAME))
|
||||
|
||||
def splitYear(label):
|
||||
try:
|
||||
match = re.compile(r'(.*) \((.*)\)', re.IGNORECASE).search(label)
|
||||
if match and match.group(2):
|
||||
label, year = match.groups()
|
||||
if year.isdigit():
|
||||
return label, int(year)
|
||||
except: pass
|
||||
return label, None
|
||||
|
||||
def getChannelSuffix(name, type):
|
||||
name = validString(name)
|
||||
if type == "TV Genres" and not LANGUAGE(32014).lower() in name.lower(): suffix = LANGUAGE(32014) #TV
|
||||
elif type == "Movie Genres" and not LANGUAGE(32015).lower() in name.lower(): suffix = LANGUAGE(32015) #Movies
|
||||
elif type == "Mixed Genres" and not LANGUAGE(32010).lower() in name.lower(): suffix = LANGUAGE(32010) #Mixed
|
||||
elif type == "Music Genres" and not LANGUAGE(32016).lower() in name.lower(): suffix = LANGUAGE(32016) #Music
|
||||
else: return name
|
||||
return '%s %s'%(name,suffix)
|
||||
|
||||
def cleanChannelSuffix(name, type):
|
||||
if type == "TV Genres" : name = name.split(' %s'%LANGUAGE(32014))[0]#TV
|
||||
elif type == "Movie Genres" : name = name.split(' %s'%LANGUAGE(32015))[0]#Movies
|
||||
elif type == "Mixed Genres" : name = name.split(' %s'%LANGUAGE(32010))[0]#Mixed
|
||||
elif type == "Music Genres" : name = name.split(' %s'%LANGUAGE(32016))[0]#Music
|
||||
return name
|
||||
|
||||
def getLabel(item, addYear=False):
|
||||
label = (item.get('name') or item.get('label') or item.get('showtitle') or item.get('title'))
|
||||
if not label: return ''
|
||||
label, year = splitYear(label)
|
||||
year = (item.get('year') or year)
|
||||
if year and addYear: return '%s (%s)'%(label, year)
|
||||
return label
|
||||
|
||||
def hasFile(file):
|
||||
if not file.startswith(tuple(VFS_TYPES + WEB_TYPES)): state = FileAccess.exists(file)
|
||||
elif file.startswith('plugin://'): state = hasAddon(file)
|
||||
else: state = True
|
||||
log("Globals: hasFile, file = %s (%s)"%(file,state))
|
||||
return state
|
||||
|
||||
def hasAddon(id, install=False, enable=False, force=False, notify=False):
|
||||
if '://' in id: id = getIDbyPath(id)
|
||||
if BUILTIN.getInfoBool('HasAddon(%s)'%(id),'System'):
|
||||
if BUILTIN.getInfoBool('AddonIsEnabled(%s)'%(id),'System'): return True
|
||||
elif enable:
|
||||
if not force:
|
||||
if not DIALOG.yesnoDialog(message=LANGUAGE(32156)%(id)): return False
|
||||
return BUILTIN.executebuiltin('EnableAddon(%s)'%(id),wait=True)
|
||||
elif install: return BUILTIN.executebuiltin('InstallAddon(%s)'%(id),wait=True)
|
||||
if notify: DIALOG.notificationDialog(LANGUAGE(32034)%(id))
|
||||
return False
|
||||
|
||||
def diffRuntime(dur, roundto=15):
|
||||
def ceil_dt(dt, delta):
|
||||
return dt + (datetime.datetime.min - dt) % delta
|
||||
now = datetime.datetime.fromtimestamp(dur)
|
||||
return (ceil_dt(now, datetime.timedelta(minutes=roundto)) - now).total_seconds()
|
||||
|
||||
def roundTimeDown(dt, offset=30): # round the given time down to the nearest
|
||||
n = datetime.datetime.fromtimestamp(dt)
|
||||
delta = datetime.timedelta(minutes=offset)
|
||||
if n.minute > (offset-1): n = n.replace(minute=offset, second=0, microsecond=0)
|
||||
else: n = n.replace(minute=0, second=0, microsecond=0)
|
||||
return time.mktime(n.timetuple())
|
||||
|
||||
def roundTimeUp(dt=None, roundTo=60):
|
||||
if dt == None : dt = datetime.datetime.now()
|
||||
seconds = (dt.replace(tzinfo=None) - dt.min).seconds
|
||||
rounding = (seconds+roundTo/2) // roundTo * roundTo
|
||||
return dt + datetime.timedelta(0,rounding-seconds,-dt.microsecond)
|
||||
|
||||
def strpTime(datestring, format=DTJSONFORMAT): #convert pvr infolabel datetime string to datetime obj, thread safe!
|
||||
try: return datetime.datetime.strptime(datestring, format)
|
||||
except TypeError: return datetime.datetime.fromtimestamp(time.mktime(time.strptime(datestring, format)))
|
||||
except: return ''
|
||||
|
||||
def epochTime(timestamp, tz=True): #convert pvr json datetime string to datetime obj
|
||||
if tz: timestamp -= getTimeoffset()
|
||||
return datetime.datetime.fromtimestamp(timestamp)
|
||||
|
||||
def getTimeoffset():
|
||||
return (int((datetime.datetime.now() - datetime.datetime.utcnow()).days * 86400 + round((datetime.datetime.now() - datetime.datetime.utcnow()).seconds, -1)))
|
||||
|
||||
def getUTCstamp():
|
||||
return time.time() - getTimeoffset()
|
||||
|
||||
def getGMTstamp():
|
||||
return time.time()
|
||||
|
||||
def randomShuffle(items=[]):
|
||||
if len(items) > 0:
|
||||
#reseed random for a "greater sudo random"
|
||||
random.seed(random.randint(0,999999999999))
|
||||
random.shuffle(items)
|
||||
return items
|
||||
|
||||
def isStack(path): #is path a stack
|
||||
return path.startswith('stack://')
|
||||
|
||||
def splitStacks(path): #split stack path for indv. files.
|
||||
if not isStack(path): return [path]
|
||||
return [_f for _f in ((path.split('stack://')[1]).split(' , ')) if _f]
|
||||
|
||||
def escapeDirJSON(path):
|
||||
mydir = path
|
||||
if (mydir.find(":")): mydir = mydir.replace("\\", "\\\\")
|
||||
return mydir
|
||||
|
||||
def KODI_LIVETV_SETTINGS(): #recommended Kodi LiveTV settings
|
||||
return {'pvrmanager.preselectplayingchannel' :'true',
|
||||
'pvrmanager.syncchannelgroups' :'true',
|
||||
'pvrmanager.backendchannelorder' :'true',
|
||||
'pvrmanager.usebackendchannelnumbers':'true',
|
||||
'pvrplayback.autoplaynextprogramme' :'true',
|
||||
# 'pvrmenu.iconpath':'',
|
||||
# 'pvrplayback.switchtofullscreenchanneltypes':1,
|
||||
# 'pvrplayback.confirmchannelswitch':'true',
|
||||
# 'epg.selectaction':2,
|
||||
# 'epg.epgupdate':120,
|
||||
'pvrmanager.startgroupchannelnumbersfromone':'false'}
|
||||
|
||||
def togglePVR(state=True, reverse=False, wait=FIFTEEN):
|
||||
if SETTINGS.getSettingBool('Enable_PVR_RELOAD'):
|
||||
isEnabled = BUILTIN.getInfoBool('AddonIsEnabled(%s)'%(PVR_CLIENT_ID),'System')
|
||||
if (state and isEnabled) or (not state and not isEnabled): return
|
||||
elif not PROPERTIES.isRunning('togglePVR'):
|
||||
with PROPERTIES.chkRunning('togglePVR'):
|
||||
BUILTIN.executebuiltin("Dialog.Close(all)")
|
||||
log('globals: togglePVR, state = %s, reverse = %s, wait = %s'%(state,reverse,wait))
|
||||
BUILTIN.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():
|
||||
MONITOR().waitForAbort(1.0)
|
||||
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
|
||||
for path in item.get('path',[item.get('file','')]):
|
||||
if path.lower().startswith(('musicdb://','special://profile/playlists/music/','special://musicplaylists/')): return True
|
||||
return False
|
||||
|
||||
def isMixed_XSP(item):
|
||||
for path in item.get('path',[item.get('file','')]):
|
||||
if path.lower().startswith('special://profile/playlists/mixed/'): return True
|
||||
return False
|
||||
|
||||
def cleanLabel(text):
|
||||
text = re.sub(r'\[COLOR=(.+?)\]', '', text)
|
||||
text = re.sub(r'\[/COLOR\]', '', text)
|
||||
text = text.replace("[B]",'').replace("[/B]",'')
|
||||
text = text.replace("[I]",'').replace("[/I]",'')
|
||||
return text.replace(":",'')
|
||||
|
||||
def cleanImage(image=LOGO):
|
||||
if not image: image = LOGO
|
||||
if not image.startswith(('image://','resource://','special://','smb://','nfs://','https://','http://')):
|
||||
realPath = FileAccess.translatePath('special://home/addons/')
|
||||
if image.startswith(realPath):# convert real path. to vfs
|
||||
image = image.replace(realPath,'special://home/addons/').replace('\\','/')
|
||||
elif image.startswith(realPath.replace('\\','/')):
|
||||
image = image.replace(realPath.replace('\\','/'),'special://home/addons/').replace('\\','/')
|
||||
return image
|
||||
|
||||
def cleanGroups(citem, enableGrouping=SETTINGS.getSettingBool('Enable_Grouping')):
|
||||
if not enableGrouping:
|
||||
citem['group'] = [ADDON_NAME]
|
||||
else:
|
||||
citem['group'].append(ADDON_NAME)
|
||||
if citem.get('favorite',False) and not LANGUAGE(32019) in citem['group']:
|
||||
citem['group'].append(LANGUAGE(32019))
|
||||
elif not citem.get('favorite',False) and LANGUAGE(32019) in citem['group']:
|
||||
citem['group'].remove(LANGUAGE(32019))
|
||||
return sorted(set(citem['group']))
|
||||
|
||||
def cleanMPAA(mpaa):
|
||||
orgMPA = mpaa
|
||||
mpaa = mpaa.lower()
|
||||
if ':' in mpaa: mpaa = re.split(':',mpaa)[1] #todo prop. regex
|
||||
if 'rated ' in mpaa: mpaa = re.split('rated ',mpaa)[1] #todo prop. regex
|
||||
#todo regex, detect other region rating formats
|
||||
# re.compile(':(.*)', re.IGNORECASE).search(text))
|
||||
text = mpaa.upper()
|
||||
try:
|
||||
text = re.sub('/ US', '' , text)
|
||||
text = re.sub('Rated ', '', text)
|
||||
mpaa = text.strip()
|
||||
except:
|
||||
mpaa = mpaa.strip()
|
||||
return mpaa
|
||||
|
||||
def getIDbyPath(url):
|
||||
try:
|
||||
if url.startswith('special://'): return re.compile('special://home/addons/(.*?)/resources', re.IGNORECASE).search(url).group(1)
|
||||
elif url.startswith('plugin://'): return re.compile('plugin://(.*?)/', re.IGNORECASE).search(url).group(1)
|
||||
except Exception as e: log('Globals: getIDbyPath failed! url = %s, %s'%(url,e), xbmc.LOGERROR)
|
||||
return url
|
||||
|
||||
def combineDicts(dict1={}, dict2={}):
|
||||
for k,v in list(dict1.items()):
|
||||
if dict2.get(k): k = dict2.pop(k)
|
||||
dict1.update(dict2)
|
||||
return dict1
|
||||
|
||||
def mergeDictLST(dict1={},dict2={}):
|
||||
for k, v in list(dict2.items()):
|
||||
dict1.setdefault(k,[]).extend(v)
|
||||
setDictLST()
|
||||
return dict1
|
||||
|
||||
def lstSetDictLst(lst=[]):
|
||||
items = dict()
|
||||
for key, dictlst in list(lst.items()):
|
||||
if isinstance(dictlst, list): dictlst = setDictLST(dictlst)
|
||||
items[key] = dictlst
|
||||
return items
|
||||
|
||||
def compareDict(dict1,dict2,sortKey):
|
||||
a = sorted(dict1, key=itemgetter(sortKey))
|
||||
b = sorted(dict2, key=itemgetter(sortKey))
|
||||
return a == b
|
||||
|
||||
def subZoom(number,percentage,multi=100):
|
||||
return round(number * (percentage*multi) / 100)
|
||||
|
||||
def addZoom(number,percentage,multi=100):
|
||||
return round((number - (number * (percentage*multi) / 100)) + number)
|
||||
|
||||
def frange(start,stop,inc):
|
||||
return [x/10.0 for x in range(start,stop,inc)]
|
||||
|
||||
def timeString2Seconds(string): #hh:mm:ss
|
||||
try: return int(sum(x*y for x, y in zip(list(map(float, string.split(':')[::-1])), (1, 60, 3600, 86400))))
|
||||
except: return -1
|
||||
|
||||
def chunkLst(lst, n):
|
||||
for i in range(0, len(lst), n):
|
||||
yield lst[i:i + n]
|
||||
|
||||
def chunkDict(items, n):
|
||||
it = iter(items)
|
||||
for i in range(0, len(items), n):
|
||||
yield {k:items[k] for k in islice(it, n)}
|
||||
|
||||
def roundupDIV(p, q):
|
||||
try:
|
||||
d, r = divmod(p, q)
|
||||
if r: d += 1
|
||||
return d
|
||||
except ZeroDivisionError:
|
||||
return 1
|
||||
|
||||
def interleave(seqs, sets=1, repeats=False):
|
||||
#evenly interleave multi-lists of different sizes, while preserving seq order and by sets of x
|
||||
# In [[1,2,3,4],['a','b','c'],['A','B','C','D','E']]
|
||||
# repeats = False
|
||||
# Out sets=0 [1, 2, 3, 4, 'a', 'b', 'c', 'A', 'B', 'C', 'D', 'E']
|
||||
# Out sets=1 [1, 'a', 'A', 2, 'b', 'B', 3, 'c', 'C', 4, 'D', 'E']
|
||||
# Out sets=2 [1, 2, 'a', 'b', 'A', 'B', 3, 4, 'c', 'C', 'D', 'E']
|
||||
# repeats = True
|
||||
# Out sets=0 [1, 2, 3, 4, 'a', 'b', 'c', 'A', 'B', 'C', 'D', 'E']
|
||||
# Out sets=1 [1, 'a', 'A', 2, 'b', 'B', 3, 'c', 'C', 4, 'a', 'D', 1, 'b', 'E']
|
||||
# Out sets=2 [1, 2, 'a', 'b', 'A', 'B', 3, 4, 'c', 'a', 'C', 'D', 1, 2, 'b', 'c', 'E', 'A']
|
||||
if sets > 0:
|
||||
# if repeats:
|
||||
# # Create cyclical iterators for each list
|
||||
# cyclical_iterators = [cycle(lst) for lst in seqs]
|
||||
# interleaved = []
|
||||
# # Determine the length of the longest list
|
||||
# max_len = max(len(lst) for lst in seqs)
|
||||
# # Calculate the number of blocks needed
|
||||
# num_blocks = (max_len + sets - 1) // sets
|
||||
# # Interleave in blocks
|
||||
# for i in range(num_blocks):
|
||||
# for iterator in cyclical_iterators:
|
||||
# # Use islice to take a block of elements from the current iterator
|
||||
# block = list(islice(iterator, sets))
|
||||
# interleaved.extend(block)
|
||||
# return interleaved
|
||||
# else:
|
||||
seqs = [list(zip_longest(*[iter(seqs)] * sets, fillvalue=None)) for seqs in seqs]
|
||||
return list([_f for _f in sum([_f for _f in chain.from_iterable(zip_longest(*seqs)) if _f], ()) if _f])
|
||||
else: return list(chain.from_iterable(seqs))
|
||||
|
||||
def percentDiff(org, new):
|
||||
try: return (abs(float(org) - float(new)) / float(new)) * 100.0
|
||||
except ZeroDivisionError: return -1
|
||||
|
||||
def pagination(list, end):
|
||||
for start in range(0, len(list), end):
|
||||
yield seq[start:start+end]
|
||||
|
||||
def isCenterlized():
|
||||
default = 'special://profile/addon_data/plugin.video.pseudotv.live/cache'
|
||||
if REAL_SETTINGS.getSetting('User_Folder') == default:
|
||||
return False
|
||||
return True
|
||||
|
||||
def isFiller(item={}):
|
||||
for genre in item.get('genre',[]):
|
||||
if genre.lower() in ['pre-roll','post-roll']: return True
|
||||
return False
|
||||
|
||||
def isShort(item={}, minDuration=SETTINGS.getSettingInt('Seek_Tolerance')):
|
||||
if item.get('duration', minDuration) < minDuration: return True
|
||||
else: return False
|
||||
|
||||
def isEnding(progress=100):
|
||||
if progress >= SETTINGS.getSettingInt('Seek_Threshold'): return True
|
||||
else: return False
|
||||
|
||||
def chkLogo(old, new=LOGO):
|
||||
if new.endswith('wlogo.png') and not old.endswith('wlogo.png'): return old
|
||||
return new
|
||||
Reference in New Issue
Block a user