From 9bd8764805723ab4af46f9a96277d51eaf43d110 Mon Sep 17 00:00:00 2001 From: Alhaziel01 Date: Fri, 6 Nov 2020 19:41:28 +0100 Subject: [PATCH] Nuovo Autorenumber + Migliorie TVDB + Migliorie TMDB --- channels/animealtadefinizione.py | 2 +- channels/animeuniverse.py | 6 +- channels/animeworld.py | 4 +- channels/paramount.py | 39 +- core/scraper.py | 78 +- core/support.py | 26 +- core/tmdb.py | 3 + core/tvdb.py | 196 ++- core/videolibrarytools.py | 29 +- platformcode/autorenumber.py | 1213 +++++++++-------- platformcode/platformtools.py | 109 ++ platformcode/xbmc_info_window.py | 46 +- .../resource.language.en_gb/strings.po | 38 + .../resource.language.it_it/strings.po | 37 + resources/skins/Default/720p/InfoWindow.xml | 18 +- resources/skins/Default/720p/Renumber.xml | 631 +++++++++ .../skins/Default/720p/TitleOrIDWindow.xml | 108 ++ resources/skins/Default/media/add.png | Bin 0 -> 2822 bytes resources/skins/Default/media/close.png | Bin 1311 -> 2750 bytes resources/skins/Default/media/delete.png | Bin 0 -> 2860 bytes resources/skins/Default/media/down.png | Bin 0 -> 2764 bytes resources/skins/Default/media/exit.png | Bin 0 -> 1228 bytes resources/skins/Default/media/left.png | Bin 0 -> 2764 bytes resources/skins/Default/media/manual.png | Bin 0 -> 2888 bytes resources/skins/Default/media/ok.png | Bin 0 -> 2829 bytes resources/skins/Default/media/pause.png | Bin 0 -> 2690 bytes resources/skins/Default/media/play.png | Bin 0 -> 2834 bytes resources/skins/Default/media/right.png | Bin 0 -> 2758 bytes resources/skins/Default/media/specials.png | Bin 0 -> 3007 bytes resources/skins/Default/media/stop.png | Bin 0 -> 2670 bytes resources/skins/Default/media/up.png | Bin 0 -> 2765 bytes resources/skins/Default/media/updn.png | Bin 0 -> 684 bytes 32 files changed, 1800 insertions(+), 783 deletions(-) create mode 100644 resources/skins/Default/720p/Renumber.xml create mode 100644 resources/skins/Default/720p/TitleOrIDWindow.xml create mode 100644 resources/skins/Default/media/add.png create mode 100644 resources/skins/Default/media/delete.png create mode 100644 resources/skins/Default/media/down.png create mode 100644 resources/skins/Default/media/exit.png create mode 100644 resources/skins/Default/media/left.png create mode 100644 resources/skins/Default/media/manual.png create mode 100644 resources/skins/Default/media/ok.png create mode 100644 resources/skins/Default/media/pause.png create mode 100644 resources/skins/Default/media/play.png create mode 100644 resources/skins/Default/media/right.png create mode 100644 resources/skins/Default/media/specials.png create mode 100644 resources/skins/Default/media/stop.png create mode 100644 resources/skins/Default/media/up.png create mode 100644 resources/skins/Default/media/updn.png diff --git a/channels/animealtadefinizione.py b/channels/animealtadefinizione.py index f78a9abe..0a64bdc6 100644 --- a/channels/animealtadefinizione.py +++ b/channels/animealtadefinizione.py @@ -85,7 +85,7 @@ def peliculas(item): typeContentDict = {'movie':['movie']} typeActionDict = {'findvideos':['movie']} - def ItemItemlistHook(item, itemlist): + def itemlistHook(itemlist): if item.search: itemlist = [ it for it in itemlist if ' Episodio ' not in it.title ] if len(itemlist) == int(perpage): diff --git a/channels/animeuniverse.py b/channels/animeuniverse.py index 40c34dcf..e872ec01 100644 --- a/channels/animeuniverse.py +++ b/channels/animeuniverse.py @@ -84,12 +84,12 @@ def peliculas(item): if not item.pag: item.pag = 1 anime=True - blacklist=['Altri Hentai'] + # blacklist=['Altri Hentai'] data = support.match(host + '/wp-content/themes/animeuniverse/functions/ajax.php', post='sorter=recent&location=&loop=main+loop&action=sort&numarticles='+perpage+'&paginated='+str(item.pag)+'¤tquery%5B'+query+'%5D='+searchtext+'&thumbnail=1').data.replace('\\','') patron=r']+>\s*(?P[A-Za-z0-9]+)\s*<span.[^>]+>(?P<other>.*?)</ul>' - def ItemItemlistHook(item, itemlist): + def itemlistHook(itemlist): itemlist.insert(0, item.clone(title=support.typo('Tutti','bold'), action='peliculas')) itemlist.append(item.clone(title=support.typo('Cerca...','bold'), action='search', search=True, thumbnail=support.thumb('search.png'))) return itemlist @@ -174,7 +174,7 @@ def peliculas(item): @support.scrape def episodios(item): anime=True - pagination = 50 + pagination = 25 # data = get_data(item) patronBlock= r'<div class="server\s*active\s*"(?P<block>.*?)(?:<div class="server|<link)' patron = r'<li[^>]*>\s*<a.*?href="(?P<url>[^"]+)"[^>]*>(?P<episode>[^<]+)<' diff --git a/channels/paramount.py b/channels/paramount.py index 6a91cf5d..024618cf 100644 --- a/channels/paramount.py +++ b/channels/paramount.py @@ -73,27 +73,28 @@ def peliculas(item): pagination = pagination_values[support.config.get_setting('pagination','paramount')] item.url = host + '/api/search?activeTab=' + Type + '&searchFilter=site&pageNumber=0&rowsPerPage=10000' data = jsontools.load(support.match(item).data)['response']['items'] - + titles = [] for it in data: title = it['meta']['header']['title'] - support.info(title, it) - d = it['meta']['date'].split('/') if it['meta']['date'] else ['0000','00','00'] - date = int(d[2] + d[1] + d[0]) - if item.search.lower() in title.lower() \ - and 'stagione' not in it['url'] \ - and 'season' not in it['url'] \ - and title not in ['Serie TV']: - itemlist.append( - item.clone(title=support.typo(title,'bold'), - action=action, - fulltitle=title, - show=title, - contentTitle=title if it['type'] == 'movie' else '', - contentSerieName=title if it['type'] != 'movie' else '', - plot= it['meta']['description'] if 'description' in it['meta'] else '', - url=host + it['url'], - date=date, - thumbnail='https:' + it['media']['image']['url'] if 'url' in it['media']['image'] else item.thumbnail)) + if title not in titles: + titles.append(title) + d = it['meta']['date'].split('/') if it['meta']['date'] else ['0000','00','00'] + date = int(d[2] + d[1] + d[0]) + if item.search.lower() in title.lower() \ + and 'stagione' not in it['url'] \ + and 'season' not in it['url'] \ + and title not in ['Serie TV']: + itemlist.append( + item.clone(title=support.typo(title,'bold'), + action=action, + fulltitle=title, + show=title, + contentTitle=title if it['type'] == 'movie' else '', + contentSerieName=title if it['type'] != 'movie' else '', + plot= it['meta']['description'] if 'description' in it['meta'] else '', + url=host + it['url'], + date=date, + thumbnail='https:' + it['media']['image']['url'] if 'url' in it['media']['image'] else item.thumbnail)) itemlist.sort(key=lambda item: item.fulltitle) if not item.search: itlist = [] diff --git a/core/scraper.py b/core/scraper.py index 2515a77d..81050a9a 100644 --- a/core/scraper.py +++ b/core/scraper.py @@ -54,70 +54,20 @@ def find_and_set_infoLabels(item): import traceback logger.error(traceback.format_exc()) - while scraper: - # We call the find_and_set_infoLabels function of the selected scraper - scraper_result = scraper.find_and_set_infoLabels(item) - - # Check if there is a 'code' - if scraper_result and item.infoLabels['code']: - # correct code - logger.info("Identificador encontrado: %s" % item.infoLabels['code']) - scraper.completar_codigos(item) - return True - elif scraper_result: - # Content found but no 'code' - msg = config.get_localized_string(60227) % title - else: - # Content not found - msg = config.get_localized_string(60228) % title - - logger.info(msg) - # Show box with other options: - if scrapers_disponibles[scraper_actual] in list_opciones_cuadro: - list_opciones_cuadro.remove(scrapers_disponibles[scraper_actual]) - index = platformtools.dialog_select(msg, list_opciones_cuadro) - - if index < 0: - logger.debug("You have clicked 'cancel' in the window '%s'" % msg) - return False - - elif index == 0: - # Ask the title - title = platformtools.dialog_input(title, config.get_localized_string(60229) % tipo_contenido) - if title: - if item.contentType == "movie": - item.contentTitle = title - else: - item.contentSerieName = title - else: - logger.debug("I clicked 'cancel' in the window 'Enter the correct name'") - return False - - elif index == 1: - # You have to create a dialog box to enter the data - logger.info("Complete information") - if cuadro_completar(item): - # correct code - logger.info("Identifier found: %s" % str(item.infoLabels['code'])) - return True - # raise - - elif list_opciones_cuadro[index] in list(scrapers_disponibles.values()): - # Get the name of the scraper module - for k, v in list(scrapers_disponibles.items()): - if list_opciones_cuadro[index] == v: - if scrapers_disponibles[scraper_actual] not in list_opciones_cuadro: - list_opciones_cuadro.append(scrapers_disponibles[scraper_actual]) - # We import the scraper k - scraper_actual = k - try: - scraper = None - scraper = __import__('core.%s' % scraper_actual, fromlist=["core.%s" % scraper_actual]) - except ImportError: - exec("import core." + scraper_actual + " as scraper_module") - break - - logger.error("Error importing the scraper module %s" % scraper_actual) + # while scraper: + # We call the find_and_set_infoLabels function of the selected scraper + scraper_result = scraper.find_and_set_infoLabels(item) + # from core.support import dbg; dbg() + # Check if there is a 'code' + if scraper_result and item.infoLabels['code']: + # correct code + logger.info("Identifier found: %s " % item.infoLabels['code']) + scraper.completar_codigos(item) + return True + else: + # Content not found + logger.info(logger.info("Identifier not found for: %s " % title)) + return def cuadro_completar(item): diff --git a/core/support.py b/core/support.py index 84e95746..e610c11f 100755 --- a/core/support.py +++ b/core/support.py @@ -351,7 +351,7 @@ def scrapeBlock(item, args, block, patron, headers, action, pagination, debug, t quality=quality, url=scraped["url"], infoLabels=infolabels, - thumbnail=item.thumbnail if not scraped["thumb"] else scraped["thumb"], + thumbnail=item.prevthumb if item.prevthumb else item.thumbnail if not scraped["thumb"] else scraped["thumb"], args=item.args, contentSerieName= title if 'movie' not in [contentType] and function != 'episodios' else item.contentSerieName, contentTitle= title if 'movie' in [contentType] and function == 'peliculas' else item.contentTitle, @@ -473,9 +473,6 @@ def scrape(func): if 'itemlistHook' in args: itemlist = args['itemlistHook'](itemlist) - if 'ItemItemlistHook' in args: - itemlist = args['ItemItemlistHook'](item, itemlist) - # if url may be changed and channel has findhost to update if 'findhost' in func.__globals__ and not itemlist: info('running findhost ' + func.__module__) @@ -511,20 +508,23 @@ def scrape(func): url=item.url, args=item.args, page=pag + 1, - thumbnail=thumb())) + thumbnail=thumb(), + prevthumb=item.prevthumb if item.prevthumb else item.thumbnail)) - if anime: + if anime and inspect.stack()[1][3] not in ['find_episodes']: from platformcode import autorenumber - if function == 'episodios' or item.action == 'episodios': autorenumber.renumber(itemlist, item, 'bold') - else: autorenumber.renumber(itemlist) + if (function == 'episodios' or item.action == 'episodios'): autorenumber.start(itemlist, item) + else: autorenumber.start(itemlist) # if anime and autorenumber.check(item) == False and len(itemlist)>0 and not scrapertools.find_single_match(itemlist[0].title, r'(\d+.\d+)'): # pass # else: - if addVideolibrary and (item.infoLabels["title"] or item.fulltitle): - # item.fulltitle = item.infoLabels["title"] - videolibrary(itemlist, item, function=function) - if function == 'episodios' or function == 'findvideos': - download(itemlist, item, function=function) + if inspect.stack()[1][3] not in ['find_episodes']: + if addVideolibrary and (item.infoLabels["title"] or item.fulltitle): + # item.fulltitle = item.infoLabels["title"] + videolibrary(itemlist, item, function=function) + if function == 'episodios' or function == 'findvideos': + download(itemlist, item, function=function) + if 'patronMenu' in args and itemlist: itemlist = thumb(itemlist, genre=True) diff --git a/core/tmdb.py b/core/tmdb.py index b17ff3f2..b31a6e28 100644 --- a/core/tmdb.py +++ b/core/tmdb.py @@ -528,6 +528,9 @@ def find_and_set_infoLabels(item): if len(results) > 1: from platformcode import platformtools tmdb_result = platformtools.show_video_info(results, item=item, caption= tipo_contenido % title) + if not tmdb_result: + res = platformtools.dialog_info(item, 'tmdb') + if res: return find_and_set_infoLabels(res) elif len(results) > 0: tmdb_result = results[0] diff --git a/core/tvdb.py b/core/tvdb.py index 674125a8..cb435137 100644 --- a/core/tvdb.py +++ b/core/tvdb.py @@ -89,7 +89,7 @@ def find_and_set_infoLabels(item): title = item.contentSerieName # If the title includes the (year) we will remove it - year = scrapertools.find_single_match(title, "^.+?\s*(\(\d{4}\))$") + year = scrapertools.find_single_match(title, r"^.+?\s*(\(\d{4}\))$") if year: title = title.replace(year, "").strip() item.infoLabels['year'] = year[1:-1] @@ -114,6 +114,9 @@ def find_and_set_infoLabels(item): if len(results) > 1: tvdb_result = platformtools.show_video_info(results, item=item, scraper=Tvdb, caption=config.get_localized_string(60298) % title) + if not tvdb_result: + res = platformtools.dialog_info(item, 'tvdb') + if res: return find_and_set_infoLabels(res) elif len(results) > 0: tvdb_result = results[0] @@ -398,19 +401,13 @@ class Tvdb(object): else: params = jsontools.dump(params) try: - req = urllib.request.Request(url, data=params, headers=DEFAULT_HEADERS) - response = urllib.request.urlopen(req) - html = response.read() - response.close() + dict_html = requests.post(url, data=params, headers=DEFAULT_HEADERS).json() except Exception as ex: message = "An exception of type %s occured. Arguments:\n%s" % (type(ex).__name__, repr(ex.args)) logger.error("error: %s" % message) else: - dict_html = jsontools.load(html) - # logger.debug("dict_html %s" % dict_html) - if "token" in dict_html: token = dict_html["token"] DEFAULT_HEADERS["Authorization"] = "Bearer " + token @@ -424,17 +421,14 @@ class Tvdb(object): is_success = False url = HOST + "/refresh_token" - try: - req = urllib.request.Request(url, headers=DEFAULT_HEADERS) - response = urllib.request.urlopen(req) - html = response.read() - response.close() + req = requests.get(url, headers=DEFAULT_HEADERS) - except urllib.error.HTTPError as err: - logger.error("err.code %s" % err.code) + + except req as err: + logger.error("err.code %s" % err.status_code) # if there is error 401 it is that the token has passed the time and we have to call login again - if err.code == 401: + if err.status_code == 401: cls.__login() else: raise @@ -444,13 +438,15 @@ class Tvdb(object): logger.error("error: %s" % message) else: - dict_html = jsontools.load(html) + dict_html = req.json() # logger.error("tokencito %s" % dict_html) if "token" in dict_html: token = dict_html["token"] DEFAULT_HEADERS["Authorization"] = "Bearer " + token TOKEN = config.set_setting("tvdb_token", token) is_success = True + else: + cls.__login() return is_success @@ -531,18 +527,16 @@ class Tvdb(object): DEFAULT_HEADERS["Accept-Language"] = lang logger.debug("url: %s, \nheaders: %s" % (url, DEFAULT_HEADERS)) - req = urllib.request.Request(url, headers=DEFAULT_HEADERS) - response = urllib.request.urlopen(req) - html = response.read() - response.close() + req = requests.get(url, headers=DEFAULT_HEADERS) except Exception as ex: message = "An exception of type %s occured. Arguments:\n%s" % (type(ex).__name__, repr(ex.args)) logger.error("error: %s" % message) else: - dict_html = jsontools.load(html) - + dict_html = req.json() + if 'Error' in dict_html: + logger.debug("code %s " % dict_html['Error']) if "data" in dict_html and "id" in dict_html["data"][0]: self.get_episode_by_id(dict_html["data"][0]["id"], lang) return dict_html["data"] @@ -590,25 +584,12 @@ class Tvdb(object): """ logger.info() - try: - url = HOST + "/series/%s/episodes?page=%s" % (_id, page) - logger.debug("url: %s, \nheaders: %s" % (url, DEFAULT_HEADERS)) - req = urllib.request.Request(url, headers=DEFAULT_HEADERS) - response = urllib.request.urlopen(req) - html = response.read() - response.close() - - except Exception as ex: - message = "An exception of type %s occured. Arguments:\n%s" % (type(ex).__name__, repr(ex.args)) - logger.error("error: %s" % message) - - else: - self.list_episodes[page] = jsontools.load(html) - - # logger.info("dict_html %s" % self.list_episodes) - - return self.list_episodes[page] + url = HOST + "/series/%s/episodes?page=%s" % (_id, page) + logger.debug("url: %s, \nheaders: %s" % (url, DEFAULT_HEADERS)) + js = requests.get(url, headers=DEFAULT_HEADERS).json() + self.list_episodes[page] = js if 'Error' not in js else {} + return self.list_episodes[page] def get_episode_by_id(self, _id, lang=DEFAULT_LANG, semaforo=None): """ @@ -681,24 +662,17 @@ class Tvdb(object): try: DEFAULT_HEADERS["Accept-Language"] = lang logger.debug("url: %s, \nheaders: %s" % (url, DEFAULT_HEADERS)) - req = urllib.request.Request(url, headers=DEFAULT_HEADERS) - response = urllib.request.urlopen(req) - html = response.read() - response.close() + dict_html = requests.get(url, headers=DEFAULT_HEADERS).json except Exception as ex: # if isinstance(ex, urllib).HTTPError: - logger.debug("code %s " % ex.code) - + logger.debug("code %s " % ex) message = "An exception of type %s occured. Arguments:\n%s" % (type(ex).__name__, repr(ex.args)) logger.error("error en: %s" % message) else: - dict_html = jsontools.load(html) - dict_html = dict_html.pop("data") - - logger.info("dict_html %s" % dict_html) - self.episodes[_id] = dict_html + # logger.info("dict_html %s" % dict_html) + self.episodes[_id] = dict_html.pop("data") if 'Error' not in dict_html else {} if semaforo: semaforo.release() @@ -730,37 +704,28 @@ class Tvdb(object): """ logger.info() - try: + params = {} + if name: + params["name"] = name + elif imdb_id: + params["imdbId"] = imdb_id + elif zap2it_id: + params["zap2itId"] = zap2it_id - params = {} - if name: - params["name"] = name - elif imdb_id: - params["imdbId"] = imdb_id - elif zap2it_id: - params["zap2itId"] = zap2it_id + params = urllib.parse.urlencode(params) - params = urllib.parse.urlencode(params) + DEFAULT_HEADERS["Accept-Language"] = lang + url = HOST + "/search/series?%s" % params + logger.debug("url: %s, \nheaders: %s" % (url, DEFAULT_HEADERS)) - DEFAULT_HEADERS["Accept-Language"] = lang - url = HOST + "/search/series?%s" % params - logger.debug("url: %s, \nheaders: %s" % (url, DEFAULT_HEADERS)) + dict_html = requests.get(url, headers=DEFAULT_HEADERS).json() - req = urllib.request.Request(url, headers=DEFAULT_HEADERS) - response = urllib.request.urlopen(req) - html = response.read() - logger.info(html) - response.close() - except Exception as ex: + if 'Error' in dict_html: # if isinstance(ex, urllib.parse).HTTPError: - logger.debug("code %s " % ex.code) - - message = "An exception of type %s occured. Arguments:\n%s" % (type(ex).__name__, repr(ex.args)) - logger.error("error: %s" % message) + logger.debug("code %s " % dict_html['Error']) else: - dict_html = jsontools.load(html) if "errors" in dict_html and "invalidLanguage" in dict_html["errors"]: # no hay información en idioma por defecto @@ -834,13 +799,9 @@ class Tvdb(object): try: DEFAULT_HEADERS["Accept-Language"] = lang - req = urllib.request.Request(url, headers=DEFAULT_HEADERS) + req = requests.get(url, headers=DEFAULT_HEADERS) logger.debug("url: %s, \nheaders: %s" % (url, DEFAULT_HEADERS)) - response = urllib.request.urlopen(req) - html = response.read() - response.close() - except Exception as ex: # if isinstance(ex, urllib).HTTPError: logger.debug("code %s " % ex) @@ -849,26 +810,24 @@ class Tvdb(object): logger.error("error: %s" % message) else: - dict_html = jsontools.load(html) - + dict_html = req.json() if "errors" in dict_html and "invalidLanguage" in dict_html["errors"]: return {} - else: - resultado1 = dict_html["data"] - if not resultado1 and from_get_list: - return self.__get_by_id(_id, "en") + resultado1 = dict_html["data"] + if not resultado1 and from_get_list: + return self.__get_by_id(_id, "en") - logger.debug("Result %s" % dict_html) - resultado2 = {"image_poster": [{'keyType': 'poster', 'fileName': 'posters/%s-1.jpg' % _id}]} - resultado3 = {"image_fanart": [{'keyType': 'fanart', 'fileName': 'fanart/original/%s-1.jpg' % _id}]} + logger.debug("Result %s" % dict_html) + resultado2 = {"image_poster": [{'keyType': 'poster', 'fileName': 'posters/%s-1.jpg' % _id}]} + resultado3 = {"image_fanart": [{'keyType': 'fanart', 'fileName': 'fanart/original/%s-1.jpg' % _id}]} - resultado = resultado1.copy() - resultado.update(resultado2) - resultado.update(resultado3) + resultado = resultado1.copy() + resultado.update(resultado2) + resultado.update(resultado3) - logger.debug("total result %s" % resultado) - self.list_results = [resultado] - self.result = resultado + logger.debug("total result %s" % resultado) + self.list_results = [resultado] + self.result = resultado return resultado @@ -909,24 +868,26 @@ class Tvdb(object): url = HOST + "/series/%s/images/query?%s" % (_id, params) logger.debug("url: %s, \nheaders: %s" % (url, DEFAULT_HEADERS)) - req = urllib.request.Request(url, headers=DEFAULT_HEADERS) - response = urllib.request.urlopen(req) - html = response.read() - response.close() + res = requests.get(url, headers=DEFAULT_HEADERS) except Exception as ex: + # if isinstance(ex, urllib).HTTPError: + logger.debug("code %s " % ex) + message = "An exception of type %s occured. Arguments:\n%s" % (type(ex).__name__, repr(ex.args)) logger.error("error: %s" % message) - return {} else: - dict_html = jsontools.load(html) + dict_html = res.json() + if 'Error' in dict_html: + # if isinstance(ex, urllib.parse).HTTPError: + logger.debug("code %s " % dict_html['Error']) + else: + dict_html["image_" + image] = dict_html.pop("data") + self.result.update(dict_html) - dict_html["image_" + image] = dict_html.pop("data") - self.result.update(dict_html) - - return dict_html + return dict_html def get_tvshow_cast(self, _id, lang=DEFAULT_LANG): """ @@ -943,15 +904,18 @@ class Tvdb(object): url = HOST + "/series/%s/actors" % _id DEFAULT_HEADERS["Accept-Language"] = lang logger.debug("url: %s, \nheaders: %s" % (url, DEFAULT_HEADERS)) - - req = urllib.request.Request(url, headers=DEFAULT_HEADERS) - response = urllib.request.urlopen(req) - html = response.read() - response.close() - - dict_html = jsontools.load(html) - - dict_html["cast"] = dict_html.pop("data") + try: + req = requests.get(url, headers=DEFAULT_HEADERS) + except Exception as ex: + logger.debug("code %s " % ex) + message = "An exception of type %s occured. Arguments:\n%s" % (type(ex).__name__, repr(ex.args)) + logger.error("error en: %s" % message) + else: + dict_html = req.json() + if 'Error' in dict_html: + logger.debug("code %s " % dict_html['Error']) + else: + dict_html["cast"] = dict_html.pop("data") self.result.update(dict_html) def get_id(self): @@ -1034,12 +998,12 @@ class Tvdb(object): if 'data' in thumbs: ret_infoLabels['thumbnail'] = HOST_IMAGE + thumbs['data'][0]['fileName'] elif 'poster' in origen and origen['poster']: - ret_infoLabels['thumbnail'] = origen['poster'] + ret_infoLabels['thumbnail'] = HOST_IMAGE + origen['poster'] fanarts = requests.get(HOST + '/series/' + str(origen['id']) + '/images/query?keyType=fanart').json() if 'data' in fanarts: ret_infoLabels['fanart'] = HOST_IMAGE + fanarts['data'][0]['fileName'] elif 'fanart' in origen and origen['fanart']: - ret_infoLabels['thumbnail'] = origen['fanart'] + ret_infoLabels['fanart'] = HOST_IMAGE + origen['fanart'] if 'overview' in origen and origen['overview']: ret_infoLabels['plot'] = origen['overview'] if 'duration' in origen and origen['duration']: diff --git a/core/videolibrarytools.py b/core/videolibrarytools.py index 9eb647a1..0e195c51 100644 --- a/core/videolibrarytools.py +++ b/core/videolibrarytools.py @@ -1000,17 +1000,17 @@ def add_movie(item): item = generictools.update_title(item) # We call the method that updates the title with tmdb.find_and_set_infoLabels #if item.tmdb_stat: # del item.tmdb_stat # We clean the status so that it is not recorded in the Video Library + if item: + new_item = item.clone(action="findvideos") + insertados, sobreescritos, fallidos, path = save_movie(new_item) - new_item = item.clone(action="findvideos") - insertados, sobreescritos, fallidos, path = save_movie(new_item) - - if fallidos == 0: - platformtools.dialog_ok(config.get_localized_string(30131), - config.get_localized_string(30135) % new_item.contentTitle) # 'has been added to the video library' - else: - filetools.rmdirtree(path) - platformtools.dialog_ok(config.get_localized_string(30131), - config.get_localized_string(60066) % new_item.contentTitle) # "ERROR, the movie has NOT been added to the video library") + if fallidos == 0: + platformtools.dialog_ok(config.get_localized_string(30131), + config.get_localized_string(30135) % new_item.contentTitle) # 'has been added to the video library' + else: + filetools.rmdirtree(path) + platformtools.dialog_ok(config.get_localized_string(30131), + config.get_localized_string(60066) % new_item.contentTitle) # "ERROR, the movie has NOT been added to the video library") def add_tvshow(item, channel=None): @@ -1071,16 +1071,19 @@ def add_tvshow(item, channel=None): # del item.tmdb_stat # We clean the status so that it is not recorded in the Video Library # Get the episode list + # from core.support import dbg;dbg() itemlist = getattr(channel, item.action)(item) if itemlist and not scrapertools.find_single_match(itemlist[0].title, r'(\d+x\d+)'): - from platformcode.autorenumber import select_type, renumber, check + from platformcode.autorenumber import start, check if not check(item): action = item.action - select_type(item) + item.renumber = True + start(item) + item.renumber = False item.action = action return add_tvshow(item, channel) else: - itemlist = renumber(itemlist) + itemlist = getattr(channel, item.action)(item) global magnet_caching magnet_caching = False diff --git a/platformcode/autorenumber.py b/platformcode/autorenumber.py index 40600842..1d20c9df 100644 --- a/platformcode/autorenumber.py +++ b/platformcode/autorenumber.py @@ -1,551 +1,40 @@ # -*- coding: utf-8 -*- # -------------------------------------------------------------------------------- -# autorenumber - Rinomina Automaticamente gli Episodi +# autorenumber - Rinumera Automaticamente gli Episodi # -------------------------------------------------------------------------------- -''' -USO: -1) utilizzare autorenumber.renumber(itemlist) nelle le funzioni peliculas e similari per aggiungere il menu contestuale -2) utilizzare autorenumber.renumber(itemlist, item, typography) nella funzione episodios -3) Aggiungere le seguinti stringhe nel json del canale (per attivare la configurazione di autonumerazione del canale) -{ - "id": "autorenumber", - "type": "bool", - "label": "@70712", - "default": false, - "enabled": true, - "visible": true -}, -{ - "id": "autorenumber_mode", - "type": "bool", - "label": "@70688", - "default": false, - "enabled": true, - "visible": "eq(-1,true)" -} -''' - -try: - import xbmcgui -except: - xbmcgui = None -import re, base64, json, inspect +import xbmcgui, re, base64, inspect, sys from core import jsontools, tvdb, scrapertools, filetools -from core.support import typo +from core.item import Item +from core.support import typo, match, dbg, Item from platformcode import config, platformtools, logger -TAG_TVSHOW_RENUMERATE = "TVSHOW_AUTORENUMBER" -TAG_ID = "ID" -TAG_SEASON = "Season" -TAG_EPISODE = "Episode" -TAG_SPECIAL = "Special" -TAG_MODE = "Mode" -TAG_EPLIST = "EpList" -TAG_CHECK = "ReCheck" -TAG_SPLIST = "SpList" -TAG_TYPE = "Type" - - -def renumber(itemlist, item='', typography=''): - logger.info() - dict_series = load(itemlist[0]) if len(itemlist) > 0 else {} - - if item: - item.channel = item.from_channel if item.from_channel else item.channel - title = item.fulltitle.rstrip() - try: - already_renumbered = scrapertools.find_single_match(itemlist[0].title, r'(\d+\D\d+)') - except: - return - if already_renumbered : - return itemlist - elif item.channel in item.channel_prefs and TAG_TVSHOW_RENUMERATE in item.channel_prefs[item.channel] and title not in dict_series: - from core.videolibrarytools import check_renumber_options - from specials.videolibrary import update_videolibrary - check_renumber_options(item) - update_videolibrary(item) - - elif inspect.stack()[2][3] == 'find_episodes': - return itemlist - - elif title in dict_series and TAG_ID in dict_series[title]: - ID = dict_series[title][TAG_ID] - Episode = dict_series[title][TAG_EPISODE] - Season = dict_series[title][TAG_SEASON] if TAG_SEASON in dict_series[title] else '' - Mode = dict_series[title][TAG_MODE] if TAG_MODE in dict_series[title] else False - Type = dict_series[title][TAG_TYPE] if TAG_TYPE in dict_series[title] else 'auto' - - renumeration(itemlist, item, typography, dict_series, ID, Season, Episode, Mode, title, Type) - - else: - if config.get_setting('autorenumber', item.channel): - config_item(item, itemlist, typography, True) - else: - return itemlist - - else: - for item in itemlist: - title = item.fulltitle.rstrip() - if title in dict_series and TAG_ID in dict_series[title]: - ID = dict_series[title][TAG_ID] - exist = True - else: - exist = False - - if item.contentType != 'movie': - if item.context: - context2 = item.context - item.show = item.fulltitle = title - item.context = context(exist) + context2 - else: - item.show = item.fulltitle = title - item.context = context(exist) - - -def config_item(item, itemlist=[], typography='', active=False): - logger.info() - # Configurazione Automatica, Tenta la numerazione Automatica degli episodi - title = item.fulltitle.rstrip() - - dict_series = load(item) - ID = dict_series[title][TAG_ID] if title in dict_series and TAG_ID in dict_series[title] else '' - - # Pulizia del Titolo - if any( word in title.lower() for word in ['specials', 'speciali']): - title = re.sub(r'\sspecials|\sspeciali', '', title.lower()) - tvdb.find_and_set_infoLabels(item) - elif not item.infoLabels['tvdb_id']: - item.contentSerieName= title.rstrip('123456789 ') - tvdb.find_and_set_infoLabels(item) - - if not ID and active: - if item.infoLabels['tvdb_id']: - ID = item.infoLabels['tvdb_id'] - dict_renumerate = {TAG_ID: ID} - dict_series[title] = dict_renumerate - # Trova La Stagione - if any(word in title.lower() for word in ['specials', 'speciali']): - dict_renumerate[TAG_SEASON] = '0' - elif RepresentsInt(title.split()[-1]): - dict_renumerate[TAG_SEASON] = title.split()[-1] - else: dict_renumerate[TAG_SEASON] = '1' - dict_renumerate[TAG_EPISODE] = '' - write(item, dict_series) - return renumber(itemlist, item, typography) - else: - return itemlist - - else: - return renumber(itemlist, item, typography) - - -def semiautomatic_config_item(item): - logger.info() - # Configurazione Semi Automatica, utile in caso la numerazione automatica fallisca - tvdb.find_and_set_infoLabels(item) - item.channel = item.from_channel if item.from_channel else item.channel - dict_series = load(item) - title = item.fulltitle.rstrip() - - # Trova l'ID della serie - while not item.infoLabels['tvdb_id']: - try: - item.show = platformtools.dialog_input(default=item.show, heading=config.get_localized_string(30112)) # <- Enter title to search - tvdb.find_and_set_infoLabels(item) - except: - heading = config.get_localized_string(70704) # <- TMDB ID (0 to cancel) - info = platformtools.dialog_numeric(0, heading) - item.infoLabels['tvdb_id'] = '0' if info == '' else info - - - if item.infoLabels['tvdb_id']: - ID = item.infoLabels['tvdb_id'] - dict_renumerate = {TAG_ID: ID} - dict_series[title] = dict_renumerate - - # Trova la Stagione - if any( word in title.lower() for word in ['specials', 'speciali'] ): - heading = config.get_localized_string(70686) # <- Enter the number of the starting season (for specials) - season = platformtools.dialog_numeric(0, heading, '0') - dict_renumerate[TAG_SEASON] = season - elif RepresentsInt(title.split()[-1]): - heading = config.get_localized_string(70686) # <- Enter the number of the starting season (for season > 1) - season = platformtools.dialog_numeric(0, heading, title.split()[-1]) - dict_renumerate[TAG_SEASON] = season - else: - heading = config.get_localized_string(70686) # <- Enter the number of the starting season (for season 1) - season = platformtools.dialog_numeric(0, heading, '1') - dict_renumerate[TAG_SEASON] = season - - mode = platformtools.dialog_yesno(config.get_localized_string(70687), config.get_localized_string(70688), nolabel=config.get_localized_string(30023), yeslabel=config.get_localized_string(30022)) - if mode == True: - dict_renumerate[TAG_MODE] = False - - if TAG_SPECIAL in dict_series[title]: - specials = dict_renumerate[TAG_SPECIAL] - else: - specials = [] - - write(item, dict_series) - _list = [] - - itemlist = find_episodes(item) - for item in itemlist: - Title = re.sub(r'\d+x\d+ - ', '', item.title) - if item.action == 'findvideos': - _list.append(Title) - - selected = platformtools.dialog_multiselect(config.get_localized_string(70734), _list) - # if len(selected) > 0: - for select in selected: - specials.append(int(scrapertools.find_single_match(_list[select], r'(\d+)'))) - dict_renumerate[TAG_SPECIAL] = specials - - dict_renumerate[TAG_MODE] = False - - dict_renumerate[TAG_TYPE] = 'auto' - dict_renumerate[TAG_EPISODE] = '' - write(item, dict_series) - # xbmc.executebuiltin("Container.Refresh") - - else: - message = config.get_localized_string(60444) - heading = item.fulltitle.strip() - platformtools.dialog_notification(heading, message) - - -def renumeration (itemlist, item, typography, dict_series, ID, Season, Episode, Mode, Title, Type): - - # Se ID è 0 salta la rinumerazione - if ID == '0': - return itemlist - - # Numerazione per gli Speciali - elif Season == '0': - EpisodeDict = {} - for item in itemlist: - if config.get_localized_string(30992) not in item.title: - number = scrapertools.find_single_match(item.title, r'\d+') - item.title = typo('0x' + number + ' - ', typography) + item.title - - - # Usa la lista degli Episodi se esiste nel Json - - elif Episode: - EpisodeDict = json.loads(base64.b64decode(Episode)) - - # Controlla che la lista egli Episodi sia della stessa lunghezza di Itemlist - if EpisodeDict == 'none': - return error(itemlist) - if Type == 'manual' and len(EpisodeDict) < len(itemlist): - EpisodeDict = manual_renumeration(item, True) - if len(EpisodeDict) >= len(itemlist) and scrapertools.find_single_match(itemlist[0].title, r'\d+') in EpisodeDict: - for item in itemlist: - if config.get_localized_string(30992) not in item.title: - number = scrapertools.find_single_match(item.title, r'\d+') - number = int(number) # if number !='0': number.lstrip('0') - item.title = typo(EpisodeDict[str(number)] + ' - ', typography) + item.title - else: - make_list(itemlist, item, typography, dict_series, ID, Season, Episode, Mode, Title) - - else: - make_list(itemlist, item, typography, dict_series, ID, Season, Episode, Mode, Title) - - -def manual_renumeration(item, modify=False): - logger.info() - _list = [] - if item.from_channel: item.channel = item.from_channel - title = item.fulltitle.rstrip() - - dict_series = load(item) - - if title not in dict_series: dict_series[title] = {} - - if TAG_EPISODE in dict_series[title] and dict_series[title][TAG_EPISODE]: - EpisodeDict = json.loads(base64.b64decode(dict_series[title][TAG_EPISODE])) - del dict_series[title][TAG_EPISODE] - else: EpisodeDict = {} - - if TAG_EPLIST in dict_series[title]: del dict_series[title][TAG_EPLIST] - if TAG_MODE in dict_series[title]: del dict_series[title][TAG_MODE] - if TAG_CHECK in dict_series[title]: del dict_series[title][TAG_CHECK] - if TAG_SEASON in dict_series[title]: del dict_series[title][TAG_SEASON] - if TAG_SPECIAL in dict_series[title]: del dict_series[title][TAG_SPECIAL] - dict_series[title][TAG_TYPE] = 'manual' - write(item, dict_series) - - if TAG_ID not in dict_series[title] or (TAG_ID in dict_series[title] and not dict_series[title][TAG_ID]): - tvdb.find_and_set_infoLabels(item) - - # Trova l'ID della serie - while not item.infoLabels['tvdb_id']: - try: - item.show = platformtools.dialog_input(default=item.show, heading=config.get_localized_string(30112)) # <- Enter title to search - tvdb.find_and_set_infoLabels(item) - except: - heading = config.get_localized_string(70704) # <- TMDB ID (0 to cancel) - info = platformtools.dialog_numeric(0, heading) - item.infoLabels['tvdb_id'] = '0' if info == '' else info - - if item.infoLabels['tvdb_id']: - ID = item.infoLabels['tvdb_id'] - dict_renumerate = {TAG_ID: ID} - dict_series[title] = dict_renumerate - - itemlist = find_episodes(item) - for it in itemlist: - Title = re.sub(r'\d+x\d+ - ', '', it.title) - if modify == True: - ep = int(scrapertools.find_single_match(Title, r'(\d+)')) - if it.action == 'findvideos' and str(ep) not in EpisodeDict: - _list.append(Title) - else: - if it.action == 'findvideos': - _list.append(Title) - - count = 1 - preselect = platformtools.dialog_select(config.get_localized_string(70732),[typo(config.get_localized_string(70518),'bold'),typo(config.get_localized_string(70519),'bold')]) - selection = [] - if preselect == 0: - for i in _list: - selection.append(_list.index(i)) - while len(_list) > 0: - selected = platformtools.dialog_multiselect(config.get_localized_string(70734), _list, preselect=selection) - if selected == None: break - season = '' - while not season: - season = platformtools.dialog_numeric(0, config.get_localized_string(70733)) - count = int(platformtools.dialog_numeric(0, config.get_localized_string(70733))) - - for select in selected: - ep = int(scrapertools.find_single_match(_list[select], r'(\d+)')) - if season == '0': - episode = '' - while not episode: - episode = platformtools.dialog_numeric(0, config.get_localized_string(70735) % _list[select] ) - EpisodeDict[str(ep)] = '%sx%s' %(season, episode.zfill(2)) - else: - EpisodeDict[str(ep)] = '%sx%s' %(season, str(count).zfill(2)) - count += 1 - - for select in reversed(selected): - del _list[select] - - - dict_series[title][TAG_TYPE] = 'manual' - EpisodeDict = base64.b64encode(json.dumps(EpisodeDict).encode()) - dict_series[title][TAG_EPISODE] = EpisodeDict.decode() - write(item, dict_series) - # xbmc.executebuiltin("Container.Refresh") - if modify == True: - return json.loads(base64.b64decode(EpisodeDict)) - - -def delete_renumeration(item): - logger.info() - if item.from_channel: item.channel = item.from_channel - title = item.fulltitle.rstrip() - - dict_series = load(item) - if title in dict_series: del dict_series[title] - write(item, dict_series) - - -def make_list(itemlist, item, typography, dict_series, ID, Season, Episode, Mode, title): - logger.info() - exist = True - item.infoLabels['tvdb_id'] = ID - tvdb.set_infoLabels_item(item) - FirstOfSeason= 0 - - EpisodeDict = json.loads(base64.b64decode(Episode)) if Episode else {} - Special = dict_series[title][TAG_SPECIAL] if TAG_SPECIAL in dict_series[title] else [] - EpList = json.loads(base64.b64decode(dict_series[title][TAG_EPLIST])) if TAG_EPLIST in dict_series[title] else [] - Pages = dict_series[title][TAG_CHECK] if TAG_CHECK in dict_series[title] else [1] - - # Ricava Informazioni da TVDB - checkpages = [] - check = True - Page = Pages[-1] - - while exist: - if check: - for page in Pages: - data = tvdb.otvdb_global.get_list_episodes(ID,page) - logger.info('DATA',data) - for episodes in data['data']: - if episodes['firstAired'] and [episodes['firstAired'], episodes['airedSeason'], episodes['airedEpisodeNumber']] not in EpList: - EpList.append([episodes['firstAired'], episodes['airedSeason'], episodes['airedEpisodeNumber']]) - else: - if page not in checkpages: - checkpages.append(page) - check = False - - data = tvdb.otvdb_global.get_list_episodes(ID,Page) - if data: - Page = Page + 1 - for episodes in data['data']: - if episodes['firstAired'] and [episodes['firstAired'], episodes['airedSeason'], episodes['airedEpisodeNumber']] not in EpList: - EpList.append([episodes['firstAired'], episodes['airedSeason'], episodes['airedEpisodeNumber']]) - else: - if page not in checkpages: - checkpages.append(Page -1) - exist = False - - EpList.sort() - - dict_series[title][TAG_CHECK] = checkpages - EpList = base64.b64encode(json.dumps(EpList).encode()) - dict_series[title][TAG_EPLIST] = EpList.decode() - write(item, dict_series) - - # Crea Dizionari per la numerazione - if EpList: - EpList = json.loads(base64.b64decode(dict_series[title][TAG_EPLIST])) - specials = [] - regular = {} - complete = {} - allep = 1 - ep = 1 - specialep = 0 - for episode in EpList: - complete[allep] = [str(episode[1]) + 'x' + str(episode[2]), episode[0]] - if episode[1] == 0: - specials.append(allep) - specialep = specialep + 1 - else: - regular[ep] = [str(episode[1]) + 'x' + str(episode[2]), str(episode[0]), allep - 1] - ep = ep + 1 - allep = allep + 1 - - # seleziona l'Episodio di partenza - if int(Season) > 1: - for numbers, data in regular.items(): - if data[0] == Season + 'x1': - FirstOfSeason = numbers - 1 - - if Mode == True: Special = specials - - addiction = 0 - for item in itemlist: - # Otiene Numerazione Episodi - scraped_ep = scrapertools.find_single_match(re.sub(r'\[[^\]]+\]','',item.title), r'\d+') - if scraped_ep: - episode = int(scraped_ep) - number = episode + FirstOfSeason - addiction - count = number + addiction - # Crea Dizionario Episodi - - if episode == 0: - EpisodeDict[str(episode)] = str(complete[regular[FirstOfSeason+1][2]][0]) - elif addiction < len(Special): - if episode in Special: - try: - season = complete[regular[count][2]][0] - EpisodeDict[str(episode)] = str(complete[regular[count][2]][0]) if season.startswith( '0' ) else '0x' + platformtools.dialog_numeric(0, item.title + '?', '') - - except: - EpisodeDict[str(episode)] = '0x' + platformtools.dialog_numeric(0, item.title + '?', '') - addiction = addiction + 1 - elif number <= len(regular): - EpisodeDict[str(episode)] = str(regular[number][0]) - else: - try: EpisodeDict[str(episode)] = str(complete[regular[number+2][2]][0]) - except: EpisodeDict[str(episode)] = '0x0' - elif number <= len(regular) and number in regular: - EpisodeDict[str(episode)] = str(regular[number][0]) - else: - try: EpisodeDict[str(episode)] = str(complete[regular[number+2][2]][0]) - except: EpisodeDict[str(episode)] = '0x0' - - # Aggiunge numerazione agli Episodi - - item.title = typo(EpisodeDict[str(episode)] + ' - ', typography) + item.title - - # Scrive Dizionario Episodi sul json - EpisodeDict = base64.b64encode(json.dumps(EpisodeDict).encode()) - dict_series[title][TAG_EPISODE] = EpisodeDict.decode() - write(item, dict_series) - - else: - heading = config.get_localized_string(70704) - ID = platformtools.dialog_numeric(0, heading) - dict_series[title][TAG_ID] = ID - write(item, dict_series) - if ID == '0': - return itemlist - else: - return make_list(itemlist, item, typography, dict_series, ID, Season, Episode, Mode, title) - - +# Json Var +TVSHOW_RENUMERATE = "TVSHOW_AUTORENUMBER" +ID = "ID" +SEASON = "Season" +EPISODE = "Episode" +SPECIAL = "Special" +MODE = "Mode" +EPLIST = "EpList" +CHECK = "ReCheck" +SPLIST = "SpList" +TYPE = "Type" + +# helper Functions def check(item): logger.info() dict_series = load(item) title = item.fulltitle.rstrip() if title in dict_series: title = dict_series[title] - return True if TAG_ID in title and TAG_EPISODE in title else False - - -def error(itemlist): - message = config.get_localized_string(70713) - heading = itemlist[0].fulltitle.strip() - platformtools.dialog_notification(heading, message) - return itemlist - - -def find_episodes(item): - logger.info() - ch = __import__('channels.' + item.channel, fromlist=["channels.%s" % item.channel]) - itemlist = ch.episodios(item) - return itemlist - - -def RepresentsInt(s): - # Controllo Numro Stagione - logger.info() - try: - int(s) - return True - except ValueError: - return False - - -def access(): - allow = False - - if config.is_xbmc(): - allow = True - - return allow - - -def context(exist): - if access(): - modify = config.get_localized_string(70714) if exist else '' - _context = [{"title": typo(modify + config.get_localized_string(70585), 'bold'), - "action": "select_type", - "channel": "autorenumber",}] - - return _context - - -def select_type(item): - select = platformtools.dialog_select(config.get_localized_string(70730),[typo(config.get_localized_string(70731),'bold'), typo(config.get_localized_string(70732),'bold'), typo(config.get_localized_string(707433),'bold')]) - if select == 0: semiautomatic_config_item(item) - elif select == 1: manual_renumeration(item) - elif select == 2: return delete_renumeration(item) - else: return - + return True if ID in title and EPISODE in title else False def filename(item): logger.info() name_file = item.channel + "_data.json" path = filetools.join(config.get_data_path(), "settings_channels") fname = filetools.join(path, name_file) - return fname @@ -553,7 +42,7 @@ def load(item): logger.info() try: json_file = open(filename(item), "r").read() - json = jsontools.load(json_file)[TAG_TVSHOW_RENUMERATE] + json = jsontools.load(json_file)[TVSHOW_RENUMERATE] except: json = {} @@ -565,7 +54,669 @@ def write(item, json): logger.info() json_file = open(filename(item), "r").read() js = jsontools.load(json_file) - js[TAG_TVSHOW_RENUMERATE] = json + js[TVSHOW_RENUMERATE] = json with open(filename(item), "w") as file: file.write(jsontools.dump(js)) file.close() + +def b64(json, mode = 'encode'): + if mode == 'encode': + ret = base64.b64encode(json) + else: + ret = jsontools.load(base64.b64decode(json)) + return ret + +def RepresentsInt(s): + # Controllo Numro Stagione + logger.info() + try: + int(s) + return True + except ValueError: + return False + +def find_episodes(item): + logger.info() + ch = __import__('channels.' + item.channel, fromlist=["channels.%s" % item.channel]) + itemlist = ch.episodios(item) + return itemlist + + + +# Main +def start(itemlist, item=None): + if type(itemlist) == Item: + item = itemlist + if item.channel in ['autorenumber']: + item.channel = item.from_channel + item.action = item.from_action + item.renumber = True + itemlist = find_episodes(item) + return autorenumber(itemlist, item) +class autorenumber(): + def __init__(self, itemlist, item=None): + self.item = item + self.itemlist = itemlist + self.auto = False + self.dictSeries = load(self.itemlist[0]) if self.itemlist else load(item) if item else {} + if self.item: + self.auto = config.get_setting('autorenumber', item.channel) + self.title = self.item.fulltitle.strip() + if match(self.itemlist[0].title, patron=r'(\d+\D\d+)').match: + return itemlist + elif self.item.channel in self.item.channel_prefs and TVSHOW_RENUMERATE in self.item.channel_prefs[item.channel] and self.title not in self.dictSeries: + from core.videolibrarytools import check_renumber_options + from specials.videolibrary import update_videolibrary + check_renumber_options(self.item) + update_videolibrary(self.item) + if self.title in self.dictSeries: + self.id = self.dictSeries[self.title][ID] + self.Episodes = b64(self.dictSeries[self.title][EPISODE], 'decode') + self.Season = self.dictSeries[self.title][SEASON] + self.Mode = self.dictSeries[self.title].get(MODE, False) + self.Type = self.dictSeries[self.title].get(TYPE, False) + if self.item.renumber: + self.config() + else: + self.renumber() + elif self.auto or self.item.renumber: + self.Episodes = {} + self.config() + + else: + for item in self.itemlist: + item.context = [{"title": typo(config.get_localized_string(70585), 'bold'), + "action": "start", + "channel": "autorenumber", + "from_channel": item.channel, + "from_action": item.action}] + + def config(self): + self.id = '' + if self.title in self.dictSeries: + self.id = self.dictSeries[self.title].get(ID,'') + + # Pulizia del Titolo + if any( word in self.title.lower() for word in ['specials', 'speciali']): + self.title = re.sub(r'\s*specials|\s*speciali', '', self.title.lower()) + tvdb.find_and_set_infoLabels(self.item) + elif not self.item.infoLabels['tvdb_id']: + self.item.contentSerieName = self.title.rstrip('123456789 ') + tvdb.find_and_set_infoLabels(self.item) + + # Rinumerazione Automatica + if (not self.id and self.auto) or self.item.renumber: + self.id = self.item.infoLabels['tvdb_id'] if 'tvdb_id' in self.item.infoLabels else '' + if self.id: + self.dictRenumber = {ID: self.id} + self.dictSeries[self.title] = self.dictRenumber + if any(word in self.title.lower() for word in ['specials', 'speciali']): season = '0' + elif RepresentsInt(self.title.split()[-1]): season = self.title.split()[-1] + else: season = '1' + # self.dictRenumber[EPISODE] = {} + self.Season = self.dictRenumber[SEASON] = season + # self.Episodes = self.dictRenumber[EPISODE] + self.renumber() + + def renumber(self): + if not self.item.renumber and self.itemlist and len(self.Episodes) >= len(self.itemlist) and match(self.itemlist[0].title, patron=r'(\d+)').match in self.Episodes: + if '|' in self.Season: + season = int(self.Season.split('|')[0]) + addNumber = int(self.Season.split('|')[-1]) - 1 + else: + season = int(self.Season) + addNumber = 0 + + for item in self.itemlist: + number = match(item.title, patron=r'(\d+)').match + number = number.lstrip('0') + if number: + if number in self.Episodes: + if season > 0: item.title = typo(self.Episodes[number] + ' - ', 'bold') + item.title + else: item.title = typo('0x%s - ' % str(int(number) + addNumber), 'bold') + item.title + else: + self.makelist() + if season > 0: item.title = typo(self.Episodes[number] + ' - ', 'bold') + item.title + else: item.title = typo('0x%s - ' % str(int(number) + addNumber), 'bold') + item.title + else: + self.makelist() + + + def makelist(self): + FirstOfSeason= 0 + self.EpList = b64(self.dictSeries[self.title][EPLIST], 'decode') if EPLIST in self.dictSeries[self.title] else [] + self.Pages = self.dictSeries[self.title].get(CHECK, [1]) + self.Mode = self.dictSeries[self.title].get(MODE, False) + self.Type = self.dictSeries[self.title].get(TYPE, False) + Specials = {} + Seasons = {} + + if '|' in self.Season: + ep = int(self.Season.split('|')[-1]) + season = int(self.Season.split('|')[0]) + else: + season = int(self.Season) + ep = 1 + + # pdialog = platformtools.dialog_progress_bg('Rinumerazione', 'creazione lista episodi') + itemlist = find_episodes(self.item) + + if self.item.renumber: + self.s = season + self.e = 1 + Season, Episode, self.Mode, Specials, Seasons, Exit = SelectreNumeration(self, itemlist) + if Exit: return + if ep != 1: self.Season = '%s|%s' % (Season, Episode) + else: self.Season = str(Season) + + + if self.Mode: + if not Seasons: + self.s = 1 + self.e = 1 + Season, Episode, self.Mode, Specials, Seasons, Exit = SelectreNumeration(self, itemlist, True) + self.Episodes = Seasons + + else: + + # Ricava Informazioni da TVDB + checkpages = [] + exist = True + Page = self.Pages[-1] + + while exist: + data = tvdb.Tvdb(tvdb_id=self.id).get_list_episodes(self.id, Page) + if data: + for episode in data['data']: + if episode['firstAired'] and [episode['firstAired'], episode['airedSeason'], episode['airedEpisodeNumber']] not in self.EpList: + self.EpList.append([episode['firstAired'], episode['airedSeason'], episode['airedEpisodeNumber']]) + Page += 1 + else: + if Page not in checkpages: + checkpages.append(Page -1) + exist = False + self.Pages = [checkpages[-1]] + self.EpList.sort() + + # Crea Dizionari per la Rinumerazione + if self.EpList: + self.specials = [] + self.regular = {} + self.complete = {} + allep = 1 + specialep = 0 + + for episode in self.EpList: + self.complete[allep] = [str(episode[1]) + 'x' + str(episode[2]), episode[0]] + if episode[1] == 0: + self.specials.append(allep) + specialep = specialep + 1 + else: + self.regular[ep] = [str(episode[1]) + 'x' + str(episode[2]), str(episode[0]), allep - 1] + ep = ep + 1 + allep = allep + 1 + + self.Episodes = {} + if season > 1: + for numbers, data in self.regular.items(): + if data[0] == str(season) + 'x1': + FirstOfSeason = numbers - 1 + else: FirstOfSeason = Episode - 1 + + addiction = 0 + # specialsCount = 1 + # pdialog.update(80, 'rinumerazione') + for item in itemlist: + # Otiene Numerazione Episodi + scraped_ep = match(re.sub(r'\[[^\]]+\]','',item.title), patron=r'(\d+)').match + if scraped_ep: + episode = int(scraped_ep) + number = episode + FirstOfSeason - addiction + if episode == 0: + self.Episodes[str(episode)] = str(self.complete[self.regular[FirstOfSeason+1][2]][0]) + elif episode in Specials: + self.Episodes[str(episode)] = Specials[episode] + addiction += 1 + elif number <= len(self.regular) and number in self.regular: + self.Episodes[str(episode)] = str(self.regular[number][0]) + else: + try: Episodes[str(episode)] = str(self.complete[self.regular[number+2][2]][0]) + except: self.Episodes[str(episode)] = '0x0' + + + self.dictSeries[self.title][EPISODE] = b64(jsontools.dump(self.Episodes)) + self.dictSeries[self.title][EPLIST] = b64(jsontools.dump(self.EpList)) + self.dictSeries[self.title][MODE] = self.Mode + self.dictSeries[self.title][SEASON] = self.Season + self.dictSeries[self.title][CHECK] = self.Pages + write(self.item, self.dictSeries) + # pdialog.close() + if self.auto: self.renumber() + + +def SelectreNumeration(opt, itemlist, manual=False): + opt.itemlist = itemlist + opt.manual = manual + return SelectreNumerationWindow('Renumber.xml', path).start(opt) + +# Select Season +SELECT = 100 +S = 101 +E = 102 +O = 103 +SS = 104 +M = 105 +D = 106 +C = 107 + +# Main +MAIN = 10000 +INFO = 10001 +OK=10002 +CLOSE = 10003 + +# Select Specials +SPECIALS = 200 +POSTER= 201 +LIST = 202 +SELECTED = 203 +BACKGROUND = 208 + +SPECIALCOMMANDS = 204 +SU = 205 +SD = 206 +SR = 207 + +# Select Manual +MANUAL = 300 +MPOSTER= 301 +MLIST = 302 +MSEASONS = 303 +MSEP = 304 +MBACKGROUND = 310 + +MANUALEP = 305 +MS = 306 +ME = 307 +MSS = 308 +MC = 309 + +# Actions +LEFT = 1 +RIGHT = 2 +UP = 3 +DOWN = 4 +EXIT = 10 +BACKSPACE = 92 + +path = config.get_runtime_path() + +class SelectreNumerationWindow(xbmcgui.WindowXMLDialog): + def start(self, opt): + self.episodes = opt.Episodes if opt.Episodes else {} + self.dictSeries = opt.dictSeries + self.item = opt.item + self.title = opt.title + self.season = opt.s + self.episode = opt.e + self.mode = opt.Mode + self.manual = opt.manual + self.offset = 0 + self.Exit = False + + self.itemlist = opt.itemlist + self.count = 1 + self.specials = {} + self.items = [] + self.selected = [] + self.seasons = {} + + self.doModal() + return self.season, self.episode, self.mode, self.specials, self.seasons, self.Exit + + def onInit(self): + # Compatibility with Kodi 18 + if config.get_platform(True)['num_version'] < 18: self.setCoordinateResolution(2) + fanart = self.item.fanart + thumb = self.item.thumbnail + # MANUAL + if self.manual: + self.getControl(SELECT).setVisible(False) + self.getControl(SPECIALS).setVisible(False) + self.getControl(MANUAL).setVisible(True) + self.getControl(MPOSTER).setImage(thumb) + if fanart: self.getControl(MBACKGROUND).setImage(fanart) + self.getControl(INFO).setLabel(typo(config.get_localized_string(70822) + self.title,'bold')) + + self.mode = True + + se = '1' + ep = '1' + position = 0 + for i, item in enumerate(self.itemlist): + title = match(item.title, patron=r'(\d+)').match + it = xbmcgui.ListItem(title) + if int(title) <= len(self.episodes): + se, ep = self.episodes[title].split('x') + else: + if position == 0: position = i + ep = str(int(ep) + 1) + it.setProperties({'season':se, "episode":ep}) + self.items.append(it) + self.makerenumber() + self.addseasons() + season = self.getControl(MSEASONS).getSelectedItem().getLabel() + self.getControl(MSEP).reset() + self.getControl(MSEP).addItems(self.episodes[season]) + self.getControl(MLIST).addItems(self.items) + self.setFocusId(MLIST) + self.getControl(MLIST).selectItem(position) + # SPECIALS + else: + self.getControl(SELECT).setVisible(True) + self.getControl(SPECIALS).setVisible(False) + self.getControl(MANUAL).setVisible(False) + + for i, item in enumerate(self.itemlist): + title = match(item.title, patron=r'(\d+)').match + it = xbmcgui.ListItem(title) + it.setProperty('index', str(i)) + self.items.append(it) + + self.getControl(POSTER).setImage(thumb) + self.getControl(MPOSTER).setImage(thumb) + if fanart: + self.getControl(BACKGROUND).setImage(fanart) + self.getControl(MBACKGROUND).setImage(fanart) + self.getControl(INFO).setLabel(typo(config.get_localized_string(70824) + self.title, 'bold')) + self.getControl(LIST).addItems(self.items) + + self.getControl(S).setLabel(str(self.season)) + # self.getControl(S).setType(1, config.get_localized_string(60385)) + self.getControl(E).setLabel(str(self.episode)) + # self.getControl(E).setType(1, config.get_localized_string(60386)) + + self.setFocusId(O) + + def onFocus(self, focus): + if focus in [S]: self.getControl(108).setLabel(typo(config.get_localized_string(70825), 'bold')) + elif focus in [E]: self.getControl(108).setLabel(typo(config.get_localized_string(70826), 'bold')) + elif focus in [O]: self.getControl(108).setLabel(typo(config.get_localized_string(70001), 'bold')) + elif focus in [SS]: self.getControl(108).setLabel(typo(config.get_localized_string(70827), 'bold')) + elif focus in [M]: self.getControl(108).setLabel(typo(config.get_localized_string(70828), 'bold')) + elif focus in [D]: self.getControl(108).setLabel(typo(config.get_localized_string(70829) + self.title, 'bold')) + elif focus in [C]: self.getControl(108).setLabel(typo(config.get_localized_string(70002), 'bold')) + + + def onAction(self, action): + action = action.getId() + focus = self.getFocusId() + # SEASON SELECT + if 100 < focus < 200: + s = int(self.getControl(S).getLabel()) + e = int(self.getControl(E).getLabel()) + if action in [RIGHT]: + if focus in [C]: self.setFocusId(S) + else: self.setFocusId(focus + 1) + elif action in [LEFT]: + if focus in [S]: self.setFocusId(C) + else: self.setFocusId(focus - 1) + elif action in [UP]: + if focus in [S]: + s += 1 + self.getControl(S).setLabel(str(s)) + elif focus in [E]: + e += 1 + self.getControl(E).setLabel(str(e)) + elif action in [DOWN]: + if focus in [S]: + if s > 0: s -= 1 + self.getControl(S).setLabel(str(s)) + elif focus in [E]: + if e > 0: e -= 1 + self.getControl(E).setLabel(str(e)) + # MANUAL + if focus in [MS, ME]: + s = int(self.getControl(MLIST).getSelectedItem().getProperty('season')) + e = int(self.getControl(MLIST).getSelectedItem().getProperty('episode')) + pos = self.getControl(MLIST).getSelectedPosition() + # Set Season + if focus in [MS] and action in [UP]: s += 1 + elif focus in [MS] and action in [DOWN] and s > 0: s -= 1 + # Set Episode + if focus in [ME] and action in [UP]: e += 1 + elif focus in [ME] and action in [DOWN] and e > 0: e -= 1 + if action in [UP, DOWN]: + if s != self.season: e = 1 + self.season = s + self.episode = e + self.makerenumber(pos) + self.addseasons() + season = self.getControl(MSEASONS).getSelectedItem().getLabel() + self.getControl(MSEP).reset() + self.getControl(MSEP).addItems(self.episodes[season]) + self.getControl(MLIST).reset() + self.getControl(MLIST).addItems(self.items) + self.getControl(MLIST).selectItem(pos) + if focus in [MSEASONS]: + season = self.getControl(MSEASONS).getSelectedItem().getLabel() + self.getControl(MSEP).reset() + self.getControl(MSEP).addItems(self.episodes[season]) + + # EXIT + if action in [EXIT, BACKSPACE]: + self.Exit = True + self.close() + + def onClick(self, control_id): + ## FIRST SECTION + if control_id in [S]: + selected = platformtools.dialog_numeric(0, config.get_localized_string(70825), self.getControl(S).getLabel()) + if selected: s = self.getControl(S).setLabel(selected) + elif control_id in [E]: + selected = platformtools.dialog_numeric(0, config.get_localized_string(70826), self.getControl(E).getLabel()) + if selected: e = self.getControl(E).setLabel(selected) + # OPEN SPECIALS OR OK + if control_id in [O, SS]: + s = self.getControl(S).getLabel() + e = self.getControl(E).getLabel() + self.season = int(s) + self.episode = int(e) + if control_id in [O]: + self.close() + elif control_id in [SS]: + self.getControl(SELECT).setVisible(False) + self.getControl(SPECIALS).setVisible(True) + self.setFocusId(OK) + # OPEN MANUAL + elif control_id in [M]: + self.getControl(INFO).setLabel(typo(config.get_localized_string(70823) + self.title, 'bold')) + self.mode = True + if self.episodes: + items = [] + se = '1' + ep = '1' + for item in self.items: + if int(item.getLabel()) <= len(self.episodes) - 1: + se, ep = self.episodes[item.getLabel()].split('x') + else: + ep = str(int(ep) + 1) + item.setProperties({'season':se, "episode":ep}) + items.append(item) + self.seasons[item.getLabel()] = '%sx%s' %(se, ep) + self.items = items + else: + self.makerenumber() + self.addseasons() + season = self.getControl(MSEASONS).getSelectedItem().getLabel() + self.getControl(MSEP).reset() + self.getControl(MSEP).addItems(self.episodes[season]) + self.getControl(MLIST).addItems(self.items) + self.getControl(SELECT).setVisible(False) + self.getControl(MANUAL).setVisible(True) + self.setFocusId(OK) + # CLOSE + elif control_id in [C]: + self.Exit = True + self.close() + # DELETE + if control_id in [D]: + self.Exit = True + self.dictSeries.pop(self.title) + write(self.item, self.dictSeries) + self.close() + + ## SPECIAL SECTION + # ADD TO SPECIALS + p1 = self.getControl(SELECTED).getSelectedPosition() + if control_id in [LIST]: + item = self.getControl(LIST).getSelectedItem() + it = xbmcgui.ListItem(str(len(self.selected) + 1)) + it.setProperty('title', item.getLabel()) + self.selected.append(it) + index = self.getControl(SELECTED).getSelectedPosition() + self.getControl(SELECTED).reset() + self.getControl(SELECTED).addItems(self.selected) + self.getControl(SELECTED).selectItem(index) + + index = self.getControl(LIST).getSelectedPosition() + self.items.pop(index) + self.getControl(LIST).reset() + self.getControl(LIST).addItems(self.items) + if index == len(self.items): index -= 1 + self.getControl(LIST).selectItem(index) + # MOVE SPECIALS + elif control_id in [SU]: + p2 = p1 - 1 + if p2 > -1: + self.selected[p1], self.selected[p2] = self.selected[p2], self.selected[p1] + for i, it in enumerate(self.selected): + it.setLabel(str(i+1)) + break + self.getControl(SELECTED).reset() + self.getControl(SELECTED).addItems(self.selected) + self.getControl(SELECTED).selectItem(p2) + + elif control_id in [SD]: + p2 = p1 + 1 + if p2 < len(self.selected): + self.selected[p1], self.selected[p2] = self.selected[p2], self.selected[p1] + for i, it in enumerate(self.selected): + it.setLabel(str(i+1)) + break + self.getControl(SELECTED).reset() + self.getControl(SELECTED).addItems(self.selected) + self.getControl(SELECTED).selectItem(p2) + # REMOVE FROM SPECIALS + elif control_id in [SR]: + item = self.getControl(SELECTED).getSelectedItem() + it = xbmcgui.ListItem(item.getProperty('title')) + if int(item.getProperty('title')) < int(self.items[-1].getLabel()): + for i, itm in enumerate(self.items): + if int(itm.getLabel()) > int(item.getProperty('title')): + self.items.insert(i, it) + break + else: + self.items.append(it) + self.getControl(LIST).reset() + self.getControl(LIST).addItems(self.items) + index = self.getControl(SELECTED).getSelectedPosition() + self.selected.pop(index) + self.getControl(SELECTED).reset() + self.getControl(SELECTED).addItems(self.selected) + + if index == len(self.selected): index -= 1 + self.getControl(SELECTED).selectItem(index) + # RELOAD SPECIALS + if control_id in [SELECTED]: + epnumber = platformtools.dialog_numeric(0, config.get_localized_string(60386)) + it = self.getControl(SELECTED).getSelectedItem() + it.setLabel(str(epnumber)) + self.selected.sort(key=lambda it: int(it.getLabel())) + for i, it in enumerate(self.selected): + if it.getLabel() == epnumber: pos = i + self.selected.sort(key=lambda it: int(it.getLabel())) + self.getControl(SELECTED).reset() + self.getControl(SELECTED).addItems(self.selected) + self.getControl(SELECTED).selectItem(pos) + break + if len(self.selected) > 0: self.getControl(SPECIALCOMMANDS).setVisible(True) + else: self.getControl(SPECIALCOMMANDS).setVisible(False) + + ## MANUAL SECTION + # SELECT SEASON EPISODE (MANUAL) + if control_id in [MS, ME]: + s = int(self.getControl(MLIST).getSelectedItem().getProperty('season')) + e = int(self.getControl(MLIST).getSelectedItem().getProperty('episode')) + pos = self.getControl(MLIST).getSelectedPosition() + if control_id in [MS]: + selected = platformtools.dialog_numeric(0, config.get_localized_string(70825), str(s)) + if selected: s = int(selected) + elif control_id in [ME]: + selected = platformtools.dialog_numeric(0, config.get_localized_string(70826), str(e)) + if selected: e = int(selected) + if s != self.season or e != self.episode: + self.season = s + self.episode = 1 if s != self.season else e + self.makerenumber(pos) + self.addseasons() + season = self.getControl(MSEASONS).getSelectedItem().getLabel() + self.getControl(MSEP).reset() + self.getControl(MSEP).addItems(self.episodes[season]) + self.getControl(MLIST).reset() + self.getControl(MLIST).addItems(self.items) + self.getControl(MLIST).selectItem(pos) + # OK + if control_id in [OK]: + for it in self.selected: + self.specials[int(it.getProperty('title'))] = '0x' + it.getLabel() + self.close() + # CLOSE + elif control_id in [CLOSE]: + self.Exit = True + self.close() + + + def makerenumber(self, pos = 0): + items = [] + currentSeason = self.items[pos].getProperty('season') + previousSeason = self.items[pos - 1 if pos > 0 else 0].getProperty('season') + prevEpisode = self.items[pos - 1 if pos > 0 else 0].getProperty('episode') + if currentSeason != str(self.season): + if str(self.season) == previousSeason: + prevEpisode = int(prevEpisode) + 1 + else: + prevEpisode = 1 + else: prevEpisode = self.episode + + for i, item in enumerate(self.items): + if (i >= pos and item.getProperty('season') == currentSeason) or not item.getProperty('season'): + if i > pos: prevEpisode += 1 + item.setProperties({'season':self.season, 'episode':prevEpisode}) + items.append(item) + self.seasons[item.getLabel()] = '%sx%s' % (item.getProperty('season'), item.getProperty('episode')) + self.items = items + logger.info('SELF',self.seasons) + + def addseasons(self): + seasonlist = [] + seasons = [] + self.episodes = {} + for ep, value in self.seasons.items(): + season = value.split('x')[0] + if season not in seasonlist: + item = xbmcgui.ListItem(season) + seasonlist.append(season) + seasons.append(item) + if season in seasonlist: + if season not in self.episodes: + self.episodes[season] = [] + item = xbmcgui.ListItem('%s - Ep. %s' % (value, ep)) + item.setProperty('episode', ep) + self.episodes[season].append(item) + logger.log('EPISODES',self.episodes[season]) + self.episodes[season].sort(key=lambda it: int(it.getProperty('episode'))) + + seasons.sort(key=lambda it: int(it.getLabel())) + self.getControl(MSEASONS).reset() + self.getControl(MSEASONS).addItems(seasons) \ No newline at end of file diff --git a/platformcode/platformtools.py b/platformcode/platformtools.py index fa820aa7..0f1ffc3a 100644 --- a/platformcode/platformtools.py +++ b/platformcode/platformtools.py @@ -178,6 +178,10 @@ def dialog_register(heading, user=False, email=False, password=False, user_defau dialog = Register('Register.xml', config.get_runtime_path()).Start(heading, user, email, password, user_default, email_default, password_default, captcha_img) return dialog +def dialog_info(item, scraper): + dialog = TitleOrIDWindow('TitleOrIDWindow.xml', config.get_runtime_path()).Start(item, scraper) + return dialog + def itemlist_refresh(): # pos = Item().fromurl(xbmc.getInfoLabel('ListItem.FileNameAndPath')).itemlistPosition @@ -1338,3 +1342,108 @@ def get_platform(): ret["arch"] = "arm" return ret + + +class Register(xbmcgui.WindowXMLDialog): + def Start(self, heading, user, email, password, user_default, email_default, password_default, captcha_img): + self.result = {} + self.heading = heading + self.user = user + self.email = email + self.password = password + self.user_default = user_default + self.email_default = email_default + self.password_default = password_default + self.captcha_img = captcha_img + self.doModal() + + return self.result + + def __init__(self, *args, **kwargs): + self.mensaje = kwargs.get("mensaje") + self.imagen = kwargs.get("imagen") + + def onInit(self): + #### Kodi 18 compatibility #### + if config.get_platform(True)['num_version'] < 18: + self.setCoordinateResolution(2) + height = 90 + self.getControl(10002).setText(self.heading) + if self.user: + self.getControl(10003).setText(self.user_default) + height+=70 + else: + self.getControl(10003).setVisible(False) + if self.email: + self.getControl(10004).setText(self.email_default) + height+=70 + else: + self.getControl(10004).setVisible(False) + if self.password: + self.getControl(10005).setText(self.password_default) + height+=70 + else: + self.getControl(10005).setVisible(False) + if self.captcha_img: + + self.getControl(10007).setImage(self.captcha_img) + height+=240 + else: + self.getControl(10005).setVisible(False) + height +=40 + if height < 250: height = 250 + self.getControl(10000).setHeight(height) + self.getControl(10001).setHeight(height) + self.getControl(10000).setPosition(255, (720-height)/2) + self.setFocusId(30000) + + def onClick(self, control): + if control in [10010]: + self.close() + + elif control in [10009]: + if self.user: self.result['user'] = self.getControl(10003).getText() + if self.email: self.result['email'] = self.getControl(10004).getText() + if self.password: self.result['password'] = self.getControl(10005).getText() + if self.captcha_img: self.result['captcha'] = self.getControl(10006).getText() + self.close() + +class TitleOrIDWindow(xbmcgui.WindowXMLDialog): + def Start(self, item, scraper): + self.item = item + self.title = item.show if item.show else item.fulltitle + self.id = item.infoLabels.get('tmdb_id','') if scraper == 'tmdb' else item.infoLabels.get('tvdb_id','') + self.scraper = scraper + self.label = 'TMDB ID:' if scraper == 'tmdb' else 'TVDB ID:' + self.doModal() + return self.item + + def onInit(self): + #### Kodi 18 compatibility #### + if config.get_platform(True)['num_version'] < 18: + self.setCoordinateResolution(2) + self.getControl(10000).setText(config.get_localized_string(60228) % self.title) + self.getControl(10001).setText(self.title) + self.getControl(10002).setLabel(self.label) + self.getControl(10002).setText(self.id) + self.getControl(10002).setType(1, self.label) + self.setFocusId(10001) + + def onClick(self, control): + if control in [10003]: + if self.getControl(10001).getText(): + self.item.contentTitle = self.getControl(10001).getText() + if self.scraper == 'tmdb' and self.getControl(10002).getText(): + self.item.infoLabels['tmdb_id'] = self.getControl(10002).getText() + elif self.scraper == 'tvdb' and self.getControl(10002).getText(): + self.item.infoLabels['tvdb_id'] = self.getControl(10002).getText() + self.close() + + elif control in [10004, 10005]: + self.item = None + self.close() + + def onAction(self, action): + if (action in [92] and self.getFocusId() not in [10001, 10002]) or action in [10]: + self.item = None + self.close() diff --git a/platformcode/xbmc_info_window.py b/platformcode/xbmc_info_window.py index a4452cd3..968e9ab1 100644 --- a/platformcode/xbmc_info_window.py +++ b/platformcode/xbmc_info_window.py @@ -1,14 +1,20 @@ # -*- coding: utf-8 -*- -import xbmcgui +import xbmcgui, sys from core.tmdb import Tmdb from platformcode import config, logger from core import filetools +if sys.version_info[0] >= 3: + from concurrent import futures +else: + from concurrent_py2 import futures BACKGROUND = 30000 LOADING = 30001 SELECT = 30002 +EXIT = 10 +BACKSPACE = 92 def imagepath(image): if len(image.split('.')) == 1: image += '.png' @@ -28,20 +34,27 @@ class InfoWindow(xbmcgui.WindowXMLDialog): logger.info('RESPONSE',self.response) return self.response + def make_items(self, i, result): + infoLabels = self.scraper().get_infoLabels(origen=result) + it = xbmcgui.ListItem(infoLabels['title']) + it.setProperty('fanart', infoLabels.get('fanart', '')) + it.setProperty('thumbnail', infoLabels.get('thumbnail', imagepath('movie' if infoLabels['mediatype'] == 'movie' else 'tv'))) + it.setProperty('genre', infoLabels.get('genre', 'N/A')) + it.setProperty('rating', str(infoLabels.get('rating', 'N/A'))) + it.setProperty('plot', str(infoLabels.get('plot', ''))) + it.setProperty('year', str(infoLabels.get('year', ''))) + it.setProperty('position', str(i)) + return it + def onInit(self): if config.get_platform(True)['num_version'] < 18: self.setCoordinateResolution(2) - - for result in self.results: - infoLabels = self.scraper().get_infoLabels(origen=result) - it = xbmcgui.ListItem(infoLabels['title']) - it.setProperty('fanart', infoLabels.get('fanart', '')) - it.setProperty('thumbnail', infoLabels.get('thumbnail', imagepath('movie' if infoLabels['mediatype'] == 'movie' else 'tv'))) - it.setProperty('genre', infoLabels.get('genre', 'N/A')) - it.setProperty('rating', str(infoLabels.get('rating', 'N/A'))) - it.setProperty('plot', str(infoLabels.get('plot', ''))) - it.setProperty('year', str(infoLabels.get('year', ''))) - self.items.append(it) + with futures.ThreadPoolExecutor() as executor: + for i, result in enumerate(self.results): + logger.info(result) + if ('seriesName' in result and result['seriesName']) or ('name' in result and result['name']) or ('title' in result and result['title']): + self.items += [executor.submit(self.make_items, i, result).result()] + self.items.sort(key=lambda it: int(it.getProperty('position'))) self.getControl(SELECT).addItems(self.items) self.getControl(BACKGROUND).setImage(self.items[0].getProperty('fanart')) @@ -53,3 +66,12 @@ class InfoWindow(xbmcgui.WindowXMLDialog): self.response = self.results[self.getControl(SELECT).getSelectedPosition()] self.close() + def onAction(self, action): + if self.getFocusId() in [SELECT]: + fanart = self.getControl(self.getFocusId()).getSelectedItem().getProperty('fanart') + self.getControl(BACKGROUND).setImage(fanart) + if action in [BACKSPACE]: + self.close() + elif action in [EXIT]: + self.close() + diff --git a/resources/language/resource.language.en_gb/strings.po b/resources/language/resource.language.en_gb/strings.po index 02cea72d..ee4ebcb3 100644 --- a/resources/language/resource.language.en_gb/strings.po +++ b/resources/language/resource.language.en_gb/strings.po @@ -6086,6 +6086,44 @@ msgctxt "#70821" msgid "Search results" msgstr "" +# RENUMBER +msgctxt "#70822" +msgid "Renumber new episodes of: " +msgstr "" + +msgctxt "#70823" +msgid "Renumber episodes of: " +msgstr "" + +msgctxt "#70824" +msgid "Select the specials of: " +msgstr "" + +msgctxt "#70825" +msgid "Select Season" +msgstr "" + +msgctxt "#70826" +msgid "Select Episode" +msgstr "" + +msgctxt "#70827" +msgid "Select Specials" +msgstr "" + +msgctxt "#70828" +msgid "Manual renumbering" +msgstr "" + +msgctxt "#70829" +msgid "Delete Numbering for: " +msgstr "" + +msgctxt "#70830" +msgid "The series / episode number should only be changed if the series has relative numbering." +msgstr "" + + # DNS start [ settings and declaration ] msgctxt "#707401" msgid "Enable DNS check alert" diff --git a/resources/language/resource.language.it_it/strings.po b/resources/language/resource.language.it_it/strings.po index d0a6603e..7ed448ed 100644 --- a/resources/language/resource.language.it_it/strings.po +++ b/resources/language/resource.language.it_it/strings.po @@ -6087,6 +6087,43 @@ msgctxt "#70821" msgid "Search results" msgstr "Risultati della ricerca" +# RENUMBER +msgctxt "#70822" +msgid "Renumber new episodes of: " +msgstr "Rinumera i nuovi episodi di: " + +msgctxt "#70823" +msgid "Renumber episodes of: " +msgstr "Rinumera gli episodi di: " + +msgctxt "#70824" +msgid "Select the specials of: " +msgstr "Seleziona gli speciali di: " + +msgctxt "#70825" +msgid "Select Season" +msgstr "Seleziona Stagione" + +msgctxt "#70826" +msgid "Select Episode" +msgstr "Seleziona Episodio" + +msgctxt "#70827" +msgid "Select Specials" +msgstr "Seleziona Speciali" + +msgctxt "#70828" +msgid "Manual renumbering" +msgstr "Rinumerazione Manuale" + +msgctxt "#70829" +msgid "Delete Numbering for: " +msgstr "Elimina Numerazione per: " + +msgctxt "#70830" +msgid "The series / episode number should only be changed if the series has relative numbering." +msgstr "Il numero della serie / episodio deve essere modificato solo se la serie ha una numerazione relativa." + # DNS start [ settings and declaration ] msgctxt "#707401" msgid "Enable DNS check alert" diff --git a/resources/skins/Default/720p/InfoWindow.xml b/resources/skins/Default/720p/InfoWindow.xml index 6c3d099d..ad013bf6 100644 --- a/resources/skins/Default/720p/InfoWindow.xml +++ b/resources/skins/Default/720p/InfoWindow.xml @@ -56,7 +56,7 @@ <viewtype>wrap</viewtype> <orientation>horizontal</orientation> <scrolltime tween="cubic" easing="out">300</scrolltime> - <itemlayout height="640" width="180"> + <itemlayout width="180"> <!-- Poster --> <control type="image"> <bottom>0</bottom> @@ -68,12 +68,12 @@ <bordersize>10</bordersize> </control> </itemlayout> - <focusedlayout height="640" width="480"> + <focusedlayout width="427"> <!-- Title --> <control type="textbox"> - <left>500</left> + <left>447</left> <top>10</top> - <width>730</width> + <width>783</width> <height>30</height> <font>font30_title</font> <textcolor>FFFFFFFF</textcolor> @@ -84,9 +84,9 @@ </control> <!-- info --> <control type="textbox"> - <left>500</left> + <left>447</left> <top>50</top> - <width>730</width> + <width>783</width> <height>30</height> <font>font13</font> <textcolor>FFFFFFFF</textcolor> @@ -96,9 +96,9 @@ </control> <!-- Plot --> <control type="textbox"> - <left>500</left> + <left>447</left> <top>90</top> - <width>730</width> + <width>783</width> <height>250</height> <font>font13</font> <textcolor>FFFFFFFF</textcolor> @@ -111,7 +111,7 @@ <control type="image"> <bottom>0</bottom> <left>0</left> - <width>480</width> + <width>427</width> <height>640</height> <texture>$INFO[ListItem.Property(thumbnail)]</texture> <aspectratio>scale</aspectratio> diff --git a/resources/skins/Default/720p/Renumber.xml b/resources/skins/Default/720p/Renumber.xml new file mode 100644 index 00000000..6571b407 --- /dev/null +++ b/resources/skins/Default/720p/Renumber.xml @@ -0,0 +1,631 @@ +<?xml version="1.0" encoding="utf-8"?> +<window> + <allowoverlays>false</allowoverlays> + <animation type="WindowOpen" reversible="false"> + <effect type="fade" start="0" end="100" time="300" /> + </animation> + <animation type="WindowClose" reversible="false"> + <effect type="fade" start="100" end="0" time="300" /> + </animation> + <controls> + <!-- MAIN SELECTION --> + <control type='group' id='100'> + <height>100%</height> + <width>100%</width> + <!-- Background --> + <control type="image"> + <height>100%</height> + <width>100%</width> + <texture colordiffuse="CC232323">white.png</texture> + </control> + <control type="textbox"> + <top>60</top> + <left>370</left> + <height>140</height> + <width>540</width> + <align>center</align> + <aligny>center</aligny> + <textcolor>80FFFFFF</textcolor> + <label>$ADDON[plugin.video.kod 70830]</label> + </control> + <!-- main selection window --> + <control type="group"> + <top>288.5</top> + <left>370</left> + <height>140</height> + <width>540</width> + <!-- Beckground --> + <control type="image"> + <height>100%</height> + <width>100%</width> + <texture colordiffuse="FF232323">white.png</texture> + </control> + <control type="button" id="101"> + <top>30</top> + <left>20</left> + <height>60</height> + <width>100</width> + <align>center</align> + <aligny>center</aligny> + <textcolor>FFFFFFFF</textcolor> + <focusedcolor>FFFFFFFF</focusedcolor> + <texturefocus colordiffuse="FFFFFFFF" border="-20,0,-20,0">updn.png</texturefocus> + <texturenofocus colordiffuse="80FFFFFF" border="-20,0,-20,0">updn.png</texturenofocus> + </control> + <!-- divider --> + <control type="textbox"> + <top>30</top> + <left>120</left> + <height>60</height> + <width>20</width> + <textcolor>FFFFFFFF</textcolor> + <align>center</align> + <aligny>center</aligny> + <label>[B]X[/B]</label> + </control> + <control type="button" id="102"> + <top>30</top> + <left>140</left> + <height>60</height> + <width>100</width> + <align>center</align> + <aligny>center</aligny> + <textcolor>FFFFFFFF</textcolor> + <focusedcolor>FFFFFFFF</focusedcolor> + <texturefocus colordiffuse="FFFFFFFF" border="-20,0,-20,0">updn.png</texturefocus> + <texturenofocus colordiffuse="80FFFFFF" border="-20,0,-20,0">updn.png</texturenofocus> + </control> + <!-- ok --> + <control type="button" id="103"> + <top>35</top> + <left>260</left> + <height>50</height> + <width>50</width> + <textcolor>FFFFFFFF</textcolor> + <aligny>center</aligny> + <align>center</align> + <texturefocus colordiffuse="FFFFFFFF">ok.png</texturefocus> + <texturenofocus colordiffuse="80FFFFFF">ok.png</texturenofocus> + </control> + <!-- Select Specials --> + <control type="button" id="104"> + <top>35</top> + <left>310</left> + <height>50</height> + <width>50</width> + <textcolor>FFFFFFFF</textcolor> + <aligny>center</aligny> + <align>center</align> + <texturefocus colordiffuse="FFFFFFFF">specials.png</texturefocus> + <texturenofocus colordiffuse="80FFFFFF">specials.png</texturenofocus> + </control> + <!-- Manual renumeration --> + <control type="button" id="105"> + <top>35</top> + <left>360</left> + <height>50</height> + <width>50</width> + <textcolor>FFFFFFFF</textcolor> + <aligny>center</aligny> + <align>center</align> + <texturefocus colordiffuse="FFFFFFFF">manual.png</texturefocus> + <texturenofocus colordiffuse="80FFFFFF">manual.png</texturenofocus> + </control> + <!-- delete --> + <control type="button" id="106"> + <top>35</top> + <left>410</left> + <height>50</height> + <width>50</width> + <textcolor>FFFFFFFF</textcolor> + <aligny>center</aligny> + <align>center</align> + <texturefocus colordiffuse="FFFFFFFF">delete.png</texturefocus> + <texturenofocus colordiffuse="80FFFFFF">delete.png</texturenofocus> + </control> + <!-- annulla --> + <control type="button" id="107"> + <top>35</top> + <left>460</left> + <height>50</height> + <width>50</width> + <textcolor>FFFFFFFF</textcolor> + <aligny>center</aligny> + <align>center</align> + <texturefocus colordiffuse="FFFFFFFF">close.png</texturefocus> + <texturenofocus colordiffuse="80FFFFFF">close.png</texturenofocus> + </control> + <control type="label" id="108"> + <bottom>5</bottom> + <width>100%</width> + <height>30</height> + <textcolor>FFFFFFFF</textcolor> + <align>center</align> + <aligny>center</aligny> + </control> + </control> + </control> + <!-- END MAIN SELECTION --> + + <!-- SPECIALS --> + <control type='group' id='200'> + <height>100%</height> + <width>100%</width> + + <!-- BACKGROUND --> + <control type="image" id="208"> + <top>0</top> + <left>0</left> + <height>100%</height> + <width>100%</width> + <texture colordiffuse="FF232323">white.png</texture> + </control> + + <!-- POSTER --> + <control type="image" id="201"> + <top>0</top> + <left>0</left> + <height>720</height> + <width>480</width> + <texture/> + </control> + + <!-- EPISODES LIST --> + <control type="list" id="202"> + <top>140</top> + <left>520</left> + <height>540</height> + <width>340</width> + <onleft>10002</onleft> + <onright>203</onright> + <itemlayout width="340" height="60"> + <control type="label"> + <height>100%</height> + <width>300</width> + <left>20</left> + <textcolor>FFFFFFFF</textcolor> + <label>[B]Episodio $INFO[ListItem.Label()][/B]</label> + <aligny>center</aligny> + </control> + </itemlayout> + <focusedlayout width="340" height="60"> + <control type="image"> + <height>100%</height> + <width>100%</width> + <texture colordiffuse="22FFFFFF">white.png</texture> + <visible allowhiddenfocus="true">Control.HasFocus(202)</visible> + </control> + <control type="image"> + <top>10</top> + <right>10</right> + <height>40</height> + <width>40</width> + <texture colordiffuse="FFFFFFFF">add.png</texture> + <visible allowhiddenfocus="true">Control.HasFocus(202)</visible> + </control> + <control type="label"> + <height>100%</height> + <width>300</width> + <left>20</left> + <textcolor>FFFFFFFF</textcolor> + <label>[B]Episodio $INFO[ListItem.Label()][/B]</label> + <aligny>center</aligny> + </control> + </focusedlayout> + </control> + + <!-- SPECIALS LIST --> + <control type='group'> + <top>140</top> + <left>900</left> + <height>540</height> + <width>340</width> + <control type="list" id="203"> + <height>540</height> + <width>340</width> + <onleft>202</onleft> + <onright>204</onright> + <itemlayout width="340" height="60"> + <!-- EP NUMBER --> + <control type="label"> + <left>20</left> + <height>60</height> + <width>140</width> + <textcolor>80FFFFFF</textcolor> + <aligny>center</aligny> + <label>[B]0x$INFO[ListItem.Label()] - Ep. $INFO[ListItem.Property(title)][/B]</label> + </control> + </itemlayout> + <focusedlayout width="340" height="60"> + <control type="image"> + <height>100%</height> + <width>100%</width> + <texture colordiffuse="22FFFFFF">white.png</texture> + <visible allowhiddenfocus="true">!Control.HasFocus(202)</visible> + </control> + <!-- EP NUMBER --> + <control type="label"> + <left>20</left> + <height>60</height> + <width>140</width> + <textcolor>FFFFFFFF</textcolor> + <aligny>center</aligny> + <label>[B]0x$INFO[ListItem.Label()] - Ep. $INFO[ListItem.Property(title)][/B]</label> + </control> + </focusedlayout> + </control> + + <!-- ITEM ACTIONS --> + <control type="group" id='204'> + <visible allowhiddenfocus="true">Integer.IsGreater(Container(203).Position,-1)</visible> + <animation effect="slide" end="0,60" condition="Integer.IsGreater(Container(203).Position,0)">Conditional</animation> + <animation effect="slide" end="0,60" condition="Integer.IsGreater(Container(203).Position,1)">Conditional</animation> + <animation effect="slide" end="0,60" condition="Integer.IsGreater(Container(203).Position,2)">Conditional</animation> + <animation effect="slide" end="0,60" condition="Integer.IsGreater(Container(203).Position,3)">Conditional</animation> + <animation effect="slide" end="0,60" condition="Integer.IsGreater(Container(203).Position,4)">Conditional</animation> + <animation effect="slide" end="0,60" condition="Integer.IsGreater(Container(203).Position,5)">Conditional</animation> + <animation effect="slide" end="0,60" condition="Integer.IsGreater(Container(203).Position,6)">Conditional</animation> + <animation effect="slide" end="0,60" condition="Integer.IsGreater(Container(203).Position,7)">Conditional</animation> + <!-- move up --> + <control type="button" id="205"> + <top>10</top> + <right>90</right> + <height>40</height> + <width>40</width> + <onleft>203</onleft> + <onright>206</onright> + <onup>Control.Move(203,-1)</onup> + <ondown>Control.Move(203,1)</ondown> + <texturefocus colordiffuse="FFFFFFFF">up.png</texturefocus> + <texturenofocus colordiffuse="80FFFFFF">up.png</texturenofocus> + </control> + <!-- move down --> + <control type="button" id="206"> + <top>10</top> + <right>50</right> + <height>40</height> + <width>40</width> + <onleft>205</onleft> + <onright>207</onright> + <onup>Control.Move(203,-1)</onup> + <ondown>Control.Move(203,1)</ondown> + <texturefocus colordiffuse="FFFFFFFF">down.png</texturefocus> + <texturenofocus colordiffuse="80FFFFFF">down.png</texturenofocus> + </control> + <!-- remove --> + <control type="button" id="207"> + <top>10</top> + <right>10</right> + <height>40</height> + <width>40</width> + <onleft>206</onleft> + <onright>10002</onright> + <onup>Control.Move(203,-1)</onup> + <ondown>Control.Move(203,1)</ondown> + <texturefocus colordiffuse="FFFFFFFF">delete.png</texturefocus> + <texturenofocus colordiffuse="80FFFFFF">delete.png</texturenofocus> + </control> + </control> + </control> + </control> + <!-- END SPECIALS --> + + <!-- MANUAL --> + <control type='group' id='300'> + <height>100%</height> + <width>100%</width> + + <!-- BACKGROUND --> + <control type="image" id="310"> + <top>0</top> + <left>0</left> + <height>100%</height> + <width>100%</width> + <texture colordiffuse="FF232323">white.png</texture> + </control> + + <!-- POSTER --> + <control type="image" id="301"> + <top>0</top> + <left>0</left> + <height>720</height> + <width>480</width> + </control> + + <!-- EPISODES LIST --> + <control type='group'> + <top>140</top> + <left>520</left> + <height>540</height> + <width>340</width> + <onleft>10002</onleft> + <onright>306</onright> + <control type="list" id="302"> + <height>100%</height> + <width>100%</width> + <onleft>10002</onleft> + <onright>306</onright> + <itemlayout width="340" height="60"> + <control type="label"> + <height>100%</height> + <width>120</width> + <left>20</left> + <textcolor>FFFFFFFF</textcolor> + <label>[B]Episodio $INFO[ListItem.Label()][/B]</label> + <aligny>center</aligny> + </control> + <!-- first season number --> + <control type="textbox"> + <right>100</right> + <height>60</height> + <width>60</width> + <onleft>302</onleft> + <onright>307</onright> + <align>center</align> + <aligny>center</aligny> + <textcolor>80FFFFFF</textcolor> + <label>[B]$INFO[ListItem.Property(season)][/B]</label> + </control> + <!-- divider --> + <control type="textbox"> + <right>80</right> + <height>60</height> + <width>20</width> + <textcolor>80FFFFFF</textcolor> + <align>center</align> + <aligny>center</aligny> + <label>[B]X[/B]</label> + </control> + <!-- first episode number --> + <control type="textbox"> + <right>20</right> + <height>60</height> + <width>60</width> + <onleft>306</onleft> + <onright>308</onright> + <align>center</align> + <aligny>center</aligny> + <textcolor>80FFFFFF</textcolor> + <label>[B]$INFO[ListItem.Property(episode)][/B]</label> + </control> + </itemlayout> + <focusedlayout width="340" height="60"> + <control type="image"> + <height>100%</height> + <width>100%</width> + <texture colordiffuse="22FFFFFF">white.png</texture> + <visible allowhiddenfocus="true">Control.HasFocus(302)</visible> + </control> + <control type="label"> + <height>100%</height> + <width>120</width> + <left>20</left> + <textcolor>FFFFFFFF</textcolor> + <label>[B]Episodio $INFO[ListItem.Label()][/B]</label> + <aligny>center</aligny> + </control> + <!-- first season number --> + <control type="textbox"> + <right>100</right> + <height>60</height> + <width>60</width> + <onleft>302</onleft> + <onright>307</onright> + <align>center</align> + <aligny>center</aligny> + <textcolor>FFFFFFFF</textcolor> + <label>[B]$INFO[ListItem.Property(season)][/B]</label> + </control> + <!-- divider --> + <control type="textbox"> + <right>80</right> + <height>60</height> + <width>20</width> + <textcolor>FFFFFFFF</textcolor> + <align>center</align> + <aligny>center</aligny> + <label>[B]X[/B]</label> + </control> + <!-- first episode number --> + <control type="textbox"> + <right>20</right> + <height>60</height> + <width>60</width> + <onleft>306</onleft> + <onright>308</onright> + <align>center</align> + <aligny>center</aligny> + <textcolor>FFFFFFFF</textcolor> + <label>[B]$INFO[ListItem.Property(episode)][/B]</label> + </control> + </focusedlayout> + </control> + + <!-- MANUAL EPISODE CONTROL --> + <control type='group' id='305'> + <visible allowhiddenfocus="true">Integer.IsGreater(Container(302).Position,-1)</visible> + <animation effect="slide" end="0,60" condition="Integer.IsGreater(Container(302).Position,0)">Conditional</animation> + <animation effect="slide" end="0,60" condition="Integer.IsGreater(Container(302).Position,1)">Conditional</animation> + <animation effect="slide" end="0,60" condition="Integer.IsGreater(Container(302).Position,2)">Conditional</animation> + <animation effect="slide" end="0,60" condition="Integer.IsGreater(Container(302).Position,3)">Conditional</animation> + <animation effect="slide" end="0,60" condition="Integer.IsGreater(Container(302).Position,4)">Conditional</animation> + <animation effect="slide" end="0,60" condition="Integer.IsGreater(Container(302).Position,5)">Conditional</animation> + <animation effect="slide" end="0,60" condition="Integer.IsGreater(Container(302).Position,6)">Conditional</animation> + <animation effect="slide" end="0,60" condition="Integer.IsGreater(Container(302).Position,7)">Conditional</animation> + <!-- first season number --> + <control type="button" id="306"> + <right>100</right> + <height>60</height> + <width>60</width> + <onleft>302</onleft> + <onright>307</onright> + <align>center</align> + <aligny>center</aligny> + <textcolor>FFFFFFFF</textcolor> + <texturefocus colordiffuse="FFFFFFFF">updn.png</texturefocus> + <texturenofocus colordiffuse="80FFFFFF">updn.png</texturenofocus> + </control> + <!-- first episode number --> + <control type="button" id="307"> + <right>20</right> + <height>60</height> + <width>60</width> + <onleft>306</onleft> + <onright>303</onright> + <align>center</align> + <aligny>center</aligny> + <textcolor>FFFFFFFF</textcolor> + <texturefocus colordiffuse="FFFFFFFF">updn.png</texturefocus> + <texturenofocus colordiffuse="80FFFFFF">updn.png</texturenofocus> + </control> + </control> + </control> + + <!-- SEASONS LIST --> + <control type='list' id='303'> + <top>140</top> + <left>880</left> + <height>540</height> + <width>80</width> + <onleft>302</onleft> + <onright>304</onright> + <itemlayout width="80" height="60"> + <control type="label"> + <height>100%</height> + <width>100%</width> + <textcolor>80FFFFFF</textcolor> + <label>[B]$INFO[ListItem.Label()][/B]</label> + <align>center</align> + <aligny>center</aligny> + </control> + </itemlayout> + <focusedlayout width="80" height="60"> + <control type="image"> + <height>100%</height> + <width>100%</width> + <texture colordiffuse="22FFFFFF">white.png</texture> + <visible allowhiddenfocus="true">Control.HasFocus(303)</visible> + </control> + <control type="label"> + <height>100%</height> + <width>100%</width> + <textcolor>FFFFFFFF</textcolor> + <label>[B]$INFO[ListItem.Label()][/B]</label> + <align>center</align> + <aligny>center</aligny> + <visible allowhiddenfocus="true">Control.HasFocus(303)</visible> + </control> + <control type="image"> + <height>100%</height> + <width>100%</width> + <texture colordiffuse="11FFFFFF">white.png</texture> + <visible allowhiddenfocus="true">!Control.HasFocus(303)</visible> + </control> + <control type="label"> + <height>100%</height> + <width>100%</width> + <textcolor>80FFFFFF</textcolor> + <label>[B]$INFO[ListItem.Label()][/B]</label> + <align>center</align> + <aligny>center</aligny> + <visible allowhiddenfocus="true">!Control.HasFocus(303)</visible> + </control> + </focusedlayout> + </control> + + <control type="image"> + <top>140</top> + <left>960</left> + <height>540</height> + <height>100%</height> + <width>290</width> + <texture colordiffuse="11FFFFFF">white.png</texture> + </control> + + <!-- EPISODES LIST --> + <control type='list' id='304'> + <top>140</top> + <left>970</left> + <height>540</height> + <width>270</width> + <onleft>303</onleft> + <onright>10002</onright> + <itemlayout width="270" height="60"> + <control type="label"> + <height>100%</height> + <width>200</width> + <left>40</left> + <textcolor>FFFFFFFF</textcolor> + <label>[B]$INFO[ListItem.Label()][/B]</label> + <aligny>center</aligny> + </control> + </itemlayout> + <focusedlayout width="270" height="60"> + <control type="image"> + <height>100%</height> + <width>100%</width> + <texture colordiffuse="22FFFFFF">white.png</texture> + <visible allowhiddenfocus="true">Control.HasFocus(304)</visible> + </control> + <control type="label"> + <height>100%</height> + <width>200</width> + <left>40</left> + <textcolor>FFFFFFFF</textcolor> + <label>[B]$INFO[ListItem.Label()][/B]</label> + <aligny>center</aligny> + </control> + </focusedlayout> + </control> + </control> + <!-- END MANUAL --> + + <!-- MAIN ACTIONS --> + <control type='group' id='10000'> + <visible allowhiddenfocus="true">Control.IsVisible(200) | Control.IsVisible(300)</visible> + <!-- info --> + <control type="label" id="10001"> + <top>40</top> + <left>540</left> + <height>50</height> + <width>560</width> + <textcolor>FFFFFFFF</textcolor> + <aligny>center</aligny> + </control> + <!-- ok --> + <control type="button" id="10002"> + <top>40</top> + <right>90</right> + <height>50</height> + <width>50</width> + <textcolor>FFFFFFFF</textcolor> + <aligny>center</aligny> + <align>center</align> + <texturefocus colordiffuse="FFFFFFFF">ok.png</texturefocus> + <texturenofocus colordiffuse="80FFFFFF">ok.png</texturenofocus> + <ondown condition="Control.IsVisible(200)">202</ondown> + <ondown condition="Control.IsVisible(300)">302</ondown> + <onleft>10003</onleft> + <onright>10003</onright> + </control> + <!-- annulla --> + <control type="button" id="10003"> + <top>40</top> + <right>40</right> + <height>50</height> + <width>50</width> + <textcolor>FFFFFFFF</textcolor> + <aligny>center</aligny> + <align>center</align> + <texturefocus colordiffuse="FFFFFFFF">close.png</texturefocus> + <texturenofocus colordiffuse="80FFFFFF">close.png</texturenofocus> + <ondown condition="Control.IsVisible(200)">202</ondown> + <ondown condition="Control.IsVisible(300)">302</ondown> + <onleft>10002</onleft> + <onright>10002</onright> + </control> + </control> + <!-- END MAIN ACTIONS --> + + </controls> +</window> diff --git a/resources/skins/Default/720p/TitleOrIDWindow.xml b/resources/skins/Default/720p/TitleOrIDWindow.xml new file mode 100644 index 00000000..3e6b2b39 --- /dev/null +++ b/resources/skins/Default/720p/TitleOrIDWindow.xml @@ -0,0 +1,108 @@ +<?xml version="1.0" encoding="utf-8"?> +<window> + <allowoverlays>false</allowoverlays> + <animation type="WindowOpen" reversible="false"> + <effect type="fade" start="0" end="100" time="300" /> + </animation> + <animation type="WindowClose" reversible="false"> + <effect type="fade" start="100" end="0" time="300" /> + </animation> + <controls> + <control type="button" id="10005"> + <top>0</top> + <left>0</left> + <height>100%</height> + <width>100%</width> + <texturefocus colordiffuse="80232323">white.png</texturefocus> + <texturenofocus colordiffuse="80232323">white.png</texturenofocus> + </control> + <control type="group"> + <top>210</top> + <left>175</left> + <height>300</height> + <width>930</width> + <!-- Beckground --> + <control type="image"> + <height>100%</height> + <width>100%</width> + <texture colordiffuse="FF232323">white.png</texture> + </control> + <!-- Header --> + <control type="textbox" id="10000"> + <top>40</top> + <left>40</left> + <height>40</height> + <width>850</width> + <font>font30_title</font> + <textcolor>FFFFFFFF</textcolor> + <label></label> + </control> + <!-- divisor --> + <control type="image"> + <top>90</top> + <left>40</left> + <height>1</height> + <width>850</width> + <texture colordiffuse="FFFFFFFF">white.png</texture> + </control> + <!-- titolo --> + <control type="edit" id="10001"> + <top>120</top> + <left>40</left> + <height>60</height> + <width>630</width> + <textcolor>FFFFFFFF</textcolor> + <texturefocus colordiffuse="FF0082C2">white.png</texturefocus> + <texturenofocus colordiffuse="FF232323">white.png</texturenofocus> + <aligny>center</aligny> + <label>$ADDON[plugin.video.kod 60230]</label> + <ondown>10002</ondown> + <onright>10003</onright> + </control> + <!-- id --> + <control type="edit" id="10002"> + <top>200</top> + <left>40</left> + <height>60</height> + <width>630</width> + <textcolor>FFFFFFFF</textcolor> + <texturefocus colordiffuse="FF0082C2">white.png</texturefocus> + <texturenofocus colordiffuse="FF232323">white.png</texturenofocus> + <aligny>center</aligny> + <label></label> + <onup>10001</onup> + <onright>10003</onright> + </control> + <!-- ok --> + <control type="button" id="10003"> + <top>120</top> + <left>690</left> + <label>$ADDON[plugin.video.kod 70001]</label> + <height>60</height> + <width>200</width> + <textcolor>FFFFFFFF</textcolor> + <aligny>center</aligny> + <align>center</align> + <texturefocus border="10" colordiffuse="FF0082C2">white.png</texturefocus> + <texturenofocus border="10" colordiffuse="FF232323">white.png</texturenofocus> + <ondown>10004</ondown> + <onleft>10001</onleft> + </control> + <!-- annulla --> + <control type="button" id="10004"> + <top>200</top> + <left>690</left> + <label>$ADDON[plugin.video.kod 70002]</label> + <height>60</height> + <width>200</width> + <textcolor>FFFFFFFF</textcolor> + <aligny>center</aligny> + <align>center</align> + <texturefocus border="10" colordiffuse="FF0082C2">white.png</texturefocus> + <texturenofocus border="10" colordiffuse="FF232323">white.png</texturenofocus> + <onup>10003</onup> + <onleft>10001</onleft> + </control> + </control> + </controls> +</window> diff --git a/resources/skins/Default/media/add.png b/resources/skins/Default/media/add.png new file mode 100644 index 0000000000000000000000000000000000000000..36e844507e7d43ea41f898c797ba11f0f70adb96 GIT binary patch literal 2822 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 ad!=>Xy{$4Wy1q{J_m%GD?^)s=mG&3O8eq5p literal 0 HcmV?d00001 diff --git a/resources/skins/Default/media/close.png b/resources/skins/Default/media/close.png index 0eb9e1337c28efa7e06edd23d7412d6de80442a3..b647ddca1eb129dffe2e0c8b8a864762f78a8e72 100644 GIT binary patch literal 2750 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 F_FvGmE}Q@W delta 941 zcmdldI-g6mGr-TCmrII^fq{Y7)59eQNE?AL2OE%-v+vl*z`*!_qUvwo#L@~|rHqo2 z0xNy}ykfn?l>DSrz2y8{{nU!gG<`!o1AU+heI+ghAgIXA$t$)3Nw`M9H6>>jB<f`q zfYnbfXSB2es<-k<%`3K50_%b(2aEXTr=;dUg-bHi(yTml6Vp?HYST+HY?X|SCjVm+ zXEssNpKQUZ$02_9wj9urO_TFjrR(>5I|Ygyx6cZkkr?3I+}vRrI;HW-r3F)1uP$Kw z8};RB{{i{_?^7mRTM(+z5%tUTN_;@}1sN7sK^JfD;y~}CulI;dIZ<r+z2@Ebd&N7x z$82yj_6p0FYROr*pw*f2`=3LLc0Zk&7IWj{gC5OYR>CtoE?uyDw_(P|W%t)y{c&gi zwfb$7E{JZ}!qu<2DokLW$h<i16>9n1<(e;VbbVM^FkekvWS{BRiq?1^ztA72Q*@>H znzlOMn7it6g{*d_uC|W;@!4lZroO5@Gu=XH=gtn7{vF(N4HTJn%uJf2P}e3g^`^hQ z9b1ZP^PhwF|3rS8wED!FAgAQ1BAHxYI-{n%;_URSzbI^EH08{{|8G~%lld09%9`oI zpA?CF+Y6gm@24LAVPn$sUU`G<A=h=&=G!++EWdc#^z3f_bG#P2*JOT_wYcB%j@@+G z(K^-HJ4|P`To=xWZ#b8|K|i!A?C$d)a*JMn3Ju?|n|*K5y`6uUBbd_fXS`W(7MMyn z3p^r=85p>Q85ruhg&7%U&5;1chlQt$V@O8h-CMC-%?1K24=xE;sPaEJx;OCU;V%mX zgI@VBvNrDIuGM<|hTV0`&Ev_78F(0ut$lx$HG#$8iWx6M8iPKAaOSpImCH;{$Ml>O z@QyT;Oj?<IhU-vQq@zr7nSj_Cz9i}3h6zbr0{>@lwXg&?{y19ypgMYW=8{A|g=vw; zR*Q2SZaMqlNK3l4!4@CYws}hrXc)b4G-5Fh=u2R^EijS!hM-`xN%M}5%H{sj9o;jw z2{85iW9@7BxHpJB=}&zTOTwRyr9#WTool%w+*IKf^F*w)dwv+hp?*%69|kNfa?UO< z4Acd4ZB6F6Z22l7Z`dIlDQwxmdzfQd@{C5K9;J?X3mq<Op288ej9GBrTbGuz4ZCIb zKGbVpxN_(J$1m6H{NLKh<YTWb6IuM<+Z0G-`$ZOqcc;rcil0=v)%*7mFl-q-UHx3v IIVCg!08}fEL;wH) diff --git a/resources/skins/Default/media/delete.png b/resources/skins/Default/media/delete.png new file mode 100644 index 0000000000000000000000000000000000000000..f810558243a4dc363b3642175145d5034ee53e1a GIT binary patch literal 2860 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 K;Z(9QH0i(lXkBjr literal 0 HcmV?d00001 diff --git a/resources/skins/Default/media/down.png b/resources/skins/Default/media/down.png new file mode 100644 index 0000000000000000000000000000000000000000..c43ed16b350b0a4dc7be5bd20ad97d088c1ac399 GIT binary patch literal 2764 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 Z*F{w3^`j}jBlUkm=|SOvHU6xe{{l?SG8X^< literal 0 HcmV?d00001 diff --git a/resources/skins/Default/media/exit.png b/resources/skins/Default/media/exit.png new file mode 100644 index 0000000000000000000000000000000000000000..b2dc396cb79ff6f100538780d620488c33d31932 GIT binary patch literal 1228 zcmV;-1T*`IP)<h;3K|Lk000e1NJLTq002Ay002M;1^@s6MjRB#0004lX+uL$b5ch_ zAW20-HZeIiHZ3wPF#rH4k#&)~O9Md=hrdKci3t%Ev9Jgdt&I49pp8X>MnNr-2)>%+ z%q2n|$6bPmtzauCh&H|o{R^}etOY?)1hKNPR<uz$=S(0$%q+9>?QeI6-C3|l(@Msu z?So7<uZKec&+Od1r~VlSZMbNrEv6XSRAeklRr|QZX2lzyRjc~Bf30yTVJKj05FSx9 zJuf^iJh_tBEZ!DwRZ_7f;ZxxrT`nkmYw7VKzgV0s@|VSWG#mtbldvaQ!SM=KQhG*M zUXC}DE-0n@STAuVvNI8ho#GuBgb5MA!vv!Q@zc++%zk_f&?_sd<oEsNPm_~eMP`jv zbe2hyB2SN~1*x8psEVGDk!IEE|KGkE>fk`Jx-$TEPp0|S1@&9-y=j`Cho<>`1T~Lv zkuB%v4q)hA^v$y7-3RwNoL!c+_zs+JK>LFh(__C<jksM^;q?SubI@`Pj)h{+O8Xq& zLv&5P>I!!Epj*zn7k>as)@RxEmIsvp000SaNLh0L01FcU01FcV0GgZ_0008-Nkl<Z zXo2mTTT4_?7>3_fAb3&TmPG`S*+DE%d5E<BMMjZP6p2Mf_K&))o32#|`VSRRA!_Dn z)SD!Ix|qFdlyUay+p`tdb2Aq+`+MJaX0vClZ+()-<MDVr9*-v<T$YQ0W0D3X9g+0G zIrq)LQ#GA4lCDcylQij^E9IdRWuhOL=0CrI(HwL}gW4g}K%ccvl!<;|o$8Dm>b%}X zV;$(X(up$B3#{#8BxKA$=VgS(D$rw*sg{XupcG*w<XHxt=P@-(K$m64N)sKxN=zdm zBPn!75^5|19oAu+fu)2-LLMj3d6H6N323$qJdx5!$io;Kk1}dZScPo_HZz*p0)}g7 zJjhrd;1AGf5x4=^Ffg<Q+^tgoUhrO4Hh_lAfdf<T3=Bc}P8q7Gdk34a0B_@DiM6x@ znk0RcbUNKvwv8=GVdyuK4jI(^A*t6nHxu>2*lTIvtfWavr_%fS$7b+)*r3+5qygvL z*Ek<ew0Qu|sT{f~xvyN9Q910FxECb4ot$%@CEb!Vm(W-q%u2eMBy4Jc7GTckVUvbA zpe6U9J3?Oq<_icr4_vHk*ox4tz(O6v{sJx+61F0A8?ac1u!}%jp<zRScFMb!yjTXV z7M*h&Kqtw!k8xN5I*ZO%72q228w7DE0o|4!EtYjV$<zt0Qp>WQ9YB}yuN$)WZfWZC z&}uI_rz8ouLi+Yk5{my@qW%ky1)#O4um@*p30wev77#Wh_2wb*BOf@g@}Bs38bL#5 zB@H^~J|(#*O#@&`(upM3<w)WNK++b^l%$g>jO}D^w|KB`;@u9kEt_~ZoO9m|YUSFd z9U^;^Kjght*n`!G$L^@*ehrP`tZA`2Y6%QYWHhsX4$}fN2kW>rFq7s${AmiEGza4O z?4&qIBM%j3dMFQ+)Z&GK&RB#-{zEMlW@?Qsy)x7p-$i4M0%o=P*oT8@7Nds)l8&lA qqlc@KrV1H7^msfTkH_QjMDQ0sJC_FzyaK)e0000<MNUMnLSTZD`6-qF literal 0 HcmV?d00001 diff --git a/resources/skins/Default/media/left.png b/resources/skins/Default/media/left.png new file mode 100644 index 0000000000000000000000000000000000000000..fc853099e46c7a54a1edb95c2b156b245ce76f52 GIT binary patch literal 2764 zcmai04P1-+9$zogi#RbM#Vnmt_S*YwCF^bHwyZX%Rf^f3wnuBv+Me1fZ$&B!g$@pp zN?t-KDbCTzsY4c(<DxkDa3`s7-IP-I+2(|Udq1B&+y4LW@Adot{(oBzD>TT`+{PS* zLRm6`{lk$@s`fE8LEb4NcV#Hl1RBWU!dzwug%2g*c>-t^fLA6+5gf7eRZ4mMcmT$% z0)(K1hV8%5fW?3U8aBe6Nn}dtfCvmul>zM3P!2yep6@Nd`udntl@ugE0s!+c$^@}Q zPEpdZy0{eNyLOv^#pq1ncp8?=WMSx#48XYK-SI@Mk2!`a6U0%%{R2NZBP$wK1jAAa zfuK+*@CsKvBoh)y-rn8>BAGxY;}8p+JVgTYlsJjpUK>Im!yl0IWuO!WAqhqsleY>= zf@xSRf@3}e2bAE)=o0yd_7NfoT7*Et6A7Q1OGOY2$wiQKyrR#1#^Em<VKD9+d9=tE z<_LXX&!FT<zh=y2{%`YygwNWL!vV<%5g$nXtcAZ4$T=xefDjJIp(Gg}2uKDbu)U56 zEiV*0B!*;cL>d4MOZvFY0+oQ6>klRX5;;OT4eLSt8yNW|I8K^ADiabQL0xrzA0Y;v z&XPfKpcujA;Xz9<i~za^$=kyNN5<<5(DF!OB=Cel2p|!{qVeP(fe29W_kC@H;v}s? zmPtT_60$C@&V~CCqhUSCh&Hvl(Ls@4g^)v|FcEbNcreeO2P2h|iDV*<M8XkWIV5)q z@*<O5h$IS8A4+Er3Bb6N|7WdLG=_@UGntfNP!2<~6y26y=4>EQztS(opibXfg=_73 zd~J6$tW*XGlK6l?*M(01T2v0j!3v%X@Dm~jOvC!c#eoP9DH!DHfI<l%!;tV^c=ykJ z6e0kLr|*r3BfD`(o)kAP3Yq9aBx`#ke6<>#=Dt~t9{pA|EbX=XycE5|r=@(2<`1P% z3EJ!b7f)2e7fk96KA9It3;I1|4j}`LFh0?c#rRAFB*-9?Arr5(ud)+`GDrszm42a4 z6v`<-DgTCxe;B&icT)bWz^cm1J#H1bCJr9ft|YWIf7gni_j)$mx~}}yvp~GN7wzD& z4mW6V$2q<%dsoH6iX6)hdEfP0A>_Bo{X<>-ziQw<Z`SM|K(M;iqC4casmYpCLzw{) zjV{UJ8q`^y`c}EHna;6WG3Az1nkKC3c*C&bP6>&<abMu0FsA33%hN8idm>|AoswQC zoD`IKBQwpC!5WP1&c`+^2t^MCRau$8bNJ7XyokdY_R}jI@@gtaY!#HVM|Qss^G&=$ zmRGO3AV^v@b^eAaGj`@a5@xRp6wJNch*~Q<yrtpXL*K^ZgR9T<h*wrF$(SFR8W6Md z8L29<jZbEakG>l_d1BUFTc<$9;Gxmq&iI@!=1qL<-SW<-wqnHWPSGQ0_P`2S#-pWe zazMS-v=vuZhmX&v)pc#&=13E7vM69C`OL|B>ne)fP#Z0Za=u*w-{`jA<WaWFvvNI( z_WD8nh(%ki@UWTVn|kD@%S;*^DMz6!rfDAo)V}>QQ7EIM41Yh4D7|-ILp<6K-P|zE zZ4cS%w<f8uvYKpNxG!)~RgK$9PVQI`+HcZe$twa2%|DiNC^O^7QKy|2rr0OpvnTZP z6Vp;t7r8>naUAZOv2DlQ18>*<d4F%pqW(Ni@sdpQ>CT*)*30)crN<P+&1W)>E8efZ zit8z`iD_8IaA~bSNq+BayV$PI(CzBBxb|}%Vmm*Sy^8UWbo1K0H%n`#MmwhG^LmCY zRm#W9Be{l6Str>A#<m7F4qJK;8H7w>A2n(<S}_q*yYaNjc1K#3`VZ2)Ulw#9KD#wF zLU2s+wDz4my4^uz+cZ2(e;tQ=R#BI<QEjAJwz5Oi`L1%<h{#+s(=)wV#hpCNS1o+C zW5CiYYIBoB_<c5kMM!@?LE5`iocYq~PV}Z)w~2w-Sw*u|qV)YGIqL5U7ubh(jG|1p zxZg`uZOiT2YSq^<#K~mPp|@pE)rM&sJXM{!T_vg$sIi?R*V@f`27PCE60hI&e{mtS z+}0;#YV7Zm|Hb|(G}&P>eEa5@XH|-^V{)9)y5%i=*E~VQ!>b-3WI%tPc4c6ma?<+! z53YP?f;-WfTYA4JUO`WbSh>A)IwNZmHOxzMG5CB>J#}6;_xk35m%GaC7>YFqwpe#& zcA6<{)?6Ij<Uk$tv6$UjA9!!VR_?>1^yq?EFOBu0F)xjc>U{I*XO3dDihg5%%JX{P zq}GA-{sp!zMQL-dyy0%H=|6U|JZIUfMlW^qoIc|=PqEeVf7g^-YJ!~CwMNyKWf+gK zD@|kH)|AuK?1BfHfu_m*OKf^XRxez`hpR7npDRx8!iU2xmk(f^?@TQut7mAsOo%0e zkCXmX;TVL9F0$!1!@3t{1~|iGKXaAO8xPuy=3QS?(EtRWPQ7=a)Z${OgC)eEIIcf) zLvzATd|KTY`mnnBr@TFc$n4sdftMkw?U_`Mwf@D%i+5g{j~XyINcE|4IzJegxS;5t zs;u0uMd=!Y(Xx?iBc+WYk7Kq=5B^^69I~3xfsc9so3w^Kp4H-T&AGfCztsaf8NECE z;Qal^jJYK*$j$@dEeoF(M%8X;1Vkb7Yf&Mcr7@UlbjNdn%Mta{S8HcJZEs!C9kJIf zzWz;N=}`Laf0P|O(QCUAmkk+co|Rx?H@yT+<WG2cyg4b)OvXBj3nz=3yQk&I1MH%s zeh^FgIp|(mkKxdiBRE$3r5Zn58uiqJlB@&7KUn>3jl%W!+Zwf3=P0W<!^{`8*~!6i U3iaj_?VnIaK&bzzB`Y)j3!slTdjJ3c literal 0 HcmV?d00001 diff --git a/resources/skins/Default/media/manual.png b/resources/skins/Default/media/manual.png new file mode 100644 index 0000000000000000000000000000000000000000..df30ed0cdc5164b2d27a87009079fddb574b830c GIT binary patch literal 2888 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 literal 0 HcmV?d00001 diff --git a/resources/skins/Default/media/ok.png b/resources/skins/Default/media/ok.png new file mode 100644 index 0000000000000000000000000000000000000000..d92923662370bd1a32e45da242f1891110ada854 GIT binary patch literal 2829 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 literal 0 HcmV?d00001 diff --git a/resources/skins/Default/media/pause.png b/resources/skins/Default/media/pause.png new file mode 100644 index 0000000000000000000000000000000000000000..c6510af68c59d935d4e2f47617627ea197dba75c GIT binary patch literal 2690 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 literal 0 HcmV?d00001 diff --git a/resources/skins/Default/media/play.png b/resources/skins/Default/media/play.png new file mode 100644 index 0000000000000000000000000000000000000000..94a92ade2adbe9e17b3a350a7426a163c1d06c94 GIT binary patch literal 2834 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 bs}VK6^%vTProrvXzp_;CK(ESG(HZ{(g^ySk literal 0 HcmV?d00001 diff --git a/resources/skins/Default/media/right.png b/resources/skins/Default/media/right.png new file mode 100644 index 0000000000000000000000000000000000000000..4e5c97fe43d3efa4019c6a3f4a4aaf274f055cef GIT binary patch literal 2758 zcmai030PBS7JY~!vJ{a(M-<5ypjeiy7)l_akc5B|WmBMxLP#FTlaLpZ2ZTjYii(Ij zpfD~7h@cKbnSg+ZOF%?BLs><oiU?{4sGxPJM8w*c#G+Q6`M$g)??30>|K9V?f0La- zt2w4d)<ysTm<IT>LeNhy&1GnSzSCxYlmWmzIusUxL~sLXd^iEm6Tq<`UXdV0ag^t+ zkn;HPAcBnrg^+}fdvsQX!$JZ&E_?--$dxid5#*mH14GkRhw;<m`5pqCH^azFK|>QH zfCvw(NDxcpGzA@}%}YbSYnBN(td@er({T~pAS@G>f!G!J6?h_!VTARP3F2rWEWbCw z=p7v=LJ%p9KuAeR!KYC0uuMoGd3bmbh-3np?20m6<*5>cr*M_X?KCNLIar{aFN35A z1WT}*oV-{#38CX~D2{!T98^GWvrFV}+DC;TXb=JkPb9ocmx^EnmWyENY(?+I%)%c8 zB2e5X_R%08&{6q5zJr1%{TP|c{onM2g!kHzBkW{Uh&Q6X*TNqg$iq^lARz>l!$~qe z$W8_&h@DmmjV?4MEQV#Fs5L-3j`Vgp2vUIJ2o{t8O5~{MbR3oVCou9ua8@*3RxT_+ zlTx&CZ=nDtGe`!<L1Gk>hj4tc0c<9f<UysnlJU9%G&<4(5_m!|5R?cJ(d_1LfpAFh z=XFhk;v|hj)=D5$60&w*ZAiphjE-|Bqu$i`MhiuL6ha>wjf=Wlz(aT}9)ea%CX$J+ zB$6wU5=L4<LthlCGm%6i>QZU>umFln{eNtYqp@BnpUb8BLvjR`rD~USHV*|8b$7a3 zF{Jgk#^D-1kFV*Dj+4q@K@uMnXuHt*UxUiwI3$H91AT<(1E%AA;^H7whg2+jbReMw zlwnEuukb70k4X`MXg*zUL|3v~7|EUH_7#mxbS9EDy%9cIjn;FYtVV}^s+u6pv3tK1 zUBJ7gd`#vyrFaoE$NvwVya*pCsUy5IFVGfrYv>$82O42^qM<jlGZB=agHVP}Ji4vV zGyv#rg;1A%;7$Noo}ZN8?aT_sT=6!^U+h<_R331v&NFbJT2V+CEB^lI!w20}{XZzq zyBCNHCovAx&8}+W0jK!Noc+}<)jLhE${+sFA4r}`e)MAS(fLv2p-0e?l@N5Q%XmES zx}iZv&5Ld9@HXdUaXrw$>+C-vyuu8#i8kwBo<15}TcvuLGH{F(`t2dVkzlTS-9?Lw zp${V0KdzCUEi&P3>)w`b8W5!3FrJT7eYqO*f>Ud5H0|)~I$n5rmYrp_!|r<J%+eHE z!|}o=!QP3N$nsOMX9Y>Ii|n_UeZDVmM3}SLPhfko4cH_r&sCis_HL_EZ>)PDj#2t% z*+-_a*Y6u6)h71x$)Vzt)33k#=VIHX%l%T+Wxuu8F`AF^=0EZ1m}WFq&ny@y8F31I z9!<|0S<@>AJ2x42xi&T7<MZiFgWLBw(#6@v1>7XYvK><t(S|LJQKE00u2&=7<96B9 z%C+vwOo0Am=**09Z=<k$f#b6?=ug)Isehy#0E{g(mmYAa_%8sUUmn2n2@`FdJfw=p z_+YN6EZh!+uxo=KVmzJsJl@gToXV`-m3qg@aq4iEIlI+%cV>1i#!`%_U#aXjHpJZz zS>pPTpO~KZg(n3bO@=L}j5}H<x?Wy*)HQVY;FRCSo-&_r4NR^3=Y>}$#vHC#95CO$ zVMoon=ild6boKli+_!3UFs*QN;b�gL9iP9QOyes-idxTjolQ6b8>0WlLCuQ%Ob# zPDb6a1sa82Yqzmu(r?oo0$R+=LXQsU(OQrrzBW>GHN&7*KV$xXx89FA+)uL0z>e27 z?4oi6ko<n*^i%&n%h9EmM>$Mtos|hraw|seEj5my+-$rs(F+7xyv{q@k{aXG`WuGG z$yoVPZ|Qfw_mmXRocSl+TZR@jJ&$YjRhvxUnNcPB7MD(xq_+rm7VkUQxo|^JyB9Z8 zZy_eCXUHnA*Q&{Ogt70qQ=ql=ZFe@uN)4RTPhMPdWbTlGy7Yu|hoksjG`^<bmr|Rb zRbtHfy;7{_f#dkniMTCe8Reb42zK|pnWiImIm`at*m8KX@6Md#1GjTdil#%0;F1k( zMAL7|Z{<}SDM??ma`5c3f@fs=n;ui#K8x;2n}R)8{9Url6gTsq4fLoC%ge7tubVE~ z&xp?C?P^_cr%rjV_<@N#aKpidGGM~Prd{?p-Te5tyhguTF_VkuaTXO{P3AQI>x=DW zV|ktd%65;^{6bdxx@^WHFUddbTV2<r4;lV_H?yF`Y8cO!-d>*69dCZ_F0}VT=OU%c zIHLD(EL><lLFxMs^kDHsWUb41g`?=F#8n2ZMa$RUe=TD^hsMRj8~m&HMmgEl05`l= zomsZMpW9Mnw(97%`8^j^`Dd)}HDvVJ(kEB<IWS-2a&}kSum79(kE2hoeRlbDs^K2- zb)oYw*Y0&}tRkec*1~4TVoxzf=WKSVP><LdIRMRhMSwE*wtmKAb<eShyGwpLp(xri z<yRZE^@iSW?FhKJkG4;dVmEjSs=8QF<Tm-L+|FjC*uJFpuN74?r&o0%$Gcxg?o@Be zrW69DC!6zf&-ar#4V6Vn!%|D@r^R(vSFewK{=G|kJ+(a-div#s8^L&+nU$)J3U)`O znZtUUs54>HYZsOZ&&_+BG(5)+bE#t?qf@cRE2F&2&a7=Xlh#iLUZti{<u%p(Rl5#$ zsyr>(ncdA_Pq)_3bD|xf?m4!@vHr!_NX((1MNfO%!e0;ewxMUnP1IAib%D2Deo)y& Tj`Ufh=I>_!do`=ZHzw<M7D6+I literal 0 HcmV?d00001 diff --git a/resources/skins/Default/media/specials.png b/resources/skins/Default/media/specials.png new file mode 100644 index 0000000000000000000000000000000000000000..17c187e100decae1f2daf8e0bee2e098a9b7e917 GIT binary patch literal 3007 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 H6P5m7?mM2) literal 0 HcmV?d00001 diff --git a/resources/skins/Default/media/stop.png b/resources/skins/Default/media/stop.png new file mode 100644 index 0000000000000000000000000000000000000000..07b7d8d31156b3c88f70be2ad0b388b37532e115 GIT binary patch literal 2670 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 X+Gmf}kej^u=-)&ZBZOYGCN|^0cf$<w literal 0 HcmV?d00001 diff --git a/resources/skins/Default/media/up.png b/resources/skins/Default/media/up.png new file mode 100644 index 0000000000000000000000000000000000000000..e2de1263535f66ac368ed064be41ad2c24efbb98 GIT binary patch literal 2765 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 Uz6BLOsQ!hrm_gp<p0U|~0lV=z3;+NC literal 0 HcmV?d00001 diff --git a/resources/skins/Default/media/updn.png b/resources/skins/Default/media/updn.png new file mode 100644 index 0000000000000000000000000000000000000000..884b049e4441657be334a150a7c35110942e9086 GIT binary patch literal 684 zcmV;d0#p5oP)<h;3K|Lk000e1NJLTq002Ay002A)1^@s6I{evk0004kX+uL$b5ch_ zAW20-HZeIiHZ3wPF#rH4k#&)|O9Md^hrb*i7)3z~3yVdfRU#e`w6RF=0<}mYcr}|f zhmd30jYzN+Yy%3SjaQ+6fwqFRASj9;Ru<NZHY)4eM2Lua!<+ZbZ{|H_-heZj)-tBM z7c$wr5grRFvvc!G-4h<%G|)n=s+sy!WHd@ud%wkD$7{b`tNOWr&9f9YHE`4m4{N%S z7akX$T+Qn?Zwa?)DRoKsMA&D@1%+>HJyzyto0Db!wAqM;L*Q%?R+7Is_KUTYkr9@c z<ISX3w4eLfFKLWtXCe|i#XB$wGe!`F2}TGJppPM$1NiBuM^;qH?=9s|lapLcW}N~C z%OpvW$0zEFRF6wch#r@breOE~Z(q&CK!3S<V-RW{E$g)l>Ner4XjvZzmi2WAt_L{J zR`PTEF!&~Vv7&kRpm`lmFDhDW8_qVM{a#lMwIsR3orwgz97EF_v|d5OLb+!@`yAat zbWOhM5_Wc>Th2ExegpRnXR|WQ4Ilsj010qNS#tmY3ljhU3ljkVnw%H_007WQL_t(& zf$iF{Q9@A^gyFpo2^n2z0oV+7q8%7ZH$olS0hFKtzlnm3jM01VINtx8x$*9a?<SHY z^$nN<^NQ@NiqIqvN$!iXrv=;q@1O6wy3eS925dRm0Ij;$Xn;F@T2=QN1+W127Y^P5 zi|T%Z1J)Og{qj`ZZ&1K3aQNlm1F)>_DM^wfNs=T<k|aq31D4%=mv;3bo*X9+yKW{A zNoS*$<TzW}d^`4;|2$ht&W34k6b#d5QwO%zUVz6WwANmy&M!gXxKxs)KjZ`E#!ee1 SQj_oi0000<MNUMnLSTY!p(O(V literal 0 HcmV?d00001