def onNotification(self, sender, method, data): # pylint: disable=invalid-name ''' Handler for notifications ''' log(2, '[Notification] sender={sender}, method={method}, data={data}', sender=sender, method=method, data=to_unicode(data)) if method.endswith('source_container'): from json import loads self._container = loads(data).get('container') return if not sender.startswith('upnextprovider'): return if not method.endswith('plugin.video.vrt.nu_play_action'): return from json import loads hexdata = loads(data) if not hexdata: return from binascii import unhexlify data = loads(unhexlify(hexdata[0])) log(2, '[Up Next notification] sender={sender}, method={method}, data={data}', sender=sender, method=method, data=to_unicode(data)) jsonrpc(method='Player.Open', params=dict(item=dict( file='plugin://plugin.video.vrt.nu/play/whatson/%s' % data.get('whatson_id'))))
def onNotification(self, sender, method, data=None): # pylint: disable=invalid-name """Handler for Kodi events and data transfer from addons""" if not self.state or self.state.is_disabled(): return sender = statichelper.to_unicode(sender) method = statichelper.to_unicode(method) data = statichelper.to_unicode(data) if data else '' self.log(' - '.join([sender, method, data])) handler = UpNextMonitor.EVENTS_MAP.get(method) if handler: handler(self, sender=sender, data=data)
def get_setting(setting_id, default=None): ''' Get an add-on setting ''' from xbmcaddon import Addon value = to_unicode(Addon().getSetting(setting_id)) if value == '' and default is not None: return default return value
def get_userdata_path(): ''' Cache and return the profile's userdata path ''' if not hasattr(get_userdata_path, 'cached'): from xbmcaddon import Addon get_userdata_path.cached = to_unicode( xbmc.translatePath(Addon().getAddonInfo('profile'))) return get_userdata_path.cached
def unfollow(program, title): ''' The API interface to unfollow a program used by the context menu ''' move_down = bool(plugin.args.get('move_down')) from favorites import Favorites Favorites().unfollow(program=program, title=to_unicode(unquote_plus(from_unicode(title))), move_down=move_down)
def unwatchlater(uuid, title, url): ''' The API interface to unwatch an episode used by the context menu ''' from resumepoints import ResumePoints ResumePoints().unwatchlater(uuid=uuid, title=to_unicode( unquote_plus(from_unicode(title))), url=url)
def log(msg, name=__name__, level=LOGINFO): """Log information to the Kodi log""" # Log everything if LOG_ENABLE_SETTING == constants.LOG_ENABLE_DEBUG: log_enable = level != LOGNONE # Only log important messages elif LOG_ENABLE_SETTING == constants.LOG_ENABLE_INFO: log_enable = LOGDEBUG < level < LOGNONE # Log nothing else: log_enable = False if not log_enable: return # Force minimum required log level to display in Kodi event log if level < MIN_LOG_LEVEL: # pylint: disable=consider-using-max-builtin level = MIN_LOG_LEVEL # Convert to unicode for string formatting with Unicode literal msg = statichelper.to_unicode(msg) msg = '[{0}] {1} -> {2}'.format(get_addon_id(), name, msg) # Convert back for older Kodi versions xbmc.log(statichelper.from_unicode(msg), level=level)
def get_search_string(): ''' Ask the user for a search string ''' search_string = None keyboard = xbmc.Keyboard('', localize(30134)) keyboard.doModal() if keyboard.isConfirmed(): search_string = to_unicode(keyboard.getText()) return search_string
def get_setting(key, default=None): """Get an add-on setting as string""" # We use Addon() here to ensure changes in settings are reflected instantly try: value = to_unicode(Addon().getSetting(key)) except RuntimeError: # Occurs when the add-on is disabled return default if value == '' and default is not None: return default return value
def getPlayingFile(self): # pylint: disable=invalid-name # Use current stored value if playing forced if self.state.forced('playing') or self.state.forced('playing_file'): actual = self.state.playing_file # Use inbuilt method to store actual value if playing not forced else: actual = getattr(xbmc.Player, 'getPlayingFile')(self) actual = statichelper.to_unicode(actual) self.state.playing_file = actual # Return actual value or forced value if forced return self.state.playing_file
def push_upnext(self, info): ''' Push episode info to Up Next service add-on''' if has_addon('service.upnext') and get_setting('useupnext', 'true') == 'true': next_info = self._apihelper.get_upnext(info) if next_info: from binascii import hexlify from json import dumps data = [to_unicode(hexlify(dumps(next_info).encode()))] sender = '%s.SIGNAL' % addon_id() notify(sender=sender, message='upnext_data', data=data)
def get_media_type(self): # Use current stored value if playing forced if self.state.forced('playing') or self.state.forced('media_type'): actual = self.state.media_type # Use inbuilt method to store actual value if playing not forced else: actual = self.getVideoInfoTag().getMediaType() actual = actual if actual else 'unknowntype' actual = statichelper.to_unicode(actual) self.state.media_type = actual # Return actual value or forced value if forced return self.state.media_type
def decode_data(encoded): """Decode data coming from a notification event""" encoding = 'base64' from binascii import Error, unhexlify try: json_data = unhexlify(encoded) except (TypeError, Error): from base64 import b64decode json_data = b64decode(encoded) else: encoding = 'hex' # NOTE: With Python 3.5 and older json.loads() does not support bytes or bytearray, so we convert to unicode return json.loads(to_unicode(json_data)), encoding
def log(msg, level=0): ''' A reimplementation of the xbmc log() function ''' color1 = '\033[32;1m' color2 = '\033[32;0m' name = LOGLEVELS[level] if level in (4, 5, 6, 7): color1 = '\033[31;1m' if level in (6, 7): raise Exception(msg) elif level in (2, 3): color1 = '\033[33;1m' elif level == 0: color2 = '\033[30;1m' print('{color1}{name}: {color2}{msg}\033[39;0m'.format( name=name, color1=color1, color2=color2, msg=to_unicode(msg)))
def get_setting(key, default='', echo=True): """Get an addon setting as string""" value = default # We use Addon() here to ensure changes in settings are reflected instantly try: value = xbmcaddon.Addon(constants.ADDON_ID).getSetting(key) value = statichelper.to_unicode(value) # Occurs when the addon is disabled except RuntimeError: value = default if echo: log('{0}: {1}'.format(key, value), 'Settings', level=LOGDEBUG) return value
def onAVStarted(self): # pylint: disable=invalid-name ''' called when Kodi has a video or audiostream ''' log(2, '[PlayerInfo] %d onAVStarted' % self._id) self._stop.clear() self._last_pos = 0 self._total = self.getTotalTime() self._path = getInfoLabel('Player.Filenameandpath') tag = self.getVideoInfoTag() self._info(dict( program=to_unicode(tag.getTVShowTitle()), playcount=tag.getPlayCount(), rating=tag.getRating(), path=self._path, runtime=self._total, )) Thread(target=self.stream_position, name='StreamPosition').start()
def decode_data(encoded_data, compat_mode=True): """Decode JSON data coming from a notification event""" decoded_json = None decoded_data = None encoding = None # Compatibility with Addon Signals which wraps serialised data in square # brackets to generate an array/list if compat_mode: try: encoded_data = json.loads(encoded_data)[0] except (IndexError, TypeError, ValueError): encoded_data = None if encoded_data: decode_methods = { 'hex': binascii.unhexlify, 'base64': base64.b64decode } for encoding, decode_method in decode_methods.items(): try: decoded_json = decode_method(encoded_data) break except (TypeError, binascii.Error): pass else: encoding = None if decoded_json: try: # NOTE: With Python 3.5 and older json.loads() does not support # bytes or bytearray, so we convert to unicode decoded_json = statichelper.to_unicode(decoded_json) decoded_data = json.loads(decoded_json) except (TypeError, ValueError): pass return decoded_data, encoding
def get_properties(self, api_data): ''' Get properties from single item json api data ''' properties = dict() # Only fill in properties when using VRT NU resumepoints because setting resumetime/totaltime breaks standard Kodi watched status if self._resumepoints.is_activated(): assetpath = self.get_assetpath(api_data) if assetpath: # We need to ensure forward slashes are quoted program_title = statichelper.to_unicode( quote_plus( statichelper.from_unicode(api_data.get('program')))) assetuuid = self._resumepoints.assetpath_to_uuid(assetpath) url = statichelper.reformat_url(api_data.get('url', ''), 'medium') properties.update(assetuuid=assetuuid, url=url, title=program_title) position = self._resumepoints.get_position(assetuuid) total = self._resumepoints.get_total(assetuuid) if position and total and SECONDS_MARGIN < position < total - SECONDS_MARGIN: properties['resumetime'] = position log(2, '[Metadata] manual resumetime set to %d' % position) duration = self.get_duration(api_data) if duration: properties['totaltime'] = duration episode = self.get_episode(api_data) season = self.get_season(api_data) if episode and season: properties['episodeno'] = 's%se%s' % (season, episode) year = self.get_year(api_data) if year: properties['year'] = year return properties
def get_addon_info(key): """Return addon information""" return statichelper.to_unicode(ADDON.getAddonInfo(key))
def get_property(key, window_id=10000): """Get a Window property""" return to_unicode(Window(window_id).getProperty(key))
def get_addon_info(key): """Return add-on information""" return to_unicode(ADDON.getAddonInfo(key))
def get_context_menu(self, api_data, program, cache_file): ''' Get context menu ''' from addon import plugin favorite_marker = '' watchlater_marker = '' context_menu = [] # WATCH LATER if self._resumepoints.is_activated(): assetpath = None # VRT NU Search API if api_data.get('type') == 'episode': program_title = api_data.get('program') assetpath = api_data.get('assetPath') # VRT NU Schedule API (some are missing vrt.whatson-id) elif api_data.get('vrt.whatson-id') or api_data.get('startTime'): program_title = api_data.get('title') assetpath = api_data.get('assetPath') if assetpath is not None: # We need to ensure forward slashes are quoted program_title = statichelper.to_unicode( quote_plus(statichelper.from_unicode(program_title))) url = statichelper.url_to_episode(api_data.get('url', '')) assetuuid = self._resumepoints.assetpath_to_uuid(assetpath) if self._resumepoints.is_watchlater(assetuuid): extras = dict() # If we are in a watchlater menu, move cursor down before removing a favorite if plugin.path.startswith('/resumepoints/watchlater'): extras = dict(move_down=True) # Unwatch context menu context_menu.append( (statichelper.capitalize(localize(30402)), 'RunPlugin(%s)' % url_for('unwatchlater', uuid=assetuuid, title=program_title, url=url, **extras))) watchlater_marker = '[COLOR yellow]ᶫ[/COLOR]' else: # Watch context menu context_menu.append( (statichelper.capitalize(localize(30401)), 'RunPlugin(%s)' % url_for('watchlater', uuid=assetuuid, title=program_title, url=url))) # FOLLOW PROGRAM if self._favorites.is_activated(): # VRT NU Search API if api_data.get('type') == 'episode': program_title = api_data.get('program') program_type = api_data.get('programType') follow_suffix = localize( 30410) if program_type != 'oneoff' else '' # program follow_enabled = True # VRT NU Suggest API elif api_data.get('type') == 'program': program_title = api_data.get('title') follow_suffix = '' follow_enabled = True # VRT NU Schedule API (some are missing vrt.whatson-id) elif api_data.get('vrt.whatson-id') or api_data.get('startTime'): program_title = api_data.get('title') follow_suffix = localize(30410) # program follow_enabled = bool(api_data.get('url')) if follow_enabled: program_title = statichelper.to_unicode( quote_plus(statichelper.from_unicode(program_title)) ) # We need to ensure forward slashes are quoted if self._favorites.is_favorite(program): extras = dict() # If we are in a favorites menu, move cursor down before removing a favorite if plugin.path.startswith('/favorites'): extras = dict(move_down=True) context_menu.append(( localize(30412, title=follow_suffix), # Unfollow 'RunPlugin(%s)' % url_for('unfollow', program=program, title=program_title, **extras))) favorite_marker = '[COLOR yellow]ᵛ[/COLOR]' else: context_menu.append(( localize(30411, title=follow_suffix), # Follow 'RunPlugin(%s)' % url_for( 'follow', program=program, title=program_title))) # GO TO PROGRAM if api_data.get('programType') != 'oneoff': if plugin.path.startswith( ('/favorites/offline', '/favorites/recent', '/offline', '/recent', '/resumepoints/continue', '/resumepoints/watchlater', '/tvguide')): context_menu.append(( localize(30417), # Go to program 'Container.Update(%s)' % url_for('programs', program=program, season='allseasons'))) # REFRESH MENU context_menu.append(( localize(30413), # Refresh menu 'RunPlugin(%s)' % url_for('delete_cache', cache_file=cache_file))) return context_menu, favorite_marker, watchlater_marker
def follow(program, title): ''' The API interface to follow a program used by the context menu ''' from favorites import Favorites Favorites().follow(program=program, title=to_unicode(unquote_plus(from_unicode(title))))
def get_property(key, window_id=constants.WINDOW_HOME): """Get a Window property""" return statichelper.to_unicode(xbmcgui.Window(window_id).getProperty(key))
def get_addon_info(key): ''' Return add-on information ''' return to_unicode(ADDON.getAddonInfo(key))
def get_property(key, window_id=10000): ''' Get a Window property ''' return to_unicode(Window(window_id).getProperty(key))
def addon_name(): ''' Cache and return VRT NU Add-on name ''' if not hasattr(addon_name, 'cached'): from xbmcaddon import Addon addon_name.cached = to_unicode(Addon().getAddonInfo('name')) return addon_name.cached
def addon_fanart(): ''' Cache and return VRT NU Add-on fanart ''' if not hasattr(addon_fanart, 'cached'): from xbmcaddon import Addon addon_fanart.cached = to_unicode(Addon().getAddonInfo('fanart')) return addon_fanart.cached
def addon_id(): ''' Cache and return VRT NU Add-on ID ''' if not hasattr(addon_id, 'cached'): from xbmcaddon import Addon addon_id.cached = to_unicode(Addon().getAddonInfo('id')) return addon_id.cached