def onNotification(self, sender, method, data): '''builtin function for the xbmc.Monitor class''' try: log_msg("Kodi_Monitor: sender %s - method: %s - data: %s" % (sender, method, data)) data = json.loads(data.decode('utf-8')) mediatype = "" if data and isinstance(data, dict): if data.get("item"): mediatype = data["item"].get("type", "") elif data.get("type"): mediatype = data["type"] if method == "VideoLibrary.OnUpdate": if not mediatype: mediatype = self.last_mediatype # temp hack self.refresh_video_widgets(mediatype) if method == "AudioLibrary.OnUpdate": self.refresh_music_widgets(mediatype) if method == "Player.OnStop": self.last_mediatype = mediatype if mediatype in ["movie", "episode", "musicvideo"]: if self.addon.getSetting("aggresive_refresh") == "true": self.refresh_video_widgets(mediatype) except Exception as exc: log_msg("Exception in KodiMonitor: %s" % exc, xbmc.LOGERROR)
def onDatabaseUpdated(self, database): '''builtin function for the xbmc.Monitor class''' log_msg("Kodi_Monitor: %s database updated" % database) if database == "music": self.refresh_music_widgets("") else: self.refresh_video_widgets("")
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 stop(self): '''cleanup on exit''' self.__exit = True if self.__spotty_proc: self.__spotty_proc.terminate() log_msg("spotty terminated") self.join(2)
def close(self): '''Cleanup Kodi Cpython instances on exit''' self.cache.close() del self.win del self.addon del self.kodidb log_msg("MainModule exited")
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 xbmc.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 >= 17: xbmc.executebuiltin("InstallAddon(%s)" % newaddon) else: xbmc.executebuiltin("RunPlugin(plugin://%s)" % newaddon)
def insert_data_into_db(timestamp, metrics): global db, last_day, last_hour if not timestamp: return new_data = [] hosts = 0 length = 0 count = 0 (day, hour, minute) = timestamp.split('|') if last_hour != hour: db.summarize_data('hour', last_day, last_hour) last_hour = hour if last_day != day: db.summarize_data('day', last_day) last_day = day for host_id in metrics: new_data.append((day, hour, minute, host_id, metrics[host_id]['length'], metrics[host_id]['count'])) hosts += 1 count += metrics[host_id]['count'] length += metrics[host_id]['length'] log_msg('adding: day='+str(day)+' '+str(hour)+':'+str(minute)+', hosts='+str(hosts)+', count='+str(count)+', length='+str(length)) db.add_bandwidth(new_data)
def close(self): '''Cleanup Kodi Cpython instances''' self.artutils.close() del self.win del self.kodimonitor del self.artutils log_msg('%s version %s stopped' % (self.addonname, self.addonversion), xbmc.LOGNOTICE)
def pvr_proceed_lookup(self, title, channel, genre, recordingdetails): '''perform some checks if we can proceed with the lookup''' filters = [] if not title: filters.append("Title is empty") for item in self.metadatautils.addon.getSetting("pvr_art_ignore_titles").split("|"): if item and item.lower() == title.lower(): filters.append("Title is in list of titles to ignore") for item in self.metadatautils.addon.getSetting("pvr_art_ignore_channels").split("|"): if item and item.lower() == channel.lower(): filters.append("Channel is in list of channels to ignore") for item in self.metadatautils.addon.getSetting("pvr_art_ignore_genres").split("|"): if genre and item and item.lower() in genre.lower(): filters.append("Genre is in list of genres to ignore") if self.metadatautils.addon.getSetting("pvr_art_ignore_commongenre") == "true": # skip common genres like sports, weather, news etc. genre = genre.lower() kodi_strings = [19516, 19517, 19518, 19520, 19548, 19549, 19551, 19552, 19553, 19554, 19555, 19556, 19557, 19558, 19559] for kodi_string in kodi_strings: kodi_string = xbmc.getLocalizedString(kodi_string).lower() if (genre and (genre in kodi_string or kodi_string in genre)) or kodi_string in title: filters.append("Common genres like weather/sports are set to be ignored") if self.metadatautils.addon.getSetting("pvr_art_recordings_only") == "true" and not recordingdetails: filters.append("PVR Artwork is enabled for recordings only") if filters: filterstr = " - ".join(filters) log_msg("PVR artwork - filter active for title: %s - channel %s --> %s" % (title, channel, filterstr)) return filterstr else: return ""
def refresh_music_widgets(self, media_type): '''refresh music widgets''' log_msg("Music database changed - type: %s - refreshing widgets...." % media_type) timestr = time.strftime("%Y%m%d%H%M%S", time.gmtime()) self.win.setProperty("widgetreload-music", timestr) self.win.setProperty("widgetreloadmusic", timestr) if media_type: self.win.setProperty("widgetreload-%ss" % media_type, timestr)
def tvshowtitle(self, showtitle, prefix=""): '''set details for single show by name''' log_msg("Set NextAired properties for TV Show: %s" % showtitle) details = self.thetvdb.get_kodishow_details(showtitle) if not details or not showtitle: self.clear_properties(prefix) else: self.set_properties(details, prefix)
def run(self): '''our main loop monitoring the listitem and folderpath changes''' log_msg("ListItemMonitor - started") self.get_settings() while not self.exit: # check screensaver and OSD self.check_screensaver() self.check_osd() # do some background stuff every 30 minutes if (self.delayed_task_interval >= 1800) and not self.exit: thread.start_new_thread(self.do_background_work, ()) self.delayed_task_interval = 0 # skip if any of the artwork context menus is opened if self.win.getProperty("SkinHelper.Artwork.ManualLookup"): self.reset_win_props() self.last_listitem = "" self.listitem_details = {} self.kodimonitor.waitForAbort(3) self.delayed_task_interval += 3 # skip when modal dialogs are opened (e.g. textviewer in musicinfo dialog) elif xbmc.getCondVisibility( "Window.IsActive(DialogSelect.xml) | Window.IsActive(progressdialog) | " "Window.IsActive(contextmenu) | Window.IsActive(busydialog)"): self.kodimonitor.waitForAbort(2) self.delayed_task_interval += 2 self.last_listitem = "" # skip when container scrolling elif xbmc.getCondVisibility( "Container.OnScrollNext | Container.OnScrollPrevious | Container.Scrolling"): self.kodimonitor.waitForAbort(1) self.delayed_task_interval += 1 self.last_listitem = "" # media window is opened or widgetcontainer set - start listitem monitoring! elif xbmc.getCondVisibility("Window.IsMedia | " "!IsEmpty(Window(Home).Property(SkinHelper.WidgetContainer))"): self.monitor_listitem() self.kodimonitor.waitForAbort(0.15) self.delayed_task_interval += 0.15 # flush any remaining window properties elif self.all_window_props: self.reset_win_props() self.win.clearProperty("SkinHelper.ContentHeader") self.win.clearProperty("contenttype") self.win.clearProperty("curlistitem") self.last_listitem = "" # other window active - do nothing else: self.kodimonitor.waitForAbort(1) self.delayed_task_interval += 1
def onPlayBackSeek(self, seekTime, seekOffset): '''Kodi event fired when the user is seeking''' if self.__ignore_seek: self.__ignore_seek = False elif self.connect_playing: log_msg("Kodiplayer seekto: %s" % seekTime) if self.connect_local: self.__ignore_seek = True self.__sp.seek_track(seekTime)
def run(self): '''called to start our webservice''' log_msg("WebService - start helper webservice on port %s" % PORT, xbmc.LOGNOTICE) try: server = StoppableHttpServer(('127.0.0.1', PORT), StoppableHttpRequestHandler) server.artutils = self.artutils server.serve_forever() except Exception as exc: log_exception(__name__, exc)
def updateshow(self, showtitle): '''force update of single show''' if showtitle: log_msg("Update show data requested for TV Show: %s" % showtitle) self.win.setProperty("nextaired.update_data", "busy") self.thetvdb.ignore_cache = True self.thetvdb.get_kodishow_details(showtitle) self.thetvdb.ignore_cache = False self.win.clearProperty("nextaired.update_data")
def refresh_video_widgets(self, media_type): '''refresh video widgets''' log_msg("Video database changed - type: %s - refreshing widgets...." % media_type) timestr = time.strftime("%Y%m%d%H%M%S", time.gmtime()) self.win.setProperty("widgetreload", timestr) if media_type: self.win.setProperty("widgetreload-%ss" % media_type, timestr) if "episode" in media_type: self.win.setProperty("widgetreload-tvshows", timestr)
def run(self): '''called to start our webservice''' log_msg("WebService - start helper webservice on port %s" % PORT, xbmc.LOGNOTICE) try: server = StoppableHttpServer(('127.0.0.1', PORT), StoppableHttpRequestHandler) server.metadatautils = self.metadatautils server.serve_forever() except Exception as exc: if "10053" not in exc: # ignore host diconnected errors log_exception(__name__, exc)
def onPlayBackStopped(self): '''Kodi event fired when playback is stopped''' if self.connect_playing: try: self.__sp.pause_playback() except Exception: pass log_msg("playback stopped") self.connect_playing = False self.connect_local = False
def stop(self): '''called when the thread needs to stop''' try: log_msg("WebService - stop called", 0) conn = httplib.HTTPConnection("127.0.0.1:%d" % PORT) conn.request("QUIT", "/") conn.getresponse() self.exit = True self.event.set() except Exception as exc: log_exception(__name__, exc)
def refresh_video_widgets(self, media_type): '''refresh video widgets''' if not self.update_video_widgets_busy: self.update_video_widgets_busy = True log_msg("Video database changed - type: %s - refreshing widgets...." % media_type) xbmc.sleep(500) timestr = time.strftime("%Y%m%d%H%M%S", time.gmtime()) self.win.setProperty("widgetreload", timestr) if media_type: self.win.setProperty("widgetreload-%ss" % media_type, timestr) self.update_video_widgets_busy = False
def do_background_work(self): '''stuff that's processed in the background''' try: if self.exit: return log_msg("Started Background worker...") self.set_generic_props() self.listitem_details = {} self.cache.check_cleanup() log_msg("Ended Background worker...") except Exception as exc: log_exception(__name__, exc)
def onNotification(self, sender, method, data): '''builtin function for the xbmc.Monitor class''' try: log_msg("Kodi_Monitor: sender %s - method: %s - data: %s" % (sender, method, data)) data = json.loads(data.decode('utf-8')) mediatype = "" dbid = 0 transaction = False if data and isinstance(data, dict): if data.get("item"): mediatype = data["item"].get("type", "") dbid = data["item"].get("id", 0) elif data.get("type"): mediatype = data["type"] id = data.get("id", 0) if data.get("transaction"): transaction = True if method == "System.OnQuit": self.win.setProperty("SkinHelperShutdownRequested", "shutdown") if method == "VideoLibrary.OnUpdate": self.process_db_update(mediatype, dbid, transaction) if method == "AudioLibrary.OnUpdate": self.process_db_update(mediatype, dbid, transaction) if method == "Player.OnStop": self.monitoring_stream = False self.infopanelshown = False self.win.clearProperty("Skinhelper.PlayerPlaying") self.win.clearProperty("TrailerPlaying") self.reset_win_props() if not dbid: self.process_db_update(mediatype, "", transaction) if method == "Player.OnPlay": self.reset_win_props() if self.wait_for_player(): if xbmc.getCondVisibility("Player.HasAudio"): if xbmc.getCondVisibility("Player.IsInternetStream"): self.monitor_radiostream() else: self.set_music_properties() elif xbmc.getCondVisibility("VideoPlayer.Content(livetv)"): self.monitor_livetv() else: self.set_video_properties(mediatype, dbid) self.show_info_panel() except Exception as exc: log_exception(__name__, exc)
def __init__(self): self.cache = SimpleCache() self.kodi_db = KodiDb() 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 update_data(self, ignore_cache=False): '''updates all data we need in cache''' if self.win.getProperty("nextaired.update_data"): log_msg("Update data skipped, another update is in progress") else: self.win.setProperty("nextaired.update_data", "busy") # build details in cache for all continuing series in the kodi db log_msg("Updating TheTVDB info for all continuing Kodi tv shows...", xbmc.LOGNOTICE) self.thetvdb.ignore_cache = ignore_cache continuing_kodi_shows = self.thetvdb.get_kodishows_details(continuing_only=True) self.win.setProperty("NextAired.Total", "%s" % len(continuing_kodi_shows)) # build nextaired episodes listing in cache log_msg("Retrieving next airing episodes for all continuing Kodi tv shows...", xbmc.LOGNOTICE) want_yesterday = self.addon.getSetting("WantYesterday") == 'true' self.thetvdb.get_kodi_unaired_episodes(include_last_episode=want_yesterday) # set the window properties for the shows that are airing today prev_total = self.win.getProperty("NextAired.TodayTotal") prev_total = int(prev_total) if prev_total else 0 # clear previous properties for count in range(prev_total + 1): self.clear_properties("%s." % count) shows_airing_today = self.thetvdb.get_kodishows_airingtoday() all_titles = [] for count, show_details in enumerate(shows_airing_today): self.set_properties(show_details, "%s." % count) all_titles.append(show_details["title"]) self.win.setProperty("NextAired.TodayTotal", "%s" % len(shows_airing_today)) self.win.setProperty("NextAired.TodayShow", "[CR]".join(all_titles)) self.thetvdb.ignore_cache = False self.win.clearProperty("nextaired.update_data") log_msg("Update complete", xbmc.LOGNOTICE)
def __init__(self): '''Initialization and main code run''' self.win = xbmcgui.Window(10000) self.addon = xbmcaddon.Addon(ADDON_ID) self.thetvdb = TheTvDb() self.set_dates() action, action_param = self.get_params() self.improve_dates = self.addon.getSetting("ImproveDates") == 'true' log_msg("MainModule called with action: %s - parameter: %s" % (action, action_param)) # launch module for action provided by this script try: getattr(self, action)(action_param) except Exception as exc: log_exception(__name__, exc) finally: self.close()
def __init__(self): addon = xbmcaddon.Addon(ADDON_ID) addonname = addon.getAddonInfo('name').decode("utf-8") addonversion = addon.getAddonInfo('version').decode("utf-8") del addon kodimonitor = xbmc.Monitor() log_msg('%s version %s started' % (addonname, addonversion), xbmc.LOGNOTICE) while not kodimonitor.abortRequested(): # run update task every hour xbmc.executebuiltin("RunScript(script.tv.show.next.aired,update=True)") kodimonitor.waitForAbort(3600) # Abort was requested while waiting. Do cleanup del kodimonitor log_msg('%s version %s stopped' % (addonname, addonversion), xbmc.LOGNOTICE)
def check_screensaver(self): '''Allow user to disable screensaver on fullscreen music playback''' if xbmc.getCondVisibility( "Window.IsActive(visualisation) + Skin.HasSetting(SkinHelper.DisableScreenSaverOnFullScreenMusic)"): if not self.screensaver_disabled: # disable screensaver when fullscreen music active self.screensaver_setting = kodi_json('Settings.GetSettingValue', '{"setting":"screensaver.mode"}') kodi_json('Settings.SetSettingValue', {"setting": "screensaver.mode", "value": None}) self.screensaver_disabled = True log_msg( "Disabled screensaver while fullscreen music playback - previous setting: %s" % self.screensaver_setting) elif self.screensaver_setting and self.screensaver_disabled: # enable screensaver again after fullscreen music playback was ended kodi_json('Settings.SetSettingValue', {"setting": "screensaver.mode", "value": self.screensaver_setting}) self.screensaver_disabled = False log_msg("fullscreen music playback ended - restoring screensaver: %s" % self.screensaver_setting)
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 get_animated_artwork(self, imdb_id, manual_select=False, ignore_cache=False): '''returns all available animated art for the given imdbid/tmdbid''' # prefer local result kodi_movie = self.kodidb.movie_by_imdbid(imdb_id) if not manual_select and kodi_movie and kodi_movie["art"].get("animatedposter"): result = { "animatedposter": kodi_movie["art"].get("animatedposter"), "animatedfanart": kodi_movie["art"].get("animatedfanart") } else: result = { "animatedposter": self.poster(imdb_id, manual_select), "animatedfanart": self.fanart(imdb_id, manual_select), "imdb_id": imdb_id } self.write_kodidb(result) log_msg("get_animated_artwork for imdbid: %s - result: %s" % (imdb_id, result)) return result
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 = "" for key, value in self.params.iteritems(): paramstring += ",%s=%s" % (key, value) if xbmc.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)
def prepare_listitem(item): '''helper to convert kodi output from json api to compatible format for listitems''' try: # fix values returned from json to be used as listitem values properties = item.get("extraproperties", {}) # set type for idvar in [('episode', 'DefaultTVShows.png'), ('tvshow', 'DefaultTVShows.png'), ('movie', 'DefaultMovies.png'), ('song', 'DefaultAudio.png'), ('album', 'DefaultAudio.png'), ('artist', 'DefaultArtist.png'), ('musicvideo', 'DefaultMusicVideos.png'), ('recording', 'DefaultTVShows.png'), ('channel', 'DefaultAddonPVRClient.png')]: dbid = item.get(idvar[0] + "id") if dbid: properties["DBID"] = str(dbid) if not item.get("type"): item["type"] = idvar[0] if not item.get("icon"): item["icon"] = idvar[1] break # general properties if "genre" in item and isinstance(item['genre'], list): item["genre"] = " / ".join(item['genre']) if "studio" in item and isinstance(item['studio'], list): item["studio"] = " / ".join(item['studio']) if "writer" in item and isinstance(item['writer'], list): item["writer"] = " / ".join(item['writer']) if 'director' in item and isinstance(item['director'], list): item["director"] = " / ".join(item['director']) if 'artist' in item and not isinstance(item['artist'], list): item["artist"] = [item['artist']] if 'artist' not in item: item["artist"] = [] if item['type'] == "album" and 'album' not in item and 'label' in item: item['album'] = item['label'] if "duration" not in item and "runtime" in item: if (item["runtime"] / 60) > 300: item["duration"] = item["runtime"] / 60 else: item["duration"] = item["runtime"] if "plot" not in item and "comment" in item: item["plot"] = item["comment"] if "tvshowtitle" not in item and "showtitle" in item: item["tvshowtitle"] = item["showtitle"] if "premiered" not in item and "firstaired" in item: item["premiered"] = item["firstaired"] if "firstaired" in item and "aired" not in item: item["aired"] = item["firstaired"] if "imdbnumber" not in properties and "imdbnumber" in item: properties["imdbnumber"] = item["imdbnumber"] if "imdbnumber" not in properties and "uniqueid" in item: for value in item["uniqueid"].values(): if value.startswith("tt"): properties["imdbnumber"] = value properties["dbtype"] = item["type"] properties["DBTYPE"] = item["type"] properties["type"] = item["type"] properties["path"] = item.get("file") # cast list_cast = [] list_castandrole = [] item["cast_org"] = item.get("cast", []) if "cast" in item and isinstance(item["cast"], list): for castmember in item["cast"]: if isinstance(castmember, dict): list_cast.append(castmember.get("name", "")) list_castandrole.append( (castmember["name"], castmember["role"])) else: list_cast.append(castmember) list_castandrole.append((castmember, "")) item["cast"] = list_cast item["castandrole"] = list_castandrole if "season" in item and "episode" in item: properties["episodeno"] = "s%se%s" % (item.get("season"), item.get("episode")) if "resume" in item: properties["resumetime"] = str(item['resume']['position']) properties["totaltime"] = str(item['resume']['total']) properties['StartOffset'] = str(item['resume']['position']) # streamdetails if "streamdetails" in item: streamdetails = item["streamdetails"] audiostreams = streamdetails.get('audio', []) videostreams = streamdetails.get('video', []) subtitles = streamdetails.get('subtitle', []) if len(videostreams) > 0: stream = videostreams[0] height = stream.get("height", "") width = stream.get("width", "") if height and width: resolution = "" if width <= 720 and height <= 480: resolution = "480" elif width <= 768 and height <= 576: resolution = "576" elif width <= 960 and height <= 544: resolution = "540" elif width <= 1280 and height <= 720: resolution = "720" elif width <= 1920 and height <= 1080: resolution = "1080" elif width * height >= 6000000: resolution = "4K" properties["VideoResolution"] = resolution if stream.get("codec", ""): properties["VideoCodec"] = str(stream["codec"]) if stream.get("aspect", ""): properties["VideoAspect"] = str( round(stream["aspect"], 2)) item["streamdetails"]["video"] = stream # grab details of first audio stream if len(audiostreams) > 0: stream = audiostreams[0] properties["AudioCodec"] = stream.get('codec', '') properties["AudioChannels"] = str( stream.get('channels', '')) properties["AudioLanguage"] = stream.get('language', '') item["streamdetails"]["audio"] = stream # grab details of first subtitle if len(subtitles) > 0: properties["SubtitleLanguage"] = subtitles[0].get( 'language', '') item["streamdetails"]["subtitle"] = subtitles[0] else: item["streamdetails"] = {} item["streamdetails"]["video"] = { 'duration': item.get('duration', 0) } # additional music properties if 'album_description' in item: properties["Album_Description"] = item.get('album_description') # pvr properties if "starttime" in item: # convert utc time to local time item["starttime"] = localdate_from_utc_string( item["starttime"]) item["endtime"] = localdate_from_utc_string(item["endtime"]) # set some localized versions of the time and date as additional properties startdate, starttime = localized_date_time(item['starttime']) enddate, endtime = localized_date_time(item['endtime']) properties["StartTime"] = starttime properties["StartDate"] = startdate properties["EndTime"] = endtime properties["EndDate"] = enddate properties["Date"] = "%s %s-%s" % (startdate, starttime, endtime) properties["StartDateTime"] = "%s %s" % (startdate, starttime) properties["EndDateTime"] = "%s %s" % (enddate, endtime) # set date to startdate item["date"] = arrow.get( item["starttime"]).format("DD.MM.YYYY") if "channellogo" in item: properties["channellogo"] = item["channellogo"] properties["channelicon"] = item["channellogo"] if "episodename" in item: properties["episodename"] = item["episodename"] if "channel" in item: properties["channel"] = item["channel"] properties["channelname"] = item["channel"] item["label2"] = item["title"] # artwork art = item.get("art", {}) if item["type"] in ["episode", "season"]: if not art.get("fanart") and art.get("season.fanart"): art["fanart"] = art["season.fanart"] if not art.get("poster") and art.get("season.poster"): art["poster"] = art["season.poster"] if not art.get("landscape") and art.get("season.landscape"): art["poster"] = art["season.landscape"] if not art.get("fanart") and art.get("tvshow.fanart"): art["fanart"] = art.get("tvshow.fanart") if not art.get("poster") and art.get("tvshow.poster"): art["poster"] = art.get("tvshow.poster") if not art.get("clearlogo") and art.get("tvshow.clearlogo"): art["clearlogo"] = art.get("tvshow.clearlogo") if not art.get("banner") and art.get("tvshow.banner"): art["banner"] = art.get("tvshow.banner") if not art.get("landscape") and art.get("tvshow.landscape"): art["landscape"] = art.get("tvshow.landscape") if not art.get("fanart") and item.get('fanart'): art["fanart"] = item.get('fanart') if not art.get("thumb") and item.get('thumbnail'): art["thumb"] = get_clean_image(item.get('thumbnail')) if not art.get("thumb") and art.get('poster'): art["thumb"] = get_clean_image(art.get('poster')) if not art.get("thumb") and item.get('icon'): art["thumb"] = get_clean_image(item.get('icon')) if not item.get("thumbnail") and art.get('thumb'): item["thumbnail"] = art["thumb"] # clean art for key, value in art.iteritems(): if not isinstance(value, (str, unicode)): art[key] = "" elif value: art[key] = get_clean_image(value) item["art"] = art item["extraproperties"] = properties if "file" not in item: log_msg("Item is missing file path ! --> %s" % item["label"], xbmc.LOGWARNING) item["file"] = "" # return the result return item except Exception as exc: log_exception(__name__, exc) log_msg(item) return None
def get_album_metadata(self, artist, album, track, disc, ignore_cache=False, flush_cache=False, manual=False): """collect all album metadata""" cache_str = "music_artwork.album.%s.%s.%s" % ( artist.lower(), album.lower(), disc.lower()) if not album: cache_str = "music_artwork.album.%s.%s" % (artist.lower(), track.lower()) details = {"art": {}, "cachestr": cache_str} log_msg("get_album_metadata --> artist: %s - album: %s - track: %s" % (artist, album, track)) # retrieve details from cache cache = self._mutils.cache.get(cache_str) if not cache and flush_cache: # nothing to do - just return empty results return details elif cache and flush_cache: # only update kodi metadata for updated counts etc details = extend_dict( self.get_album_kodi_metadata(artist, album, track, disc), cache) elif cache and not ignore_cache: # we have a valid cache - return that details = cache elif cache and manual: # user wants to manually override the artwork in the cache details = self.manual_set_music_artwork(cache, "album") else: # nothing in cache - start metadata retrieval local_path = "" local_path_custom = "" # get metadata from kodi db details = extend_dict( details, self.get_album_kodi_metadata(artist, album, track, disc)) if not album and details.get("title"): album = details["title"] # get artwork from songlevel path if details.get( "local_path_custom") and self._mutils.addon.getSetting( "music_art_musicfolders") == "true": details["art"] = extend_dict( details["art"], self.lookup_albumart_in_folder( details["local_path_custom"])) local_path = details["local_path_custom"] # get artwork from custom folder custom_path = None if self._mutils.addon.getSetting("music_art_custom") == "true": if sys.version_info.major == 3: custom_path = self._mutils.addon.getSetting( "music_art_custom_path") else: custom_path = self._mutils.addon.getSetting( "music_art_custom_path").decode("utf-8") local_path_custom = self.get_custom_album_path( custom_path, artist, album, disc) details["art"] = extend_dict( details["art"], self.lookup_albumart_in_folder(local_path_custom)) details["customartpath"] = local_path_custom # lookup online metadata if self._mutils.addon.getSetting("music_art_scraper") == "true": # prefer the musicbrainzid that is already in the kodi database - only perform lookup if missing mb_albumid = details.get("musicbrainzalbumid") if not mb_albumid: mb_albumid = self.get_mb_album_id(artist, album, track) adb_album = self.audiodb.get_album_id(artist, album, track) if mb_albumid: # get artwork from fanarttv if self._mutils.addon.getSetting( "music_art_scraper_fatv") == "true": details["art"] = extend_dict( details["art"], self._mutils.fanarttv.album(mb_albumid)) # get metadata from theaudiodb if self._mutils.addon.getSetting( "music_art_scraper_adb") == "true": details = extend_dict( details, self.audiodb.album_info(artist, adb_album)) # get metadata from lastfm if self._mutils.addon.getSetting( "music_art_scraper_lfm") == "true": details = extend_dict( details, self.lastfm.album_info(mb_albumid)) # metadata from musicbrainz if not details.get("year") or not details.get("genre"): details = extend_dict( details, self.mbrainz.get_albuminfo(mb_albumid)) # musicbrainz thumb as last resort if not details["art"].get("thumb"): details["art"]["thumb"] = self.mbrainz.get_albumthumb( mb_albumid) # download artwork to music folder # get artwork from custom folder # (yes again, but this time we might have an album where we didnt have that before) if custom_path and not album and details.get("title"): album = details["title"] diskpath = self.get_custom_album_path( custom_path, artist, album, disc) if diskpath: details["art"] = extend_dict( details["art"], self.lookup_albumart_in_folder(diskpath)) local_path_custom = diskpath details["customartpath"] = diskpath # download artwork to custom folder if custom_path and self._mutils.addon.getSetting( "music_art_download_custom") == "true": if local_path_custom: # allow folder creation if we enabled downloads and the folder does not exist artist_path = self.get_customfolder_path( custom_path, artist) if artist_path: local_path_custom = os.path.join( artist_path, album) else: local_path_custom = os.path.join( custom_path, artist, album) details["customartpath"] = local_path_custom details["art"] = download_artwork( local_path_custom, details["art"]) # set default details if not details.get("album") and details.get("title"): details["album"] = details["title"] if details["art"].get("thumb"): details["art"]["albumthumb"] = details["art"]["thumb"] # store results in cache and return results self._mutils.cache.set(cache_str, details) # self.write_kodidb(details) return details
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 lms(self, filename, **kwargs): ''' fake lms hook to retrieve events form spotty daemon''' method = cherrypy.request.method.upper() if method != "POST" or filename != "jsonrpc.js": raise cherrypy.HTTPError(405) input_json = cherrypy.request.json if input_json and input_json.get("params"): event = input_json["params"][1] log_msg("lms event hook called. Event: %s" % event) # check username, it might have changed spotty_user = self.__spotty.get_username() cur_user = xbmc.getInfoLabel( "Window(Home).Property(spotify-username)").decode("utf-8") if spotty_user != cur_user: log_msg("user change detected") xbmc.executebuiltin("SetProperty(spotify-cmd,__LOGOUT__,Home)") if "start" in event: log_msg("playback start requested by connect") xbmc.executebuiltin( "RunPlugin(plugin://plugin.audio.spotify/?action=play_connect)" ) elif "change" in event: log_msg("playback change requested by connect") # we ignore this as track changes are #xbmc.executebuiltin("RunPlugin(plugin://plugin.audio.spotify/?action=play_connect)") elif "stop" in event: log_msg("playback stop requested by connect") xbmc.executebuiltin("PlayerControl(Stop)") elif "volume" in event: vol_level = event[2] log_msg("volume change detected on connect player: %s" % vol_level) # ignore for now as it needs more work #xbmc.executebuiltin("SetVolume(%s,true)" % vol_level) return {"operation": "request", "result": "success"}
def archive_table(name='', weeks=''): global db day = (date.today() - timedelta(weeks=weeks)).strftime('%Y-%m-%d') log_msg('archiving table: name=' + str(name) + ', weeks=' + str(weeks) + ', day=' + str(day)) log_msg('archiving done')
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 xbmcplugin.setContent(ADDON_HANDLE, media_type) # try to get from cache first... # we use a checksum based on the options to make sure the cache is ignored when needed all_items = [] cache_str = "SkinHelper.Widgets.%s.%s" % (media_type, action) if not self.win.getProperty("widgetreload2"): # at startup we simply accept whatever is in the cache cache_checksum = None else: cache_checksum = "" for key in sorted(self.options): cache_checksum += "%s.%s" % (key, self.options[key]) cache = self.cache.get(cache_str, checksum=cache_checksum) if cache and not self.options.get("skipcache") == "true": log_msg( "MEDIATYPE: %s - ACTION: %s -- got items from cache - CHECKSUM: %s" % (media_type, action, 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 -- no cache, quering kodi api to get items - CHECKSUM: %s" % (media_type, action, 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.artutils, 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 = process_method_on_list( self.artutils.kodidb.prepare_listitem, all_items) self.cache.set(cache_str, all_items, checksum=cache_checksum) # fill that listing... xbmcplugin.addSortMethod(int(sys.argv[1]), xbmcplugin.SORT_METHOD_UNSORTED) all_items = process_method_on_list( self.artutils.kodidb.create_listitem, all_items) xbmcplugin.addDirectoryItems(ADDON_HANDLE, all_items, len(all_items)) # end directory listing xbmcplugin.endOfDirectory(handle=ADDON_HANDLE)
def run(self): log_msg("Start Spotify Connect Daemon") self.__exit = False self.daemon_active = True spotty_args = ["--lms", "localhost:52308/lms", "--player-mac", "None"] disable_discovery = False if xbmcvfs.exists("/run/libreelec/"): disable_discovery = True # avahi on libreelec conflicts with the mdns implementation of librespot xbmc.executebuiltin("SetProperty(spotify-discovery,disabled,Home)") try: try: log_msg("trying AP Port 443", xbmc.LOGNOTICE) self.__spotty_proc = self.__spotty.run_spotty( arguments=spotty_args, disable_discovery=disable_discovery, ap_port="443") except: try: log_msg("trying AP Port 80", xbmc.LOGNOTICE) self.__spotty_proc = self.__spotty.run_spotty( arguments=spotty_args, disable_discovery=disable_discovery, ap_port="80") except: log_msg("trying AP Port 4070", xbmc.LOGNOTICE) self.__spotty_proc = self.__spotty.run_spotty( arguments=spotty_args, disable_discovery=disable_discovery, ap_port="4070") while not self.__exit: line = self.__spotty_proc.stdout.readline() if self.__spotty_proc.returncode and self.__spotty_proc.returncode > 0 and not self.__exit: # daemon crashed ? restart ? log_msg("spotty stopped!", xbmc.LOGNOTICE) break xbmc.sleep(100) self.daemon_active = False log_msg("Stopped Spotify Connect Daemon") except: self.daemon_active = False log_msg("Cannot run SPOTTY, No APs available", xbmc.LOGNOTICE)
def privmsg(**kwargs): if kwargs["target"] == '#turkish': nick = kwargs["nick"] msg = kwargs["message"] log_msg(nick, msg)
if title: log_msg( "Animated Art: lookup imdbid by title and year: (%s - %s)" % (title, year), xbmc.LOGNOTICE) imdb_id = artutils.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 = ArtUtils() 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()
def get_json(jsonmethod, sort=None, filters=None, fields=None, limits=None, returntype=None, optparam=None, filtertype=None): """method to get details from the kodi json api""" kodi_json = {} kodi_json["jsonrpc"] = "2.0" kodi_json["method"] = jsonmethod kodi_json["params"] = {} if optparam: if isinstance(optparam, list): for param in optparam: kodi_json["params"][param[0]] = param[1] else: kodi_json["params"][optparam[0]] = optparam[1] kodi_json["id"] = 1 if sort: kodi_json["params"]["sort"] = sort if filters: if not filtertype: filtertype = "and" if len(filters) > 1: kodi_json["params"]["filter"] = {filtertype: filters} else: kodi_json["params"]["filter"] = filters[0] if fields: kodi_json["params"]["properties"] = fields if limits: kodi_json["params"]["limits"] = { "start": limits[0], "end": limits[1] } json_response = xbmc.executeJSONRPC(try_encode(json.dumps(kodi_json))) if sys.version_info.major == 3: json_object = json.loads(json_response) else: json_object = json.loads(json_response.decode('utf-8', 'replace')) # set the default returntype to prevent errors if "details" in jsonmethod.lower(): result = {} else: result = [] if 'result' in json_object: if returntype and returntype in json_object['result']: # returntype specified, return immediately result = json_object['result'][returntype] else: # no returntype specified, we'll have to look for it if sys.version_info.major == 3: for key, value in json_object['result'].items(): if not key == "limits" and (isinstance(value, list) or isinstance(value, dict)): result = value else: for key, value in json_object['result'].items(): if not key == "limits" and (isinstance(value, list) or isinstance(value, dict)): result = value else: log_msg(json_response) log_msg(kodi_json) return result
def monitor_lms(self): '''monitor the state of the self.lmsserver/player''' # poll the status every interval self.lmsserver.update_status() if self.exit: return # make sure that the status is not actually changing right now if not self.lmsserver.state_changing and not self.kodiplayer.is_busy: # monitor LMS player and server details if self._sl_exec and ( self.lmsserver.power == 1 or not self._temp_power_off ) and xbmc.getCondVisibility("Player.HasVideo"): # turn off lms player when kodi is playing video self.lmsserver.send_command("power 0") self._temp_power_off = True self.kodiplayer.is_playing = False log_msg("Kodi started playing video - disabled the LMS player") elif self._temp_power_off and not xbmc.getCondVisibility( "Player.HasVideo"): # turn on player again when video playback was finished self.lmsserver.send_command("power 1") self._temp_power_off = False elif self.kodiplayer.is_playing and self._prev_checksum != self.lmsserver.timestamp: # the playlist was modified self._prev_checksum = self.lmsserver.timestamp log_msg("playlist changed on lms server") self.kodiplayer.update_playlist() # self.kodiplayer.play(self.kodiplayer.playlist, startpos=self.lmsserver.cur_index) # Don't start playing again elif not self.kodiplayer.is_playing and self.lmsserver.mode == "play": # playback started log_msg("play started by lms server") self._prev_checksum = self.lmsserver.timestamp # Set Timestemp on start of playing self.kodiplayer.update_playlist( ) # Update playlist on start of playing # if not len(self.kodiplayer.playlist): # self.kodiplayer.update_playlist() self.kodiplayer.play(self.kodiplayer.playlist, startpos=self.lmsserver.cur_index) elif self.kodiplayer.is_playing: # monitor some conditions if the player is playing if self.kodiplayer.is_playing and self.lmsserver.mode == "stop": # playback stopped log_msg("stop requested by lms server") self.kodiplayer.stop() elif xbmc.getCondVisibility("Playlist.IsRandom"): # make sure that the kodi player doesnt have shuffle enabled log_msg("Playlist is randomized! Reload to unshuffle....") self.kodiplayer.playlist.unshuffle() self.kodiplayer.update_playlist() self.kodiplayer.is_playing = False # it seems that xbmc.player calls the OnPlayBackStopped function if the play function is called while already playing. self.kodiplayer.play(self.kodiplayer.playlist, startpos=self.lmsserver.cur_index) elif xbmc.getCondVisibility( "Player.Paused") and self.lmsserver.mode == "play": # playback resumed log_msg("resume requested by lms server") xbmc.executebuiltin("PlayerControl(play)") elif not xbmc.getCondVisibility( "Player.Paused") and self.lmsserver.mode == "pause": # playback paused log_msg("pause requested by lms server") self.kodiplayer.pause() elif self.kodiplayer.playlist.getposition( ) != self.lmsserver.cur_index: # other track requested self.kodiplayer.is_playing = False # it seems that xbmc.player calls the OnPlayBackStopped function if the play function is called while already playing. log_msg("other track requested by lms server") self.kodiplayer.play(self.kodiplayer.playlist, startpos=self.lmsserver.cur_index) elif self.lmsserver.status["title"] != xbmc.getInfoLabel( "MusicPlayer.Title").decode("utf-8"): # monitor if title still matches log_msg("title mismatch - updating playlist...") self.kodiplayer.update_playlist() log_msg("other track requested by lms server") self.kodiplayer.is_playing = False # it seems that xbmc.player calls the OnPlayBackStopped function if the play function is called while already playing. self.kodiplayer.play(self.kodiplayer.playlist, startpos=self.lmsserver.cur_index) elif self.lmsserver.mode == "play" and not self.lmsserver.status[ "current_title"]: # check if seeking is needed - if current_title has value, it means it's a radio stream so we ignore that # we accept a difference of max 2 seconds cur_time_lms = int(self.lmsserver.time) cur_time_kodi = self.kodiplayer.cur_time() if cur_time_kodi > 2: if cur_time_kodi != cur_time_lms and abs( cur_time_lms - cur_time_kodi ) > 2 and not xbmc.getCondVisibility("Player.Paused"): # seek started log_msg( "seek requested by lms server - kodi-time: %s - lmstime: %s" % (cur_time_kodi, cur_time_lms)) self.kodiplayer.is_busy = True self.kodiplayer.seekTime(cur_time_lms) xbmc.sleep(250) self.kodiplayer.is_busy = False
def nexttrack(self, **kwargs): '''play silence while spotify connect player is waiting for the next track''' log_msg('play silence while spotify connect player is waiting for the next track', xbmc.LOGDEBUG) return self.silence(20)
def onPlayBackPaused(self): '''Kodi event fired when playback is paused''' if self.connect_playing and not self.__is_paused: self.__sp.pause_playback() log_msg("Playback paused") self.__is_paused = True
def default(self, path): '''all other requests go here''' log_msg("Webservice: Unknown method called ! (%s)" % path, xbmc.LOGWARNING) raise cherrypy.HTTPError(404, "Unknown method called")
def onPlayBackResumed(self): '''Kodi event fired when playback is resumed after pause''' if self.connect_playing and self.__is_paused: self.__sp.start_playback() log_msg("Playback unpaused") self.__is_paused = False
def create_tables(): global db log_msg('creating tables') db.create_tables()
def run(self): '''our main loop monitoring the listitem and folderpath changes''' log_msg("ListItemMonitor - started") self.get_settings() while not self.exit: # check screensaver and OSD self.check_screensaver() self.check_osd() # do some background stuff every 30 minutes if (self.delayed_task_interval >= 1800) and not self.exit: thread.start_new_thread(self.do_background_work, ()) self.delayed_task_interval = 0 # skip if any of the artwork context menus is opened if self.win.getProperty("SkinHelper.Artwork.ManualLookup"): self.reset_win_props() self.last_listitem = "" self.listitem_details = {} self.kodimonitor.waitForAbort(3) self.delayed_task_interval += 3 # skip when modal dialogs are opened (e.g. textviewer in musicinfo dialog) elif getCondVisibility( "Window.IsActive(DialogSelect.xml) | Window.IsActive(progressdialog) | " "Window.IsActive(contextmenu) | Window.IsActive(busydialog)" ): self.kodimonitor.waitForAbort(2) self.delayed_task_interval += 2 self.last_listitem = "" # skip when container scrolling elif getCondVisibility( "Container.OnScrollNext | Container.OnScrollPrevious | Container.Scrolling" ): self.kodimonitor.waitForAbort(1) self.delayed_task_interval += 1 self.last_listitem = "" # media window is opened or widgetcontainer set - start listitem monitoring! elif getCondVisibility( "Window.IsMedia | " "!IsEmpty(Window(Home).Property(SkinHelper.WidgetContainer))" ): self.monitor_listitem() self.kodimonitor.waitForAbort(0.15) self.delayed_task_interval += 0.15 # flush any remaining window properties elif self.all_window_props: self.reset_win_props() self.win.clearProperty("SkinHelper.ContentHeader") self.win.clearProperty("contenttype") self.win.clearProperty("curlistitem") self.last_listitem = "" # other window active - do nothing else: self.kodimonitor.waitForAbort(1) self.delayed_task_interval += 1
def close(self): '''Cleanup Kodi Cpython instances on exit''' del self.win del self.addon del self.thetvdb log_msg("MainModule exited")
log_msg('rebuilding table: name=' + str(name)) day = db.get_min_full_day() db.summarize_data(name, day, compare='>=') log_msg('rebuilding done') def archive_table(name='', weeks=''): global db day = (date.today() - timedelta(weeks=weeks)).strftime('%Y-%m-%d') log_msg('archiving table: name=' + str(name) + ', weeks=' + str(weeks) + ', day=' + str(day)) log_msg('archiving done') if __name__ == "__main__": global db log_msg('Initializing ' + __file__ + '...') args = parse_args() init_globals(args) rebuild_table('hour') rebuild_table('day') #archive_table('minute', args.minute_table) #archive_table('hour', args.hour_table) #archive_table('day', args.day_table) log_msg('Done.')
def get_pvr_artwork(self, title, channel, genre="", manual_select=False, ignore_cache=False): """ collect full metadata and artwork for pvr entries parameters: title (required) channel: channel name (required) year: year or date (optional) genre: (optional) the more optional parameters are supplied, the better the search results """ details = {"art": {}} # try cache first # use searchtitle when searching cache cache_title = title.lower() cache_channel = channel.lower() searchtitle = self.get_searchtitle(cache_title, cache_channel) # original cache_str assignment cache_str = "pvr_artwork.%s.%s" % (title.lower(), channel.lower()) cache_str = "pvr_artwork.%s.%s" % (searchtitle, channel.lower()) cache = self._mutils.cache.get(cache_str) if cache and not manual_select and not ignore_cache: log_msg("get_pvr_artwork - return data from cache - %s" % cache_str) details = cache else: # no cache - start our lookup adventure log_msg("get_pvr_artwork - no data in cache - start lookup - %s" % cache_str) # workaround for recordings recordingdetails = self.lookup_local_recording(title, channel) if recordingdetails and not (channel and genre): genre = recordingdetails["genre"] channel = recordingdetails["channel"] details["pvrtitle"] = title details["pvrchannel"] = channel details["pvrgenre"] = genre details["cachestr"] = cache_str details["media_type"] = "" details["art"] = {} # filter genre unknown/other if not genre or genre.split(" / ")[0] in xbmc.getLocalizedString( 19499).split(" / "): details["genre"] = [] genre = "" log_msg("genre is unknown so ignore....") else: details["genre"] = genre.split(" / ") details["media_type"] = self.get_mediatype_from_genre(genre) searchtitle = self.get_searchtitle(title, channel) # only continue if we pass our basic checks filterstr = self.pvr_proceed_lookup(title, channel, genre, recordingdetails) proceed_lookup = False if filterstr else True if not proceed_lookup and manual_select: # warn user about active skip filter proceed_lookup = xbmcgui.Dialog().yesno( line1=self._mutils.addon.getLocalizedString(32027), line2=filterstr, heading=xbmc.getLocalizedString(750)) if proceed_lookup: # if manual lookup get the title from the user if manual_select: if sys.version_info.major == 3: searchtitle = xbmcgui.Dialog().input( xbmc.getLocalizedString(16017), searchtitle, type=xbmcgui.INPUT_ALPHANUM) else: searchtitle = xbmcgui.Dialog().input( xbmc.getLocalizedString(16017), searchtitle, type=xbmcgui.INPUT_ALPHANUM).decode("utf-8") if not searchtitle: return # if manual lookup and no mediatype, ask the user if manual_select and not details["media_type"]: yesbtn = self._mutils.addon.getLocalizedString(32042) nobtn = self._mutils.addon.getLocalizedString(32043) header = self._mutils.addon.getLocalizedString(32041) if xbmcgui.Dialog().yesno(header, header, yeslabel=yesbtn, nolabel=nobtn): details["media_type"] = "movie" else: details["media_type"] = "tvshow" # append thumb from recordingdetails if recordingdetails and recordingdetails.get("thumbnail"): details["art"]["thumb"] = recordingdetails["thumbnail"] # lookup custom path details = extend_dict( details, self.lookup_custom_path(searchtitle, title)) # lookup movie/tv library details = extend_dict( details, self.lookup_local_library(searchtitle, details["media_type"])) # do internet scraping if enabled if self._mutils.addon.getSetting("pvr_art_scraper") == "true": log_msg( "pvrart start scraping metadata for title: %s - media_type: %s" % (searchtitle, details["media_type"])) # prefer tmdb scraper tmdb_result = self._mutils.get_tmdb_details( "", "", searchtitle, "", "", details["media_type"], manual_select=manual_select, ignore_cache=manual_select) log_msg("pvrart lookup for title: %s - TMDB result: %s" % (searchtitle, tmdb_result)) if tmdb_result: details["media_type"] = tmdb_result["media_type"] details = extend_dict(details, tmdb_result) # fallback to tvdb scraper # following 3 lines added as part of "auto refresh" fix. ensure manual_select=true for TVDB lookup. No idea why this works tempmanualselect = manual_select manual_select = "true" log_msg( "DEBUG INFO: TVDB lookup: searchtitle: %s channel: %s manual_select: %s" % (searchtitle, channel, manual_select)) if (not tmdb_result or (tmdb_result and not tmdb_result.get("art")) or details["media_type"] == "tvshow"): # original code: tvdb_match = self.lookup_tvdb(searchtitle, channel, manual_select=manual_select). part of "auto refresh" fix. tvdb_match = self.lookup_tvdb( searchtitle, channel, manual_select=manual_select, tempmanualselect=tempmanualselect) log_msg( "pvrart lookup for title: %s - TVDB result: %s" % (searchtitle, tvdb_match)) if tvdb_match: # get full tvdb results and extend with tmdb if not details["media_type"]: details["media_type"] = "tvshow" details = extend_dict( details, self._mutils.thetvdb.get_series(tvdb_match)) details = extend_dict( details, self._mutils.tmdb. get_videodetails_by_externalid( tvdb_match, "tvdb_id"), ["poster", "fanart"]) # part of "auto refresh" fix - revert manual_select to original value manual_select = tempmanualselect # fanart.tv scraping - append result to existing art if details.get( "imdbnumber") and details["media_type"] == "movie": details["art"] = extend_dict( details["art"], self._mutils.fanarttv.movie(details["imdbnumber"]), ["poster", "fanart", "landscape"]) elif details.get( "tvdb_id") and details["media_type"] == "tvshow": details["art"] = extend_dict( details["art"], self._mutils.fanarttv.tvshow(details["tvdb_id"]), ["poster", "fanart", "landscape"]) # append omdb details if details.get("imdbnumber"): details = extend_dict( details, self._mutils.omdb.get_details_by_imdbid( details["imdbnumber"]), ["rating", "votes"]) # set thumbnail - prefer scrapers thumb = "" if details.get("thumbnail"): thumb = details["thumbnail"] elif details["art"].get("landscape"): thumb = details["art"]["landscape"] elif details["art"].get("fanart"): thumb = details["art"]["fanart"] elif details["art"].get("poster"): thumb = details["art"]["poster"] # use google images as last-resort fallback for thumbs - if enabled elif self._mutils.addon.getSetting( "pvr_art_google") == "true": if manual_select: google_title = searchtitle else: google_title = '%s %s' % ( searchtitle, channel.lower().split(" hd")[0]) thumb = self._mutils.google.search_image( google_title, manual_select) if thumb: details["thumbnail"] = thumb details["art"]["thumb"] = thumb # extrafanart if details["art"].get("fanarts"): for count, item in enumerate( details["art"]["fanarts"]): details["art"]["fanart.%s" % count] = item if not details["art"].get("extrafanart") and len( details["art"]["fanarts"]) > 1: details["art"]["extrafanart"] = "plugin://script.skin.helper.service/"\ "?action=extrafanart&fanarts=%s" % quote_plus(repr(details["art"]["fanarts"])) # download artwork to custom folder if self._mutils.addon.getSetting( "pvr_art_download") == "true": details["art"] = download_artwork( self.get_custom_path(searchtitle, title), details["art"]) log_msg("pvrart lookup for title: %s - final result: %s" % (searchtitle, details)) # always store result in cache # manual lookups should not expire too often if manual_select: self._mutils.cache.set(cache_str, details, expiration=timedelta(days=365)) else: self._mutils.cache.set(cache_str, details, expiration=timedelta(days=365)) return details
def rebuild_table(name=''): global db log_msg('rebuilding table: name=' + str(name)) day = db.get_min_full_day() db.summarize_data(name, day, compare='>=') log_msg('rebuilding done')
def lookup_tvdb(self, searchtitle, channel, manual_select=False, tempmanualselect=False): """helper to select a match on tvdb""" tvdb_match = None searchtitle = searchtitle.lower() tvdb_result = self._mutils.thetvdb.search_series(searchtitle, True) searchchannel = channel.lower().split("hd")[0].replace(" ", "") if " FHD" in channel: searchchannel = channel.lower().split("fhd")[0].replace(" ", "") if " HD" in channel: searchchannel = channel.lower().split("hd")[0].replace(" ", "") if " SD" in channel: searchchannel = channel.lower().split("sd")[0].replace(" ", "") match_results = [] if tvdb_result: for item in tvdb_result: item["score"] = 0 if not item["seriesName"]: continue # seriesname can be None in some conditions itemtitle = item["seriesName"].lower() if not item["network"]: continue # network can be None in some conditions network = item["network"].lower().replace(" ", "") # high score if channel name matches if network in searchchannel or searchchannel in network: item["score"] += 800 # exact match on title - very high score if searchtitle == itemtitle: item["score"] += 1000 # match title by replacing some characters if re.sub('\*|,|.\"|\'| |:|;', '', searchtitle) == re.sub('\*|,|.\"|\'| |:|;', '', itemtitle): item["score"] += 750 # add SequenceMatcher score to the results stringmatchscore = SM(None, searchtitle, itemtitle).ratio() if stringmatchscore > 0.7: item["score"] += stringmatchscore * 500 # prefer items with native language as we've searched with localized info enabled try: if item["overview"]: item["score"] += 250 except KeyError: log_msg( "pvrartwork.py - Overview Key Error in lookup_tvb: %s" % searchchannel) # prefer items with artwork if item["banner"]: item["score"] += 1 if item["score"] > 500 or manual_select: match_results.append(item) # sort our new list by score match_results = sorted(match_results, key=itemgetter("score"), reverse=True) # original code: if match_results and manual_select:. part of "auto refresh" fix. if match_results and manual_select and tempmanualselect: # show selectdialog to manually select the item listitems = [] for item in match_results: thumb = "http://thetvdb.com%s" % item["poster"] if item[ "poster"] else "" try: listitem = xbmcgui.ListItem(label=item["seriesName"], label2=item["overview"]) except KeyError: listitem = xbmcgui.ListItem(label=item["seriesName"]) log_msg( "pvrartwork.py - Overview Key Error in lookup_tvb: %s" % searchchannel) listitem.setArt({'icon': thumb}) listitems.append(listitem) dialog = DialogSelect("DialogSelect.xml", "", listing=listitems, window_title="%s - TVDB" % xbmc.getLocalizedString(283)) dialog.doModal() selected_item = dialog.result del dialog if selected_item != -1: tvdb_match = match_results[selected_item]["id"] else: match_results = [] if not tvdb_match and match_results: # just grab the first item as best match tvdb_match = match_results[0]["id"] return tvdb_match
def stop(self): '''called when the thread has to stop working''' log_msg("ListItemMonitor - stop called") self.exit = True self.event.set()
import os import sys sys.path.append(os.path.join(os.path.dirname(__file__), "resources", "lib")) from main_service import MainService from httpproxy import ProxyRunner from utils import log_msg import xbmc kodimonitor = xbmc.Monitor() # start the webservice (which hosts our silenced audio tracks) proxy_runner = ProxyRunner(host='127.0.0.1', allow_ranges=True) proxy_runner.start() webport = proxy_runner.get_port() log_msg('started webproxy at port {0}'.format(webport)) # run the main background service main = MainService(kodimonitor=kodimonitor, webport=webport) main.start() # keep thread alive and send signal when we need to exit while not kodimonitor.waitForAbort(10): pass # stop requested log_msg("Abort requested !", xbmc.LOGNOTICE) main.stop() proxy_runner.stop() log_msg("Stopped", xbmc.LOGNOTICE)
def close(self): '''Cleanup Kodi Cpython instances''' self.metadatautils.close() del self.addon del self.win log_msg("MainModule exited")
def create_listitem(item, as_tuple=True, offscreen=True): '''helper to create a kodi listitem from kodi compatible dict with mediainfo''' try: if KODI_VERSION > 17: liz = xbmcgui.ListItem(label=item.get("label", ""), label2=item.get("label2", ""), path=item['file'], offscreen=offscreen) else: liz = xbmcgui.ListItem(label=item.get("label", ""), label2=item.get("label2", ""), path=item['file']) # only set isPlayable prop if really needed if item.get("isFolder", False): liz.setProperty('IsPlayable', 'false') elif "plugin://script.skin.helper" not in item['file']: liz.setProperty('IsPlayable', 'true') nodetype = "Video" if item["type"] in ["song", "album", "artist"]: nodetype = "Music" # extra properties for key, value in item["extraproperties"].iteritems(): liz.setProperty(key, value) # video infolabels if nodetype == "Video": infolabels = { "title": item.get("title"), "size": item.get("size"), "genre": item.get("genre"), "year": item.get("year"), "top250": item.get("top250"), "tracknumber": item.get("tracknumber"), "rating": item.get("rating"), "playcount": item.get("playcount"), "overlay": item.get("overlay"), "cast": item.get("cast"), "castandrole": item.get("castandrole"), "director": item.get("director"), "mpaa": item.get("mpaa"), "plot": item.get("plot"), "plotoutline": item.get("plotoutline"), "originaltitle": item.get("originaltitle"), "sorttitle": item.get("sorttitle"), "duration": item.get("duration"), "studio": item.get("studio"), "tagline": item.get("tagline"), "writer": item.get("writer"), "tvshowtitle": item.get("tvshowtitle"), "premiered": item.get("premiered"), "status": item.get("status"), "code": item.get("imdbnumber"), "imdbnumber": item.get("imdbnumber"), "aired": item.get("aired"), "credits": item.get("credits"), "album": item.get("album"), "artist": item.get("artist"), "votes": item.get("votes"), "trailer": item.get("trailer"), "progress": item.get('progresspercentage') } if item["type"] == "episode": infolabels["season"] = item["season"] infolabels["episode"] = item["episode"] # streamdetails if item.get("streamdetails"): liz.addStreamInfo("video", item["streamdetails"].get("video", {})) liz.addStreamInfo("audio", item["streamdetails"].get("audio", {})) liz.addStreamInfo( "subtitle", item["streamdetails"].get("subtitle", {})) if "dateadded" in item: infolabels["dateadded"] = item["dateadded"] if "date" in item: infolabels["date"] = item["date"] # music infolabels else: infolabels = { "title": item.get("title"), "size": item.get("size"), "genre": item.get("genre"), "year": item.get("year"), "tracknumber": item.get("track"), "album": item.get("album"), "artist": " / ".join(item.get('artist')), "rating": str(item.get("rating", 0)), "lyrics": item.get("lyrics"), "playcount": item.get("playcount") } if "date" in item: infolabels["date"] = item["date"] if "duration" in item: infolabels["duration"] = item["duration"] if "lastplayed" in item: infolabels["lastplayed"] = item["lastplayed"] # setting the dbtype and dbid is supported from kodi krypton and up if KODI_VERSION > 16 and item["type"] not in [ "recording", "channel", "favourite" ]: infolabels["mediatype"] = item["type"] # setting the dbid on music items is not supported ? if nodetype == "Video" and "DBID" in item["extraproperties"]: infolabels["dbid"] = item["extraproperties"]["DBID"] if "lastplayed" in item: infolabels["lastplayed"] = item["lastplayed"] # assign the infolabels liz.setInfo(type=nodetype, infoLabels=infolabels) # artwork liz.setArt(item.get("art", {})) if "icon" in item: liz.setIconImage(item['icon']) if "thumbnail" in item: liz.setThumbnailImage(item['thumbnail']) # contextmenu if item["type"] in ["episode", "season" ] and "season" in item and "tvshowid" in item: # add series and season level to widgets if "contextmenu" not in item: item["contextmenu"] = [] item["contextmenu"] += [ (xbmc.getLocalizedString(20364), "ActivateWindow(Video,videodb://tvshows/titles/%s/,return)" % (item["tvshowid"])), (xbmc.getLocalizedString(20373), "ActivateWindow(Video,videodb://tvshows/titles/%s/%s/,return)" % (item["tvshowid"], item["season"])) ] if "contextmenu" in item: liz.addContextMenuItems(item["contextmenu"]) if as_tuple: return (item["file"], liz, item.get("isFolder", False)) else: return liz except Exception as exc: log_exception(__name__, exc) log_msg(item) return None
def switch_user(self, restart_daemon=False): '''called whenever we switch to a different user/credentials''' log_msg("login credentials changed") if self.renew_token(): xbmc.executebuiltin("Container.Refresh")
def get_artist_metadata(self, artist, album, track, ignore_cache=False, flush_cache=False, manual=False): """collect artist metadata for given artist""" details = {"art": {}} cache_str = "music_artwork.artist.%s" % artist.lower() # retrieve details from cache cache = self._mutils.cache.get(cache_str) if not cache and flush_cache: # nothing to do - just return empty results return details elif cache and flush_cache: # only update kodi metadata for updated counts etc details = extend_dict(self.get_artist_kodi_metadata(artist), cache) elif cache and not ignore_cache: # we have a valid cache - return that details = cache elif cache and manual: # user wants to manually override the artwork in the cache details = self.manual_set_music_artwork(cache, "artist") else: # nothing in cache - start metadata retrieval log_msg( "get_artist_metadata --> artist: %s - album: %s - track: %s" % (artist, album, track)) details["cachestr"] = cache_str local_path = "" local_path_custom = "" # get metadata from kodi db details = extend_dict(details, self.get_artist_kodi_metadata(artist)) # get artwork from songlevel path if details.get("diskpath") and self._mutils.addon.getSetting( "music_art_musicfolders") == "true": details["art"] = extend_dict( details["art"], self.lookup_artistart_in_folder(details["diskpath"])) local_path = details["diskpath"] # get artwork from custom folder custom_path = None if self._mutils.addon.getSetting("music_art_custom") == "true": if sys.version_info.major == 3: custom_path = self._mutils.addon.getSetting( "music_art_custom_path") else: custom_path = self._mutils.addon.getSetting( "music_art_custom_path").decode("utf-8") local_path_custom = self.get_customfolder_path( custom_path, artist) log_msg("custom path on disk for artist: %s --> %s" % (artist, local_path_custom)) details["art"] = extend_dict( details["art"], self.lookup_artistart_in_folder(local_path_custom)) details["customartpath"] = local_path_custom # lookup online metadata if self._mutils.addon.getSetting("music_art_scraper") == "true": if not album and not track: album = details.get("ref_album") track = details.get("ref_track") # prefer the musicbrainzid that is already in the kodi database - only perform lookup if missing mb_artistid = details.get( "musicbrainzartistid", self.get_mb_artist_id(artist, album, track)) details["musicbrainzartistid"] = mb_artistid if mb_artistid: # get artwork from fanarttv if self._mutils.addon.getSetting( "music_art_scraper_fatv") == "true": details["art"] = extend_dict( details["art"], self._mutils.fanarttv.artist(mb_artistid)) # get metadata from theaudiodb if self._mutils.addon.getSetting( "music_art_scraper_adb") == "true": details = extend_dict(details, self.audiodb.artist_info(artist)) # get metadata from lastfm if self._mutils.addon.getSetting( "music_art_scraper_lfm") == "true": details = extend_dict( details, self.lastfm.artist_info(mb_artistid)) # download artwork to music folder if local_path and self._mutils.addon.getSetting( "music_art_download") == "true": details["art"] = download_artwork( local_path, details["art"]) # download artwork to custom folder if local_path_custom and self._mutils.addon.getSetting( "music_art_download_custom") == "true": details["art"] = download_artwork( local_path_custom, details["art"]) # fix extrafanart if details["art"].get("fanarts"): for count, item in enumerate( details["art"]["fanarts"]): details["art"]["fanart.%s" % count] = item if not details["art"].get("extrafanart") and len( details["art"]["fanarts"]) > 1: details["art"]["extrafanart"] = "plugin://script.skin.helper.service/"\ "?action=extrafanart&fanarts=%s" % quote_plus(repr(details["art"]["fanarts"])) # multi-image path for all images for each arttype for arttype in ["banners", "clearlogos", "thumbs"]: art = details["art"].get(arttype, []) if len(art) > 1: # use the extrafanart plugin entry to display multi images details["art"][arttype] = "plugin://script.skin.helper.service/"\ "?action=extrafanart&fanarts=%s" % quote_plus(repr(art)) # set default details if not details.get("artist"): details["artist"] = artist if details["art"].get("thumb"): details["art"]["artistthumb"] = details["art"]["thumb"] # always store results in cache and return results self._mutils.cache.set(cache_str, details) return details
def build_wallimages(self, win_prop, wall_images, art_type): '''build wall images with PIL module for the collection''' return_images = [] if not SUPPORTS_PIL: log_msg( "Wall backgrounds disabled - PIL is not supported on this device!", xbmc.LOGWARNING) return [] log_msg( "Building Wall background for %s - this might take a while..." % win_prop) if art_type == "thumb": # square images img_columns = 11 img_rows = 7 img_width = 260 img_height = 260 elif art_type == "poster": # poster images img_columns = 15 img_rows = 5 img_width = 128 img_height = 216 else: # landscaped images img_columns = 8 img_rows = 8 img_width = 240 img_height = 135 size = img_width, img_height # build the wall images images_required = img_columns * img_rows if wall_images: # duplicate images if we don't have enough while len(wall_images) < images_required: wall_images += wall_images for count in range(self.max_wallimages): if self.exit: return [] random.shuffle(wall_images) img_canvas = Image.new( "RGBA", (img_width * img_columns, img_height * img_rows)) img_count = 0 for x in range(img_rows): for y in range(img_columns): file = xbmcvfs.File(wall_images[img_count]) try: img_obj = io.BytesIO(bytearray(file.readBytes())) img = Image.open(img_obj) img = img.resize(size) img_canvas.paste(img, (y * img_width, x * img_height)) del img except Exception: log_msg( "Invalid image file found! --> %s" % wall_images[img_count], xbmc.LOGWARNING) finally: file.close() img_count += 1 # save the files.. out_file = "%s%s.%s.jpg" % (WALLS_PATH, win_prop, count) out_file = xbmc.translatePath(out_file).decode("utf-8") if xbmcvfs.exists(out_file): xbmcvfs.delete(out_file) xbmc.sleep(500) img_canvas.save(out_file, "JPEG") out_file_bw = "%s%s_BW.%s.jpg" % (WALLS_PATH, win_prop, count) out_file_bw = xbmc.translatePath(out_file_bw).decode("utf-8") if xbmcvfs.exists(out_file_bw): xbmcvfs.delete(out_file_bw) xbmc.sleep(500) img_canvas = img_canvas.convert("L") img_canvas.save(out_file_bw, "JPEG") del img_canvas # add our images to the dict return_images.append({"wall": out_file, "wallbw": out_file_bw}) log_msg("Building Wall background %s DONE" % win_prop) return return_images
debug_flag = False for o, a in opts: if o in ("-s", "--server-name"): server_name = a else: if o in ("-d", "--debug"): debug_flag = True else: if o in ("--remove-after"): remove_after = True else: assert False, "unhandled option" server_log_id = "Node:" + server_name utils.log_msg(" ".join([server_log_id, "Starting build-one"]), "INFO", config.print_to) pyrax.set_setting("identity_type", "rackspace") pyrax.set_setting("region", config.cloud_region) try: pyrax.set_credentials(config.cloud_user, config.cloud_api_key, region=config.cloud_region) except pyrax.exc.AuthenticationFailed: utils.log_msg( " ".join([server_log_id, "Pyrax auth failed using", config.cloud_user]), "ERROR", config.print_to) utils.log_msg( " ".join([server_log_id, "Authenticated using", config.cloud_user]), "INFO", config.print_to)