def __init__(self): self.win = xbmcgui.Window(10000) self.addon = xbmcaddon.Addon(ADDON_ID) self.metadatautils = MetadataUtils() self.addonname = self.addon.getAddonInfo('name').decode("utf-8") self.addonversion = self.addon.getAddonInfo('version').decode("utf-8") self.kodimonitor = KodiMonitor(metadatautils=self.metadatautils, win=self.win) self.listitem_monitor = ListItemMonitor( metadatautils=self.metadatautils, win=self.win, monitor=self.kodimonitor) self.webservice = WebService(self.metadatautils) self.win.clearProperty("SkinHelperShutdownRequested") # start the extra threads self.listitem_monitor.start() self.webservice.start() log_msg('%s version %s started' % (self.addonname, self.addonversion), xbmc.LOGNOTICE) # run as service, check skin every 10 seconds and keep the other threads alive while not self.kodimonitor.abortRequested(): # check skin version info self.check_skin_version() # sleep for 10 seconds self.kodimonitor.waitForAbort(10) # Abort was requested while waiting. We should exit self.close()
def favourites_widgets(): """widgets from favourites""" favourites = kodi_json( 'Favourites.GetFavourites', { "type": None, "properties": ["path", "thumbnail", "window", "windowparameter"] }) widgets = [] if favourites: for fav in favourites: if "windowparameter" in fav: content = fav["windowparameter"] # check if this is a valid path with content if ("script://" not in content.lower() and "mode=9" not in content.lower() and "search" not in content.lower() and "play" not in content.lower()): label = fav["title"] log_msg("skinshortcuts widgets processing favourite: %s" % label) mutils = MetadataUtils() mediatype = mutils.detect_plugin_content(content) del mutils if mediatype and mediatype != "empty": widgets.append([label, content, mediatype]) return widgets
def getMeta(label, type='tvshows', yrs=''): metadatautils = MetadataUtils() metadatautils.tmdb.api_key = '9c47d05a3f5f3a00104f6586412306af' return metadatautils.get_tmdb_details(title=label, year=yrs, media_type=type, manual_select=False)
def playlists_widgets(): '''skin provided playlists''' widgets = [] import xml.etree.ElementTree as xmltree for playlist_path in ["special://skin/playlists/", "special://skin/extras/widgetplaylists/", "special://skin/extras/playlists/"]: if xbmcvfs.exists(playlist_path): log_msg("skinshortcuts widgets processing: %s" % playlist_path) media_array = kodi_json('Files.GetDirectory', {"directory": playlist_path, "media": "files"}) for item in media_array: if item["file"].endswith(".xsp"): playlist = item["file"] contents = xbmcvfs.File(item["file"], 'r') contents_data = contents.read().decode('utf-8') contents.close() xmldata = xmltree.fromstring(contents_data.encode('utf-8')) media_type = "" label = item["label"] for line in xmldata.getiterator(): if line.tag == "smartplaylist": media_type = line.attrib['type'] if line.tag == "name": label = line.text try: languageid = int(label) label = xbmc.getLocalizedString(languageid) except Exception: pass if not media_type: mutils = MetadataUtils() media_type = mutils.detect_plugin_content(playlist) del mutils widgets.append([label, playlist, media_type]) return widgets
def plugin_widgetlisting(pluginpath, sublevel=""): """get all nodes in a plugin listing""" widgets = [] if sublevel: media_array = kodi_json('Files.GetDirectory', { "directory": pluginpath, "media": "files" }) else: if not getCondVisibility("System.HasAddon(%s)" % pluginpath): return [] media_array = kodi_json('Files.GetDirectory', { "directory": "plugin://%s" % pluginpath, "media": "files" }) for item in media_array: log_msg("skinshortcuts widgets processing: %s" % (item["file"])) content = item["file"] label = item["label"] # extendedinfo has some login-required widgets, skip those if ("script.extendedinfo" in pluginpath and not EXTINFO_CREDS and ("info=starred" in content or "info=rated" in content or "info=account" in content)): continue if item.get("filetype", "") == "file": continue mutils = MetadataUtils() media_type = mutils.detect_plugin_content(item["file"]) del mutils if media_type == "empty": continue if media_type == "folder": content = "plugin://script.skin.helper.service?action=widgets&path=%s&sublevel=%s" % ( urlencode(item["file"]), label) # add reload param for widgets if "reload=" not in content: if "movies" in content: reloadstr = "&reload=$INFO[Window(Home).Property(widgetreload-movies)]" elif "episodes" in content: reloadstr = "&reload=$INFO[Window(Home).Property(widgetreload-episodes)]" elif "tvshows" in content: reloadstr = "&reload=$INFO[Window(Home).Property(widgetreload-tvshows)]" elif "musicvideos" in content: reloadstr = "&reload=$INFO[Window(Home).Property(widgetreload-musicvideos)]" elif "albums" in content or "songs" in content or "artists" in content: reloadstr = "&reload=$INFO[Window(Home).Property(widgetreload-music)]" else: reloadstr = "&reload=$INFO[Window(Home).Property(widgetreload)]"\ "$INFO[Window(Home).Property(widgetreload2)]" content = content + reloadstr content = content.replace("&limit=100", "&limit=25") widgets.append([label, content, media_type]) if pluginpath == "script.extendedinfo" and not sublevel: # some additional entrypoints for extendedinfo... widgets += extendedinfo_youtube_widgets() return widgets
def __init__(self, *args, **kwargs): self.cache = SimpleCache() self.mutils = MetadataUtils() self.win = xbmcgui.Window(10000) self.addon = xbmcaddon.Addon(ADDON_ID) self.smartshortcuts = SmartShortCuts(self) self.wallimages = WallImages(self) self.kodimonitor = kwargs.get("kodimonitor") self.event = threading.Event() threading.Thread.__init__(self, *args)
def show_infodialog(dbid="", media_type=""): '''shows the special info dialog for this media''' cont_prefix = get_cont_prefix() metadatautils = MetadataUtils() item_details = {} # if dbid is provided we prefer that info else we try to locate the dbid and dbtype if not (dbid and media_type): dbid, media_type = get_cur_listitem(cont_prefix) if media_type.endswith("s"): media_type = media_type[:-1] # get basic details from kodi db if we have a valid dbid and dbtype if dbid and media_type: if hasattr(metadatautils.kodidb.__class__, media_type): item_details = getattr(metadatautils.kodidb, media_type)(dbid) # only proceed if we have a media_type if media_type: title = try_decode(xbmc.getInfoLabel("%sListItem.Title" % cont_prefix)) # music content if media_type in ["album", "artist", "song"]: artist = try_decode(xbmc.getInfoLabel("%sListItem.AlbumArtist" % cont_prefix)) if not artist: artist = try_decode(xbmc.getInfoLabel("%sListItem.Artist" % cont_prefix)) album = try_decode(xbmc.getInfoLabel("%sListItem.Album" % cont_prefix)) disc = try_decode(xbmc.getInfoLabel("%sListItem.DiscNumber" % cont_prefix)) if artist: item_details = metadatautils.extend_dict(item_details, metadatautils.get_music_artwork(artist, album, title, disc)) # movieset elif media_type == "movieset" and dbid: item_details = metadatautils.extend_dict(item_details, metadatautils.get_moviesetdetails(dbid)) # pvr item elif media_type in ["tvchannel", "tvrecording", "channel", "recording"]: channel = try_decode(xbmc.getInfoLabel("%sListItem.ChannelName" % cont_prefix)) genre = xbmc.getInfoLabel("%sListItem.Genre" % cont_prefix) item_details["type"] = media_type item_details = metadatautils.extend_dict(item_details, metadatautils.get_pvr_artwork(title, channel, genre)) metadatautils.close() # proceed with infodialog if we have details if item_details: widget_container = xbmc.getInfoLabel("Window(Home).Property(SkinHelper.WidgetContainer)") win = DialogVideoInfo("DialogVideoInfo.xml", "", listitem=item_details) xbmc.executebuiltin("SetProperty(SkinHelper.WidgetContainer,50,Home)") win.doModal() xbmc.executebuiltin("SetProperty(SkinHelper.WidgetContainer,%s,Home)" % widget_container) del win
def __init__(self): self.win = xbmcgui.Window(10000) self.addon = xbmcaddon.Addon(ADDON_ID) self.metadatautils = MetadataUtils() self.addonname = self.addon.getAddonInfo('name').decode("utf-8") self.addonversion = self.addon.getAddonInfo('version').decode("utf-8") self.kodimonitor = KodiMonitor(metadatautils=self.metadatautils, win=self.win) listitem_monitor = ListItemMonitor( metadatautils=self.metadatautils, win=self.win, monitor=self.kodimonitor) webservice = WebService(metadatautils=self.metadatautils) # start the extra threads listitem_monitor.start() webservice.start() self.win.clearProperty("SkinHelperShutdownRequested") log_msg('%s version %s started' % (self.addonname, self.addonversion), xbmc.LOGNOTICE) # run as service, check skin every 10 seconds and keep the other threads alive while not self.kodimonitor.abortRequested(): # check skin version info self.check_skin_version() # sleep for 10 seconds self.kodimonitor.waitForAbort(10) # Abort was requested while waiting. We should exit self.win.setProperty("SkinHelperShutdownRequested", "shutdown") log_msg('Shutdown requested !', xbmc.LOGNOTICE) # stop the extra threads listitem_monitor.stop() webservice.stop() # cleanup objects self.close()
def onInit(self): '''triggered when the dialog is drawn''' if self.listitem: self.clearList() mutils = MetadataUtils() if isinstance(self.listitem, dict): self.listitem = mutils.kodidb.prepare_listitem(self.listitem) self.listitem = mutils.kodidb.create_listitem(self.listitem, False) del mutils self.addItem(self.listitem) # disable some controls if existing disable_controls = [9, 7, 101, 6] for item in disable_controls: try: self.getControl(item).setVisible(False) except Exception: pass # enable some controls if existing disable_controls = [351, 352] for item in disable_controls: try: self.getControl(item).setVisible(True) self.getControl(item).setEnabled(True) except Exception: pass
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 __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 __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) # do cleanup self.close()
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 __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 show_infodialog(dbid="", media_type=""): '''shows the special info dialog for this media''' cont_prefix = get_cont_prefix() metadatautils = MetadataUtils() item_details = {} # if dbid is provided we prefer that info else we try to locate the dbid and dbtype if not (dbid and media_type): dbid, media_type = get_cur_listitem(cont_prefix) if media_type.endswith("s"): media_type = media_type[:-1] # get basic details from kodi db if we have a valid dbid and dbtype if dbid and media_type: if hasattr(metadatautils.kodidb.__class__, media_type): item_details = getattr(metadatautils.kodidb, media_type)(dbid) # only proceed if we have a media_type if media_type: title = xbmc.getInfoLabel("%sListItem.Title" % cont_prefix).decode('utf-8') # music content if media_type in ["album", "artist", "song"]: artist = xbmc.getInfoLabel("%sListItem.AlbumArtist" % cont_prefix).decode('utf-8') if not artist: artist = xbmc.getInfoLabel("%sListItem.Artist" % cont_prefix).decode('utf-8') album = xbmc.getInfoLabel("%sListItem.Album" % cont_prefix).decode('utf-8') disc = xbmc.getInfoLabel("%sListItem.DiscNumber" % cont_prefix).decode('utf-8') if artist: item_details = extend_dict(item_details, metadatautils.get_music_artwork(artist, album, title, disc)) # movieset elif media_type == "movieset" and dbid: item_details = extend_dict(item_details, metadatautils.get_moviesetdetails(dbid)) # pvr item elif media_type in ["tvchannel", "tvrecording", "channel", "recording"]: channel = xbmc.getInfoLabel("%sListItem.ChannelName" % cont_prefix).decode('utf-8') genre = xbmc.getInfoLabel("%sListItem.Genre" % cont_prefix) item_details["type"] = media_type item_details = extend_dict(item_details, metadatautils.get_pvr_artwork(title, channel, genre)) metadatautils.close() # proceed with infodialog if we have details if item_details: win = DialogVideoInfo("DialogVideoInfo.xml", "", listitem=item_details) win.doModal() del win
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)
script.skin.helper.service Contextmenu for Music art ''' import xbmc import xbmcgui from metadatautils import MetadataUtils import time # pylint: disable-msg=invalid-constant-name # Kodi contextmenu item to configure music artwork if __name__ == '__main__': win = xbmcgui.Window(10000) metadatautils = MetadataUtils() win.setProperty("SkinHelper.Artwork.ManualLookup", "busy") track = xbmc.getInfoLabel("ListItem.Title").decode('utf-8') album = xbmc.getInfoLabel("ListItem.Album").decode('utf-8') artist = xbmc.getInfoLabel("ListItem.Artist").decode('utf-8') disc = xbmc.getInfoLabel("ListItem.DiscNumber").decode('utf-8') metadatautils.music_artwork_options(artist, album, track, disc) metadatautils.close() # refresh music widgets timestr = time.strftime("%Y%m%d%H%M%S", time.gmtime()) win.setProperty("widgetreload-music", timestr) win.setProperty("widgetreloadmusic", timestr) win.setProperty("widgetreload-albums", timestr) win.setProperty("widgetreload-songs", timestr) win.clearProperty("SkinHelper.Artwork.ManualLookup") del win
if imdb_id and not imdb_id.startswith("tt"): imdb_id = "" if not imdb_id: year = xbmc.getInfoLabel("ListItem.Year").decode('utf-8') title = xbmc.getInfoLabel("ListItem.Title").decode('utf-8').split(",")[0].split("(")[0] if content_type in ["episodes", "seasons"]: title = xbmc.getInfoLabel("ListItem.TvShowTitle").decode('utf-8') if title: log_msg("Animated Art: lookup imdbid by title and year: (%s - %s)" % (title, year), xbmc.LOGNOTICE) imdb_id = metadatautils.get_omdb_info("", title, year, content_type).get("imdbnumber", "") if not imdb_id: return title return imdb_id # Kodi contextmenu item to configure the artwork if __name__ == '__main__': xbmc.executebuiltin("ActivateWindow(busydialog)") log_msg("Contextmenu for Animated Art opened", xbmc.LOGNOTICE) ARTUTILS = MetadataUtils() WIN = xbmcgui.Window(10000) imdb_id = get_imdb_id(WIN, ARTUTILS) WIN.setProperty("SkinHelper.Artwork.ManualLookup", "busy") log_msg("Animated Art: Query animated art by IMDBID: %s" % imdb_id, xbmc.LOGNOTICE) artwork = ARTUTILS.get_animated_artwork(imdb_id, ignore_cache=True, manual_select=True) log_msg("Animated Art result: %s" % artwork, xbmc.LOGNOTICE) xbmc.executebuiltin("Dialog.Close(busydialog)") xbmc.executebuiltin("Container.Refresh") WIN.clearProperty("SkinHelper.Artwork.ManualLookup") del WIN ARTUTILS.close()
''' script.skin.helper.service Contextmenu for Pvr art ''' import xbmc import xbmcgui from metadatautils import MetadataUtils # pylint: disable-msg=invalid-constant-name # Kodi contextmenu item to configure pvr artwork if __name__ == '__main__': ##### PVR Artwork ######## win = xbmcgui.Window(10000) win.setProperty("SkinHelper.Artwork.ManualLookup", "busy") xbmc.executebuiltin("ActivateWindow(busydialog)") title = xbmc.getInfoLabel("ListItem.Title").decode('utf-8') if not title: title = xbmc.getInfoLabel("ListItem.Label").decode('utf-8') channel = xbmc.getInfoLabel("ListItem.ChannelName").decode('utf-8') genre = xbmc.getInfoLabel("ListItem.Genre").decode('utf-8') metadatautils = MetadataUtils() metadatautils.pvr_artwork_options(title, channel, genre) xbmc.executebuiltin("Dialog.Close(busydialog)") win.clearProperty("SkinHelper.Artwork.ManualLookup") metadatautils.close() del win
class BackgroundsUpdater(threading.Thread): '''Background service providing rotating backgrounds to Kodi skins''' exit = False event = None all_backgrounds = {} all_backgrounds2 = {} all_backgrounds_labels = [] backgrounds_delay = 0 walls_delay = 30 enable_walls = False all_backgrounds_keys = {} prefetch_images = 30 # number of images to cache in memory for each library path pvr_bg_recordingsonly = False custom_picturespath = "" winprops = {} def __init__(self, *args, **kwargs): self.cache = SimpleCache() self.mutils = MetadataUtils() self.win = xbmcgui.Window(10000) self.addon = xbmcaddon.Addon(ADDON_ID) self.smartshortcuts = SmartShortCuts(self) self.wallimages = WallImages(self) self.kodimonitor = kwargs.get("kodimonitor") self.event = threading.Event() threading.Thread.__init__(self, *args) def stop(self): '''stop running our background service ''' self.smartshortcuts.exit = True self.wallimages.exit = True self.exit = True self.event.set() self.event.clear() self.join(0.5) del self.smartshortcuts del self.wallimages del self.win del self.addon def run(self): '''called to start our background service ''' log_msg("BackgroundsUpdater - started", xbmc.LOGINFO) self.winpropcache() self.get_config() backgrounds_task_interval = 0 walls_task_interval = 0 delayed_task_interval = 112 while not self.exit: # Process backgrounds only if we're not watching fullscreen video if xbmc.getCondVisibility( "![Window.IsActive(fullscreenvideo) | Window.IsActive(script.pseudotv.TVOverlay.xml) | " "Window.IsActive(script.pseudotv.live.TVOverlay.xml)] | " "Window.IsActive(script.pseudotv.live.EPG.xml)"): # background stuff like reading the skin settings and generating smart shortcuts if delayed_task_interval >= 120: delayed_task_interval = 0 self.get_config() self.report_allbackgrounds() self.smartshortcuts.build_smartshortcuts() self.report_allbackgrounds() self.winpropcache(True) if self.exit: break # force refresh smart shortcuts on request if self.win.getProperty("refreshsmartshortcuts"): self.win.clearProperty("refreshsmartshortcuts") self.smartshortcuts.build_smartshortcuts() # Update home backgrounds every interval (if enabled by skinner) if self.backgrounds_delay and backgrounds_task_interval >= self.backgrounds_delay: backgrounds_task_interval = 0 self.update_backgrounds() if self.exit: break # Update wall images every interval (if enabled by skinner) if self.enable_walls and self.walls_delay and ( walls_task_interval >= self.walls_delay): walls_task_interval = 0 _thread.start_new_thread( self.wallimages.update_wallbackgrounds, ()) self.wallimages.update_manualwalls() self.kodimonitor.waitForAbort(1) backgrounds_task_interval += 1 walls_task_interval += 1 delayed_task_interval += 1 def get_config(self): '''gets various settings for the script as set by the skinner or user''' # skinner (or user) enables the random fanart images by setting the randomfanartdelay skin string try: self.backgrounds_delay = int( xbmc.getInfoLabel("Skin.String(SkinHelper.RandomFanartDelay)")) except Exception: pass self.walls_delay = int(self.addon.getSetting("wallimages_delay")) self.wallimages.max_wallimages = int( self.addon.getSetting("max_wallimages")) self.pvr_bg_recordingsonly = self.addon.getSetting( "pvr_bg_recordingsonly") == "true" self.enable_walls = xbmc.getCondVisibility( "Skin.HasSetting(SkinHelper.EnableWallBackgrounds)") if self.addon.getSetting("enable_custom_images_path") == "true": self.custom_picturespath = self.addon.getSetting( "custom_images_path") else: self.custom_picturespath = "" try: # skinner can enable manual wall images generation so check for these settings # store in memory so wo do not have to query the skin settings too often if self.walls_delay: for key in self.all_backgrounds_keys.keys(): limitrange = xbmc.getInfoLabel( "Skin.String(%s.EnableWallImages)" % key) if limitrange: self.wallimages.manual_walls[key] = int(limitrange) except Exception as exc: log_exception(__name__, exc) def report_allbackgrounds(self): '''sets a list of all known backgrounds as winprop to be retrieved from skinshortcuts''' if self.all_backgrounds_labels: self.set_winprop("SkinHelper.AllBackgrounds", repr(self.all_backgrounds_labels)) def set_winprop(self, key, value): '''sets a window property and writes it to our global list''' if self.exit: return self.winprops[key] = value if isinstance(value, str): value = value.encode("utf-8") self.win.setProperty(key, value) def winpropcache(self, setcache=False): '''sets/gets the current window props in a global cache to load them immediately at startup''' cachestr = "skinhelper.backgrounds.%s" % xbmc.getInfoLabel( "System.ProfileName") if setcache: self.cache.set(cachestr, self.winprops) else: cache = self.cache.get(cachestr) if cache: for key, value in cache.items(): if value: self.win.setProperty(key, value) def get_images_from_vfspath(self, lib_path): '''get all images from the given vfs path''' result = [] # safety check: check if no library windows are active to prevent any addons setting the view if (xbmc.getCondVisibility("Window.IsMedia") and "plugin" in lib_path) or self.exit: return result lib_path = get_content_path(lib_path) if "plugin.video.emby-next-gen" in lib_path and "browsecontent" in lib_path and "filter" not in lib_path: lib_path = lib_path + "&filter=random" items = self.mutils.kodidb.get_json( "Files.GetDirectory", returntype="", optparam=("directory", lib_path), fields=["title", "art", "thumbnail", "fanart"], sort={ "method": "random", "order": "descending" }, limits=(0, self.prefetch_images * 2)) for media in items: image = {} if media['label'].lower() == "next page": continue if media.get('art'): if media['art'].get('fanart'): image["fanart"] = self.mutils.get_clean_image( media['art']['fanart']) elif media['art'].get('tvshow.fanart'): image["fanart"] = self.mutils.get_clean_image( media['art']['tvshow.fanart']) elif media['art'].get('artist.fanart'): image["fanart"] = self.mutils.get_clean_image( media['art']['artist.fanart']) if media['art'].get('thumb'): image["thumbnail"] = self.mutils.get_clean_image( media['art']['thumb']) if not image.get('fanart') and media.get("fanart"): image["fanart"] = self.mutils.get_clean_image(media['fanart']) if not image.get("thumbnail") and media.get("thumbnail"): image["thumbnail"] = self.mutils.get_clean_image( media["thumbnail"]) # only append items which have a fanart image if image.get("fanart"): # also append other art to the dict image["title"] = media.get('title', '') if not image.get("title"): image["title"] = media.get('label', '') image["landscape"] = self.mutils.get_clean_image( media.get('art', {}).get('landscape', '')) image["poster"] = self.mutils.get_clean_image( media.get('art', {}).get('poster', '')) image["clearlogo"] = self.mutils.get_clean_image( media.get('art', {}).get('clearlogo', '')) result.append(image) if len(result) == self.prefetch_images: break random.shuffle(result) return result def get_pictures(self): '''get images we can use as pictures background''' images = [] # load the pictures from the custom path or from all picture sources if self.custom_picturespath: # load images from custom path files = xbmcvfs.listdir(self.custom_picturespath)[1] random.shuffle(files) # pick max 20 images from path for file in files[:20]: if file.lower().endswith(".jpg") or file.lower().endswith( ".png"): image = os.path.join(self.custom_picturespath, file.decode("utf-8")) images.append({ "fanart": image, "title": file.decode("utf-8") }) else: # load pictures from all picture sources media_array = self.mutils.kodidb.get_json('Files.GetSources', optparam=("media", "pictures")) randomdirs = [] for source in media_array: if 'file' in source: if "plugin://" not in source["file"]: dirs = xbmcvfs.listdir(source["file"])[0] random.shuffle(dirs) # randomize output if dirs: # pick 10 subdirectories for randomdir in dirs[:10]: randomdir = os.path.join( source["file"], randomdir.decode("utf-8")) randomdirs.append(randomdir) # append root to dirs so we can also list images in the root randomdirs.append(source["file"]) # pick 5 images from each dir for item in randomdirs: files2 = xbmcvfs.listdir(item)[1] random.shuffle(files2) for count, filename in enumerate(files2): if (filename.endswith(".jpg") or filename.endswith(".png") ) and count < 6: filename = filename.decode("utf-8") image = os.path.join(item, filename) images.append({ "fanart": image, "title": filename }) return images def set_background(self, win_prop, lib_path, fallback_image="", label=None): '''set the window property for the background image''' if self.exit: return image = None if win_prop in self.all_backgrounds2: # pick one random image from the small list using normal random function if len(self.all_backgrounds2[win_prop]) > 0: image = random.choice(self.all_backgrounds2[win_prop]) elif win_prop in self.all_backgrounds and len( self.all_backgrounds[win_prop]) > 0: # list is already in memory and still contains images, grab the next item in line image = self.all_backgrounds[win_prop][0] # delete image from list when we've used it so we have truly randomized images with minimized possibility of duplicates del self.all_backgrounds[win_prop][0] else: # no images in memory - load them from vfs if lib_path == "pictures": images = self.get_pictures() elif lib_path == "pvr": images = self.get_pvr_backgrounds() else: images = self.get_images_from_vfspath(lib_path) # store images in memory if (len(images) < self.prefetch_images): # this path did not return enough images so we store it in a different list # which will not be flushed self.all_backgrounds2[win_prop] = images if images: image = random.choice(images) else: # normal approach: store the current set of images in a list # images are taken from that list one-by-one untill it's empty # once empty a fresh pair of images will be retrieved for the path # this way we have fully randomized images while there's no need # to store a big pile of data in memory image = images[0] del images[0] self.all_backgrounds[win_prop] = images # also store the key + label in a list for skinshortcuts - only if the path actually has images if image: self.save_background_label(win_prop, label) # set the image self.set_image(win_prop, image, fallback_image) def set_global_background(self, win_prop, keys, fallback_image="", label=None): '''get random background from random other collection''' image = None # pick random category-key random.shuffle(keys) for key in keys: if key in self.all_backgrounds2 and self.all_backgrounds2[key]: # pick random image from this category image = random.choice(self.all_backgrounds2[key]) elif key in self.all_backgrounds and self.all_backgrounds[key]: # pick random image from this category image = random.choice(self.all_backgrounds[key]) if image or self.exit: break # also store the win_prop + label in a list for skinshortcuts - only if the path actually has images if image: self.save_background_label(win_prop, label) # set the image self.set_image(win_prop, image, fallback_image) return image def set_image(self, win_prop, image, fallback_image): ''' actually set the image window property''' if image: for key, value in image.items(): # image is actually a dict if key == "fanart": self.set_winprop(win_prop, value) else: # set additional image properties self.set_winprop("%s.%s" % (win_prop, key), value) elif fallback_image: # no image - use fallback_image self.set_winprop(win_prop, fallback_image) def save_background_label(self, win_prop, label): ''' store background label in list, used for exachnge with other scripts''' if not any(win_prop in item for item in self.all_backgrounds_labels): if label and isinstance(label, int): label = xbmc.getInfoLabel("$ADDON[%s %s]" % (ADDON_ID, label)) elif not label: label = win_prop self.all_backgrounds_labels.append((win_prop, label)) def get_pvr_backgrounds(self): '''get the images for pvr items by using the skinhelper widgets as source''' images = [] widgetreload = self.win.getProperty("widgetreload2") rec_images = self.get_images_from_vfspath( "plugin://script.skin.helper.widgets/?mediatype=pvr" "&action=recordings&limit=50&reload=%s" % widgetreload) if rec_images: # result can be None images = rec_images if not self.pvr_bg_recordingsonly: tv_images = self.get_images_from_vfspath( "plugin://script.skin.helper.widgets/?mediatype=pvr" "&action=channels&limit=25&reload=%s" % widgetreload) if tv_images: # result can be None images += tv_images return images def update_backgrounds(self): '''update all our provided backgrounds''' # conditional background self.win.setProperty("SkinHelper.ConditionalBackground", get_cond_background()) # movies backgrounds if xbmc.getCondVisibility("Library.HasContent(movies)"): # random/all movies self.set_background("SkinHelper.AllMoviesBackground", "videodb://movies/titles/", label=32010) # in progress movies self.set_background( "SkinHelper.InProgressMoviesBackground", "videodb://movies/titles/?xsp=%s" % urlencode( '{"limit":50,"order":{"direction":"ascending","method":"random"},' '"rules":{"and":[{"field":"inprogress","operator":"true","value":[]}]},"type":"movies"}' ), label=32012) # recent movies self.set_background("SkinHelper.RecentMoviesBackground", "videodb://recentlyaddedmovies/", label=32011) # unwatched movies self.set_background( "SkinHelper.UnwatchedMoviesBackground", "videodb://movies/titles/?xsp=%s" % urlencode( '{"limit":50,"order":{"direction":"ascending","method":"random"},' '"rules":{"and":[{"field":"playcount","operator":"is","value":0}]},"type":"movies"}' ), label=32013) # tvshows backgrounds if xbmc.getCondVisibility("Library.HasContent(tvshows)"): # random/all tvshows self.set_background("SkinHelper.AllTvShowsBackground", "videodb://tvshows/titles/", label=32014) # in progress tv shows self.set_background( "SkinHelper.InProgressShowsBackground", "videodb://tvshows/titles/?xsp=%s" % urlencode( '{"limit":50,"order":{"direction":"ascending","method":"random"},' '"rules":{"and":[{"field":"inprogress","operator":"true","value":[]}]},"type":"tvshows"}' ), label=32016) # recent episodes self.set_background("SkinHelper.RecentEpisodesBackground", "videodb://recentlyaddedepisodes/", label=32015) # all musicvideos if xbmc.getCondVisibility("Library.HasContent(musicvideos)"): self.set_background("SkinHelper.AllMusicVideosBackground", "videodb://musicvideos/titles", label=32018) # all music if xbmc.getCondVisibility("Library.HasContent(music)"): # music artists self.set_background("SkinHelper.AllMusicBackground", "musicdb://artists/", label=32019) # recent albums self.set_background("SkinHelper.RecentMusicBackground", "musicdb://recentlyaddedalbums/", label=32023) # random songs self.set_background("SkinHelper.AllMusicSongsBackground", "musicdb://songs/", label=32022) # tmdb backgrounds (extendedinfo) if xbmc.getCondVisibility("System.HasAddon(script.extendedinfo)"): self.set_background( "SkinHelper.TopRatedMovies", "plugin://script.extendedinfo/?info=topratedmovies", label=32020) self.set_background( "SkinHelper.TopRatedShows", "plugin://script.extendedinfo/?info=topratedtvshows", label=32021) # pictures background self.set_background("SkinHelper.PicturesBackground", "pictures", label=32017) # pvr background if xbmc.getCondVisibility("PVR.HasTvChannels"): self.set_background("SkinHelper.PvrBackground", "pvr", label=32024) # smartshortcuts backgrounds for node in self.smartshortcuts.get_smartshortcuts_nodes(): self.set_background(node[0], node[1], label=node[2]) # global backgrounds self.set_global_background("SkinHelper.GlobalFanartBackground", [ "SkinHelper.AllMoviesBackground", "SkinHelper.AllTvShowsBackground", "SkinHelper.AllMusicVideosBackground", "SkinHelper.AllMusicBackground" ], label=32009) self.set_global_background("SkinHelper.AllVideosBackground", [ "SkinHelper.AllMoviesBackground", "SkinHelper.AllTvShowsBackground", "SkinHelper.AllMusicVideosBackground" ], label=32025) self.set_global_background("SkinHelper.AllVideosBackground2", [ "SkinHelper.AllMoviesBackground", "SkinHelper.AllTvShowsBackground" ], label=32026) self.set_global_background("SkinHelper.RecentVideosBackground", [ "SkinHelper.RecentMoviesBackground", "SkinHelper.RecentEpisodesBackground" ], label=32027) self.set_global_background("SkinHelper.InProgressVideosBackground", [ "SkinHelper.InProgressMoviesBackground", "SkinHelper.InProgressShowsBackground" ], label=32028)
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 __init__(self, *args, **kwargs): self.metadatautils = MetadataUtils() xbmcgui.WindowXMLDialog.__init__(self, *args, **kwargs)
''' import os, sys import xbmc import xbmcgui from metadatautils import MetadataUtils from resources.lib.utils import try_decode import time # pylint: disable-msg=invalid-constant-name # Kodi contextmenu item to configure music artwork if __name__ == '__main__': win = xbmcgui.Window(10000) metadatautils = MetadataUtils() win.setProperty("SkinHelper.Artwork.ManualLookup", "busy") track = try_decode(xbmc.getInfoLabel("ListItem.Title")) album = try_decode(xbmc.getInfoLabel("ListItem.Album")) artist = try_decode(xbmc.getInfoLabel("ListItem.Artist")) disc = try_decode(xbmc.getInfoLabel("ListItem.DiscNumber")) metadatautils.music_artwork_options(artist, album, track, disc) metadatautils.close() # refresh music widgets timestr = time.strftime("%Y%m%d%H%M%S", time.gmtime()) win.setProperty("widgetreload-music", timestr) win.setProperty("widgetreloadmusic", timestr) win.setProperty("widgetreload-albums", timestr) win.setProperty("widgetreload-songs", timestr) win.clearProperty("SkinHelper.Artwork.ManualLookup") del win
class RoonOSD(xbmcgui.WindowXMLDialog): ''' Special OSD to control Roon zone''' update_thread = None roon = None is_playing = True shuffle_state = False repeat_state = "off" def __init__(self, *args, **kwargs): self.metadatautils = MetadataUtils() xbmcgui.WindowXMLDialog.__init__(self, *args, **kwargs) def onInit(self): '''triggers on initialization of the dialog''' if not self.roon: host = addon_setting("proxy_host") port = addon_setting("proxy_port") zone_id = addon_setting("zone_id") self.roon = RoonServer(host, port, zone_id) self.update_thread = RoonOSDUpdateThread() self.update_thread.set_dialog(self) self.update_thread.start() def onAction(self, action): '''triggers on kodi navigation events''' action_id = action.getId() #log_msg("onAction: %s" % action_id) if action_id in (9, 10, 92, 216, 247, 257, 275, 61467, 61448): self.close_dialog() elif action_id in (12, 68, 79, 229): self.toggle_playback() elif action_id in (184, 14, 97, 3): self.roon.next_track() elif action_id in (185, 15, 98, 4): self.roon.previous_track() elif action_id in (13, ): self.roon.stop_playback() elif action_id in (88, ): self.roon.volume_up() elif action_id in (89, ): self.roon.volume_down() def close_dialog(self): '''stop background thread and close the dialog''' self.update_thread.stop_running() self.metadatautils.close() self.close() def onClick(self, control_id): '''Kodi builtin: triggers if window is clicked''' if control_id == 3201: self.roon.previous_track() elif control_id == 3203: self.toggle_playback() elif control_id == 3204: self.roon.next_track() elif control_id == 3206 and self.shuffle_state: self.roon.shuffle(False) elif control_id == 3206 and not self.shuffle_state: self.roon.shuffle(True) elif control_id == 3208: self.roon.toggle_repeat() elif control_id == 3209: self.roon.stop_playback() elif control_id == 3210: self.select_zone() elif control_id == 3212: self.roon.volume_down() elif control_id == 3214: self.roon.volume_up() def toggle_playback(self): '''toggle play/pause''' if self.is_playing: self.is_playing = False self.getControl(3202).setEnabled(False) self.roon.pause_playback() else: self.is_playing = True self.getControl(3202).setEnabled(True) self.roon.start_playback() def select_zone(self): ''' select active zone ''' xbmc.executebuiltin("ActivateWindow(busydialog") all_zones = self.roon.send_request("zones") if all_zones: all_zones = all_zones["zones"].values() all_zone_names = [item["display_name"] for item in all_zones] else: all_zone_names = [] addon_setting("proxy_host", "") dialog = xbmcgui.Dialog() ret = dialog.select("Select zone", all_zone_names) if ret != -1: selected_zone = all_zones[ret] self.roon.zone_id = selected_zone["zone_id"] addon_setting("zone_id", selected_zone["zone_id"]) addon_setting("zone_name", selected_zone["display_name"]) del dialog xbmc.executebuiltin("Dialog.Close(busydialog)") xbmc.executebuiltin("Container.Refresh")
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 MainService: '''our main background service running the various threads''' last_skin = "" def __init__(self): self.win = xbmcgui.Window(10000) self.addon = xbmcaddon.Addon(ADDON_ID) self.metadatautils = MetadataUtils() self.addonname = self.addon.getAddonInfo('name').decode("utf-8") self.addonversion = self.addon.getAddonInfo('version').decode("utf-8") self.kodimonitor = KodiMonitor(metadatautils=self.metadatautils, win=self.win) listitem_monitor = ListItemMonitor(metadatautils=self.metadatautils, win=self.win, monitor=self.kodimonitor) webservice = WebService(metadatautils=self.metadatautils) # start the extra threads listitem_monitor.start() webservice.start() self.win.clearProperty("SkinHelperShutdownRequested") log_msg('%s version %s started' % (self.addonname, self.addonversion), xbmc.LOGNOTICE) # run as service, check skin every 10 seconds and keep the other threads alive while not self.kodimonitor.abortRequested(): # check skin version info self.check_skin_version() # sleep for 10 seconds self.kodimonitor.waitForAbort(10) # Abort was requested while waiting. We should exit self.win.setProperty("SkinHelperShutdownRequested", "shutdown") log_msg('Shutdown requested !', xbmc.LOGNOTICE) # stop the extra threads listitem_monitor.stop() webservice.stop() # cleanup objects self.close() def close(self): '''Cleanup Kodi Cpython instances''' self.metadatautils.close() del self.win del self.kodimonitor del self.metadatautils log_msg('%s version %s stopped' % (self.addonname, self.addonversion), xbmc.LOGNOTICE) def check_skin_version(self): '''check if skin changed''' try: skin = xbmc.getSkinDir() skin_addon = xbmcaddon.Addon(id=skin) skin_label = skin_addon.getAddonInfo('name').decode("utf-8") skin_version = skin_addon.getAddonInfo('version').decode("utf-8") del skin_addon if self.last_skin != skin_label + skin_version: # auto correct skin settings self.last_skin = skin_label + skin_version self.win.setProperty( "SkinHelper.skinTitle", "%s - %s: %s" % (skin_label, xbmc.getLocalizedString(19114), skin_version)) self.win.setProperty( "SkinHelper.skin_version", "%s: %s" % (xbmc.getLocalizedString(19114), skin_version)) self.win.setProperty("SkinHelper.Version", self.addonversion.replace(".", "")) SkinSettings().correct_skin_settings() except Exception as exc: log_exception(__name__, exc)
class SpotifyOSD(xbmcgui.WindowXMLDialog): ''' Special OSD to control Spotify Connect player''' update_thread = None sp = None is_playing = True shuffle_state = False repeat_state = "off" def __init__(self, *args, **kwargs): self.metadatautils = MetadataUtils() xbmcgui.WindowXMLDialog.__init__(self, *args, **kwargs) def onInit(self): '''triggers on initialization of the dialog''' self.update_thread = SpotifyOSDUpdateThread() self.update_thread.set_dialog(self) self.update_thread.start() def onAction(self, action): '''triggers on kodi navigation events''' action_id = action.getId() if action_id in (9, 10, 92, 216, 247, 257, 275, 61467, 61448, ): self.close_dialog() elif action_id in (12, 68, 79, 229): self.toggle_playback() elif action_id in (184, 14, 97): self.sp.next_track() elif action_id in (185, 15, 98): self.sp.previous_track() def close_dialog(self): '''stop background thread and close the dialog''' self.update_thread.stop_running() try: self.sp.pause_playback() except: pass self.metadatautils.close() self.close() def onClick(self, control_id): '''Kodi builtin: triggers if window is clicked''' if control_id == 3201: self.sp.previous_track() elif control_id == 3203: self.toggle_playback() elif control_id == 3204: self.sp.next_track() elif control_id == 3206 and self.shuffle_state: self.sp.shuffle(False) elif control_id == 3206 and not self.shuffle_state: self.sp.shuffle(True) elif control_id == 3208 and self.repeat_state == "off": self.sp.repeat("track") elif control_id == 3208 and self.repeat_state == "track": self.sp.repeat("context") elif control_id == 3208 and self.repeat_state == "context": self.sp.repeat("off") def toggle_playback(self): '''toggle play/pause''' if self.is_playing: self.is_playing = False self.getControl(3202).setEnabled(False) try: self.sp.pause_playback() except Exception: pass else: self.is_playing = True self.getControl(3202).setEnabled(True) self.sp.start_playback()
class SpotifyOSD(xbmcgui.WindowXMLDialog): ''' Special OSD to control Spotify Connect player''' update_thread = None sp = None is_playing = True shuffle_state = False repeat_state = "off" def __init__(self, *args, **kwargs): self.metadatautils = MetadataUtils() xbmcgui.WindowXMLDialog.__init__(self, *args, **kwargs) def onInit(self): '''triggers on initialization of the dialog''' self.update_thread = SpotifyOSDUpdateThread() self.update_thread.set_dialog(self) self.update_thread.start() def onAction(self, action): '''triggers on kodi navigation events''' action_id = action.getId() if action_id in ( 9, 10, 92, 216, 247, 257, 275, 61467, 61448, ): self.close_dialog() elif action_id in (12, 68, 79, 229): self.toggle_playback() elif action_id in (184, 14, 97): self.sp.next_track() elif action_id in (185, 15, 98): self.sp.previous_track() def close_dialog(self): '''stop background thread and close the dialog''' self.update_thread.stop_running() try: self.sp.pause_playback() except: pass self.metadatautils.close() self.close() def onClick(self, control_id): '''Kodi builtin: triggers if window is clicked''' if control_id == 3201: self.sp.previous_track() elif control_id == 3203: self.toggle_playback() elif control_id == 3204: self.sp.next_track() elif control_id == 3206 and self.shuffle_state: self.sp.shuffle(False) elif control_id == 3206 and not self.shuffle_state: self.sp.shuffle(True) elif control_id == 3208 and self.repeat_state == "off": self.sp.repeat("track") elif control_id == 3208 and self.repeat_state == "track": self.sp.repeat("context") elif control_id == 3208 and self.repeat_state == "context": self.sp.repeat("off") def toggle_playback(self): '''toggle play/pause''' if self.is_playing: self.is_playing = False self.getControl(3202).setEnabled(False) try: self.sp.pause_playback() except Exception: pass else: self.is_playing = True self.getControl(3202).setEnabled(True) self.sp.start_playback()
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)
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)
script.skin.helper.service Contextmenu for Pvr art ''' import os, sys import xbmc import xbmcgui from metadatautils import MetadataUtils from utils import try_decode # pylint: disable-msg=invalid-constant-name # Kodi contextmenu item to configure pvr artwork if __name__ == '__main__': ##### PVR Artwork ######## win = xbmcgui.Window(10000) win.setProperty("SkinHelper.Artwork.ManualLookup", "busy") xbmc.executebuiltin("ActivateWindow(busydialog)") title = try_decode(xbmc.getInfoLabel("ListItem.Title")) if not title: title = try_decode(xbmc.getInfoLabel("ListItem.Label")) channel = try_decode(xbmc.getInfoLabel("ListItem.ChannelName")) genre = try_decode(xbmc.getInfoLabel("ListItem.Genre")) metadatautils = MetadataUtils() metadatautils.pvr_artwork_options(title, channel, genre) xbmc.executebuiltin("Dialog.Close(busydialog)") win.clearProperty("SkinHelper.Artwork.ManualLookup") metadatautils.close() del win
log_msg( "Animated Art: lookup imdbid by title and year: (%s - %s)" % (title, year), xbmc.LOGNOTICE) imdb_id = metadatautils.get_omdb_info("", title, year, content_type).get( "imdbnumber", "") if not imdb_id: return title return imdb_id # Kodi contextmenu item to configure the artwork if __name__ == '__main__': xbmc.executebuiltin("ActivateWindow(busydialog)") log_msg("Contextmenu for Animated Art opened", xbmc.LOGNOTICE) ARTUTILS = MetadataUtils() WIN = xbmcgui.Window(10000) imdb_id = get_imdb_id(WIN, ARTUTILS) WIN.setProperty("SkinHelper.Artwork.ManualLookup", "busy") log_msg("Animated Art: Query animated art by IMDBID: %s" % imdb_id, xbmc.LOGNOTICE) artwork = ARTUTILS.get_animated_artwork(imdb_id, manual_select=True, ignore_cache=True) log_msg("Animated Art result: %s" % artwork, xbmc.LOGNOTICE) xbmc.executebuiltin("Dialog.Close(busydialog)") xbmc.executebuiltin("Container.Refresh") WIN.clearProperty("SkinHelper.Artwork.ManualLookup") del WIN ARTUTILS.close()
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)
class MainService: '''our main background service running the various threads''' last_skin = "" def __init__(self): self.win = xbmcgui.Window(10000) self.addon = xbmcaddon.Addon(ADDON_ID) self.metadatautils = MetadataUtils() self.addonname = self.addon.getAddonInfo('name').decode("utf-8") self.addonversion = self.addon.getAddonInfo('version').decode("utf-8") self.kodimonitor = KodiMonitor(metadatautils=self.metadatautils, win=self.win) listitem_monitor = ListItemMonitor( metadatautils=self.metadatautils, win=self.win, monitor=self.kodimonitor) webservice = WebService(metadatautils=self.metadatautils) # start the extra threads listitem_monitor.start() webservice.start() self.win.clearProperty("SkinHelperShutdownRequested") log_msg('%s version %s started' % (self.addonname, self.addonversion), xbmc.LOGNOTICE) # run as service, check skin every 10 seconds and keep the other threads alive while not self.kodimonitor.abortRequested(): # check skin version info self.check_skin_version() # sleep for 10 seconds self.kodimonitor.waitForAbort(10) # Abort was requested while waiting. We should exit self.win.setProperty("SkinHelperShutdownRequested", "shutdown") log_msg('Shutdown requested !', xbmc.LOGNOTICE) # stop the extra threads listitem_monitor.stop() webservice.stop() # cleanup objects self.close() def close(self): '''Cleanup Kodi Cpython instances''' self.metadatautils.close() del self.win del self.kodimonitor del self.metadatautils log_msg('%s version %s stopped' % (self.addonname, self.addonversion), xbmc.LOGNOTICE) def check_skin_version(self): '''check if skin changed''' try: skin = xbmc.getSkinDir() skin_addon = xbmcaddon.Addon(id=skin) skin_label = skin_addon.getAddonInfo('name').decode("utf-8") skin_version = skin_addon.getAddonInfo('version').decode("utf-8") this_skin = "%s-%s" % (skin_label, skin_version) del skin_addon if self.last_skin != this_skin: # auto correct skin settings if needed self.last_skin = this_skin self.win.setProperty("SkinHelper.skinTitle", "%s - %s: %s" % (skin_label, xbmc.getLocalizedString(19114), skin_version)) self.win.setProperty("SkinHelper.skin_version", "%s: %s" % (xbmc.getLocalizedString(19114), skin_version)) self.win.setProperty("SkinHelper.Version", self.addonversion.replace(".", "")) SkinSettings().correct_skin_settings() except Exception as exc: log_exception(__name__, exc)