#!/usr/bin/python # -*- coding: utf-8 -*- ''' script.skin.helper.service searchdialog.py Special window to search the Kodi video database ''' import os, sys import threading if sys.version_info.major == 3: import _thread as thread else: import thread from resources.lib.utils import getCondVisibility, try_decode import xbmc import xbmcgui from metadatautils import MetadataUtils class SearchDialog(xbmcgui.WindowXMLDialog): ''' Special window to search the Kodi video database''' search_thread = None search_string = "" def __init__(self, *args, **kwargs): self.mutils = MetadataUtils() xbmcgui.WindowXMLDialog.__init__(self, *args, **kwargs) def onInit(self): '''triggers on initialization of the dialog''' self.search_thread = SearchBackgroundThread() self.search_thread.set_dialog(self) self.search_thread.start() def onAction(self, action): '''triggers on kodi navigation events''' if self.getFocusId() in [3110, 3111, 3112]: # one of the media lists is focused if action.getId() in (11, ): # info key on media item self.show_info() if action.getId() in (9, 10, 92, 216, 247, 257, 275, 61467, 61448, ): # close dialog self.close_dialog() else: # search keyboard is focused if action.getId() in (9, 10, 92, 216, 247, 257, 275, 61467, 61448, ): # backspace self.remove_char() else: self.action_textbox(action) def close_dialog(self): '''stop background thread and close the dialog''' self.search_thread.stop_running() self.mutils.close() self.close() def remove_char(self): '''remove character from query string''' if len(self.search_string) == 0 or self.search_string == " ": self.close_dialog() else: if len(self.search_string) == 1: search_term = " " else: search_term = self.search_string[:-1] self.setFocusId(3056) self.getControl(3010).setLabel(search_term) self.search_string = search_term self.search_thread.set_search(search_term) def action_textbox(self, act): '''special handler to allow direct typing to search''' action_number_0 = 58 action_number_9 = 67 action = act.getId() button = act.getButtonCode() # Upper-case values if button >= 0x2f041 and button <= 0x2f05b: self.add_character(chr(button - 0x2F000)) # Lower-case values if button >= 0xf041 and button <= 0xf05b: self.add_character(chr(button - 0xEFE0)) # Numbers if action >= action_number_0 and action <= action_number_9: self.add_character(chr(action - action_number_0 + 48)) # Backspace if button == 0xF008: if len(self.search_string) >= 1: self.remove_char() # Delete if button == 0xF02E: self.clear_search() # Space if button == 0xF020: self.add_character(" ") if getCondVisibility("Window.IsVisible(10111)"): # close shutdown window if visible xbmc.executebuiltin("Dialog.close(10111)") def focus_char(self, char): '''focus specified character''' alphanum = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '', ' '].index(str(char).upper()) self.setFocusId(3020 + alphanum) def onClick(self, control_id): '''Kodi builtin: triggers if window is clicked''' if control_id == 3020: self.add_character("A") elif control_id == 3021: self.add_character("B") elif control_id == 3022: self.add_character("C") elif control_id == 3023: self.add_character("D") elif control_id == 3024: self.add_character("E") elif control_id == 3025: self.add_character("F") elif control_id == 3026: self.add_character("G") elif control_id == 3027: self.add_character("H") elif control_id == 3028: self.add_character("I") elif control_id == 3029: self.add_character("J") elif control_id == 3030: self.add_character("K") elif control_id == 3031: self.add_character("L") elif control_id == 3032: self.add_character("M") elif control_id == 3033: self.add_character("N") elif control_id == 3034: self.add_character("O") elif control_id == 3035: self.add_character("P") elif control_id == 3036: self.add_character("Q") elif control_id == 3037: self.add_character("R") elif control_id == 3038: self.add_character("S") elif control_id == 3039: self.add_character("T") elif control_id == 3040: self.add_character("U") elif control_id == 3041: self.add_character("V") elif control_id == 3042: self.add_character("W") elif control_id == 3043: self.add_character("X") elif control_id == 3044: self.add_character("Y") elif control_id == 3045: self.add_character("Z") elif control_id == 3046: self.add_character("0") elif control_id == 3047: self.add_character("1") elif control_id == 3048: self.add_character("2") elif control_id == 3049: self.add_character("3") elif control_id == 3050: self.add_character("4") elif control_id == 3051: self.add_character("5") elif control_id == 3052: self.add_character("6") elif control_id == 3053: self.add_character("7") elif control_id == 3054: self.add_character("8") elif control_id == 3055: self.add_character("9") elif control_id == 3056: self.remove_char() elif control_id == 3057: self.add_character(" ") elif control_id == 3058: self.clear_search() elif control_id == 3010: search_term = xbmcgui.Dialog().input(xbmc.getLocalizedString(16017), type=xbmcgui.INPUT_ALPHANUM) self.getControl(3010).setLabel(search_term) self.search_string = search_term self.search_thread.set_search(search_term) elif control_id in [3110, 3111, 3112]: self.open_item() def clear_search(self): '''clears the search textbox''' self.setFocusId(3058) self.getControl(3010).setLabel(" ") self.search_string = "" self.search_thread.set_search("") def add_character(self, char): '''add character to our search textbox''' self.focus_char(char) search_term = self.search_string + char self.getControl(3010).setLabel(search_term) self.search_string = search_term self.search_thread.set_search(search_term) def show_info(self): '''show info dialog for selected item''' control_id = self.getFocusId() listitem = self.getControl(control_id).getSelectedItem() if "actor" in listitem.getProperty("DBTYPE"): xbmc.executebuiltin("RunScript(script.extendedinfo,info=extendedactorinfo,name=%s)" % listitem.getLabel()) else: from .infodialog import DialogVideoInfo win = DialogVideoInfo("DialogVideoInfo.xml", "", listitem=listitem) win.doModal() result = win.result del win if result: self.close_dialog() def open_item(self): '''open selected item''' control_id = self.getFocusId() listitem = self.getControl(control_id).getSelectedItem() if "videodb:" in listitem.getLabel(): # tvshow: open path xbmc.executebuiltin('ReplaceWindow(Videos,"%s")' % self.listitem.getLabel()) self.close_dialog() elif "actor" in listitem.getProperty("DBTYPE"): # cast dialog if sys.version_info.major == 3: from .dialogselect import DialogSelect else: from dialogselect import DialogSelect results = [] name = try_decode(listitem.getLabel()) items = self.mutils.kodidb.castmedia(name) items = self.mutils.process_method_on_list(self.mutils.kodidb.prepare_listitem, items) for item in items: if item["file"].startswith("videodb://"): item["file"] = "ActivateWindow(Videos,%s,return)" % item["file"] else: item["file"] = 'PlayMedia("%s")' % item["file"] results.append(self.mutils.kodidb.create_listitem(item, False)) # finished lookup - display listing with results dialog = DialogSelect("DialogSelect.xml", "", listing=results, windowtitle=name, richlayout=True) dialog.doModal() result = dialog.result del dialog if result: xbmc.executebuiltin(result.getLabel()) self.close_dialog() else: # video file: start playback xbmc.executebuiltin('PlayMedia("%s")' % listitem.getLabel()) self.close_dialog() class SearchBackgroundThread(threading.Thread): '''Background thread to complement our search dialog, fills the listing while UI keeps responsive''' active = True dialog = None search_string = "" def __init__(self, *args): xbmc.log("SearchBackgroundThread Init") threading.Thread.__init__(self, *args) self.mutils = MetadataUtils() self.actors = [] thread.start_new_thread(self.set_actors, ()) def set_search(self, searchstr): '''set search query''' self.search_string = searchstr def stop_running(self): '''stop thread end exit''' self.active = False def set_dialog(self, dialog): '''set the active dialog to perform actions''' self.dialog = dialog def set_actors(self): '''fill list with all actors''' self.actors = self.dialog.mutils.kodidb.actors() def run(self): '''Main run loop for the background thread''' last_searchstring = "" monitor = xbmc.Monitor() while not monitor.abortRequested() and self.active: if self.search_string != last_searchstring: last_searchstring = self.search_string self.do_search(self.search_string) monitor.waitForAbort(1) del monitor def do_search(self, search_term): '''scrape results for search query''' movies_list = self.dialog.getControl(3110) series_list = self.dialog.getControl(3111) cast_list = self.dialog.getControl(3112) # clear current values movies_list.reset() series_list.reset() cast_list.reset() if len(search_term) == 0: return filters = [{"operator": "contains", "field": "title", "value": search_term}] # Process movies items = self.dialog.mutils.kodidb.movies(filters=filters) items = self.mutils.process_method_on_list(self.dialog.mutils.kodidb.prepare_listitem, items) result = [] for item in items: result.append(self.dialog.mutils.kodidb.create_listitem(item, False)) movies_list.addItems(result) # Process tvshows items = self.dialog.mutils.kodidb.tvshows(filters=filters) items = self.mutils.process_method_on_list(self.dialog.mutils.kodidb.prepare_listitem, items) result = [] for item in items: item["file"] = 'videodb://tvshows/titles/%s' % item['tvshowid'] item["isFolder"] = True result.append(self.dialog.mutils.kodidb.create_listitem(item, False)) series_list.addItems(result) # Process cast result = [] for item in self.actors: if search_term.lower() in item["label"].lower(): item = self.dialog.mutils.kodidb.prepare_listitem(item) item["file"] = "RunScript(script.skin.helper.service,action=getcastmedia,name=%s)" % item["label"] result.append(self.dialog.mutils.kodidb.create_listitem(item, False)) cast_list.addItems(result)