Ejemplo n.º 1
0
class MainModule:
    '''mainmodule provides the script methods for the skinhelper addon'''
    def __init__(self):
        '''Initialization and main code run'''
        self.win = xbmcgui.Window(10000)
        self.addon = xbmcaddon.Addon(ADDON_ID)
        self.mutils = MetadataUtils()
        self.cache = self.mutils.cache

        self.params = self.get_params()
        log_msg("MainModule called with parameters: %s" % self.params)
        action = self.params.get("action", "")
        # launch module for action provided by this script
        try:
            getattr(self, action)()
        except AttributeError:
            log_exception(__name__, "No such action: %s" % action)
        except Exception as exc:
            log_exception(__name__, exc)
        finally:
            xbmc.executebuiltin("dialog.Close(busydialog)")

        # do cleanup
        self.close()

    def close(self):
        '''Cleanup Kodi Cpython instances on exit'''
        self.mutils.close()
        del self.win
        del self.addon
        log_msg("MainModule exited")

    @classmethod
    def get_params(self):
        '''extract the params from the called script path'''
        params = {}
        for arg in sys.argv[1:]:
            paramname = arg.split('=')[0]
            paramvalue = arg.replace(paramname + "=", "")
            paramname = paramname.lower()
            if paramname == "action":
                paramvalue = paramvalue.lower()
            params[paramname] = paramvalue
        return params

    def deprecated_method(self, newaddon):
        '''
            used when one of the deprecated methods is called
            print warning in log and call the external script with the same parameters
        '''
        action = self.params.get("action")
        log_msg(
            "Deprecated method: %s. Please call %s directly" %
            (action, newaddon), xbmc.LOGWARNING)
        paramstring = ""
        if sys.version_info.major == 3:
            for key, value in self.params.items():
                paramstring += ",%s=%s" % (key, value)
        else:
            for key, value in self.params.iteritems():
                paramstring += ",%s=%s" % (key, value)
        if getCondVisibility("System.HasAddon(%s)" % newaddon):
            xbmc.executebuiltin("RunAddon(%s%s)" % (newaddon, paramstring))
        else:
            # trigger install of the addon
            if KODI_VERSION > 16:
                xbmc.executebuiltin("InstallAddon(%s)" % newaddon)
            else:
                xbmc.executebuiltin("RunPlugin(plugin://%s)" % newaddon)

    @staticmethod
    def musicsearch():
        '''helper to go directly to music search dialog'''
        xbmc.executebuiltin("ActivateWindow(Music)")
        xbmc.executebuiltin("SendClick(8)")

    def setview(self):
        '''sets the selected viewmode for the container'''
        xbmc.executebuiltin("ActivateWindow(busydialog)")
        content_type = get_current_content_type()
        if not content_type:
            content_type = "files"
        current_view = try_decode(xbmc.getInfoLabel("Container.Viewmode"))
        view_id, view_label = self.selectview(content_type, current_view)
        current_forced_view = xbmc.getInfoLabel(
            "Skin.String(SkinHelper.ForcedViews.%s)" % content_type)

        if view_id is not None:
            # also store forced view
            if (content_type and current_forced_view
                    and current_forced_view != "None" and getCondVisibility(
                        "Skin.HasSetting(SkinHelper.ForcedViews.Enabled)")):
                xbmc.executebuiltin(
                    "Skin.SetString(SkinHelper.ForcedViews.%s,%s)" %
                    (content_type, view_id))
                xbmc.executebuiltin(
                    "Skin.SetString(SkinHelper.ForcedViews.%s.label,%s)" %
                    (content_type, view_label))
                self.win.setProperty("SkinHelper.ForcedView", view_id)
                if not getCondVisibility(
                        "Control.HasFocus(%s)" % current_forced_view):
                    xbmc.sleep(100)
                    xbmc.executebuiltin("Container.SetViewMode(%s)" % view_id)
                    xbmc.executebuiltin("SetFocus(%s)" % view_id)
            else:
                self.win.clearProperty("SkinHelper.ForcedView")
            # set view
            xbmc.executebuiltin("Container.SetViewMode(%s)" % view_id)

    def selectview(self,
                   content_type="other",
                   current_view=None,
                   display_none=False):
        '''reads skinfile with all views to present a dialog to choose from'''
        cur_view_select_id = None
        label = ""
        all_views = []
        if display_none:
            listitem = xbmcgui.ListItem(label="None")
            listitem.setProperty("id", "None")
            all_views.append(listitem)
        # read the special skin views file
        if sys.version_info.major == 3:
            views_file = try_decode(
                xbmcvfs.translatePath('special://skin/extras/views.xml'))
        else:
            views_file = try_decode(
                xbmc.translatePath('special://skin/extras/views.xml'))
        if xbmcvfs.exists(views_file):
            doc = parse(views_file)
            listing = doc.documentElement.getElementsByTagName('view')
            itemcount = 0
            for view in listing:
                label = xbmc.getLocalizedString(
                    int(view.attributes['languageid'].nodeValue))
                viewid = view.attributes['value'].nodeValue
                mediatypes = view.attributes['type'].nodeValue.lower().split(
                    ",")
                if label.lower() == current_view.lower(
                ) or viewid == current_view:
                    cur_view_select_id = itemcount
                    if display_none:
                        cur_view_select_id += 1
                if (("all" in mediatypes or content_type.lower() in mediatypes)
                        and (not "!" + content_type.lower() in mediatypes)
                        and not getCondVisibility(
                            "Skin.HasSetting(SkinHelper.view.Disabled.%s)" %
                            viewid)):
                    image = "special://skin/extras/viewthumbs/%s.jpg" % viewid
                    listitem = xbmcgui.ListItem(label=label)
                    listitem.setArt({'icon': image})
                    listitem.setProperty("viewid", viewid)
                    listitem.setProperty("icon", image)
                    all_views.append(listitem)
                    itemcount += 1
        dialog = DialogSelect("DialogSelect.xml",
                              "",
                              listing=all_views,
                              windowtitle=self.addon.getLocalizedString(32012),
                              richlayout=True)
        dialog.autofocus_id = cur_view_select_id
        dialog.doModal()
        result = dialog.result
        del dialog
        if result:
            viewid = result.getProperty("viewid")
            label = try_decode(result.getLabel())
            return (viewid, label)
        else:
            return (None, None)

    # pylint: disable-msg=too-many-local-variables
    def enableviews(self):
        '''show select dialog to enable/disable views'''
        all_views = []
        if sys.version_info.major == 3:
            views_file = try_decode(
                xbmcvfs.translatePath('special://skin/extras/views.xml'))
        else:
            views_file = try_decode(
                xbmc.translatePath('special://skin/extras/views.xml'))
        richlayout = self.params.get("richlayout", "") == "true"
        if xbmcvfs.exists(views_file):
            doc = parse(views_file)
            listing = doc.documentElement.getElementsByTagName('view')
            for view in listing:
                view_id = view.attributes['value'].nodeValue
                label = xbmc.getLocalizedString(
                    int(view.attributes['languageid'].nodeValue))
                desc = label + " (" + str(view_id) + ")"
                image = "special://skin/extras/viewthumbs/%s.jpg" % view_id
                listitem = xbmcgui.ListItem(label=label, label2=desc)
                listitem.setArt({'icon': image})
                listitem.setProperty("viewid", view_id)
                if not getCondVisibility(
                        "Skin.HasSetting(SkinHelper.view.Disabled.%s)" %
                        view_id):
                    listitem.select(selected=True)
                excludefromdisable = False
                try:
                    excludefromdisable = view.attributes[
                        'excludefromdisable'].nodeValue == "true"
                except Exception:
                    pass
                if not excludefromdisable:
                    all_views.append(listitem)

        dialog = DialogSelect("DialogSelect.xml",
                              "",
                              listing=all_views,
                              windowtitle=self.addon.getLocalizedString(32013),
                              multiselect=True,
                              richlayout=richlayout)
        dialog.doModal()
        result = dialog.result
        del dialog
        if result:
            for item in result:
                view_id = item.getProperty("viewid")
                if item.isSelected():
                    # view is enabled
                    xbmc.executebuiltin(
                        "Skin.Reset(SkinHelper.view.Disabled.%s)" % view_id)
                else:
                    # view is disabled
                    xbmc.executebuiltin(
                        "Skin.SetBool(SkinHelper.view.Disabled.%s)" % view_id)

    # pylint: enable-msg=too-many-local-variables

    def setforcedview(self):
        '''helper that sets a forced view for a specific content type'''
        content_type = self.params.get("contenttype")
        if content_type:
            current_view = xbmc.getInfoLabel(
                "Skin.String(SkinHelper.ForcedViews.%s)" % content_type)
            if not current_view:
                current_view = "0"
            view_id, view_label = self.selectview(content_type, current_view,
                                                  True)
            if view_id or view_label:
                xbmc.executebuiltin(
                    "Skin.SetString(SkinHelper.ForcedViews.%s,%s)" %
                    (content_type, view_id))
                xbmc.executebuiltin(
                    "Skin.SetString(SkinHelper.ForcedViews.%s.label,%s)" %
                    (content_type, view_label))

    @staticmethod
    def get_youtube_listing(searchquery):
        '''get items from youtube plugin by query'''
        lib_path = "plugin://plugin.video.youtube/kodion/search/query/?q=%s" % searchquery
        metadatautils = MetadataUtils()
        files = metadatautils.kodidb.files(lib_path)
        del metadatautils
        return files

    def searchyoutube(self):
        '''helper to search youtube for the given title'''
        xbmc.executebuiltin("ActivateWindow(busydialog)")
        title = self.params.get("title", "")
        window_header = self.params.get("header", "")
        results = []
        for media in self.get_youtube_listing(title):
            if not media["filetype"] == "directory":
                label = media["label"]
                label2 = media["plot"]
                image = ""
                if media.get('art'):
                    if media['art'].get('thumb'):
                        image = (media['art']['thumb'])
                listitem = xbmcgui.ListItem(label=label, label2=label2)
                listitem.setArt({'icon': image})
                listitem.setProperty("path", media["file"])
                results.append(listitem)

        # finished lookup - display listing with results
        xbmc.executebuiltin("dialog.Close(busydialog)")
        dialog = DialogSelect("DialogSelect.xml",
                              "",
                              listing=results,
                              windowtitle=window_header,
                              multiselect=False,
                              richlayout=True)
        dialog.doModal()
        result = dialog.result
        del dialog
        if result:
            if getCondVisibility(
                    "Window.IsActive(script-skin_helper_service-CustomInfo.xml) | "
                    "Window.IsActive(movieinformation)"):
                xbmc.executebuiltin("Dialog.Close(movieinformation)")
                xbmc.executebuiltin(
                    "Dialog.Close(script-skin_helper_service-CustomInfo.xml)")
                xbmc.sleep(1000)
            xbmc.executebuiltin('PlayMedia("%s")' % result.getProperty("path"))
            del result

    def getcastmedia(self):
        '''helper to show a dialog with all media for a specific actor'''
        xbmc.executebuiltin("ActivateWindow(busydialog)")
        name = self.params.get("name", "")
        window_header = self.params.get("name", "")
        results = []
        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
        xbmc.executebuiltin("dialog.Close(busydialog)")
        dialog = DialogSelect("DialogSelect.xml",
                              "",
                              listing=results,
                              windowtitle=window_header,
                              richlayout=True)
        dialog.doModal()
        result = dialog.result
        del dialog
        if result:
            while getCondVisibility(
                    "System.HasModalDialog | System.HasVisibleModalDialog"):
                xbmc.executebuiltin("Action(Back)")
                xbmc.sleep(300)
            xbmc.executebuiltin(result.getfilename())
            del result

    def setfocus(self):
        '''helper to set focus on a list or control'''
        control = self.params.get("control")
        fallback = self.params.get("fallback")
        position = self.params.get("position", "0")
        relativeposition = self.params.get("relativeposition")
        if relativeposition:
            position = int(relativeposition) - 1
        count = 0
        if control:
            while not getCondVisibility("Control.HasFocus(%s)" % control):
                if getCondVisibility("Window.IsActive(busydialog)"):
                    xbmc.sleep(150)
                    continue
                elif count == 20 or (getCondVisibility(
                        "!Control.IsVisible(%s) | "
                        "!Integer.IsGreater(Container(%s).NumItems,0)" %
                    (control, control))):
                    if fallback:
                        xbmc.executebuiltin("Control.SetFocus(%s)" % fallback)
                    break
                else:
                    xbmc.executebuiltin("Control.SetFocus(%s,%s)" %
                                        (control, position))
                    xbmc.sleep(50)
                    count += 1

    def setwidgetcontainer(self):
        '''helper that reports the current selected widget container/control'''
        controls = self.params.get("controls", "").split("-")
        if controls:
            xbmc.sleep(50)
            for i in range(10):
                for control in controls:
                    if getCondVisibility(
                            "Control.IsVisible(%s) + Integer.IsGreater(Container(%s).NumItems,0)"
                            % (control, control)):
                        self.win.setProperty("SkinHelper.WidgetContainer",
                                             control)
                        return
                xbmc.sleep(50)

    def saveskinimage(self):
        '''let the user select an image and save it to addon_data for easy backup'''
        skinstring = self.params.get("skinstring", "")
        allow_multi = self.params.get("multi", "") == "true"
        header = self.params.get("header", "")
        value = SkinSettings().save_skin_image(skinstring, allow_multi, header)
        if value:
            xbmc.executebuiltin(
                "Skin.SetString(%s,%s)" %
                (skinstring.encode("utf-8"), value.encode("utf-8")))

    @staticmethod
    def checkskinsettings():
        '''performs check of all default skin settings and labels'''
        SkinSettings().correct_skin_settings()

    def setskinsetting(self):
        '''allows the user to set a skin setting with a select dialog'''
        setting = self.params.get("setting", "")
        org_id = self.params.get("id", "")
        if "$" in org_id:
            org_id = try_decode(xbmc.getInfoLabel(org_id))
        header = self.params.get("header", "")
        SkinSettings().set_skin_setting(setting=setting,
                                        window_header=header,
                                        original_id=org_id)

    def setskinconstant(self):
        '''allows the user to set a skin constant with a select dialog'''
        setting = self.params.get("setting", "")
        value = self.params.get("value", "")
        header = self.params.get("header", "")
        SkinSettings().set_skin_constant(setting, header, value)

    def setskinconstants(self):
        '''allows the skinner to set multiple skin constants'''
        settings = self.params.get("settings", "").split("|")
        values = self.params.get("values", "").split("|")
        SkinSettings().set_skin_constants(settings, values)

    def setskinshortcutsproperty(self):
        '''allows the user to make a setting for skinshortcuts using the special skinsettings dialogs'''
        setting = self.params.get("setting", "")
        prop = self.params.get("property", "")
        header = self.params.get("header", "")
        SkinSettings().set_skinshortcuts_property(setting, header, prop)

    def togglekodisetting(self):
        '''toggle kodi setting'''
        settingname = self.params.get("setting", "")
        cur_value = getCondVisibility("system.getbool(%s)" % settingname)
        if cur_value:
            new_value = "false"
        else:
            new_value = "true"
        xbmc.executeJSONRPC(
            '{"jsonrpc":"2.0", "id":1, "method":"Settings.SetSettingValue","params":{"setting":"%s","value":%s}}'
            % (settingname, new_value))

    def setkodisetting(self):
        '''set kodi setting'''
        settingname = self.params.get("setting", "")
        value = self.params.get("value", '')
        numvalue = self.params.get("numvalue", '')
        if numvalue:
            value = '%s' % numvalue
        else:
            value = '"%s"' % value
        xbmc.executeJSONRPC(
            '{"jsonrpc":"2.0", "id":1, "method":"Settings.SetSettingValue",\
            "params":{"setting":"%s","value":%s}}' % (settingname, value))

    def playtrailer(self):
        '''auto play windowed trailer inside video listing'''
        if not getCondVisibility(
                "Container.Scrolling | Container.OnNext | "
                "Container.OnPrevious | !String.IsEmpty(Window(Home).Property(traileractionbusy))"
        ):
            self.win.setProperty("traileractionbusy", "traileractionbusy")
            widget_container = self.params.get("widgetcontainer", "")
            trailer_mode = self.params.get("mode", "").replace("auto_", "")
            allow_youtube = self.params.get("youtube", "") == "true"
            if not trailer_mode:
                trailer_mode = "windowed"
            if widget_container:
                widget_container_prefix = "Container(%s)." % widget_container
            else:
                widget_container_prefix = ""

            li_title = xbmc.getInfoLabel("%sListItem.Title" %
                                         widget_container_prefix)
            li_trailer = xbmc.getInfoLabel("%sListItem.Trailer" %
                                           widget_container_prefix)
            if not li_trailer and allow_youtube:
                youtube_result = self.get_youtube_listing("%s Trailer" %
                                                          li_title)
                if youtube_result:
                    li_trailer = youtube_result[0].get("file")
            # always wait a bit to prevent trailer start playing when we're scrolling the list
            xbmc.Monitor().waitForAbort(3)
            if li_trailer and (li_title == xbmc.getInfoLabel(
                    "%sListItem.Title" % widget_container_prefix)):
                if trailer_mode == "fullscreen" and li_trailer:
                    xbmc.executebuiltin('PlayMedia("%s")' % li_trailer)
                else:
                    xbmc.executebuiltin('PlayMedia("%s",1)' % li_trailer)
                self.win.setProperty("TrailerPlaying", trailer_mode)
            self.win.clearProperty("traileractionbusy")

    def colorpicker(self):
        '''legacy'''
        self.deprecated_method("script.skin.helper.colorpicker")

    def backup(self):
        '''legacy'''
        self.deprecated_method("script.skin.helper.skinbackup")

    def restore(self):
        '''legacy'''
        self.deprecated_method("script.skin.helper.skinbackup")

    def reset(self):
        '''legacy'''
        self.deprecated_method("script.skin.helper.skinbackup")

    def colorthemes(self):
        '''legacy'''
        self.deprecated_method("script.skin.helper.skinbackup")

    def createcolortheme(self):
        '''legacy'''
        self.deprecated_method("script.skin.helper.skinbackup")

    def restorecolortheme(self):
        '''legacy'''
        self.deprecated_method("script.skin.helper.skinbackup")

    def conditionalbackgrounds(self):
        '''legacy'''
        self.deprecated_method("script.skin.helper.backgrounds")

    def splashscreen(self):
        '''helper to show a user defined splashscreen in the skin'''
        import time
        splashfile = self.params.get("file", "")
        duration = int(self.params.get("duration", 5))
        if (splashfile.lower().endswith("jpg")
                or splashfile.lower().endswith("gif")
                or splashfile.lower().endswith("png")
                or splashfile.lower().endswith("tiff")):
            # this is an image file
            self.win.setProperty("SkinHelper.SplashScreen", splashfile)
            # for images we just wait for X seconds to close the splash again
            start_time = time.time()
            while (time.time() - start_time) <= duration:
                xbmc.sleep(500)
        else:
            # for video or audio we have to wait for the player to finish...
            xbmc.Player().play(splashfile, windowed=True)
            xbmc.sleep(500)
            while getCondVisibility("Player.HasMedia"):
                xbmc.sleep(150)
        # replace startup window with home
        startupwindow = xbmc.getInfoLabel("System.StartupWindow")
        xbmc.executebuiltin("ReplaceWindow(%s)" % startupwindow)
        autostart_playlist = xbmc.getInfoLabel(
            "$ESCINFO[Skin.String(autostart_playlist)]")
        if autostart_playlist:
            xbmc.executebuiltin("PlayMedia(%s)" % autostart_playlist)

    def videosearch(self):
        '''show the special search dialog'''
        from resources.lib.searchdialog import SearchDialog
        search_dialog = SearchDialog(
            "script-skin_helper_service-CustomSearch.xml",
            try_decode(self.addon.getAddonInfo('path')), "Default", "1080i")
        search_dialog.doModal()
        del search_dialog

    def showinfo(self):
        '''shows our special videoinfo dialog'''
        dbid = self.params.get("dbid", "")
        dbtype = self.params.get("dbtype", "")
        from .infodialog import show_infodialog
        show_infodialog(dbid, dbtype)

    def deletedir(self):
        '''helper to delete a directory, input can be normal filesystem path or vfs'''
        del_path = self.params.get("path")
        if del_path:
            ret = xbmcgui.Dialog().yesno(message="%s" %
                                         (xbmc.getLocalizedString(125)),
                                         heading=xbmc.getLocalizedString(122))
            if ret:
                success = recursive_delete_dir(del_path)
                if success:
                    xbmcgui.Dialog().ok(
                        heading=xbmc.getLocalizedString(19179),
                        message=self.addon.getLocalizedString(32014),
                        line2=self.addon.getLocalizedString(32014))
                else:
                    xbmcgui.Dialog().ok(
                        heading=xbmc.getLocalizedString(16205),
                        message=xbmc.getLocalizedString(32015),
                        line2=self.addon.getLocalizedString(32014))

    def overlaytexture(self):
        '''legacy: helper to let the user choose a background overlay from a skin defined folder'''
        skinstring = self.params.get("skinstring", "BackgroundOverlayTexture")
        self.params["skinstring"] = skinstring
        self.params["resourceaddon"] = "resource.images.backgroundoverlays"
        self.params["customfolder"] = "special://skin/extras/bgoverlays/"
        self.params["allowmulti"] = "false"
        self.params["header"] = self.addon.getLocalizedString(32002)
        self.selectimage()

    def busytexture(self):
        '''legacy: helper which lets the user select a busy spinner from predefined spinners in the skin'''
        skinstring = self.params.get("skinstring", "SkinHelper.SpinnerTexture")
        self.params["skinstring"] = skinstring
        self.params["resourceaddon"] = "resource.images.busyspinners"
        self.params["customfolder"] = "special://skin/extras/busy_spinners/"
        self.params["allowmulti"] = "true"
        self.params["header"] = self.addon.getLocalizedString(32006)
        self.selectimage()

    def selectimage(self):
        '''helper which lets the user select an image or imagepath from resourceaddons or custom path'''
        skinsettings = SkinSettings()
        skinstring = self.params.get("skinstring", "")
        skinshortcutsprop = self.params.get("skinshortcutsproperty", "")
        current_value = self.params.get("currentvalue", "")
        resource_addon = self.params.get("resourceaddon", "")
        allow_multi = self.params.get("allowmulti", "false") == "true"
        windowheader = self.params.get("header", "")
        skinhelper_backgrounds = self.params.get("skinhelperbackgrounds",
                                                 "false") == "true"
        label, value = skinsettings.select_image(
            skinstring,
            allow_multi=allow_multi,
            windowheader=windowheader,
            resource_addon=resource_addon,
            skinhelper_backgrounds=skinhelper_backgrounds,
            current_value=current_value)
        if label:
            if skinshortcutsprop:
                # write value to skinshortcuts prop
                from .skinshortcuts import set_skinshortcuts_property
                set_skinshortcuts_property(skinshortcutsprop, value, label)
            else:
                # write the values to skin strings
                if value.startswith("$INFO"):
                    # we got an dynamic image from window property
                    skinsettings.set_skin_variable(skinstring, value)
                    value = "$VAR[%s]" % skinstring
                skinstring = skinstring.encode("utf-8")
                label = label.encode("utf-8")
                xbmc.executebuiltin("Skin.SetString(%s.label,%s)" %
                                    (skinstring, label))
                xbmc.executebuiltin("Skin.SetString(%s.name,%s)" %
                                    (skinstring, label))
                xbmc.executebuiltin("Skin.SetString(%s,%s)" %
                                    (skinstring, value))
                xbmc.executebuiltin("Skin.SetString(%s.path,%s)" %
                                    (skinstring, value))
        del skinsettings

    def dialogok(self):
        '''helper to show an OK dialog with a message'''
        headertxt = clean_string(self.params.get("header", ""))
        bodytxt = clean_string(self.params.get("message", ""))
        dialog = xbmcgui.Dialog()
        dialog.ok(heading=headertxt, message=bodytxt)
        del dialog

    def dialogyesno(self):
        '''helper to show a YES/NO dialog with a message'''
        headertxt = clean_string(self.params.get("header", ""))
        bodytxt = clean_string(self.params.get("message", ""))
        yesactions = self.params.get("yesaction", "").split("|")
        noactions = self.params.get("noaction", "").split("|")
        if xbmcgui.Dialog().yesno(heading=headertxt, message=bodytxt):
            for action in yesactions:
                xbmc.executebuiltin(action)
        else:
            for action in noactions:
                xbmc.executebuiltin(action)

    def textviewer(self):
        '''helper to show a textviewer dialog with a message'''
        headertxt = clean_string(self.params.get("header", ""))
        bodytxt = clean_string(self.params.get("message", ""))
        xbmcgui.Dialog().textviewer(headertxt, bodytxt)

    def fileexists(self):
        '''helper to let the skinner check if a file exists
        and write the outcome to a window prop or skinstring'''
        filename = self.params.get("file")
        skinstring = self.params.get("skinstring")
        windowprop = self.params.get("winprop")
        if xbmcvfs.exists(filename):
            if windowprop:
                self.win.setProperty(windowprop, "exists")
            if skinstring:
                xbmc.executebuiltin("Skin.SetString(%s,exists)" % skinstring)
        else:
            if windowprop:
                self.win.clearProperty(windowprop)
            if skinstring:
                xbmc.executebuiltin("Skin.Reset(%s)" % skinstring)

    def stripstring(self):
        '''helper to allow the skinner to strip a string and write results to a skin string'''
        splitchar = self.params.get("splitchar")
        if splitchar.upper() == "[SPACE]":
            splitchar = " "
        skinstring = self.params.get("string")
        if not skinstring:
            skinstring = self.params.get("skinstring")
        output = self.params.get("output")
        index = self.params.get("index", 0)
        if skinstring is not None:
            skinstring = skinstring.split(splitchar)[int(index)]
        self.win.setProperty(output, skinstring)

    def getfilename(self, filename=""):
        '''helper to display a sanitized filename in the vidoeinfo dialog'''
        output = self.params.get("output")
        if not filename:
            filename = xbmc.getInfoLabel("ListItem.FileNameAndPath")
        if not filename:
            filename = xbmc.getInfoLabel("ListItem.FileName")
        if "filename=" in filename:
            url_params = dict(urllib.parse.parse_qsl(filename))
            filename = url_params.get("filename")
        self.win.setProperty(output, filename)

    def getplayerfilename(self):
        '''helper to parse the filename from a plugin (e.g. emby) filename'''
        filename = xbmc.getInfoLabel("Player.FileNameAndPath")
        if not filename:
            filename = xbmc.getInfoLabel("Player.FileName")
        self.getfilename(filename)

    def getpercentage(self):
        '''helper to calculate the percentage of 2 numbers and write results to a skinstring'''
        total = int(params.get("total"))
        count = int(params.get("count"))
        roundsteps = self.params.get("roundsteps")
        skinstring = self.params.get("skinstring")
        percentage = int(round((1.0 * count / total) * 100))
        if roundsteps:
            roundsteps = int(roundsteps)
            percentage = percentage + (roundsteps - percentage) % roundsteps
        xbmc.executebuiltin("Skin.SetString(%s,%s)" % (skinstring, percentage))

    def setresourceaddon(self):
        '''helper to let the user choose a resource addon and set that as skin string'''
        from .resourceaddons import setresourceaddon
        addontype = self.params.get("addontype", "")
        skinstring = self.params.get("skinstring", "")
        setresourceaddon(addontype, skinstring)

    def checkresourceaddons(self):
        '''allow the skinner to perform a basic check if some required resource addons are available'''
        from .resourceaddons import checkresourceaddons
        addonslist = self.params.get("addonslist", [])
        if addonslist:
            addonslist = addonslist.split("|")
        checkresourceaddons(addonslist)
Ejemplo n.º 2
0
class Main(object):
    """Main entry path for our widget listing. Process the arguments and load correct class and module"""

    def __init__(self):
        """ Initialization """

        self.metadatautils = MetadataUtils()
        self.addon = xbmcaddon.Addon(ADDON_ID)
        self.win = xbmcgui.Window(10000)
        self.options = self.get_options()

        # skip if shutdown requested
        if self.win.getProperty("SkinHelperShutdownRequested"):
            log_msg("Not forfilling request: Kodi is exiting!", xbmc.LOGWARNING)
            xbmcplugin.endOfDirectory(handle=ADDON_HANDLE)

        elif "mediatype" not in self.options or "action" not in self.options:
            # we need both mediatype and action, so show the main listing
            self.mainlisting()
        else:
            # we have a mediatype and action so display the widget listing
            self.show_widget_listing()

        self.close()

    def close(self):
        """Cleanup Kodi Cpython instances"""
        self.metadatautils.close()
        del self.addon
        del self.win
        log_msg("MainModule exited")

    def get_options(self):
        """get the options provided to the plugin path"""

        options = dict(urlparse.parse_qsl(sys.argv[2].replace('?', '').lower().decode("utf-8")))

        # set the widget settings as options
        options["hide_watched"] = self.addon.getSetting("hide_watched") == "true"
        if self.addon.getSetting("hide_watched_recent") == "true" and "recent" in options.get("action", ""):
            options["hide_watched"] = True
        options["num_recent_similar"] = int(self.addon.getSetting("num_recent_similar"))
        options["exp_recommended"] = self.addon.getSetting("exp_recommended") == "true"
        options["mylist"] = self.addon.getSetting("mylist") == "true"
        options["extended_info"] = self.addon.getSetting("extended_info") == "true"
        options["hide_watched_similar"] = self.addon.getSetting("hide_watched_similar") == "true"
        options["next_inprogress_only"] = self.addon.getSetting("nextup_inprogressonly") == "true"
        options["episodes_enable_specials"] = self.addon.getSetting("episodes_enable_specials") == "true"
        options["group_episodes"] = self.addon.getSetting("episodes_grouping") == "true"
        if "limit" in options:
            options["limit"] = int(options["limit"])
        else:
            options["limit"] = int(self.addon.getSetting("default_limit"))

        if "mediatype" not in options and "action" in options:
            # get the mediatype and action from the path (for backwards compatability with old style paths)
            for item in [
                ("movies", "movies"),
                ("shows", "tvshows"),
                ("episode", "episodes"),
                ("musicvideos", "musicvideos"),
                ("pvr", "pvr"),
                ("albums", "albums"),
                ("songs", "songs"),
                ("artists", "artists"),
                ("media", "media"),
                ("favourites", "favourites"),
                ("favorites", "favourites")]:
                if item[0] in options["action"]:
                    options["mediatype"] = item[1]
                    options["action"] = options["action"].replace(item[1], "").replace(item[0], "")
                    break

        # prefer reload param for the mediatype
        if "mediatype" in options:
            alt_reload = self.win.getProperty("widgetreload-%s" % options["mediatype"])
            if options["mediatype"] == "favourites" or "favourite" in options["action"]:
                options["skipcache"] = "true"
            elif alt_reload:
                options["reload"] = alt_reload
            if not options.get("action") and options["mediatype"] == "favourites":
                options["action"] = "favourites"
            elif not options.get("action"):
                options["action"] = "listing"
            if "listing" in options["action"]:
                options["skipcache"] = "true"
            if options["action"] == "browsegenres" and options["mediatype"] == "randommovies":
                options["mediatype"] = "movies"
                options["random"] = True
            elif options["action"] == "browsegenres" and options["mediatype"] == "randomtvshows":
                options["mediatype"] = "tvshows"
                options["random"] = True

        return options

    def show_widget_listing(self):
        """display the listing for the provided action and mediatype"""
        media_type = self.options["mediatype"]
        action = self.options["action"]
        # set widget content type
        if media_type in ["favourites", "pvr", "media"]:
            xbmcplugin.setContent(ADDON_HANDLE, "files")
        else:
            xbmcplugin.setContent(ADDON_HANDLE, media_type)

        # try to get from cache first...
        all_items = []
        # alter cache_str depending on whether "tag" is available
        if self.options["action"] == "similar":
            # if action is similar, use imdbid
            cache_id = self.options.get("imdbid", "")
            # if similar was called without imdbid, skip cache
            if not self.options.get("imdbid", ""):
                self.options["skipcache"] = "true"
        elif self.options["action"] == "playlist" and self.options["mediatype"] == "media":
            # if action is mixed playlist, use playlist labels
            cache_id = self.options.get("movie_label") + self.options.get("tv_label") + self.options.get("sort")
        elif self.options["action"] == "forgenre" and "genre" in self.options:
            cache_id = self.options.get("genre")
        else:
            # use tag otherwise
            cache_id = self.options.get("tag")
        cache_str = "SkinHelper.Widgets.%s.%s.%s.%s.%s" % (media_type,
                                                           action, self.options["limit"], self.options.get("path"),
                                                           cache_id)
        if not self.win.getProperty("widgetreload2"):
            # at startup we simply accept whatever is in the cache
            cache_checksum = None
        else:
            # we use a checksum based on the reloadparam to make sure we have the most recent data
            cache_checksum = self.options.get("reload", "")
        # only check cache if not "skipcache"
        if not self.options.get("skipcache") == "true":
            cache = self.metadatautils.cache.get(cache_str, checksum=cache_checksum)
            if cache:
                log_msg("MEDIATYPE: %s - ACTION: %s - PATH: %s - TAG: %s -- got items from cache - CHECKSUM: %s"
                        % (media_type, action, self.options.get("path"), self.options.get("tag"), cache_checksum))
                all_items = cache

        # Call the correct method to get the content from json when no cache
        if not all_items:
            log_msg(
                "MEDIATYPE: %s - ACTION: %s - PATH: %s - TAG: %s -- no cache, quering kodi api to get items - CHECKSUM: %s"
                % (media_type, action, self.options.get("path"), self.options.get("tag"), cache_checksum))

            # dynamically import and load the correct module, class and function
            try:
                media_module = __import__(media_type)
                media_class = getattr(
                    media_module,
                    media_type.capitalize())(self.addon, self.metadatautils, self.options)
                all_items = getattr(media_class, action)()
                del media_class
            except AttributeError:
                log_exception(__name__, "Incorrect widget action or type called")
            except Exception as exc:
                log_exception(__name__, exc)

            # randomize output if requested by skinner or user
            if self.options.get("randomize", "") == "true":
                all_items = sorted(all_items, key=lambda k: random.random())

            # prepare listitems and store in cache
            all_items = self.metadatautils.process_method_on_list(self.metadatautils.kodidb.prepare_listitem, all_items)
            self.metadatautils.cache.set(cache_str, all_items, checksum=cache_checksum)

        # fill that listing...
        xbmcplugin.addSortMethod(int(sys.argv[1]), xbmcplugin.SORT_METHOD_UNSORTED)
        all_items = self.metadatautils.process_method_on_list(self.metadatautils.kodidb.create_listitem, all_items)
        xbmcplugin.addDirectoryItems(ADDON_HANDLE, all_items, len(all_items))

        # end directory listing
        xbmcplugin.endOfDirectory(handle=ADDON_HANDLE)

    def mainlisting(self):
        """main listing"""
        all_items = []
        xbmcplugin.setContent(ADDON_HANDLE, "files")

        # movie node
        if xbmc.getCondVisibility("Library.HasContent(movies)"):
            all_items.append((xbmc.getLocalizedString(342), "movieslisting", "DefaultMovies.png"))

        # tvshows and episodes nodes
        if xbmc.getCondVisibility("Library.HasContent(tvshows)"):
            all_items.append((xbmc.getLocalizedString(20343), "tvshowslisting", "DefaultTvShows.png"))
            all_items.append((xbmc.getLocalizedString(20360), "episodeslisting", "DefaultTvShows.png"))

        # pvr node
        if xbmc.getCondVisibility("Pvr.HasTVChannels"):
            all_items.append((self.addon.getLocalizedString(32054), "pvrlisting", "DefaultAddonPVRClient.png"))

        # music nodes
        if xbmc.getCondVisibility("Library.HasContent(music)"):
            all_items.append((xbmc.getLocalizedString(132), "albumslisting", "DefaultAlbumCover.png"))
            all_items.append((xbmc.getLocalizedString(134), "songslisting", "DefaultMusicSongs.png"))
            all_items.append((xbmc.getLocalizedString(133), "artistslisting", "DefaultArtist.png"))

        # musicvideo node
        if xbmc.getCondVisibility("Library.HasContent(musicvideos)"):
            all_items.append((xbmc.getLocalizedString(20389), "musicvideoslisting", "DefaultAddonAlbumInfo.png"))

        # media node
        if xbmc.getCondVisibility(
                "Library.HasContent(movies) | Library.HasContent(tvshows) | Library.HasContent(music)"):
            all_items.append((self.addon.getLocalizedString(32057), "medialisting", "DefaultAddonAlbumInfo.png"))

        # favourites node
        all_items.append((xbmc.getLocalizedString(10134), "favouriteslisting", "DefaultAddonAlbumInfo.png"))

        # process the listitems and display listing
        all_items = self.metadatautils.process_method_on_list(create_main_entry, all_items)
        all_items = self.metadatautils.process_method_on_list(self.metadatautils.kodidb.prepare_listitem, all_items)
        all_items = self.metadatautils.process_method_on_list(self.metadatautils.kodidb.create_listitem, all_items)
        xbmcplugin.addDirectoryItems(ADDON_HANDLE, all_items, len(all_items))
        xbmcplugin.endOfDirectory(handle=ADDON_HANDLE)
class PluginContent:
    '''Hidden plugin entry point providing some helper features'''
    params = {}
    win = None

    def __init__(self):
        self.cache = SimpleCache()
        self.mutils = MetadataUtils()
        self.win = xbmcgui.Window(10000)
        try:
            self.params = dict(
                urlparse.parse_qsl(sys.argv[2].replace(
                    '?', '').lower().decode("utf-8")))
            log_msg("plugin called with parameters: %s" % self.params)
            self.main()
        except Exception as exc:
            log_exception(__name__, exc)
            xbmcplugin.endOfDirectory(handle=int(sys.argv[1]))

        # cleanup when done processing
        self.close()

    def close(self):
        '''Cleanup Kodi Cpython instances'''
        self.cache.close()
        self.mutils.close()
        del self.mutils
        del self.win

    def main(self):
        '''main action, load correct function'''
        action = self.params.get("action", "")
        if self.win.getProperty("SkinHelperShutdownRequested"):
            # do not proceed if kodi wants to exit
            log_msg(
                "%s --> Not forfilling request: Kodi is exiting" % __name__,
                xbmc.LOGWARNING)
            xbmcplugin.endOfDirectory(handle=int(sys.argv[1]))
        else:
            try:
                if hasattr(self.__class__, action):
                    # launch module for action provided by this plugin
                    getattr(self, action)()
                else:
                    # legacy (widget) path called !!!
                    self.load_widget()
            except Exception as exc:
                log_exception(__name__, exc)

    def load_widget(self):
        '''legacy entrypoint called (widgets are moved to seperate addon), start redirect...'''
        action = self.params.get("action", "")
        newaddon = "script.skin.helper.widgets"
        log_msg(
            "Deprecated method: %s. Please reassign your widgets to get rid of this message. -"
            "This automatic redirect will be removed in the future" % (action),
            xbmc.LOGWARNING)
        paramstring = ""
        for key, value in self.params.iteritems():
            paramstring += ",%s=%s" % (key, value)
        if getCondVisibility("System.HasAddon(%s)" % newaddon):
            # TEMP !!! for backwards compatability reasons only - to be removed in the near future!!
            import imp
            addon = xbmcaddon.Addon(newaddon)
            addon_path = addon.getAddonInfo('path').decode("utf-8")
            imp.load_source('plugin', os.path.join(addon_path, "plugin.py"))
            from plugin import main
            main.Main()
            del addon
        else:
            # trigger install of the addon
            if KODI_VERSION > 16:
                xbmc.executebuiltin("InstallAddon(%s)" % newaddon)
            else:
                xbmc.executebuiltin("RunPlugin(plugin://%s)" % newaddon)

    def playchannel(self):
        '''play channel from widget helper'''
        params = {"item": {"channelid": int(self.params["channelid"])}}
        self.mutils.kodidb.set_json("Player.Open", params)

    def playrecording(self):
        '''retrieve the recording and play to get resume working'''
        recording = self.mutils.kodidb.recording(self.params["recordingid"])
        params = {"item": {"recordingid": recording["recordingid"]}}
        self.mutils.kodidb.set_json("Player.Open", params)
        # manually seek because passing resume to the player json cmd doesn't seem to work
        if recording["resume"].get("position"):
            for i in range(50):
                if getCondVisibility("Player.HasVideo"):
                    break
                xbmc.sleep(50)
            xbmc.Player().seekTime(recording["resume"].get("position"))

    def launch(self):
        '''launch any builtin action using a plugin listitem'''
        if "runscript" in self.params["path"]:
            self.params["path"] = self.params["path"].replace("?", ",")
        xbmc.executebuiltin(self.params["path"])

    def playalbum(self):
        '''helper to play an entire album'''
        xbmc.executeJSONRPC(
            '{ "jsonrpc": "2.0", "method": "Player.Open", "params": { "item": { "albumid": %d } }, "id": 1 }'
            % int(self.params["albumid"]))

    def smartshortcuts(self):
        '''called from skinshortcuts to retrieve listing of all smart shortcuts'''
        import skinshortcuts
        skinshortcuts.get_smartshortcuts(self.params.get("path", ""))

    @staticmethod
    def backgrounds():
        '''called from skinshortcuts to retrieve listing of all backgrounds'''
        import skinshortcuts
        skinshortcuts.get_backgrounds()

    def widgets(self):
        '''called from skinshortcuts to retrieve listing of all widgetss'''
        import skinshortcuts
        skinshortcuts.get_widgets(self.params.get("path", ""),
                                  self.params.get("sublevel", ""))

    def resourceimages(self):
        '''retrieve listing of specific resource addon images'''
        from resourceaddons import get_resourceimages
        addontype = self.params.get("addontype", "")
        for item in get_resourceimages(addontype, True):
            listitem = xbmcgui.ListItem(item[0],
                                        label2=item[2],
                                        path=item[1],
                                        iconImage=item[3])
            xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]),
                                        url=item[1],
                                        listitem=listitem,
                                        isFolder=False)
        xbmcplugin.endOfDirectory(handle=int(sys.argv[1]))

    def extrafanart(self):
        '''helper to display extrafanart in multiimage control in the skin'''
        fanarts = eval(self.params["fanarts"])
        # process extrafanarts
        for count, item in enumerate(fanarts):
            listitem = xbmcgui.ListItem("fanart%s" % count, path=item)
            listitem.setProperty('mimetype', 'image/jpeg')
            xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]),
                                        url=item,
                                        listitem=listitem)
        xbmcplugin.endOfDirectory(handle=int(sys.argv[1]))

    def extraposter(self):
        '''helper to display extraposter in multiimage control in the skin'''
        posters = eval(self.params["posters"])
        # process extraposters
        for count, item in enumerate(posters):
            listitem = xbmcgui.ListItem("poster%s" % count, path=item)
            listitem.setProperty('mimetype', 'image/jpeg')
            xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]),
                                        url=item,
                                        listitem=listitem)
        xbmcplugin.endOfDirectory(handle=int(sys.argv[1]))

    def genrebackground(self):
        '''helper to display images for a specific genre in multiimage control in the skin'''
        genre = self.params.get("genre").split(".")[0]
        arttype = self.params.get("arttype", "fanart")
        randomize = self.params.get("random", "false") == "true"
        mediatype = self.params.get("mediatype", "movies")
        if genre and genre != "..":
            filters = [{"operator": "is", "field": "genre", "value": genre}]
            if randomize:
                sort = {"method": "random", "order": "descending"}
            else:
                sort = {"method": "sorttitle", "order": "ascending"}
            items = getattr(self.mutils.kodidb, mediatype)(sort=sort,
                                                           filters=filters,
                                                           limits=(0, 50))
            for item in items:
                image = self.mutils.get_clean_image(item["art"].get(
                    arttype, ""))
                if image:
                    image = self.mutils.get_clean_image(item["art"][arttype])
                    listitem = xbmcgui.ListItem(image, path=image)
                    listitem.setProperty('mimetype', 'image/jpeg')
                    xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]),
                                                url=image,
                                                listitem=listitem)
        xbmcplugin.endOfDirectory(handle=int(sys.argv[1]))

    def getcastmedia(self):
        '''helper to display get all media for a specific actor'''
        name = self.params.get("name")
        if name:
            all_items = self.mutils.kodidb.castmedia(name)
            all_items = self.mutils.process_method_on_list(
                self.mutils.kodidb.prepare_listitem, all_items)
            all_items = self.mutils.process_method_on_list(
                self.mutils.kodidb.create_listitem, all_items)
            xbmcplugin.addDirectoryItems(int(sys.argv[1]), all_items,
                                         len(all_items))
        xbmcplugin.endOfDirectory(handle=int(sys.argv[1]))

    def getcast(self):
        '''helper to get all cast for a given media item'''
        db_id = None
        all_cast = []
        all_cast_names = list()
        cache_str = ""
        download_thumbs = self.params.get("downloadthumbs", "") == "true"
        extended_cast_action = self.params.get("castaction",
                                               "") == "extendedinfo"
        movie = self.params.get("movie")
        tvshow = self.params.get("tvshow")
        episode = self.params.get("episode")
        movieset = self.params.get("movieset")

        try:  # try to parse db_id
            if movieset:
                cache_str = "movieset.castcache-%s-%s" % (
                    self.params["movieset"], download_thumbs)
                db_id = int(movieset)
            elif tvshow:
                cache_str = "tvshow.castcache-%s-%s" % (self.params["tvshow"],
                                                        download_thumbs)
                db_id = int(tvshow)
            elif movie:
                cache_str = "movie.castcache-%s-%s" % (self.params["movie"],
                                                       download_thumbs)
                db_id = int(movie)
            elif episode:
                cache_str = "episode.castcache-%s-%s" % (
                    self.params["episode"], download_thumbs)
                db_id = int(episode)
        except Exception:
            pass

        cachedata = self.cache.get(cache_str)
        if cachedata:
            # get data from cache
            all_cast = cachedata
        else:
            # retrieve data from json api...
            if movie and db_id:
                all_cast = self.mutils.kodidb.movie(db_id)["cast"]
            elif movie and not db_id:
                filters = [{
                    "operator": "is",
                    "field": "title",
                    "value": movie
                }]
                result = self.mutils.kodidb.movies(filters=filters)
                all_cast = result[0]["cast"] if result else []
            elif tvshow and db_id:
                all_cast = self.mutils.kodidb.tvshow(db_id)["cast"]
            elif tvshow and not db_id:
                filters = [{
                    "operator": "is",
                    "field": "title",
                    "value": tvshow
                }]
                result = self.mutils.kodidb.tvshows(filters=filters)
                all_cast = result[0]["cast"] if result else []
            elif episode and db_id:
                all_cast = self.mutils.kodidb.episode(db_id)["cast"]
            elif episode and not db_id:
                filters = [{
                    "operator": "is",
                    "field": "title",
                    "value": episode
                }]
                result = self.mutils.kodidb.episodes(filters=filters)
                all_cast = result[0]["cast"] if result else []
            elif movieset:
                if not db_id:
                    for item in self.mutils.kodidb.moviesets():
                        if item["title"].lower() == movieset.lower():
                            db_id = item["setid"]
                if db_id:
                    json_result = self.mutils.kodidb.movieset(
                        db_id, include_set_movies_fields=["cast"])
                    if "movies" in json_result:
                        for movie in json_result['movies']:
                            all_cast += movie['cast']

            # optional: download missing actor thumbs
            if all_cast and download_thumbs:
                for cast in all_cast:
                    if cast.get("thumbnail"):
                        cast["thumbnail"] = self.mutils.get_clean_image(
                            cast.get("thumbnail"))
                    if not cast.get("thumbnail"):
                        artwork = self.mutils.tmdb.get_actor(cast["name"])
                        cast["thumbnail"] = artwork.get("thumb", "")
            # lookup tmdb if item is requested that is not in local db
            if not all_cast:
                tmdbdetails = {}
                if movie and not db_id:
                    tmdbdetails = self.mutils.tmdb.search_movie(movie)
                elif tvshow and not db_id:
                    tmdbdetails = self.mutils.tmdb.search_tvshow(tvshow)
                if tmdbdetails.get("cast"):
                    all_cast = tmdbdetails["cast"]
            # save to cache
            self.cache.set(cache_str, all_cast)

        # process listing with the results...
        for cast in all_cast:
            if cast.get("name") not in all_cast_names:
                liz = xbmcgui.ListItem(label=cast.get("name"),
                                       label2=cast.get("role"),
                                       iconImage=cast.get("thumbnail"))
                if extended_cast_action:
                    url = "RunScript(script.extendedinfo,info=extendedactorinfo,name=%s)" % cast.get(
                        "name")
                    url = "plugin://script.skin.helper.service/?action=launch&path=%s" % url
                    is_folder = False
                else:
                    url = "RunScript(script.skin.helper.service,action=getcastmedia,name=%s)" % cast.get(
                        "name")
                    url = "plugin://script.skin.helper.service/?action=launch&path=%s" % urlencode(
                        url)
                    is_folder = False
                all_cast_names.append(cast.get("name"))
                liz.setThumbnailImage(cast.get("thumbnail"))
                xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]),
                                            url=url,
                                            listitem=liz,
                                            isFolder=is_folder)
        xbmcplugin.endOfDirectory(int(sys.argv[1]))

    @staticmethod
    def alphabet():
        '''display an alphabet scrollbar in listings'''
        all_letters = []
        if xbmc.getInfoLabel("Container.NumItems"):
            for i in range(int(xbmc.getInfoLabel("Container.NumItems"))):
                all_letters.append(
                    xbmc.getInfoLabel("Listitem(%s).SortLetter" % i).upper())
            start_number = ""
            for number in ["2", "3", "4", "5", "6", "7", "8", "9"]:
                if number in all_letters:
                    start_number = number
                    break
            for letter in [
                    start_number, "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"
            ]:
                if letter == start_number:
                    label = "#"
                else:
                    label = letter
                listitem = xbmcgui.ListItem(label=label)
                if letter not in all_letters:
                    lipath = "noop"
                    listitem.setProperty("NotAvailable", "true")
                else:
                    lipath = "plugin://script.skin.helper.service/?action=alphabetletter&letter=%s" % letter
                xbmcplugin.addDirectoryItem(int(sys.argv[1]),
                                            lipath,
                                            listitem,
                                            isFolder=False)
        xbmcplugin.endOfDirectory(handle=int(sys.argv[1]))

    def alphabetletter(self):
        '''used with the alphabet scrollbar to jump to a letter'''
        if KODI_VERSION > 16:
            xbmcplugin.setResolvedUrl(handle=int(sys.argv[1]),
                                      succeeded=False,
                                      listitem=xbmcgui.ListItem())
        letter = self.params.get("letter", "").upper()
        jumpcmd = ""
        if letter in ["A", "B", "C", "2"]:
            jumpcmd = "2"
        elif letter in ["D", "E", "F", "3"]:
            jumpcmd = "3"
        elif letter in ["G", "H", "I", "4"]:
            jumpcmd = "4"
        elif letter in ["J", "K", "L", "5"]:
            jumpcmd = "5"
        elif letter in ["M", "N", "O", "6"]:
            jumpcmd = "6"
        elif letter in ["P", "Q", "R", "S", "7"]:
            jumpcmd = "7"
        elif letter in ["T", "U", "V", "8"]:
            jumpcmd = "8"
        elif letter in ["W", "X", "Y", "Z", "9"]:
            jumpcmd = "9"
        if jumpcmd:
            xbmc.executebuiltin("SetFocus(50)")
            for i in range(40):
                xbmc.executeJSONRPC(
                    '{ "jsonrpc": "2.0", "method": "Input.ExecuteAction",\
                    "params": { "action": "jumpsms%s" }, "id": 1 }' %
                    (jumpcmd))
                xbmc.sleep(50)
                if xbmc.getInfoLabel("ListItem.Sortletter").upper() == letter:
                    break
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)
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()
Ejemplo n.º 6
0
class Main(object):
    '''Main entry path for our widget listing. Process the arguments and load correct class and module'''

    def __init__(self):
        ''' Initialization '''

        self.metadatautils = MetadataUtils()
        self.addon = xbmcaddon.Addon(ADDON_ID)
        self.win = xbmcgui.Window(10000)
        self.options = self.get_options()

        # skip if shutdown requested
        if self.win.getProperty("SkinHelperShutdownRequested"):
            log_msg("Not forfilling request: Kodi is exiting!", xbmc.LOGWARNING)
            xbmcplugin.endOfDirectory(handle=ADDON_HANDLE)

        elif not "mediatype" in self.options or not "action" in self.options:
            # we need both mediatype and action, so show the main listing
            self.mainlisting()
        else:
            # we have a mediatype and action so display the widget listing
            self.show_widget_listing()

        self.close()

    def close(self):
        '''Cleanup Kodi Cpython instances'''
        self.metadatautils.close()
        del self.addon
        del self.win
        log_msg("MainModule exited")

    def get_options(self):
        '''get the options provided to the plugin path'''

        options = dict(urlparse.parse_qsl(sys.argv[2].replace('?', '').lower().decode("utf-8")))

        # set the widget settings as options
        options["hide_watched"] = self.addon.getSetting("hide_watched") == "true"
        if self.addon.getSetting("hide_watched_recent") == "true" and "recent" in options.get("action", ""):
            options["hide_watched"] = True
        options["num_recent_similar"] = int(self.addon.getSetting("num_recent_similar"))
        options["exp_recommended"] = self.addon.getSetting("exp_recommended") == "true"
        options["hide_watched_similar"] = self.addon.getSetting("hide_watched_similar") == "true"
        options["next_inprogress_only"] = self.addon.getSetting("nextup_inprogressonly") == "true"
        options["episodes_enable_specials"] = self.addon.getSetting("episodes_enable_specials") == "true"
        options["group_episodes"] = self.addon.getSetting("episodes_grouping") == "true"
        if "limit" in options:
            options["limit"] = int(options["limit"])
        else:
            options["limit"] = int(self.addon.getSetting("default_limit"))

        if not "mediatype" in options and "action" in options:
            # get the mediatype and action from the path (for backwards compatability with old style paths)
            for item in [
                ("movies", "movies"),
                ("shows", "tvshows"),
                ("episode", "episodes"),
                ("musicvideos", "musicvideos"),
                ("pvr", "pvr"),
                ("albums", "albums"),
                ("songs", "songs"),
                ("artists", "artists"),
                ("media", "media"),
                ("favourites", "favourites"),
                    ("favorites", "favourites")]:
                if item[0] in options["action"]:
                    options["mediatype"] = item[1]
                    options["action"] = options["action"].replace(item[1], "").replace(item[0], "")
                    break

        # prefer reload param for the mediatype
        if "mediatype" in options:
            alt_reload = self.win.getProperty("widgetreload-%s" % options["mediatype"])
            if options["mediatype"] == "favourites" or "favourite" in options["action"]:
                options["skipcache"] = "true"
            elif alt_reload:
                options["reload"] = alt_reload
            if not options.get("action") and options["mediatype"] == "favourites":
                options["action"] = "favourites"
            elif not options.get("action"):
                options["action"] = "listing"
            if "listing" in options["action"]:
                options["skipcache"] = "true"
            if options["action"] == "browsegenres" and options["mediatype"] == "randommovies":
                options["mediatype"] = "movies"
                options["random"] = True
            elif options["action"] == "browsegenres" and options["mediatype"] == "randomtvshows":
                options["mediatype"] = "tvshows"
                options["random"] = True

        return options

    def show_widget_listing(self):
        '''display the listing for the provided action and mediatype'''
        media_type = self.options["mediatype"]
        action = self.options["action"]
        # set widget content type
        if media_type in ["favourites", "pvr", "media"]:
            xbmcplugin.setContent(ADDON_HANDLE, "files")
        else:
            xbmcplugin.setContent(ADDON_HANDLE, media_type)

        # try to get from cache first...
        all_items = []
        # alter cache_str depending on whether "tag" is available
        if self.options["action"] == "similar":
            # if action is similar, use imdbid
            cache_id = self.options.get("imdbid", "")
            # if similar was called without imdbid, skip cache
            if not self.options.get("imdbid", ""):
                self.options["skipcache"] = "true"
        elif self.options["action"] == "playlist" and self.options["mediatype"]=="media":
            # if action is mixed playlist, use playlist labels
            cache_id = self.options.get("movie_label")+self.options.get("tv_label")
        else:
            # use tag otherwise
            cache_id = self.options.get("tag")
        cache_str = "SkinHelper.Widgets.%s.%s.%s.%s.%s" % (media_type,
                    action, self.options["limit"], self.options.get("path"), cache_id)
        if not self.win.getProperty("widgetreload2"):
            # at startup we simply accept whatever is in the cache
            cache_checksum = None
        else:
            # we use a checksum based on the reloadparam to make sure we have the most recent data
            cache_checksum = self.options.get("reload","")
        # only check cache if not "skipcache"
        if not self.options.get("skipcache") == "true":
            cache = self.metadatautils.cache.get(cache_str, checksum=cache_checksum)
            if cache:
                log_msg("MEDIATYPE: %s - ACTION: %s - PATH: %s - TAG: %s -- got items from cache - CHECKSUM: %s"
                        % (media_type, action, self.options.get("path"), self.options.get("tag"), cache_checksum))
                all_items = cache

        # Call the correct method to get the content from json when no cache
        if not all_items:
            log_msg("MEDIATYPE: %s - ACTION: %s - PATH: %s - TAG: %s -- no cache, quering kodi api to get items - CHECKSUM: %s"
                    % (media_type, action, self.options.get("path"), self.options.get("tag"), cache_checksum))

            # dynamically import and load the correct module, class and function
            try:
                media_module = __import__(media_type)
                media_class = getattr(
                    media_module,
                    media_type.capitalize())(self.addon, self.metadatautils, self.options)
                all_items = getattr(media_class, action)()
                del media_class
            except AttributeError:
                log_exception(__name__, "Incorrect widget action or type called")
            except Exception as exc:
                log_exception(__name__, exc)

            # randomize output if requested by skinner or user
            if self.options.get("randomize", "") == "true":
                all_items = sorted(all_items, key=lambda k: random.random())

            # prepare listitems and store in cache
            all_items = self.metadatautils.process_method_on_list(self.metadatautils.kodidb.prepare_listitem, all_items)
            self.metadatautils.cache.set(cache_str, all_items, checksum=cache_checksum)

        # fill that listing...
        xbmcplugin.addSortMethod(int(sys.argv[1]), xbmcplugin.SORT_METHOD_UNSORTED)
        all_items = self.metadatautils.process_method_on_list(self.metadatautils.kodidb.create_listitem, all_items)
        xbmcplugin.addDirectoryItems(ADDON_HANDLE, all_items, len(all_items))

        # end directory listing
        xbmcplugin.endOfDirectory(handle=ADDON_HANDLE)

    def mainlisting(self):
        '''main listing'''
        all_items = []
        xbmcplugin.setContent(ADDON_HANDLE, "files")

        # movie node
        if xbmc.getCondVisibility("Library.HasContent(movies)"):
            all_items.append((xbmc.getLocalizedString(342), "movieslisting", "DefaultMovies.png"))

        # tvshows and episodes nodes
        if xbmc.getCondVisibility("Library.HasContent(tvshows)"):
            all_items.append((xbmc.getLocalizedString(20343), "tvshowslisting", "DefaultTvShows.png"))
            all_items.append((xbmc.getLocalizedString(20360), "episodeslisting", "DefaultTvShows.png"))

        # pvr node
        if xbmc.getCondVisibility("Pvr.HasTVChannels"):
            all_items.append((self.addon.getLocalizedString(32054), "pvrlisting", "DefaultAddonPVRClient.png"))

        # music nodes
        if xbmc.getCondVisibility("Library.HasContent(music)"):
            all_items.append((xbmc.getLocalizedString(132), "albumslisting", "DefaultAlbumCover.png"))
            all_items.append((xbmc.getLocalizedString(134), "songslisting", "DefaultMusicSongs.png"))
            all_items.append((xbmc.getLocalizedString(133), "artistslisting", "DefaultArtist.png"))

        # musicvideo node
        if xbmc.getCondVisibility("Library.HasContent(musicvideos)"):
            all_items.append((xbmc.getLocalizedString(20389), "musicvideoslisting", "DefaultAddonAlbumInfo.png"))

        # media node
        if xbmc.getCondVisibility(
                "Library.HasContent(movies) | Library.HasContent(tvshows) | Library.HasContent(music)"):
            all_items.append((self.addon.getLocalizedString(32057), "medialisting", "DefaultAddonAlbumInfo.png"))

        # favourites node
        all_items.append((xbmc.getLocalizedString(10134), "favouriteslisting", "DefaultAddonAlbumInfo.png"))

        # process the listitems and display listing
        all_items = self.metadatautils.process_method_on_list(create_main_entry, all_items)
        all_items = self.metadatautils.process_method_on_list(self.metadatautils.kodidb.prepare_listitem, all_items)
        all_items = self.metadatautils.process_method_on_list(self.metadatautils.kodidb.create_listitem, all_items)
        xbmcplugin.addDirectoryItems(ADDON_HANDLE, all_items, len(all_items))
        xbmcplugin.endOfDirectory(handle=ADDON_HANDLE)