# -*- coding: utf-8 -*- import threading import xbmc, xbmcgui, sys, channelselector, time, os from core.support import dbg, tmdb from core.item import Item from core import channeltools, servertools, scrapertools from platformcode import platformtools, config, logger from platformcode.launcher import run from threading import Thread from specials.search import save_search if sys.version_info[0] >= 3: 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 def_lang = info_language[config.get_setting("info_language", "videolibrary")] close_action = False update_lock = threading.Lock() moduleDict = {} searchActions = [] thread = None def busy(state): if state: xbmc.executebuiltin('ActivateWindow(busydialognocancel)') else: xbmc.executebuiltin('Dialog.Close(busydialognocancel)') def set_workers(): workers = config.get_setting('thread_number') if config.get_setting('thread_number') > 0 else None return workers def Search(*args): xbmc.executebuiltin('Dialog.Close(all)') w = SearchWindow('GlobalSearch.xml', config.get_runtime_path()) w.start(*args) del w # Actions LEFT = 1 RIGHT = 2 UP = 3 DOWN = 4 ENTER = 7 EXIT = 10 BACKSPACE = 92 SWIPEUP = 531 CONTEXT = 117 MOUSEMOVE = 107 FULLSCREEN = 18 # Container SEARCH = 1 EPISODES = 2 SERVERS = 3 NORESULTS = 4 LOADING = 5 # Search MAINTITLE = 100 CHANNELS = 101 RESULTS = 102 PROGRESS = 500 COUNT = 501 MENU = 502 BACK = 503 CLOSE = 504 QUALITYTAG = 505 # Servers EPISODESLIST = 200 SERVERLIST = 300 class SearchWindow(xbmcgui.WindowXML): global thread def start(self, item, thActions=None): logger.debug() self.exit = False self.item = item self.type = self.item.mode self.channels = [] self.persons = [] self.episodes = [] self.results = {} self.focus = SEARCH self.page = 1 self.moduleDict = moduleDict self.searchActions = searchActions self.thread = None self.selected = False self.pos = 0 self.items = [] self.search_threads = [] if not thActions: self.thActions = Thread(target=self.getActionsThread) self.thActions.start() else: self.thActions = thActions self.lastSearch() if not self.item.text: return self.doModal() def lastSearch(self): logger.debug() if not self.item.text: if self.item.contentTitle: self.item.text = self.item.contentTitle elif self.item.contentSerieName: self.item.text = self.item.contentSerieName if not self.item.text: if config.get_setting('last_search'): last_search = channeltools.get_channel_setting('Last_searched', 'search', '') else: last_search = '' if not self.item.text: self.item.text = platformtools.dialog_input(default=last_search, heading='') if self.item.text: channeltools.set_channel_setting('Last_searched', self.item.text, 'search') if self.item.mode == 'all': save_search(self.item.text) else: if self.item.context: del self.item.context # needed for preventing same content twice in saved search save_search(self.item.__dict__) def getActionsThread(self): logger.debug() self.channelsList = self.get_channels() for channel in self.channelsList: logger.debug(channel) try: module = __import__('channels.%s' % channel, fromlist=["channels.%s" % channel]) mainlist = getattr(module, 'mainlist')(Item(channel=channel, global_search=True)) action = [elem for elem in mainlist if elem.action == "search" and ( self.item.mode in ['all', 'person'] or elem.contentType in [self.item.type, 'undefined'])] self.moduleDict[channel] = module self.searchActions += action except: import traceback logger.error('error importing/getting search items of ' + channel) logger.error(traceback.format_exc()) def getActions(self): # return immediately all actions that are already loadead for action in self.searchActions: yield action # wait and return as getActionsThread load lastLen = len(self.searchActions) logger.debug('LAST LEN:', lastLen) while self.thActions.is_alive() or lastLen < len(self.searchActions): while len(self.searchActions) == lastLen: if not self.thActions.is_alive(): return # time.sleep(0.1) yield self.searchActions[lastLen - 1] lastLen = len(self.searchActions) logger.debug(lastLen) def select(self): logger.debug() self.PROGRESS.setVisible(False) self.items = [] if self.item.mode == 'person_': tmdb_info = tmdb.discovery(self.item, dict_=self.item.discovery) results = tmdb_info.results.get('cast',[]) else: tmdb_info = tmdb.Tmdb(searched_text=self.item.text, search_type=self.item.mode.replace('show', '')) results = tmdb_info.results for result in results: result = tmdb_info.get_infoLabels(result, origen=result) if self.item.mode == 'movie': title = result['title'] result['mode'] = 'movie' elif self.item.mode == 'tvshow': title = result['name'] result['mode'] = 'tvshow' else: title = result.get('title', '') result['mode'] = result['media_type'].replace('tv', 'tvshow') thumbnail = result.get('thumbnail', '') noThumb = 'Infoplus/' + result['mode'].replace('show','') + '.png' fanart = result.get('fanart', '') year = result.get('release_date', '') rating = str(result.get('vote_average', '')) new_item = Item(channel='globalsearch', action="Search", title=title, thumbnail=thumbnail, fanart=fanart, mode='search', type=result['mode'], contentType=result['mode'], text=title, infoLabels=result) if self.item.mode == 'movie': new_item.contentTitle = result['title'] else: new_item.contentSerieName = result['name'] it = xbmcgui.ListItem(title) it.setProperties({'thumb': result.get('thumbnail', noThumb), 'fanart': result.get('fanart', ''), 'rating': ' [' + rating + ']' if rating else '', 'plot': result.get('overview', ''), 'search': 'search', 'release_date': '', 'item': new_item.tourl(), 'year': ' [' + year.split('/')[-1] + ']' if year else ' [' + result.get('first_air_date','').split('-')[0] + ']'}) self.items.append(it) if self.items: self.RESULTS.reset() self.RESULTS.addItems(self.items) self.setFocusId(RESULTS) else: self.RESULTS.setVisible(False) self.NORESULTS.setVisible(True) self.setFocusId(CLOSE) def actors(self): logger.debug() self.PROGRESS.setVisible(False) items = [] dict_ = {'url': 'search/person', 'language': def_lang, 'query': self.item.text, 'page':self.page} prof = {'Acting': 'Actor', 'Directing': 'Director', 'Production': 'Productor'} plot = '' self.item.search_type = 'person' tmdb_inf = tmdb.discovery(self.item, dict_=dict_) results = tmdb_inf.results for elem in results: name = elem.get('name', '') if not name: continue rol = elem.get('known_for_department', '') rol = prof.get(rol, rol) know_for = elem.get('known_for', '') cast_id = elem.get('id', '') if know_for: t_k = know_for[0].get('title', '') if t_k: plot = '%s in %s' % (rol, t_k) t = elem.get('profile_path', '') if t: thumb = 'https://image.tmdb.org/t/p/original' + t else : thumb = 'Infoplus/no_photo.png' discovery = {'url': 'person/%s/combined_credits' % cast_id, 'page': '1', 'sort_by': 'primary_release_date.desc', 'language': def_lang} self.persons.append(discovery) new_item = Item(channel='globalsearch', action="Search", title=name, thumbnail=thumb, mode='search') it = xbmcgui.ListItem(name) it.setProperties({'thumb': thumb, 'plot': plot, 'search': 'persons', 'item': new_item.tourl()}) items.append(it) if len(results) > 19: it = xbmcgui.ListItem(config.get_localized_string(70006)) it.setProperty('thumb', 'Infoplus/next_focus.png') it.setProperty('search','next') items.append(it) if self.page > 1: it = xbmcgui.ListItem(config.get_localized_string(70005)) it.setProperty('thumb', 'Infoplus/previous_focus.png') it.setProperty('search','previous') items.insert(0, it) if items: self.RESULTS.reset() self.RESULTS.addItems(items) self.setFocusId(RESULTS) else: self.RESULTS.setVisible(False) self.NORESULTS.setVisible(True) self.setFocusId(CLOSE) def get_channels(self): logger.debug() channels_list = [] all_channels = channelselector.filterchannels('all') for ch in all_channels: channel = ch.channel ch_param = channeltools.get_channel_parameters(channel) if not ch_param.get("active", False): continue list_cat = ch_param.get("categories", []) if not ch_param.get("include_in_global_search", False): continue if 'anime' in list_cat: n = list_cat.index('anime') list_cat[n] = 'tvshow' if self.item.mode in ['all', 'person'] or self.item.mode in list_cat or self.item.type in list_cat: if config.get_setting("include_in_global_search", channel) and ch_param.get("active", False): channels_list.append(channel) logger.debug('search in channels:', channels_list) return channels_list def timer(self): while self.searchActions or self.thActions.is_alive(): if self.exit: return try: percent = (float(self.count) / len(self.searchActions)) * 100 except ZeroDivisionError: percent = 0 self.PROGRESS.setPercent(percent) self.COUNT.setText('%s/%s [%s"]' % (self.count, len(self.searchActions), int(time.time() - self.time))) if percent == 100 and not self.thActions.is_alive(): self.channels = [] self.moduleDict = {} self.searchActions = [] # if no results total = 0 for num in self.results.values(): total += num if not total: self.PROGRESS.setVisible(False) self.NORESULTS.setVisible(True) self.setFocusId(CLOSE) time.sleep(1) def search(self): logger.debug() self.count = 0 self.LOADING.setVisible(True) Thread(target=self.timer).start() try: with futures.ThreadPoolExecutor(max_workers=set_workers()) as executor: for searchAction in self.getActions(): if self.exit: break self.search_threads.append(executor.submit(self.get_channel_results, searchAction)) for res in futures.as_completed(self.search_threads): if self.exit: break if res.result(): channel, valid, results = res.result() self.update(channel, valid, results) # if results: # name = results[0].channel # if name not in results: # self.channels[name] = [] # self.channels[name].extend(results) # if valid or results: # self.update() except: import traceback logger.error(traceback.format_exc()) self.count = len(self.searchActions) def get_channel_results(self, searchAction): def channel_search(text): valid = [] other = [] results = self.moduleDict[channel].search(searchAction, text) if len(results) == 1: if not results[0].action or results[0].nextPage: results = [] if self.item.mode != 'all': for elem in results: if elem.infoLabels.get('tmdb_id') == self.item.infoLabels.get('tmdb_id'): elem.from_channel = channel elem.verified = 1 valid.append(elem) else: other.append(elem) return results, valid, other logger.debug() channel = searchAction.channel results = [] valid = [] other = [] try: results, valid, other = channel_search(self.item.text) if self.exit: return # 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 = channel_search(self.item.text + " " + str(self.item.infoLabels['year'])) if self.exit: return # some channels may use original title if self.item.mode != 'all' and not valid and self.item.infoLabels.get('originaltitle'): original = scrapertools.title_unify(self.item.infoLabels.get('originaltitle')) if self.item.text != original: logger.debug('retring with original title on channel ' + channel) dummy, valid, dummy = channel_search(original) except: import traceback logger.error(traceback.format_exc()) if self.exit: return # update_lock.acquire() self.count += 1 return channel, valid, other if other else results # update_lock.release() def makeItem(self, url): item = Item().fromurl(url) channelParams = channeltools.get_channel_parameters(item.channel) thumb = item.thumbnail if item.thumbnail else 'Infoplus/' + item.contentType.replace('show', '') + '.png' logger.info('THUMB', thumb) it = xbmcgui.ListItem(item.title) year = str(item.year if item.year else item.infoLabels.get('year', '')) rating = str(item.infoLabels.get('rating', '')) it.setProperties({'thumb': thumb, 'fanart': item.fanart, 'plot': item.plot, 'year': ' [' + year + ']' if year else '', 'rating':' [' + rating + ']' if rating else '', 'item': url, 'verified': item.verified, 'channel':channelParams['title'], 'channelthumb': channelParams['thumbnail'] if item.verified else ''}) if item.server: color = scrapertools.find_single_match(item.alive, r'(FF[^\]]+)') it.setProperties({'channel': channeltools.get_channel_parameters(item.channel).get('title', ''), 'thumb': config.get_online_server_thumb(item.server), 'servername': servertools.get_server_parameters(item.server.lower()).get('name', item.server), 'color': color if color else 'FF0082C2'}) return it def update(self, channel, valid, results): update_lock.acquire() self.LOADING.setVisible(False) if self.exit: return logger.debug('Search on channel', channel) if self.item.mode != 'all' and 'valid' not in self.results: self.results['valid'] = 0 item = xbmcgui.ListItem('valid') item.setProperties({'thumb': 'valid.png', 'position': '0', 'results': '0'}) self.channels.append(item) pos = self.CHANNELS.getSelectedPosition() self.CHANNELS.addItems(self.channels) self.CHANNELS.selectItem(pos) self.setFocusId(RESULTS) if valid and self.CHANNELS.size(): item = self.CHANNELS.getListItem(0) resultsList = item.getProperty('items') for result in valid: resultsList += result.tourl() + '|' item.setProperty('items', resultsList) res = len(resultsList.split('|')) self.channels[0].setProperty('results', str(res - 1 if res > 0 else 0)) if self.CHANNELS.getSelectedPosition() == 0: items = [] for result in valid: if result: items.append(self.makeItem(result.tourl())) pos = self.RESULTS.getSelectedPosition() self.RESULTS.addItems(items) if pos < 0: self.setFocusId(RESULTS) pos = 0 self.RESULTS.selectItem(pos) if results: resultsList = '' channelParams = channeltools.get_channel_parameters(channel) name = channelParams['title'] if name not in self.results: item = xbmcgui.ListItem(name) item.setProperties({'thumb': channelParams['thumbnail'], 'position': '0', 'results': str(len(results)) }) for result in results: resultsList += result.tourl() + '|' item.setProperty('items', resultsList) self.results[name] = len(self.results) self.channels.append(item) else: item = self.CHANNELS.getListItem(self.results[name]) resultsList = item.getProperty('items') for result in results: resultsList += result.tourl() + '|' item.setProperty('items',resultsList) logger.log(self.channels[int(self.results[name])]) res = len(resultsList.split('|')) self.channels[int(self.results[name])].setProperty('results', str(res - 1 if res > 0 else 0)) pos = self.CHANNELS.getSelectedPosition() self.CHANNELS.reset() self.CHANNELS.addItems(self.channels) self.CHANNELS.selectItem(pos) if len(self.channels) == 1: self.setFocusId(CHANNELS) channelResults = self.CHANNELS.getListItem(self.results[name]).getProperty('items').split('|') items = [] for result in channelResults: if result: items.append(self.makeItem(result)) self.RESULTS.reset() self.RESULTS.addItems(items) update_lock.release() def onInit(self): self.time = time.time() # collect controls self.CHANNELS = self.getControl(CHANNELS) self.RESULTS = self.getControl(RESULTS) self.PROGRESS = self.getControl(PROGRESS) self.COUNT = self.getControl(COUNT) self.MAINTITLE = self.getControl(MAINTITLE) self.MAINTITLE.setText(config.get_localized_string(30993).replace('...', '') % '"%s"' % self.item.text) self.SEARCH = self.getControl(SEARCH) self.EPISODES = self.getControl(EPISODES) self.EPISODESLIST = self.getControl(EPISODESLIST) self.SERVERS = self.getControl(SERVERS) self.SERVERLIST = self.getControl(SERVERLIST) self.NORESULTS = self.getControl(NORESULTS) self.NORESULTS.setVisible(False) self.LOADING = self.getControl(LOADING) self.LOADING.setVisible(False) self.Focus(self.focus) if self.type: self.type = None if self.item.mode in ['all', 'search']: if self.item.type: self.item.mode = self.item.type self.item.text = scrapertools.title_unify(self.item.text) thread = Thread(target=self.search) thread.start() elif self.item.mode in ['movie', 'tvshow', 'person_']: self.select() elif self.item.mode in ['person']: self.actors() def Focus(self, focusid): if focusid in [SEARCH]: self.focus = CHANNELS self.SEARCH.setVisible(True) self.EPISODES.setVisible(False) self.SERVERS.setVisible(False) if focusid in [EPISODES]: self.focus = focusid self.SEARCH.setVisible(False) self.EPISODES.setVisible(True) self.SERVERS.setVisible(False) if focusid in [SERVERS]: self.focus = SERVERLIST self.SEARCH.setVisible(False) self.EPISODES.setVisible(False) self.SERVERS.setVisible(True) def onAction(self, action): global close_action action = action.getId() focus = self.getFocusId() if action in [CONTEXT] and focus in [RESULTS, EPISODESLIST, SERVERLIST]: self.context() elif action in [SWIPEUP] and self.CHANNELS.isVisible(): self.setFocusId(CHANNELS) pos = self.CHANNELS.getSelectedPosition() self.CHANNELS.selectItem(pos) elif action in [LEFT, RIGHT, MOUSEMOVE] and focus in [CHANNELS] and self.CHANNELS.isVisible(): update_lock.acquire() items = [] name = self.CHANNELS.getSelectedItem().getLabel() subpos = int(self.CHANNELS.getSelectedItem().getProperty('position')) channelResults = self.CHANNELS.getListItem(self.results[name]).getProperty('items').split('|') for result in channelResults: if result: items.append(self.makeItem(result)) self.RESULTS.reset() self.RESULTS.addItems(items) self.RESULTS.selectItem(subpos) update_lock.release() elif (action in [DOWN] and focus in [BACK, CLOSE, MENU]) or focus not in [BACK, CLOSE, MENU, SERVERLIST, EPISODESLIST, RESULTS, CHANNELS]: if self.SERVERS.isVisible(): self.setFocusId(SERVERLIST) elif self.EPISODES.isVisible(): self.setFocusId(EPISODESLIST) elif self.RESULTS.isVisible() and self.RESULTS.size() > 0: self.setFocusId(RESULTS) elif self.CHANNELS.isVisible(): self.setFocusId(CHANNELS) elif focus in [RESULTS]: pos = self.RESULTS.getSelectedPosition() try: self.CHANNELS.getSelectedItem().setProperty('position', str(pos)) except: pass elif action == ENTER and focus in [CHANNELS]: self.setFocusId(RESULTS) if action in [BACKSPACE]: self.Back() elif action in [EXIT]: self.Close() close_action = True xbmc.sleep(500) def onClick(self, control_id): global close_action if self.RESULTS.getSelectedItem(): search = self.RESULTS.getSelectedItem().getProperty('search') else: search = None if control_id in [CHANNELS]: items = [] name = self.CHANNELS.getSelectedItem().getLabel() subpos = int(self.CHANNELS.getSelectedItem().getProperty('position')) channelResults = self.CHANNELS.getListItem(self.results[name]).getProperty('items').split('|') for result in channelResults: if result: items.append(self.makeItem(result)) self.RESULTS.reset() self.RESULTS.addItems(items) self.RESULTS.selectItem(subpos) self.CHANNELS.getSelectedItem().setProperty('position', str(subpos)) elif control_id in [BACK]: self.Back() elif control_id in [CLOSE]: self.Close() close_action = True elif control_id in [MENU]: self.context() elif search: pos = self.RESULTS.getSelectedPosition() if search == 'next': self.page += 1 self.actors() elif search == 'previous': self.page -= 1 self.actors() elif search == 'persons': item = Item().fromurl(self.RESULTS.getSelectedItem().getProperty('item')).clone(mode='person_', discovery=self.persons[pos], text=True, folder=False) Search(item, self.thActions) if close_action: self.close() else: item = Item().fromurl(self.RESULTS.getSelectedItem().getProperty('item')) if self.item.mode == 'movie': item.contentTitle = self.RESULTS.getSelectedItem().getLabel() else: item.contentSerieName = self.RESULTS.getSelectedItem().getLabel() item.folder = False logger.debug(item) Search(item, self.thActions) if close_action: self.close() elif control_id in [RESULTS, EPISODESLIST]: busy(True) if control_id in [RESULTS]: name = self.CHANNELS.getSelectedItem().getLabel() self.pos = self.RESULTS.getSelectedPosition() item = Item().fromurl(self.RESULTS.getSelectedItem().getProperty('item')) else: item_url = self.EPISODESLIST.getSelectedItem().getProperty('item') if item_url: item = Item().fromurl(item_url) else: # no results item busy(False) return if item.action != 'episodios': xbmc.executebuiltin("RunPlugin(plugin://plugin.video.kod/?" + item_url + ")") busy(False) return try: self.channel = __import__('channels.%s' % item.channel, fromlist=["channels.%s" % item.channel]) self.itemsResult = getattr(self.channel, item.action)(item) if self.itemsResult and self.itemsResult[0].server: from platformcode.launcher import findvideos busy(False) findvideos(self.item, self.itemsResult) return except: import traceback logger.error('error importing/getting search items of ' + item.channel) logger.error(traceback.format_exc()) self.itemsResult = [] self.episodes = self.itemsResult if self.itemsResult else [] self.itemsResult = [] ep = [] for item in self.episodes: it = xbmcgui.ListItem(item.title) it.setProperty('item', item.tourl()) ep.append(it) if not ep: ep = [xbmcgui.ListItem(config.get_localized_string(60347))] ep[0].setProperty('thumb', channelselector.get_thumb('nofolder.png')) self.Focus(EPISODES) self.EPISODESLIST.reset() self.EPISODESLIST.addItems(ep) self.setFocusId(EPISODESLIST) busy(False) elif control_id in [SERVERLIST]: server = Item().fromurl(self.getControl(control_id).getSelectedItem().getProperty('item')) return self.play(server) def Back(self): self.getControl(QUALITYTAG).setText('') if self.SERVERS.isVisible(): if self.episodes: self.Focus(EPISODES) self.setFocusId(EPISODESLIST) else: self.Focus(SEARCH) self.setFocusId(RESULTS) self.RESULTS.selectItem(self.pos) elif self.EPISODES.isVisible(): self.episodes = [] self.Focus(SEARCH) self.setFocusId(RESULTS) self.RESULTS.selectItem(self.pos) else: self.Close() def Close(self): self.exit = True if thread and thread.is_alive(): busy(True) for th in self.search_threads: th.cancel() thread.join() busy(False) self.close() def context(self): focus = self.getFocusId() if focus == EPISODESLIST: # context on episode item_url = self.EPISODESLIST.getSelectedItem().getProperty('item') parent = Item().fromurl(self.RESULTS.getSelectedItem().getProperty('item')) elif focus == SERVERLIST: item_url = self.SERVERLIST.getSelectedItem().getProperty('item') parent = Item().fromurl(self.RESULTS.getSelectedItem().getProperty('item')) else: item_url = self.RESULTS.getSelectedItem().getProperty('item') parent = self.item item = Item().fromurl(item_url) parent.noMainMenu = True commands = platformtools.set_context_commands(item, item_url, parent) context = [c[0] for c in commands] context_commands = [c[1].replace('Container.Refresh', 'RunPlugin').replace('Container.Update', 'RunPlugin') for c in commands] index = xbmcgui.Dialog().contextmenu(context) if index > -1: xbmc.executebuiltin(context_commands[index]) def play(self, server=None): platformtools.prevent_busy() server.window = True server.globalsearch = True return run(server)