KoD 1.6.1

-Migliorata l'efficacia del riconoscimento dei contenuti in ricerca film/serie
- corretti alcuni bug e fatti alcuni fix per i soliti cambi di struttura
This commit is contained in:
mac12m99
2021-03-09 22:08:09 +01:00
parent f1da5c7a0b
commit fa0fe6e534
29 changed files with 377 additions and 330 deletions
+3 -5
View File
@@ -1,4 +1,4 @@
<addon id="plugin.video.kod" name="Kodi on Demand" version="1.6" provider-name="KoD Team"> <addon id="plugin.video.kod" name="Kodi on Demand" version="1.6.1" provider-name="KoD Team">
<requires> <requires>
<!-- <import addon="script.module.libtorrent" optional="true"/> --> <!-- <import addon="script.module.libtorrent" optional="true"/> -->
<import addon="metadata.themoviedb.org"/> <import addon="metadata.themoviedb.org"/>
@@ -27,10 +27,8 @@
<screenshot>resources/media/themes/ss/2.png</screenshot> <screenshot>resources/media/themes/ss/2.png</screenshot>
<screenshot>resources/media/themes/ss/3.png</screenshot> <screenshot>resources/media/themes/ss/3.png</screenshot>
</assets> </assets>
<news>- rimosso supporto a TVDB (l'accesso alle API diventerà a pagamento) <news>-Migliorata l'efficacia del riconoscimento dei contenuti in ricerca film/serie
- aggiunto canale Discovery+ - corretti alcuni bug e fatti alcuni fix per i soliti cambi di struttura</news>
- aggiunta possibilità di scegliere numerazioni alternative per le serie tv
- migliorie interne di vario tipo (tra cui un migliore riconoscimento dei contenuti nel caso siano scritti male)</news>
<description lang="it">Naviga velocemente sul web e guarda i contenuti presenti</description> <description lang="it">Naviga velocemente sul web e guarda i contenuti presenti</description>
<disclaimer>[COLOR red]The owners and submitters to this addon do not host or distribute any of the content displayed by these addons nor do they have any affiliation with the content providers.[/COLOR] <disclaimer>[COLOR red]The owners and submitters to this addon do not host or distribute any of the content displayed by these addons nor do they have any affiliation with the content providers.[/COLOR]
[COLOR yellow]Kodi © is a registered trademark of the XBMC Foundation. We are not connected to or in any other way affiliated with Kodi, Team Kodi, or the XBMC Foundation. Furthermore, any software, addons, or products offered by us will receive no support in official Kodi channels, including the Kodi forums and various social networks.[/COLOR]</disclaimer> [COLOR yellow]Kodi © is a registered trademark of the XBMC Foundation. We are not connected to or in any other way affiliated with Kodi, Team Kodi, or the XBMC Foundation. Furthermore, any software, addons, or products offered by us will receive no support in official Kodi channels, including the Kodi forums and various social networks.[/COLOR]</disclaimer>
+1 -1
View File
@@ -22,7 +22,7 @@
"eurostreaming": "https://eurostreaming.team", "eurostreaming": "https://eurostreaming.team",
"filmgratis": "https://www.filmaltadefinizione.me", "filmgratis": "https://www.filmaltadefinizione.me",
"filmigratis": "https://filmigratis.org", "filmigratis": "https://filmigratis.org",
"filmsenzalimiticc": "https://www.filmsenzalimiti01.surf", "filmsenzalimiticc": "https://www.filmsenzalimiti01.xyz",
"filmstreaming01": "https://filmstreaming01.com", "filmstreaming01": "https://filmstreaming01.com",
"guardaserie_stream": "https://guardaserie.yoga", "guardaserie_stream": "https://guardaserie.yoga",
"guardaseriecam": "https://guardaserie.cam", "guardaseriecam": "https://guardaserie.cam",
+2 -9
View File
@@ -10,14 +10,7 @@ typo = support.typo
session = requests.Session() session = requests.Session()
host = support.config.get_channel_url() host = support.config.get_channel_url()
def getToken(): token = session.get('https://disco-api.discoveryplus.it/token?realm=dplayit').json()['data']['attributes']['token']
token = config.get_setting('token', 'discoveryplus', None)
if not token:
token = session.get('https://disco-api.discoveryplus.it/token?realm=dplayit').json()['data']['attributes']['token']
config.set_setting('token', token, 'discoveryplus')
return token
token = getToken()
api = "https://disco-api.discoveryplus.it" api = "https://disco-api.discoveryplus.it"
headers = {'User-Agent': 'Mozilla/50.0 (Windows NT 10.0; WOW64; rv:45.0) Gecko/20100101 Firefox/45.0', headers = {'User-Agent': 'Mozilla/50.0 (Windows NT 10.0; WOW64; rv:45.0) Gecko/20100101 Firefox/45.0',
@@ -70,7 +63,7 @@ def live(item):
logger.debug() logger.debug()
itemlist =[] itemlist =[]
for name, values in liveDict().items(): for name, values in liveDict().items():
itemlist.append(item.clone(title=typo(name), fulltitle=name, plot=values['plot'], url=values['url'], id=values['id'], action='play', forcethumb=True, no_return=True)) itemlist.append(item.clone(title=typo(name,'bold'), fulltitle=name, plot=values['plot'], url=values['url'], id=values['id'], action='play', forcethumb=True, no_return=True))
return support.thumb(itemlist, live=True) return support.thumb(itemlist, live=True)
+1 -1
View File
@@ -307,7 +307,7 @@ def play(item):
data = support.match(sec_data, patron=r'<video src="([^"]+)').match data = support.match(sec_data, patron=r'<video src="([^"]+)').match
break break
else: else:
support.dbg() # support.dbg()
data = url data = url
return support.servertools.find_video_items(item, data=data) return support.servertools.find_video_items(item, data=data)
+1 -1
View File
@@ -88,7 +88,7 @@ def peliculas(item):
patron = r'href="(?P<url>[^"]+)"[^>]+>(?P<title>.+?)(?:\s(?P<year>\d{4})|<)' patron = r'href="(?P<url>[^"]+)"[^>]+>(?P<title>.+?)(?:\s(?P<year>\d{4})|<)'
patronBlock = r'Lista dei film disponibili in streaming e anche in download\.</p>(?P<block>.*?)<div class="footer_c">' patronBlock = r'Lista dei film disponibili in streaming e anche in download\.</p>(?P<block>.*?)<div class="footer_c">'
else: else:
patron = r'<tr><td><a href="(?P<url>[^"]+)"(?:|.+?)?>(?:&nbsp;&nbsp;)?[ ]?(?P<title>.*?)[ ]?(?P<quality>HD)?[ ]?(?P<year>\d+)?(?: | HD | Streaming | MD(?: iSTANCE)? )?</a>' patron = r'<tr><td><a href="(?P<url>[^"]+)"(?:|.+?)?>(?:&nbsp;&nbsp;)?[ ]?(?P<title>.*?)[ ]?(?P<quality>HD)?[ ]?(?P<year>\d+)?(?: | HD[^<]*| Streaming[^<]*| MD(?: iSTANCE)? [^<]*)?</a>'
def itemHook(item): def itemHook(item):
if 'film' in item.url: if 'film' in item.url:
+13 -26
View File
@@ -5,17 +5,11 @@
from core import support from core import support
import sys import sys
if sys.version_info[0] >= 3: from concurrent import futures
else: from concurrent_py2 import futures
host = support.config.get_channel_url() host = support.config.get_channel_url()
headers = [['Referer', host]] headers = [['Referer', host]]
@support.menu @support.menu
def mainlist(item): def mainlist(item):
@@ -33,22 +27,10 @@ def search(item, text):
support.info(text) support.info(text)
# item.args='search' # item.args='search'
item.text = text item.text = text
itemlist = [] item.url = item.url + '/?%73=' + text.replace(' ', '+')
itlist = []
try: try:
# item.url = host + '/lista-serie-tv/' return peliculas(item)
# item.contentType = 'tvshow'
# itemlist += peliculas(item)
with futures.ThreadPoolExecutor() as executor:
for par in [['/serie-tv/', 'tvshow', ''],['/anime/', 'tvshow', ''], ['/-anime-sub-ita/', 'tvshow', 'sub'], ['/film-animazione/', 'movie', '']]:
item.url = host + par[0]
item.contentType = par[1]
item.args = par[2]
itlist.append(executor.submit(peliculas, item))
for res in futures.as_completed(itlist):
itemlist += res.result()
return itemlist
# Continua la ricerca in caso di errore # Continua la ricerca in caso di errore
except: except:
import sys import sys
@@ -75,21 +57,26 @@ def newest(categoria):
@support.scrape @support.scrape
def peliculas(item): def peliculas(item):
search = item.text # debugBlock = True
# debug = True
# search = item.text
if item.contentType != 'movie': anime = True if item.contentType != 'movie': anime = True
action = 'findvideos' if item.contentType == 'movie' else 'episodios' action = 'findvideos' if item.contentType == 'movie' else 'episodios'
blacklist = ['-Film Animazione disponibili in attesa di recensione '] blacklist = ['-Film Animazione disponibili in attesa di recensione ']
if search: if item.action == 'search':
pagination = '' pagination = ''
patronBlock = '"lcp_catlist"[^>]+>(?P<block>.*)</ul>' #patronBlock = '"lcp_catlist"[^>]+>(?P<block>.*)</ul>'
patron = r'href="(?P<url>[^"]+)" title="(?P<title>[^"]+)"' patronBlock = '<main[^>]+>(?P<block>.*?)</ma'
#patron = r'href="(?P<url>[^"]+)" title="(?P<title>[^"]+)"'
patron = r'<a href="(?P<url>[^"]+)"[^>]*>(?P<title>[^<]+)<[^>]+>[^>]+><div'
elif item.args == 'last': elif item.args == 'last':
patronBlock = 'Aggiornamenti</h2>(?P<block>.*)</ul>' patronBlock = 'Aggiornamenti</h2>(?P<block>.*)</ul>'
patron = r'<a href="(?P<url>[^"]+)">\s*<img[^>]+src(?:set)?="(?P<thumbnail>[^ ]+)[^>]+>\s*<span[^>]+>(?P<title>[^<]+)' patron = r'<a href="(?P<url>[^"]+)">\s*<img[^>]+src[set]{0,3}="(?P<thumbnail>[^ ]+)[^>]+>\s*<span[^>]+>(?P<title>[^<]+)'
else: else:
patronBlock = '<main[^>]+>(?P<block>.*)</main>' patronBlock = '<main[^>]+>(?P<block>.*)</main>'
patron = r'<a href="(?P<url>[^"]+)" rel="bookmark">(?P<title>[^<]+)</a>[^>]+>[^>]+>[^>]+><img.*?src="(?P<thumb>[^"]+)".*?<p>(?P<plot>[^<]+)</p>.*?<span class="cat-links">Pubblicato in.*?.*?(?P<type>(?:[Ff]ilm|</artic))[^>]+>' # patron = r'<a href="(?P<url>[^"]+)" rel="bookmark">(?P<title>[^<]+)</a>[^>]+>[^>]+>[^>]+><img.*?src="(?P<thumb>[^"]+)".*?<p>(?P<plot>[^<]+)</p>.*?<span class="cat-links">Pubblicato in.*?.*?(?P<type>(?:[Ff]ilm|</artic))[^>]+>'
patron = r'<a href="(?P<url>[^"]+)"[^>]+>(?P<title>[^<]+)</a>[^>]+>[^>]+>[^>]+><img.*?src="(?P<thumb>[^"]+)".*?<p>(?P<plot>[^<]+)</p>.*?tag">.*?(?P<type>(?:[Ff]ilm|</art|Serie Tv))'
typeContentDict={'movie':['film']} typeContentDict={'movie':['film']}
typeActionDict={'findvideos':['film']} typeActionDict={'findvideos':['film']}
patronNext = '<a class="next page-numbers" href="([^"]+)">' patronNext = '<a class="next page-numbers" href="([^"]+)">'
+1 -1
View File
@@ -350,7 +350,7 @@ class Item(object):
def fromurl(self, url): def fromurl(self, url):
""" """
Generate an item from a text string. The string can be created by the tourl () function or have Generate an item from a text string. The string can be created by the tourl () function or have
        the old format: plugin: //plugin.video.kod/? channel = ... (+ other parameters) the old format: plugin: //plugin.video.kod/? channel = ... (+ other parameters)
Use: item.fromurl("string") Use: item.fromurl("string")
@param url: url @param url: url
+23 -13
View File
@@ -66,6 +66,10 @@ otmdb_global = None
from core import db from core import db
def clean_cache():
db['tmdb_cache'].clear()
# The function name is the name of the decorator and receives the function that decorates. # The function name is the name of the decorator and receives the function that decorates.
def cache_response(fn): def cache_response(fn):
logger.debug() logger.debug()
@@ -180,7 +184,8 @@ def set_infoLabels_itemlist(item_list, seekTmdb=False, idioma_busqueda=def_lang,
""" """
Concurrently, it gets the data of the items included in the item_list. Concurrently, it gets the data of the items included in the item_list.
The API has a limit of 40 requests per IP every 10 '' and that is why the list should not have more than 30 items to ensure the proper functioning of this function. The API in the past had a limit of 40 requests per IP every 10 '', now there's no limit (https://developers.themoviedb.org/3/getting-started/request-rate-limiting)
If a limit will be re-added uncomment "tmdb_threads" and related code
@param item_list: list of Item objects that represent movies, series or chapters. The infoLabels attribute of each Item object will be modified including the extra localized data. @param item_list: list of Item objects that represent movies, series or chapters. The infoLabels attribute of each Item object will be modified including the extra localized data.
@type item_list: list @type item_list: list
@@ -197,18 +202,18 @@ def set_infoLabels_itemlist(item_list, seekTmdb=False, idioma_busqueda=def_lang,
return return
import threading import threading
threads_num = config.get_setting("tmdb_threads", default=20) # threads_num = config.get_setting("tmdb_threads", default=20)
semaforo = threading.Semaphore(threads_num) # semaforo = threading.Semaphore(threads_num)
lock = threading.Lock() lock = threading.Lock()
r_list = list() r_list = list()
i = 0 i = 0
l_hilo = list() l_hilo = list()
def sub_thread(_item, _i, _seekTmdb): def sub_thread(_item, _i, _seekTmdb):
semaforo.acquire() # semaforo.acquire()
ret = set_infoLabels_item(_item, _seekTmdb, idioma_busqueda, lock) ret = set_infoLabels_item(_item, _seekTmdb, idioma_busqueda, lock)
# logger.debug(str(ret) + "item: " + _item.tostring()) # logger.debug(str(ret) + "item: " + _item.tostring())
semaforo.release() # semaforo.release()
r_list.append((_i, _item, ret)) r_list.append((_i, _item, ret))
for item in item_list: for item in item_list:
@@ -560,16 +565,14 @@ def select_group(groups, item):
selected = -1 selected = -1
url = 'https://api.themoviedb.org/3/tv/{}?api_key=a1ab8b8669da03637a4b98fa39c39228&language={}'.format(item.infoLabels['tmdb_id'], def_lang) url = 'https://api.themoviedb.org/3/tv/{}?api_key=a1ab8b8669da03637a4b98fa39c39228&language={}'.format(item.infoLabels['tmdb_id'], def_lang)
res = requests.get(url).json() res = requests.get(url).json()
selections = ['[B]Original[/B] Seasons: {} Episodes: {}'.format(res.get('number_of_seasons',0), res.get('number_of_episodes',0))] selections = [['Original',res.get('number_of_seasons',0), res.get('number_of_episodes',0), '', item.thumbnail]]
ids = ['original'] ids = ['original']
for group in groups: for group in groups:
name = '[B]{}[/B] Seasons: {} Episodes: {}'.format(group.get('name',''), group.get('group_count',0), group.get('episode_count',0)) # name = '{} Seasons: {} Episodes: {}'.format(group.get('name',''), group.get('group_count',0), group.get('episode_count',0))
description = group.get('description','') # description = group.get('description','')
if description:
name = '{}\n{}'.format(name, description)
ID = group.get('id','') ID = group.get('id','')
if ID: if ID:
selections.append(name) selections.append([group.get('name',''), group.get('group_count',0), group.get('episode_count',0), group.get('description',''), item.thumbnail])
ids.append(ID) ids.append(ID)
if selections and ids: if selections and ids:
selected = platformtools.dialog_select_group(config.get_localized_string(70831), selections) selected = platformtools.dialog_select_group(config.get_localized_string(70831), selections)
@@ -1454,7 +1457,14 @@ class Tmdb(object):
def get_list_episodes(self): def get_list_episodes(self):
url = 'https://api.themoviedb.org/3/tv/{id}?api_key=a1ab8b8669da03637a4b98fa39c39228&language={lang}'.format(id=self.busqueda_id, lang=self.busqueda_idioma) url = 'https://api.themoviedb.org/3/tv/{id}?api_key=a1ab8b8669da03637a4b98fa39c39228&language={lang}'.format(id=self.busqueda_id, lang=self.busqueda_idioma)
results = requests.get(url).json().get('seasons', []) results = requests.get(url).json().get('seasons', [])
return results if 'Error' not in results else [] seasons = []
if results and 'Error' not in results:
for season in results:
url = 'https://api.themoviedb.org/3/tv/{id}/season/{season}?api_key=a1ab8b8669da03637a4b98fa39c39228&language={lang}'.format(id=self.busqueda_id, season=season['season_number'], lang=self.busqueda_idioma)
try: start_from = requests.get(url).json()['episodes'][0]['episode_number']
except: start_from = 1
seasons.append({'season_number':season['season_number'], 'episode_count':season['episode_count'], 'start_from':start_from})
return seasons
def get_videos(self): def get_videos(self):
""" """
@@ -1617,7 +1627,7 @@ class Tmdb(object):
elif k == 'credits_cast' or k == 'temporada_cast' or k == 'episodio_guest_stars': elif k == 'credits_cast' or k == 'temporada_cast' or k == 'episodio_guest_stars':
dic_aux = dict((name, character) for (name, character) in l_castandrole) dic_aux = dict((name, character) for (name, character) in l_castandrole)
l_castandrole.extend([(p['name'], p.get('character', '') or p.get('character_name', '')) \ l_castandrole.extend([(p['name'], p.get('character', '') or p.get('character_name', '')) \
for p in v if p['name'] not in list(dic_aux.keys())]) for p in v if 'name' in p and p['name'] not in list(dic_aux.keys())])
elif k == 'videos': elif k == 'videos':
if not isinstance(v, list): if not isinstance(v, list):
+1 -1
View File
@@ -33,7 +33,7 @@ def country(config, common_words):
return CountryFinder(allowed_countries, common_words).find(string) return CountryFinder(allowed_countries, common_words).find(string)
rebulk.functional(find_countries, rebulk.functional(find_countries,
#  Prefer language and any other property over country if not US or GB. # Prefer language and any other property over country if not US or GB.
conflict_solver=lambda match, other: match conflict_solver=lambda match, other: match
if other.name != 'language' or match.value not in (babelfish.Country('US'), if other.name != 'language' or match.value not in (babelfish.Country('US'),
babelfish.Country('GB')) babelfish.Country('GB'))
+2 -2
View File
@@ -1370,13 +1370,13 @@
video_codec: Xvid video_codec: Xvid
release_group: Etc-Group release_group: Etc-Group
type: movie type: movie
# Fallback to movie type because we can't tell it's a series ... # Fallback to movie type because we can't tell it's a series ...
? Show.Name.Part.1.and.Part.2.Blah-Group ? Show.Name.Part.1.and.Part.2.Blah-Group
: part: [1, 2] : part: [1, 2]
title: Show Name title: Show Name
type: movie type: movie
# Fallback to movie type because we can't tell it's a series ... # Fallback to movie type because we can't tell it's a series ...
? Show Name - 01 - Ep Name ? Show Name - 01 - Ep Name
: episode: 1 : episode: 1
+1 -1
View File
@@ -544,7 +544,7 @@
episode: episode:
- 1 - 1
- 7 - 7
episode_title: FooBar-Group # Make sure it doesn't conflict with uuid episode_title: FooBar-Group # Make sure it doesn't conflict with uuid
season: 1 season: 1
title: Test title: Test
type: episode type: episode
+3 -3
View File
@@ -191,20 +191,20 @@ class autorenumber():
seasons =[] seasons =[]
groupedSeasons = tmdb.get_group(self.group.replace('\n','').split('/')[-1]) groupedSeasons = tmdb.get_group(self.group.replace('\n','').split('/')[-1])
for groupedSeason in groupedSeasons: for groupedSeason in groupedSeasons:
seasons.append({'season_number':groupedSeason['order'], 'episode_count':len(groupedSeason['episodes'])}) seasons.append({'season_number':groupedSeason['order'], 'episode_count':len(groupedSeason['episodes']), 'start_from':groupedSeason['episodes'][0]['episode_number']})
else: else:
seasons = tmdb.Tmdb(id_Tmdb=self.id).get_list_episodes() seasons = tmdb.Tmdb(id_Tmdb=self.id).get_list_episodes()
count = 0 count = 0
for season in seasons: for season in seasons:
s = season['season_number'] s = season['season_number']
c = season['episode_count'] c = season['episode_count']
fe = season['start_from']
self.seasonsdict[str(s)] = c self.seasonsdict[str(s)] = c
if s > 0: if s > 0:
for e in range(1, c + 1): for e in range(1, c + 1):
count += 1 count += 1
self.epdict[count] = '{}x{:02d}'.format(s,e) self.epdict[count] = '{}x{:02d}'.format(s, e + fe - 1)
if self.item.renumber or self.manual: if self.item.renumber or self.manual:
self.item.renumber = False self.item.renumber = False
+3 -3
View File
@@ -170,13 +170,13 @@ def enable_disable_autorun(is_enabled):
set_setting('autostart', True) set_setting('autostart', True)
return True return True
def get_all_settings_addon(): def get_all_settings_addon():
# Read the settings.xml file and return a dictionary with {id: value} # Read the settings.xml file and return a dictionary with {id: value}
from core import scrapertools from core import scrapertools
infile = open(os.path.join(get_data_path(), "settings.xml"), "r") with open(os.path.join(get_data_path(), "settings.xml"), "rb") as infile:
data = infile.read() data = infile.read().decode('utf-8')
infile.close()
ret = {} ret = {}
matches = scrapertools.find_multiple_matches(data, '<setting id=\"([^\"]+)\"[^>]*>([^<]*)</setting>') matches = scrapertools.find_multiple_matches(data, '<setting id=\"([^\"]+)\"[^>]*>([^<]*)</setting>')
+3 -3
View File
@@ -134,8 +134,8 @@ def run(item=None):
elif item.action == "script": elif item.action == "script":
from core import tmdb from core import tmdb
if tmdb.drop_bd(): tmdb.clean_cache()
platformtools.dialog_notification(config.get_localized_string(20000), config.get_localized_string(60011), time=2000, sound=False) platformtools.dialog_notification(config.get_localized_string(20000), config.get_localized_string(60011), time=2000, sound=False)
elif item.action == "itemInfo": elif item.action == "itemInfo":
platformtools.dialog_textviewer('Item info', item.parent) platformtools.dialog_textviewer('Item info', item.parent)
elif item.action == "open_browser": elif item.action == "open_browser":
@@ -332,7 +332,7 @@ def run(item=None):
platformtools.dialog_ok(config.get_localized_string(60087) % Channel, config.get_localized_string(60014)) platformtools.dialog_ok(config.get_localized_string(60087) % Channel, config.get_localized_string(60014))
else: else:
if platformtools.dialog_yesno(config.get_localized_string(60038), config.get_localized_string(60015)): if platformtools.dialog_yesno(config.get_localized_string(60038), config.get_localized_string(60015)):
run(Item(channel="setting", action="report_menu")) platformtools.itemlist_update(Item(channel="setting", action="report_menu"), True)
finally: finally:
# db need to be closed when not used, it will cause freezes # db need to be closed when not used, it will cause freezes
from core import db from core import db
+58 -44
View File
@@ -239,9 +239,14 @@ def dialog_select_group(heading, _list, preselect=0):
def onInit(self): def onInit(self):
self.getControl(1).setText(self.heading) self.getControl(1).setText(self.heading)
itemlist = [] itemlist = []
for n, text in enumerate(self.list): for n, it in enumerate(self.list):
logger.debug(it)
item = xbmcgui.ListItem(str(n)) item = xbmcgui.ListItem(str(n))
item.setProperty('title', text) item.setProperty('title', it[0])
item.setProperty('seasons', str(it[1]))
item.setProperty('episodes', str(it[2]))
item.setProperty('description', '\n' + it[3])
item.setProperty('thumb', it[4])
itemlist.append(item) itemlist.append(item)
self.getControl(2).addItems(itemlist) self.getControl(2).addItems(itemlist)
@@ -441,13 +446,13 @@ def set_view_mode(item, parent_item):
def set_infolabels(listitem, item, player=False): def set_infolabels(listitem, item, player=False):
""" """
Method to pass the information to the listitem (see tmdb.set_InfoLabels()) Method to pass the information to the listitem (see tmdb.set_InfoLabels())
    item.infoLabels is a dictionary with the key / value pairs described in: item.infoLabels is a dictionary with the key / value pairs described in:
    http://mirrors.xbmc.org/docs/python-docs/14.x-helix/xbmcgui.html#ListItem-setInfo http://mirrors.xbmc.org/docs/python-docs/14.x-helix/xbmcgui.html#ListItem-setInfo
    https://kodi.wiki/view/InfoLabels https://kodi.wiki/view/InfoLabels
    @param listitem: xbmcgui.ListItem object @param listitem: xbmcgui.ListItem object
    @type listitem: xbmcgui.ListItem @type listitem: xbmcgui.ListItem
    @param item: Item object that represents a movie, series or chapter @param item: Item object that represents a movie, series or chapter
    @type item: item @type item: item
""" """
infoLabels_dict = {'aired': 'aired', 'album': 'album', 'artist': 'artist', 'cast': 'cast', infoLabels_dict = {'aired': 'aired', 'album': 'album', 'artist': 'artist', 'cast': 'cast',
@@ -482,31 +487,30 @@ def set_infolabels(listitem, item, player=False):
def set_context_commands(item, item_url, parent_item, **kwargs): def set_context_commands(item, item_url, parent_item, **kwargs):
""" """
Function to generate context menus. Function to generate context menus.
        1. Based on the data in item.context 1. Based on the data in item.context
            a. Old method item.context type str separating options by "|" (example: item.context = "1 | 2 | 3") a. Old method item.context type str separating options by "|" (example: item.context = "1 | 2 | 3")
                (only predefined) (only predefined)
            b. List method: item.context is a list with the different menu options: b. List method: item.context is a list with the different menu options:
                - Predefined: A predefined option will be loaded with a name. - Predefined: A predefined option will be loaded with a name.
                    item.context = ["1", "2", "3"] item.context = ["1", "2", "3"]
                - dict (): The current item will be loaded modifying the fields included in the dict () in case of - dict (): The current item will be loaded modifying the fields included in the dict () in case of
                    modify the channel and action fields these will be saved in from_channel and from_action. modify the channel and action fields these will be saved in from_channel and from_action.
                    item.context = [{"title": "Name of the menu", "action": "action of the menu", "channel": "menu channel"}, {...}] item.context = [{"title": "Name of the menu", "action": "action of the menu", "channel": "menu channel"}, {...}]
        2. Adding options according to criteria 2. Adding options according to criteria
            Options can be added to the context menu to items that meet certain conditions. Options can be added to the context menu to items that meet certain conditions.
3. Adding options to all items
Options can be added to the context menu for all items
        3. Adding options to all items 4. You can disable the context menu options by adding a command 'no_context' to the item.context.
            Options can be added to the context menu for all items The options that Kodi, the skin or another added add to the contextual menu cannot be disabled.
        4. You can disable the context menu options by adding a command 'no_context' to the item.context. @param item: element that contains the contextual menus
            The options that Kodi, the skin or another added add to the contextual menu cannot be disabled. @type item: item
@param parent_item:
    @param item: element that contains the contextual menus @type parent_item: item
    @type item: item
    @param parent_item:
    @type parent_item: item
""" """
context_commands = [] context_commands = []
# num_version_xbmc = config.get_platform(True)['num_version'] # num_version_xbmc = config.get_platform(True)['num_version']
@@ -821,9 +825,9 @@ def show_channel_settings(**kwargs):
def show_video_info(*args, **kwargs): def show_video_info(*args, **kwargs):
""" """
It shows a window with the info of the video. It shows a window with the info of the video.
    The parameters passed to it can be seen in the method that is called The parameters passed to it can be seen in the method that is called
    @return: returns the window with the elements @return: returns the window with the elements
@rtype: InfoWindow @rtype: InfoWindow
""" """
@@ -1420,6 +1424,7 @@ def get_played_time(item):
logger.debug() logger.debug()
from core import db from core import db
played_time = 0
if not item.infoLabels: if not item.infoLabels:
return 0 return 0
ID = item.infoLabels.get('tmdb_id', '') ID = item.infoLabels.get('tmdb_id', '')
@@ -1430,13 +1435,18 @@ def get_played_time(item):
E = item.infoLabels.get('episode') E = item.infoLabels.get('episode')
result = None result = None
if item.contentType == 'movie': try:
result = db['viewed'].get(ID) if item.contentType == 'movie':
elif S and E: result = db['viewed'].get(ID)
result = db['viewed'].get(ID, {}).get(str(S)+'x'+str(E)) elif S and E:
result = db['viewed'].get(ID, {}).get(str(S)+'x'+str(E))
if not result: played_time = 0 if result:
else: played_time = result played_time = result
except:
import traceback
logger.error(traceback.format_exc())
del db['viewed'][ID]
return played_time return played_time
@@ -1456,13 +1466,17 @@ def set_played_time(item):
S = item.infoLabels.get('season', 0) S = item.infoLabels.get('season', 0)
E = item.infoLabels.get('episode') E = item.infoLabels.get('episode')
try:
if item.contentType == 'movie': if item.contentType == 'movie':
db['viewed'][ID] = played_time db['viewed'][ID] = played_time
elif E: elif E:
newDict = db['viewed'].get(ID, {}) newDict = db['viewed'].get(ID, {})
newDict[str(S) + 'x' + str(E)] = played_time newDict[str(S) + 'x' + str(E)] = played_time
db['viewed'][ID] = newDict db['viewed'][ID] = newDict
except:
import traceback
logger.error(traceback.format_exc())
del db['viewed'][ID]
def prevent_busy(item): def prevent_busy(item):
+1 -1
View File
@@ -132,7 +132,7 @@ def SettingOnPosition(item):
def select(item): def select(item):
from core.support import dbg;dbg() # from core.support import dbg;dbg()
from platformcode import config, platformtools from platformcode import config, platformtools
# item.id = setting ID # item.id = setting ID
# item.type = labels or values # item.type = labels or values
+2 -2
View File
@@ -104,7 +104,7 @@ def check(background=False):
patch_url = commitJson['html_url'] + '.patch' patch_url = commitJson['html_url'] + '.patch'
logger.info('applicando ' + patch_url) logger.info('applicando ' + patch_url)
from lib import patch from lib import patch
patch.fromurl(patch_url).apply(root=addonDir) patchOk = patch.fromurl(patch_url).apply(root=addonDir)
for file in commitJson['files']: for file in commitJson['files']:
if file["filename"] == trackingFile: # il file di tracking non si modifica if file["filename"] == trackingFile: # il file di tracking non si modifica
@@ -115,7 +115,7 @@ def check(background=False):
poFilesChanged = True poFilesChanged = True
if 'service.py' in file["filename"]: if 'service.py' in file["filename"]:
serviceChanged = True serviceChanged = True
if (file['status'] == 'modified' and 'patch' not in file) or file['status'] == 'added': if (file['status'] == 'modified' and 'patch' not in file) or file['status'] == 'added' or (file['status'] == 'modified' and not patchOk):
# è un file NON testuale che è stato modificato, oppure è un file nuovo (la libreria non supporta la creazione di un nuovo file) # è un file NON testuale che è stato modificato, oppure è un file nuovo (la libreria non supporta la creazione di un nuovo file)
# lo devo scaricare # lo devo scaricare
filename = os.path.join(addonDir, file['filename']) filename = os.path.join(addonDir, file['filename'])
@@ -2017,7 +2017,7 @@ msgid "Yes, the option to display merged or split results by channels can be fou
msgstr "" msgstr ""
msgctxt "#60467" msgctxt "#60467"
msgid "To report a problem on'https://t.me/kodiondemand' you need to:|the version you're using of Alpha.|The version you're using of Kodi, mediaserver, etc.|the version and name of the operating system you're using.|The name of the skin (in case you're using Kodi) and whether using the default skin has solved the problem.|Description of the problem and any test cases.To activate the log in detailed mode, go to:|Configuration.|Preferences.|In the General tab - Check the option: Generate detailed log. The detailed log file can be found in the following path: \n\n%s" msgid "To report a problem on'https://t.me/kodiondemand' you need to:|the version you're using of Alpha.|The version you're using of Kodi, mediaserver, etc.|the version and name of the operating system you're using.|The name of the skin (in case you're using Kodi) and whether using the default skin has solved the problem.|Description of the problem and any test cases.To activate the log in detailed mode, go to:|Configuration.|Preferences.|In the General tab - Check the option: Generate detailed log. The detailed log file can be found in the following path: \n\n%s"
msgstr "" msgstr ""
msgctxt "#60468" msgctxt "#60468"
@@ -2016,7 +2016,7 @@ msgid "Yes, the option to display merged or split results by channels can be fou
msgstr "Sì, l'opzione per mostrare i risultati uniti o divisi per canali si trova in 'Impostazioni>Impostazioni ricerca globale>Altre impostazioni'. Vuoi accedere a queste impostazioni?" msgstr "Sì, l'opzione per mostrare i risultati uniti o divisi per canali si trova in 'Impostazioni>Impostazioni ricerca globale>Altre impostazioni'. Vuoi accedere a queste impostazioni?"
msgctxt "#60467" msgctxt "#60467"
msgid "To report a problem on'https://t.me/kodiondemand' you need to:|the version you're using of Alpha.|The version you're using of Kodi, mediaserver, etc.|the version and name of the operating system you're using.|The name of the skin (in case you're using Kodi) and whether using the default skin has solved the problem.|Description of the problem and any test cases.To activate the log in detailed mode, go to:|Configuration.|Preferences.|In the General tab - Check the option: Generate detailed log. The detailed log file can be found in the following path: \n\n%s" msgid "To report a problem on'https://t.me/kodiondemand' you need to:|the version you're using of Alpha.|The version you're using of Kodi, mediaserver, etc.|the version and name of the operating system you're using.|The name of the skin (in case you're using Kodi) and whether using the default skin has solved the problem.|Description of the problem and any test cases.To activate the log in detailed mode, go to:|Configuration.|Preferences.|In the General tab - Check the option: Generate detailed log. The detailed log file can be found in the following path: \n\n%s"
msgstr "Per segnalare un problema su 'https://t.me/kodiondemand' è necessario:|la versione che si sta usando di Kodi on Demand.|La versione che si sta usando di Kodi, mediaserver, ecc.|la versione e il nome del sistema operativo che si sta usando.|Il nome della skin (nel caso in cui si stia usando Kodi) e se l'utilizzo della skin predefinita ha risolto il problema.|La descrizione del problema e tutti i casi di test.Per attivare il log in modalità dettagliata, andare su:|Configurazione.|Preferenze.|Nella scheda Generale - Selezionare l'opzione: Genera log dettagliato Il file di log dettagliato si trova nel seguente percorso: \n\n%s" msgstr "Per segnalare un problema su 'https://t.me/kodiondemand' è necessario:|la versione che si sta usando di Kodi on Demand.|La versione che si sta usando di Kodi, mediaserver, ecc.|la versione e il nome del sistema operativo che si sta usando.|Il nome della skin (nel caso in cui si stia usando Kodi) e se l'utilizzo della skin predefinita ha risolto il problema.|La descrizione del problema e tutti i casi di test.Per attivare il log in modalità dettagliata, andare su:|Configurazione.|Preferenze.|Nella scheda Generale - Selezionare l'opzione: Genera log dettagliato Il file di log dettagliato si trova nel seguente percorso: \n\n%s"
msgctxt "#60468" msgctxt "#60468"
+1 -1
View File
@@ -90,7 +90,7 @@
<setting id="search_channels" type="action" label="59994" action="RunPlugin(plugin://plugin.video.kod/?ew0KICAgICJhY3Rpb24iOiJzZXR0aW5nX2NoYW5uZWxfbmV3IiwNCiAgICAiY2hhbm5lbCI6InNlYXJjaCINCn0=)"/> <setting id="search_channels" type="action" label="59994" action="RunPlugin(plugin://plugin.video.kod/?ew0KICAgICJhY3Rpb24iOiJzZXR0aW5nX2NoYW5uZWxfbmV3IiwNCiAgICAiY2hhbm5lbCI6InNlYXJjaCINCn0=)"/>
<setting label="70154" type="lsep"/> <setting label="70154" type="lsep"/>
<setting id="tmdb_active" default="true" visible="false"/> <setting id="tmdb_active" default="true" visible="false"/>
<setting id="tmdb_threads" type="slider" option="int" range="5,5,30" label="70155" default="20"/> <!-- <setting id="tmdb_threads" type="slider" option="int" range="5,5,30" label="70155" default="20"/>-->
<setting id="tmdb_plus_info" type="bool" label="70156" default="false"/> <setting id="tmdb_plus_info" type="bool" label="70156" default="false"/>
<setting id="tmdb_cache" type="bool" label="70157" default="true"/> <setting id="tmdb_cache" type="bool" label="70157" default="true"/>
<setting id="tmdb_cache_expire" type="select" lvalues="70158|70159|70160|70161|70170" label="70162" enable="eq(-1,true)" default="4"/> <setting id="tmdb_cache_expire" type="select" lvalues="70158|70159|70160|70161|70170" label="70162" enable="eq(-1,true)" default="4"/>
+31 -41
View File
@@ -12,9 +12,9 @@
<controls> <controls>
<control type="group"> <control type="group">
<description>Container</description> <description>Container</description>
<left>200</left> <left>40</left>
<top>60</top> <top>60</top>
<width>860</width> <width>1200</width>
<height>600</height> <height>600</height>
<control type="image"> <control type="image">
<description>Background</description> <description>Background</description>
@@ -22,49 +22,56 @@
<height>100%</height> <height>100%</height>
<texture colordiffuse="FF232323">white.png</texture> <texture colordiffuse="FF232323">white.png</texture>
</control> </control>
<control type="image">
<description>Poster</description>
<top>0</top>
<left>0</left>
<height>600</height>
<width>400</width>
<texture>$INFO[Container(2).ListItem.Property(thumb)]</texture>
</control>
<control type="textbox" id="1"> <control type="textbox" id="1">
<description>Heading</description> <description>Heading</description>
<top>0</top> <top>0</top>
<left>0</left> <left>460</left>
<height>60</height> <height>80</height>
<width>100%</width> <width>680</width>
<font>font13</font> <font>font13</font>
<textcolor>FFFFFFFF</textcolor> <textcolor>FFFFFFFF</textcolor>
<align>center</align> <align>left</align>
<aligny>center</aligny> <aligny>center</aligny>
<label></label> <label></label>
</control> </control>
<control type="group"> <control type="group">
<left>30</left> <left>440</left>
<top>70</top> <top>80</top>
<width>795</width> <width>720</width>
<height>530</height> <height>480</height>
<control type="list" id="2"> <control type="list" id="2">
<description>List</description> <description>List</description>
<left>0</left> <left>0</left>
<top>0</top> <top>0</top>
<width>795</width> <width>720</width>
<height>530</height> <height>530</height>
<onup>100</onup> <onup>100</onup>
<onright>4</onright> <onright>4</onright>
<orientation>vertical</orientation> <orientation>vertical</orientation>
<scrolltime>200</scrolltime> <scrolltime>200</scrolltime>
<pagecontrol>4</pagecontrol> <itemlayout height="60" width="720">
<itemlayout height="150" width="795">
<control type="textbox"> <control type="textbox">
<description>Selected Item</description> <description>Selected Item</description>
<left>20</left> <left>20</left>
<top>20</top> <top>0</top>
<width>765</width> <width>680</width>
<height>110</height> <height>60</height>
<font>font13</font> <font>font13</font>
<textcolor>FFFFFFFF</textcolor> <textcolor>FFFFFFFF</textcolor>
<label>$INFO[ListItem.Property(title)]</label> <label>[B]$INFO[ListItem.Property(title)][/B] $ADDON[plugin.video.kod 30140]: $INFO[ListItem.Property(seasons)] $ADDON[plugin.video.kod 70362]: $INFO[ListItem.Property(episodes)]</label>
<align>center</align> <align>left</align>
<aligny>center</aligny> <aligny>center</aligny>
</control> </control>
</itemlayout> </itemlayout>
<focusedlayout height="150" width="795"> <focusedlayout height="200" width="720">
<control type="image"> <control type="image">
<top>1</top> <top>1</top>
<width>100%</width> <width>100%</width>
@@ -75,39 +82,22 @@
<description>Selected Item</description> <description>Selected Item</description>
<left>20</left> <left>20</left>
<top>20</top> <top>20</top>
<width>765</width> <width>680</width>
<height>110</height> <height>160</height>
<font>font13</font> <font>font13</font>
<textcolor>FFFFFFFF</textcolor> <textcolor>FFFFFFFF</textcolor>
<label>$INFO[ListItem.Property(title)]</label> <label>[B]$INFO[ListItem.Property(title)][/B] $ADDON[plugin.video.kod 30140]: $INFO[ListItem.Property(seasons)] $ADDON[plugin.video.kod 70362]: $INFO[ListItem.Property(episodes)]$INFO[ListItem.Property(description)]</label>
<autoscroll time="3000" delay="3000" repeat="3000">True</autoscroll> <autoscroll time="3000" delay="3000" repeat="3000">True</autoscroll>
<align>center</align> <align>left</align>
<aligny>center</aligny> <aligny>center</aligny>
</control> </control>
</focusedlayout> </focusedlayout>
</control> </control>
<control type="scrollbar" id="4">
<description>Scrollbar</description>
<left>800</left>
<top>0</top>
<width>5</width>
<height>470</height>
<visible>true</visible>
<texturesliderbackground colordiffuse="FF232323">white.png</texturesliderbackground>
<texturesliderbar colordiffuse="FFFFFFFF">white.png</texturesliderbar>
<texturesliderbarfocus colordiffuse="FF0081C2">white.png</texturesliderbarfocus>
<textureslidernib colordiffuse="FFFFFFFF">white.png</textureslidernib>
<textureslidernibfocus colordiffuse="FFFFFFFF">white.png</textureslidernibfocus>
<orientation>vertical</orientation>
<showonepage>false</showonepage>
<onleft>2</onleft>
<onright>3</onright>
</control>
</control> </control>
<control type="button" id="100"> <control type="button" id="100">
<description>Close Button</description> <description>Close Button</description>
<right>20</right> <right>20</right>
<top>10</top> <top>20</top>
<width>40</width> <width>40</width>
<height>40</height> <height>40</height>
<texturefocus colordiffuse="FFFFFFFF">close.png</texturefocus> <texturefocus colordiffuse="FFFFFFFF">close.png</texturefocus>
+1 -2
View File
@@ -1,10 +1,9 @@
{ {
"active": true, "active": true,
"find_videos": { "find_videos": {
"ignore_urls": [],
"patterns": [ "patterns": [
{ {
"pattern": "ninjastream.to/(?:watch/)?([0-9a-zA-Z]+)", "pattern": "ninjastream\\.to/(?:watch|download)/([0-9a-zA-Z]+)",
"url": "https://ninjastream.to/watch/\\1" "url": "https://ninjastream.to/watch/\\1"
} }
] ]
+9 -3
View File
@@ -20,11 +20,17 @@ def get_video_url(page_url, premium=False, user="", password="", video_password=
logger.debug("URL", page_url) logger.debug("URL", page_url)
video_urls = [] video_urls = []
h = json.loads(support.match(data, patron='stream="([^"]+)"').match.replace('&quot;','"').replace('\\','')) h = json.loads(support.match(data, patron='stream="([^"]+)"').match.replace('&quot;','"'))
baseurl = h['host'] + h['hash'] baseurl = decode(h['host']) + h['hash']
matches = support.match(baseurl + '/index.m3u8', patron=r'RESOLUTION=\d+x(\d+)\s*([^\s]+)').matches matches = support.match(baseurl + '/index.m3u8', patron=r'RESOLUTION=\d+x(\d+)\s*([^\s]+)').matches
for quality, url in matches: for quality, url in matches:
video_urls.append(["{} {}p [NinjaStream]".format(url.split('.')[-1], quality), '{}/{}'.format(baseurl, url)]) video_urls.append(["{} {}p [NinjaStream]".format(url.split('.')[-1], quality), '{}/{}'.format(baseurl, url)])
return video_urls return video_urls
def decode(host):
Host = ''
for n in range(len(host)):
Host += chr(ord(host[n]) ^ ord('2'))
return Host
+3 -3
View File
@@ -12,10 +12,10 @@ def test_video_exists(page_url):
logger.debug("(page_url='%s')" % page_url) logger.debug("(page_url='%s')" % page_url)
global data global data
data = httptools.downloadpage(page_url, cookies=False).data data = httptools.downloadpage(page_url, cookies=False).data
if 'Video embed restricted for this domain'in data: if 'Video embed restricted for this domain' in data:
headers = {'Referer':''} headers = {'Referer': ''}
data = httptools.downloadpage(page_url, headers=headers, cookies=False).data data = httptools.downloadpage(page_url, headers=headers, cookies=False).data
if 'File is no longer available as it expired or has been deleted' in data: if 'File is no longer available as it expired or has been deleted' in data or 'fake-' in data:
return False, config.get_localized_string(70449) % "SuperVideo" return False, config.get_localized_string(70449) % "SuperVideo"
return True, "" return True, ""
+3
View File
@@ -18,5 +18,8 @@ def get_video_url(page_url, premium=False, user="", password="", video_password=
logger.debug("url=" + page_url) logger.debug("url=" + page_url)
global data global data
video_urls = support.get_jwplayer_mediaurl(data, 'Vidmoly') video_urls = support.get_jwplayer_mediaurl(data, 'Vidmoly')
for url in video_urls:
logger.debug(url)
url[-1] = url[-1].replace(',','').replace('.urlset','').replace('/hls','')
return video_urls return video_urls
+8 -8
View File
@@ -477,15 +477,15 @@ def get_server_position(server):
def get_match_list(data, match_list, order_list=None, only_ascii=False, ignorecase=False): def get_match_list(data, match_list, order_list=None, only_ascii=False, ignorecase=False):
""" """
Search for matches in a text string, with a dictionary of "ID" / "List of search strings": Search for matches in a text string, with a dictionary of "ID" / "List of search strings":
    {"ID1": ["String 1", "String 2", "String 3"], {"ID1": ["String 1", "String 2", "String 3"],
      "ID2": ["String 4", "String 5", "String 6"] "ID2": ["String 4", "String 5", "String 6"]
    } }
    The dictionary could not contain the same search string in several IDs. The dictionary could not contain the same search string in several IDs.
    The search is performed in order of search string size (from longest to shortest) if a string matches, The search is performed in order of search string size (from longest to shortest) if a string matches,
    it is removed from the search string for the following, so that two categories are not detected if one string is part of another: it is removed from the search string for the following, so that two categories are not detected if one string is part of another:
    for example: "Spanish Language" and "Spanish" if the first appears in the string "Pablo knows how to speak the Spanish Language" for example: "Spanish Language" and "Spanish" if the first appears in the string "Pablo knows how to speak the Spanish Language"
    It will match "Spanish Language" but not "Spanish" since the longest match has priority. It will match "Spanish Language" but not "Spanish" since the longest match has priority.
""" """
match_dict = dict() match_dict = dict()
+6 -4
View File
@@ -235,23 +235,25 @@ def new_search(item):
def live(item): def live(item):
import sys import sys
import channelselector
if sys.version_info[0] >= 3: if sys.version_info[0] >= 3:
from concurrent import futures from concurrent import futures
else: else:
from concurrent_py2 import futures from concurrent_py2 import futures
itemlist = [] itemlist = []
channels_dict = {} channels_dict = {}
channels = ['raiplay', 'mediasetplay', 'la7', 'paramount'] channels = channelselector.filterchannels('live')
with futures.ThreadPoolExecutor() as executor: with futures.ThreadPoolExecutor() as executor:
itlist = [executor.submit(load_live, channel) for channel in channels] itlist = [executor.submit(load_live, ch.channel) for ch in channels]
for res in futures.as_completed(itlist): for res in futures.as_completed(itlist):
if res.result(): if res.result():
channel_name, itlist = res.result() channel_name, itlist = res.result()
channels_dict[channel_name] = itlist channels_dict[channel_name] = itlist
for channel in channels: for ch in channels:
itemlist += channels_dict[channel] itemlist += channels_dict[ch.channel]
return itemlist return itemlist
+75 -36
View File
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import xbmc, xbmcgui, sys, channelselector, time, os import xbmc, xbmcgui, sys, channelselector, time, os
from core import support
from core.support import dbg, tmdb from core.support import dbg, tmdb
from core.item import Item from core.item import Item
from core import channeltools, servertools, scrapertools from core import channeltools, servertools, scrapertools
@@ -8,8 +9,12 @@ from platformcode import platformtools, config, logger
from platformcode.launcher import run from platformcode.launcher import run
from threading import Thread from threading import Thread
if sys.version_info[0] >= 3: from concurrent import futures if sys.version_info[0] >= 3:
else: from concurrent_py2 import futures PY3 = True
from concurrent import futures
else:
PY3 = False
from concurrent_py2 import futures
info_language = ["de", "en", "es", "fr", "it", "pt"] # from videolibrary.json info_language = ["de", "en", "es", "fr", "it", "pt"] # from videolibrary.json
def_lang = info_language[config.get_setting("info_language", "videolibrary")] def_lang = info_language[config.get_setting("info_language", "videolibrary")]
@@ -75,17 +80,14 @@ class SearchWindow(xbmcgui.WindowXML):
self.channels = [] self.channels = []
self.persons = [] self.persons = []
self.episodes = [] self.episodes = []
self.servers = []
self.results = {} self.results = {}
self.focus = SEARCH self.focus = SEARCH
self.process = True
self.page = 1 self.page = 1
self.moduleDict = moduleDict self.moduleDict = moduleDict
self.searchActions = searchActions self.searchActions = searchActions
self.thread = None self.thread = None
self.selected = False self.selected = False
self.pos = 0 self.pos = 0
selfeppos = 0
self.items = [] self.items = []
if not searchActions: if not searchActions:
@@ -116,6 +118,7 @@ class SearchWindow(xbmcgui.WindowXML):
from specials.search import save_search from specials.search import save_search
save_search(self.item.text) save_search(self.item.text)
def getActions(self): def getActions(self):
logger.debug() logger.debug()
count = 0 count = 0
@@ -144,9 +147,12 @@ class SearchWindow(xbmcgui.WindowXML):
if self.item.mode == 'movie': if self.item.mode == 'movie':
title = result['title'] title = result['title']
result['mode'] = 'movie' result['mode'] = 'movie'
else: elif self.item.mode == 'tvshow':
title = result['name'] title = result['name']
result['mode'] = 'tvshow' result['mode'] = 'tvshow'
else:
title = result.get('title', '')
result['mode'] = result['media_type'].replace('tv', 'tvshow')
thumbnail = result.get('thumbnail', '') thumbnail = result.get('thumbnail', '')
noThumb = 'Infoplus/' + result['mode'].replace('show','') + '.png' noThumb = 'Infoplus/' + result['mode'].replace('show','') + '.png'
@@ -304,16 +310,12 @@ class SearchWindow(xbmcgui.WindowXML):
logger.debug('end search for:', searchAction.channel) logger.debug('end search for:', searchAction.channel)
def get_channel_results(self, searchAction): def get_channel_results(self, searchAction):
logger.debug() def search(text):
channel = searchAction.channel valid = []
results = [] other = []
valid = [] results = self.moduleDict[channel].search(searchAction, text)
other = []
try:
results = self.moduleDict[channel].search(searchAction, self.item.text)
if len(results) == 1: if len(results) == 1:
if not results[0].action or config.get_localized_string(70006).lower() in results[0].title.lower(): if not results[0].action or results[0].nextPage:
results = [] results = []
if self.item.mode != 'all': if self.item.mode != 'all':
@@ -324,6 +326,23 @@ class SearchWindow(xbmcgui.WindowXML):
valid.append(elem) valid.append(elem)
else: else:
other.append(elem) other.append(elem)
return results, valid, other
logger.debug()
channel = searchAction.channel
results = []
valid = []
other = []
try:
results, valid, other = search(self.item.text)
# if we are on movie search but no valid results is found, and there's a lot of results (more pages), try
# to add year to search text for better filtering
if self.item.contentType == 'movie' and not valid and other and other[-1].nextPage \
and self.item.infoLabels['year']:
logger.debug('retring adding year on channel ' + channel)
dummy, valid, dummy = search(self.item.text + " " + str(self.item.infoLabels['year']))
except: except:
pass pass
@@ -424,9 +443,15 @@ class SearchWindow(xbmcgui.WindowXML):
self.channels = [] self.channels = []
self.moduleDict = {} self.moduleDict = {}
self.searchActions = [] self.searchActions = []
if percent == 100 and not self.results:
self.PROGRESS.setVisible(False) # if no results
self.NORESULTS.setVisible(True) total = 0
for num in self.results.values():
total += num
if not total:
self.PROGRESS.setVisible(False)
self.NORESULTS.setVisible(True)
self.setFocusId(CLOSE)
def onInit(self): def onInit(self):
self.time = time.time() self.time = time.time()
@@ -453,7 +478,9 @@ class SearchWindow(xbmcgui.WindowXML):
if self.type: if self.type:
self.type = None self.type = None
if self.item.mode in ['all', 'search']: if self.item.mode in ['all', 'search']:
if self.item.type: self.item.mode = self.item.type if self.item.type:
self.item.mode = self.item.type
self.item.text = title_unify(self.item.text)
self.thread = Thread(target=self.search) self.thread = Thread(target=self.search)
self.thread.start() self.thread.start()
elif self.item.mode in ['movie', 'tvshow', 'person_']: elif self.item.mode in ['movie', 'tvshow', 'person_']:
@@ -507,9 +534,12 @@ class SearchWindow(xbmcgui.WindowXML):
elif self.EPISODES.isVisible(): self.setFocusId(EPISODESLIST) elif self.EPISODES.isVisible(): self.setFocusId(EPISODESLIST)
elif self.RESULTS.isVisible(): self.setFocusId(RESULTS) elif self.RESULTS.isVisible(): self.setFocusId(RESULTS)
elif focus in [RESULTS] and self.item.mode == 'all': elif focus in [RESULTS]:
pos = self.RESULTS.getSelectedPosition() pos = self.RESULTS.getSelectedPosition()
self.CHANNELS.getSelectedItem().setProperty('position', str(pos)) try:
self.CHANNELS.getSelectedItem().setProperty('position', str(pos))
except:
pass
elif action == ENTER and focus in [CHANNELS]: elif action == ENTER and focus in [CHANNELS]:
self.setFocusId(RESULTS) self.setFocusId(RESULTS)
@@ -559,25 +589,14 @@ class SearchWindow(xbmcgui.WindowXML):
self.actors() self.actors()
elif search == 'persons': elif search == 'persons':
item = self.item.clone(mode='person_', discovery=self.persons[pos]) item = self.item.clone(mode='person_', discovery=self.persons[pos])
# self.close()
Search(item, self.moduleDict, self.searchActions) Search(item, self.moduleDict, self.searchActions)
if close_action: if close_action:
self.close self.close()
else: else:
item = Item().fromurl(self.RESULTS.getSelectedItem().getProperty('item')) item = Item().fromurl(self.RESULTS.getSelectedItem().getProperty('item'))
if self.item.mode == 'movie': item.contentTitle = self.RESULTS.getSelectedItem().getLabel() if self.item.mode == 'movie': item.contentTitle = self.RESULTS.getSelectedItem().getLabel()
else: item.contentSerieName = self.RESULTS.getSelectedItem().getLabel() else: item.contentSerieName = self.RESULTS.getSelectedItem().getLabel()
self.RESULTS.reset()
self.RESULTS.setVisible(False)
self.PROGRESS.setVisible(True)
self.selected = True
self.thActions.join()
self.RESULTS.addItems(self.items)
self.RESULTS.setVisible(True)
self.PROGRESS.setVisible(False)
# self.close()
Search(item, self.moduleDict, self.searchActions) Search(item, self.moduleDict, self.searchActions)
if close_action: if close_action:
self.close() self.close()
@@ -589,7 +608,6 @@ class SearchWindow(xbmcgui.WindowXML):
self.pos = self.RESULTS.getSelectedPosition() self.pos = self.RESULTS.getSelectedPosition()
item = Item().fromurl(self.RESULTS.getSelectedItem().getProperty('item')) item = Item().fromurl(self.RESULTS.getSelectedItem().getProperty('item'))
else: else:
self.eppos = self.EPISODESLIST.getSelectedPosition()
item_url = self.EPISODESLIST.getSelectedItem().getProperty('item') item_url = self.EPISODESLIST.getSelectedItem().getProperty('item')
if item_url: if item_url:
item = Item().fromurl(item_url) item = Item().fromurl(item_url)
@@ -706,8 +724,6 @@ class SearchWindow(xbmcgui.WindowXML):
self.Focus(SEARCH) self.Focus(SEARCH)
self.setFocusId(RESULTS) self.setFocusId(RESULTS)
self.RESULTS.selectItem(self.pos) self.RESULTS.selectItem(self.pos)
elif self.item.mode in ['person']:
self.actors()
else: else:
self.Close() self.Close()
@@ -746,3 +762,26 @@ class SearchWindow(xbmcgui.WindowXML):
server.globalsearch = True server.globalsearch = True
return run(server) return run(server)
def title_unify(title):
import unicodedata
u_title = ''
if type(title) == str: title = u'' + title
for c in unicodedata.normalize('NFD', title):
cat = unicodedata.category(c)
if cat != 'Mn':
if cat == 'Pd':
c_new = '-'
elif cat in ['Ll', 'Lu'] or c == ':':
c_new = c
else:
c_new = ' '
u_title += c_new
if (u_title.count(':') + u_title.count('-')) == 1:
# subtitle, split but only if there's one, it might be part of title
spl = u_title.replace(':', '-').split('-')
u_title = spl[0] if len(spl[0]) > 5 else spl[1]
return u_title.strip()
+119 -113
View File
@@ -13,7 +13,6 @@ from core import filetools, scrapertools, videolibrarytools
from core.support import typo, thumb from core.support import typo, thumb
from core.item import Item from core.item import Item
from platformcode import config, logger, platformtools from platformcode import config, logger, platformtools
from distutils import dir_util
if PY3: if PY3:
from concurrent import futures from concurrent import futures
else: else:
@@ -202,14 +201,14 @@ def configure_update_videolibrary(item):
# Select Dialog # Select Dialog
ret = platformtools.dialog_multiselect(config.get_localized_string(60601), lista, preselect=preselect, useDetails=True) ret = platformtools.dialog_multiselect(config.get_localized_string(60601), lista, preselect=preselect, useDetails=True)
if ret < 0: if ret is None:
return False # order cancel return False # order cancel
seleccionados = [ids[i] for i in ret] selection = [ids[i] for i in ret]
for tvshow in ids: for tvshow in ids:
if tvshow not in seleccionados: if tvshow not in selection:
tvshow.active = 0 tvshow.active = 0
elif tvshow in seleccionados: elif tvshow in selection:
tvshow.active = 1 tvshow.active = 1
mark_tvshow_as_updatable(tvshow, silent=True) mark_tvshow_as_updatable(tvshow, silent=True)
@@ -416,124 +415,129 @@ def findvideos(item):
itemlist = [] itemlist = []
all_videolibrary = [] all_videolibrary = []
for nom_canal, json_path in list(list_canales.items()): ch_results = []
if filtro_canal and filtro_canal != nom_canal.capitalize(): with futures.ThreadPoolExecutor() as executor:
continue for nom_canal, json_path in list(list_canales.items()):
if filtro_canal and filtro_canal != nom_canal.capitalize():
item_canal = Item()
# We import the channel of the selected part
try:
if nom_canal == 'community':
channel = __import__('specials.%s' % nom_canal, fromlist=["channels.%s" % nom_canal])
else:
channel = __import__('channels.%s' % nom_canal, fromlist=["channels.%s" % nom_canal])
except ImportError:
exec("import channels." + nom_canal + " as channel")
except:
dead_list = []
zombie_list = []
if nom_canal not in dead_list and nom_canal not in zombie_list: confirm = platformtools.dialog_yesno(config.get_localized_string(30131), config.get_localized_string(30132) % nom_canal.upper() + '\n' + config.get_localized_string(30133))
elif nom_canal in zombie_list: confirm = False
else: confirm = True
if confirm:
# delete the channel from all movie and tvshow
from past.utils import old_div
num_enlaces = 0
dialog = platformtools.dialog_progress(config.get_localized_string(30131), config.get_localized_string(60005) % nom_canal)
if not all_videolibrary:
all_videolibrary = list_movies(Item()) + list_tvshows(Item())
for n, it in enumerate(all_videolibrary):
if nom_canal in it.library_urls:
dead_item = Item(multichannel=len(it.library_urls) > 1,
contentType=it.contentType,
dead=nom_canal,
path=filetools.split(it.nfo)[0],
nfo=it.nfo,
library_urls=it.library_urls,
infoLabels={'title': it.contentTitle})
num_enlaces += delete(dead_item)
dialog.update(old_div(100*n, len(all_videolibrary)))
dialog.close()
msg_txt = config.get_localized_string(70087) % (num_enlaces, nom_canal)
logger.info(msg_txt)
platformtools.dialog_notification(config.get_localized_string(30131), msg_txt)
platformtools.itemlist_refresh()
if nom_canal not in dead_list:
dead_list.append(nom_canal)
continue continue
else:
if nom_canal not in zombie_list:
zombie_list.append(nom_canal)
if len(dead_list) > 0: # We import the channel of the selected part
for nom_canal in dead_list: try:
if nom_canal in item.library_urls: if nom_canal == 'community':
del item.library_urls[nom_canal] channel = __import__('specials.%s' % nom_canal, fromlist=["channels.%s" % nom_canal])
else:
channel = __import__('channels.%s' % nom_canal, fromlist=["channels.%s" % nom_canal])
except ImportError:
exec("import channels." + nom_canal + " as channel")
except:
dead_list = []
zombie_list = []
item_json = Item().fromjson(filetools.read(json_path)) if nom_canal not in dead_list and nom_canal not in zombie_list: confirm = platformtools.dialog_yesno(config.get_localized_string(30131), config.get_localized_string(30132) % nom_canal.upper() + '\n' + config.get_localized_string(30133))
list_servers = [] elif nom_canal in zombie_list: confirm = False
else: confirm = True
try: if confirm:
# FILTERTOOLS # delete the channel from all movie and tvshow
# if the channel has a filter, the name it has saved is passed to it so that it filters correctly. from past.utils import old_div
if "list_language" in item_json: num_enlaces = 0
# if it comes from the addon video library dialog = platformtools.dialog_progress(config.get_localized_string(30131), config.get_localized_string(60005) % nom_canal)
if "library_filter_show" in item: if not all_videolibrary:
item_json.show = item.library_filter_show.get(nom_canal, "") all_videolibrary = list_movies(Item()) + list_tvshows(Item())
for n, it in enumerate(all_videolibrary):
if nom_canal in it.library_urls:
dead_item = Item(multichannel=len(it.library_urls) > 1,
contentType=it.contentType,
dead=nom_canal,
path=filetools.split(it.nfo)[0],
nfo=it.nfo,
library_urls=it.library_urls,
infoLabels={'title': it.contentTitle})
num_enlaces += delete(dead_item)
dialog.update(old_div(100*n, len(all_videolibrary)))
# We run find_videos, from the channel or common dialog.close()
item_json.contentChannel = 'videolibrary' msg_txt = config.get_localized_string(70087) % (num_enlaces, nom_canal)
item_json.play_from = item.play_from logger.info(msg_txt)
item_json.nfo = item.nfo platformtools.dialog_notification(config.get_localized_string(30131), msg_txt)
item_json.strm_path = item.strm_path platformtools.itemlist_refresh()
if hasattr(channel, 'findvideos'):
from core import servertools
if item_json.videolibray_emergency_urls:
del item_json.videolibray_emergency_urls
list_servers = getattr(channel, 'findvideos')(item_json)
elif item_json.action == 'play':
from platformcode import platformtools
# autoplay.set_status(True)
item_json.contentChannel = item_json.channel
item_json.channel = "videolibrary"
platformtools.play_video(item_json)
return ''
else:
from core import servertools
list_servers = servertools.find_video_items(item_json)
except Exception as ex:
logger.error("The findvideos function for the channel %s failed" % nom_canal)
template = "An exception of type %s occured. Arguments:\n%r"
message = template % (type(ex).__name__, ex.args)
logger.error(message)
logger.error(traceback.format_exc())
# Change the title to the servers adding the name of the channel in front and the infoLabels and the images of the item if the server does not have if nom_canal not in dead_list:
for server in list_servers: dead_list.append(nom_canal)
server.contentChannel = server.channel continue
server.channel = "videolibrary" else:
server.nfo = item.nfo if nom_canal not in zombie_list:
server.strm_path = item.strm_path zombie_list.append(nom_canal)
server.play_from = item.play_from
# Kodi 18 Compatibility - Prevents wheel from spinning around in Direct Links if len(dead_list) > 0:
if server.action == 'play': for nom_canal in dead_list:
server.folder = False if nom_canal in item.library_urls:
del item.library_urls[nom_canal]
# Channel name is added if desired item_json = Item().fromjson(filetools.read(json_path))
if config.get_setting("quit_channel_name", "videolibrary") == 0: list_servers = []
server.title = "%s: %s" % (nom_canal.capitalize(), server.title)
if not server.thumbnail: try:
server.thumbnail = item.thumbnail # FILTERTOOLS
# if the channel has a filter, the name it has saved is passed to it so that it filters correctly.
if "list_language" in item_json:
# if it comes from the addon video library
if "library_filter_show" in item:
item_json.show = item.library_filter_show.get(nom_canal, "")
# logger.debug("server:\n%s" % server.tostring('\n')) # We run find_videos, from the channel or common
itemlist.append(server) item_json.contentChannel = 'videolibrary'
item_json.play_from = item.play_from
item_json.nfo = item.nfo
item_json.strm_path = item.strm_path
if hasattr(channel, 'findvideos'):
from core import servertools
if item_json.videolibray_emergency_urls:
del item_json.videolibray_emergency_urls
ch_results.append(executor.submit(getattr(channel, 'findvideos'), item_json))
elif item_json.action == 'play':
from platformcode import platformtools
# autoplay.set_status(True)
item_json.contentChannel = item_json.channel
item_json.channel = "videolibrary"
platformtools.play_video(item_json)
return ''
else:
from core import servertools
ch_results.append(executor.submit(servertools.find_video_items, item_json))
except Exception as ex:
logger.error("The findvideos function for the channel %s failed" % nom_canal)
template = "An exception of type %s occured. Arguments:\n%r"
message = template % (type(ex).__name__, ex.args)
logger.error(message)
logger.error(traceback.format_exc())
for ris in futures.as_completed(ch_results):
list_servers.extend(ris.result())
# Change the title to the servers adding the name of the channel in front and the infoLabels and the images of the item if the server does not have
for server in list_servers:
server.contentChannel = server.channel
server.channel = "videolibrary"
server.nfo = item.nfo
server.strm_path = item.strm_path
server.play_from = item.play_from
# Kodi 18 Compatibility - Prevents wheel from spinning around in Direct Links
if server.action == 'play':
server.folder = False
# Channel name is added if desired
if config.get_setting("quit_channel_name", "videolibrary") == 0:
server.title = "%s: %s" % (server.contentChannel.capitalize(), server.title)
if not server.thumbnail:
server.thumbnail = item.thumbnail
# logger.debug("server:\n%s" % server.tostring('\n'))
itemlist.append(server)
if autoplay.play_multi_channel(item, itemlist): # hideserver if autoplay.play_multi_channel(item, itemlist): # hideserver
return [] return []
@@ -605,6 +609,8 @@ def update_videolibrary(item=''):
def move_videolibrary(current_path, new_path, current_movies_folder, new_movies_folder, current_tvshows_folder, new_tvshows_folder): def move_videolibrary(current_path, new_path, current_movies_folder, new_movies_folder, current_tvshows_folder, new_tvshows_folder):
from distutils import dir_util
logger.debug() logger.debug()
backup_current_path = current_path backup_current_path = current_path