# -*- coding: utf-8 -*- import glob import os import re import time from threading import Thread from core import channeltools from core import config from core import logger from core.item import Item from platformcode import platformtools def mainlist(item): logger.info() item.channel = "search" itemlist = list() context = [{"title": "Elegir canales incluidos", "action": "setting_channel", "channel": item.channel}] itemlist.append(Item(channel=item.channel, action="search", title="Buscar por titulo", context=context, thumbnail=config.get_thumb("thumb_search.png"))) itemlist.append(Item(channel=item.channel, action="search", title="Buscar por categorias (búsqueda avanzada)", extra="categorias", context=context, thumbnail=config.get_thumb("thumb_search.png"))) itemlist.append(Item(channel=item.channel, action="opciones", title="Opciones", thumbnail=config.get_thumb("thumb_search.png"))) saved_searches_list = get_saved_searches() context2 = context[:] context2.append({"title": "Borrar búsquedas guardadas", "action": "clear_saved_searches", "channel": item.channel}) logger.info("saved_searches_list=%s" % saved_searches_list) if saved_searches_list: itemlist.append(Item(channel=item.channel, action="", title="Búsquedas guardadas:", context=context2, thumbnail=config.get_thumb("thumb_search.png"))) for saved_search_text in saved_searches_list: itemlist.append(Item(channel=item.channel, action="do_search", title=' "' + saved_search_text + '"', extra=saved_search_text, context=context2, category=saved_search_text, thumbnail=config.get_thumb("thumb_search.png"))) return itemlist def opciones(item): itemlist = list() itemlist.append(Item(channel=item.channel, action="setting_channel", title="Elegir canales incluidos en la búsqueda", folder=False, thumbnail=config.get_thumb("thumb_search.png"))) itemlist.append(Item(channel=item.channel, action="clear_saved_searches", title="Borrar búsquedas guardadas", folder=False, thumbnail=config.get_thumb("thumb_search.png"))) itemlist.append(Item(channel=item.channel, action="settings", title="Otros ajustes", folder=False, thumbnail=config.get_thumb("thumb_search.png"))) return itemlist def settings(item): return platformtools.show_channel_settings(caption="configuración -- Buscador") def setting_channel(item): channels_path = os.path.join(config.get_runtime_path(), "channels", '*.json') channel_language = config.get_setting("channel_language") if channel_language == "": channel_language = "all" list_controls = [] for infile in sorted(glob.glob(channels_path)): channel_name = os.path.basename(infile)[:-5] channel_parameters = channeltools.get_channel_parameters(channel_name) # No incluir si es un canal inactivo if not channel_parameters["active"]: continue # No incluir si es un canal para adultos, y el modo adulto está desactivado if channel_parameters["adult"] and config.get_setting("adult_mode") == 0: continue # No incluir si el canal es en un idioma filtrado if channel_language != "all" and channel_parameters["language"] != channel_language: continue # No incluir si en la configuracion del canal no existe "include_in_global_search" include_in_global_search = channel_parameters["include_in_global_search"] if not include_in_global_search: continue else: # Se busca en la configuración del canal el valor guardado include_in_global_search = config.get_setting("include_in_global_search", channel_name) control = {'id': channel_name, 'type': "bool", 'label': channel_parameters["title"], 'default': include_in_global_search, 'enabled': True, 'visible': True} list_controls.append(control) if config.get_setting("custom_button_value", item.channel): custom_button_label = "Ninguno" else: custom_button_label = "Todos" return platformtools.show_channel_settings(list_controls=list_controls, caption="Canales incluidos en la búsqueda global", callback="save_settings", item=item, custom_button={'visible': True, 'function': "cb_custom_button", 'close': False, 'label': custom_button_label}) def save_settings(item, dict_values): progreso = platformtools.dialog_progress("Guardando configuración...", "Espere un momento por favor.") n = len(dict_values) for i, v in enumerate(dict_values): progreso.update((i * 100) / n, "Guardando configuración...") config.set_setting("include_in_global_search", dict_values[v], v) progreso.close() def cb_custom_button(item, dict_values): value = config.get_setting("custom_button_value", item.channel) if value == "": value = False for v in dict_values.keys(): dict_values[v] = not value if config.set_setting("custom_button_value", not value, item.channel) == True: return {"label": "Ninguno"} else: return {"label": "Todos"} def searchbycat(item): # Only in xbmc/kodi # Abre un cuadro de dialogo con las categorías en las que hacer la búsqueda categories = ["Películas", "Series", "Anime", "Documentales", "VOS", "Latino"] categories_id = ["movie", "tvshow", "anime", "documentary", "vos", "latino"] list_controls = [] for i, category in enumerate(categories): control = {'id': categories_id[i], 'type': "bool", 'label': category, 'default': False, 'enabled': True, 'visible': True} list_controls.append(control) control = {'id': "separador", 'type': "label", 'label': '', 'default': "", 'enabled': True, 'visible': True} list_controls.append(control) control = {'id': "torrent", 'type': "bool", 'label': 'Incluir en la búsqueda canales Torrent', 'default': True, 'enabled': True, 'visible': True} list_controls.append(control) return platformtools.show_channel_settings(list_controls=list_controls, caption="Elegir categorías", callback="search_cb", item=item) def search_cb(item, values=""): cat = [] for c in values: if values[c]: cat.append(c) if not len(cat): return None else: logger.info(item.tostring()) logger.info(str(cat)) return do_search(item, cat) # Al llamar a esta función, el sistema pedirá primero el texto a buscar # y lo pasará en el parámetro "tecleado" def search(item, tecleado): logger.info() tecleado = tecleado.replace("+", " ") item.category = tecleado if tecleado != "": save_search(tecleado) if item.extra == "categorias": item.extra = tecleado itemlist = searchbycat(item) else: item.extra = tecleado itemlist = do_search(item, []) return itemlist def show_result(item): tecleado = None if item.adult and config.get_setting("adult_request_password"): # Solicitar contraseña tecleado = platformtools.dialog_input("", "Contraseña para canales de adultos", True) if tecleado is None or tecleado != config.get_setting("adult_pin"): return [] item.channel = item.__dict__.pop('from_channel') item.action = item.__dict__.pop('from_action') if item.__dict__.has_key('tecleado'): tecleado = item.__dict__.pop('tecleado') try: channel = __import__('channels.%s' % item.channel, fromlist=["channels.%s" % item.channel]) except: import traceback logger.error(traceback.format_exc()) return [] if tecleado: # Mostrar resultados: agrupados por canales return channel.search(item, tecleado) else: # Mostrar resultados: todos juntos try: from platformcode import launcher launcher.run(item) except ImportError: return getattr(channel, item.action)(item) def channel_search(search_results, channel_parameters, tecleado): try: exec "from channels import " + channel_parameters["channel"] + " as module" mainlist = module.mainlist(Item(channel=channel_parameters["channel"])) search_items = [item for item in mainlist if item.action == "search"] if not search_items: search_items = [Item(channel=channel_parameters["channel"], action="search")] for item in search_items: result = module.search(item.clone(), tecleado) if result is None: result = [] if len(result): if not channel_parameters["title"] in search_results: search_results[channel_parameters["title"]] = [] search_results[channel_parameters["title"]].append({"item": item, "itemlist": result, "adult": channel_parameters["adult"]}) except: logger.error("No se puede buscar en: %s" % channel_parameters["title"]) import traceback logger.error(traceback.format_exc()) # Esta es la función que realmente realiza la búsqueda def do_search(item, categories=None): logger.info("blaa categorias %s" % categories) if categories is None: categories = [] multithread = config.get_setting("multithread", "search") result_mode = config.get_setting("result_mode", "search") tecleado = item.extra itemlist = [] channels_path = os.path.join(config.get_runtime_path(), "channels", '*.json') logger.info("channels_path=%s" % channels_path) channel_language = config.get_setting("channel_language") logger.info("channel_language=%s" % channel_language) if channel_language == "": channel_language = "all" logger.info("channel_language=%s" % channel_language) # Para Kodi es necesario esperar antes de cargar el progreso, de lo contrario # el cuadro de progreso queda "detras" del cuadro "cargando..." y no se le puede dar a cancelar time.sleep(0.5) progreso = platformtools.dialog_progress("Buscando '%s'..." % tecleado, "") channel_files = sorted(glob.glob(channels_path), key=lambda x: os.path.basename(x)) import math # fix float porque la division se hace mal en python 2.x number_of_channels = float(100) / len(channel_files) threads = [] search_results = {} start_time = time.time() for index, infile in enumerate(channel_files): try: percentage = int(math.ceil((index + 1) * number_of_channels)) basename = os.path.basename(infile) basename_without_extension = basename[:-5] logger.info("%s..." % basename_without_extension) channel_parameters = channeltools.get_channel_parameters(basename_without_extension) # No busca si es un canal inactivo if not channel_parameters["active"]: logger.info("%s -no activo-" % basename_without_extension) continue # En caso de búsqueda por categorias if categories: # Si no se ha seleccionado torrent no se muestra if "torrent" not in categories: if "torrent" in channel_parameters["categories"]: logger.info("%s -torrent-" % basename_without_extension) continue for cat in categories: if cat not in channel_parameters["categories"]: logger.info("%s -no en %s-" % (basename_without_extension, cat)) continue # No busca si es un canal para adultos, y el modo adulto está desactivado if channel_parameters["adult"] and config.get_setting("adult_mode") == 0: logger.info("%s -adulto-" % basename_without_extension) continue # No busca si el canal es en un idioma filtrado if channel_language != "all" and channel_parameters["language"] != channel_language: logger.info("%s -idioma no válido-" % basename_without_extension) continue # No busca si es un canal excluido de la búsqueda global include_in_global_search = channel_parameters["include_in_global_search"] if include_in_global_search: # Buscar en la configuracion del canal include_in_global_search = config.get_setting("include_in_global_search", basename_without_extension) if not include_in_global_search: logger.info("%s -no incluido en lista a buscar-" % basename_without_extension) continue if progreso.iscanceled(): progreso.close() logger.info("Búsqueda cancelada") return itemlist # Modo Multi Thread if multithread: t = Thread(target=channel_search, args=[search_results, channel_parameters, tecleado], name=channel_parameters["title"]) t.setDaemon(True) t.start() threads.append(t) # Modo single Thread else: logger.info("Intentado búsqueda en %s de %s " % (basename_without_extension, tecleado)) channel_search(search_results, channel_parameters, tecleado) logger.info("%s incluido en la búsqueda" % basename_without_extension) progreso.update(percentage, "Buscando en %s..." % channel_parameters["title"]) except: logger.error("No se puede buscar en: %s" % channel_parameters["title"]) import traceback logger.error(traceback.format_exc()) continue # Modo Multi Thread # Usando isAlive() no es necesario try-except, # ya que esta funcion (a diferencia de is_alive()) # es compatible tanto con versiones antiguas de python como nuevas if multithread: pendent = [a for a in threads if a.isAlive()] t = float(100) / len(pendent) while pendent: index = (len(threads) - len(pendent)) + 1 percentage = int(math.ceil(index * t)) list_pendent_names = [a.getName() for a in pendent] mensaje = "Buscando en %s" % (", ".join(list_pendent_names)) progreso.update(percentage, "Finalizado en %d/%d canales..." % (len(threads) - len(pendent), len(threads)), mensaje) logger.debug(mensaje) if progreso.iscanceled(): logger.info("Búsqueda cancelada") break time.sleep(0.5) pendent = [a for a in threads if a.isAlive()] total = 0 for channel in sorted(search_results.keys()): for element in search_results[channel]: total += len(element["itemlist"]) title = channel # resultados agrupados por canales if result_mode == 0: if len(search_results[channel]) > 1: title += " [%s]" % element["item"].title.strip() title += " (%s)" % len(element["itemlist"]) title = re.sub("\[COLOR [^\]]+\]", "", title) title = re.sub("\[/COLOR]", "", title) itemlist.append(Item(title=title, channel="search", action="show_result", url=element["item"].url, extra=element["item"].extra, folder=True, adult=element["adult"], from_action="search", from_channel=element["item"].channel, tecleado=tecleado)) # todos los resultados juntos, en la misma lista else: title = " [ Resultados del canal %s ] " % channel itemlist.append(Item(title=title, channel="search", action="", folder=False, text_bold=True)) for i in element["itemlist"]: if i.action: title = " " + i.title itemlist.append(i.clone(title=title, from_action=i.action, from_channel=i.channel, channel="search", action="show_result", adult=element["adult"])) title = "Buscando: '%s' | Encontrado: %d vídeos | Tiempo: %2.f segundos" % (tecleado, total, time.time() - start_time) itemlist.insert(0, Item(title=title, text_color='yellow')) progreso.close() return itemlist def save_search(text): saved_searches_limit = int((10, 20, 30, 40,)[int(config.get_setting("saved_searches_limit", "search"))]) current_saved_searches_list = config.get_setting("saved_searches_list", "search") if current_saved_searches_list is None: saved_searches_list = [] else: saved_searches_list = list(current_saved_searches_list) if text in saved_searches_list: saved_searches_list.remove(text) saved_searches_list.insert(0, text) config.set_setting("saved_searches_list", saved_searches_list[:saved_searches_limit], "search") def clear_saved_searches(item): config.set_setting("saved_searches_list", list(), "search") platformtools.dialog_ok("Buscador", "Búsquedas borradas correctamente") def get_saved_searches(): current_saved_searches_list = config.get_setting("saved_searches_list", "search") if current_saved_searches_list is None: saved_searches_list = [] else: saved_searches_list = list(current_saved_searches_list) return saved_searches_list