def update_progress_control(self, timeout, wait): if self.progress_control is None: return self.current_progress_percent -= 100 * wait / timeout self.progress_control.setPercent(self.current_progress_percent) # pylint: disable=no-member,useless-suppression self.setProperty('remaining', from_unicode('%02d' % ceil((timeout / 1000) * (self.current_progress_percent / 100)))) self.setProperty('endtime', from_unicode(localize_time(datetime.now() + timedelta(seconds=50 * 60))))
def update_progress(self, remaining): # Run time and end time for next episode runtime = utils.get_int(self.item, 'runtime', 0) if runtime: runtime = datetime.timedelta(seconds=runtime) endtime = datetime.datetime.now() + runtime endtime = statichelper.from_unicode(utils.localize_time(endtime)) self.setProperty('endtime', endtime) # Remaining time countdown for current episode remaining_str = '{0:02.0f}'.format(remaining) self.log(remaining_str) self.setProperty( 'remaining', statichelper.from_unicode(remaining_str) ) if not self.progress_control: return # Set total countdown time on initial progress update if remaining and self.countdown_total_time is None: self.countdown_total_time = remaining # Calculate countdown progress on subsequent updates elif remaining: percent = 100 * remaining / self.countdown_total_time self.current_progress_percent = min(100, max(0, percent)) self.update_progress_control()
def _get_login_json(self): ''' Get login json ''' from json import load payload = dict( loginID=from_unicode(get_setting('username')), password=from_unicode(get_setting('password')), sessionExpiration='-1', APIKey=self._API_KEY, targetEnv='jssdk', ) data = urlencode(payload).encode() log(2, 'URL post: {url}', url=unquote(self._LOGIN_URL)) req = Request(self._LOGIN_URL, data=data) login_json = load(urlopen(req)) return login_json
def log_error(message, **kwargs): ''' Log error messages to Kodi ''' if kwargs: from string import Formatter message = Formatter().vformat(message, (), SafeDict(**kwargs)) message = '[{addon}] {message}'.format(addon=addon_id(), message=message) xbmc.log(from_unicode(message), 4)
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 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 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 _get_new_xvrttoken(self, login_json, token_variant=None): ''' Get new X-VRT-Token from VRT NU website ''' token = None login_token = login_json.get('sessionInfo', dict()).get('login_token') if login_token: from json import dumps login_cookie = 'glt_%s=%s' % (self._API_KEY, login_token) payload = dict( uid=login_json.get('UID'), uidsig=login_json.get('UIDSignature'), ts=login_json.get('signatureTimestamp'), email=from_unicode(get_setting('username')), ) data = dumps(payload).encode() headers = { 'Content-Type': 'application/json', 'Cookie': login_cookie } log(2, 'URL post: {url}', url=unquote(self._TOKEN_GATEWAY_URL)) req = Request(self._TOKEN_GATEWAY_URL, data=data, headers=headers) try: # Python 3 setcookie_header = urlopen(req).info().get('Set-Cookie') except AttributeError: # Python 2 setcookie_header = urlopen(req).info().getheader('Set-Cookie') xvrttoken = TokenResolver._create_token_dictionary( setcookie_header) if token_variant == 'roaming': xvrttoken = self._get_roaming_xvrttoken(xvrttoken) if xvrttoken is not None: token = xvrttoken.get('X-VRT-Token') self._set_cached_token(xvrttoken, token_variant) notification(message=localize(30952)) # Login succeeded. return token
def update_progress_control(self, remaining=None, runtime=None): self.current_progress_percent = self.current_progress_percent - self.progress_step_size try: self.progress_control = self.getControl(3014) except RuntimeError: # Occurs when skin does not include progress control pass else: self.progress_control.setPercent(self.current_progress_percent) # pylint: disable=no-member,useless-suppression if remaining: self.setProperty('remaining', from_unicode('%02d' % remaining)) if runtime: self.setProperty( 'endtime', from_unicode( localize_time(datetime.now() + timedelta(seconds=runtime))))
def log(msg, name=None, level=1): """Log information to the Kodi log""" log_level = get_setting_int('logLevel', level) debug_logging = get_global_setting('debug.showloginfo') set_property('logLevel', log_level) if not debug_logging and log_level < level: return level = LOGDEBUG if debug_logging else LOGNOTICE xlog('[%s] %s -> %s' % (addon_id(), name, from_unicode(msg)), level=level)
def log(level=1, message='', **kwargs): ''' Log info messages to Kodi ''' debug_logging = get_global_setting( 'debug.showloginfo') # Returns a boolean max_log_level = int(get_setting('max_log_level', 0)) if not debug_logging and not (level <= max_log_level and max_log_level != 0): return if kwargs: from string import Formatter message = Formatter().vformat(message, (), SafeDict(**kwargs)) message = '[{addon}] {message}'.format(addon=addon_id(), message=message) xbmc.log(from_unicode(message), level % 3 if debug_logging else 2)
def log(msg, name=None, level=1): """Log information to the Kodi log""" log_level = get_setting_int('logLevel', level) debug_logging = get_global_setting('debug.showloginfo') set_property('logLevel', log_level) if not debug_logging and log_level < level: return if debug_logging: level = LOGDEBUG elif get_kodi_version() >= 19: level = LOGINFO else: level = LOGINFO + 1 xlog('[%s] %s -> %s' % (addon_id(), name, from_unicode(msg)), level=level)
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 set_property(key, value, window_id=10000): """Set a Window property""" return Window(window_id).setProperty(key, from_unicode(str(value)))
def get_episodes(self, program=None, season=None, episodes=None, category=None, feature=None, programtype=None, keywords=None, whatson_id=None, video_id=None, video_url=None, page=None, use_favorites=False, variety=None, cache_file=None): ''' Get episodes or season data from VRT NU Search API ''' # Contruct params if page: page = statichelper.realpage(page) all_items = False params = { 'from': ((page - 1) * 50) + 1, 'i': 'video', 'size': 50, } elif variety == 'single': all_items = False params = { 'i': 'video', 'size': '1', } else: all_items = True params = { 'i': 'video', 'size': '300', } if variety: season = 'allseasons' if variety == 'offline': from datetime import datetime import dateutil.tz params['facets[assetOffTime]'] = datetime.now(dateutil.tz.gettz('Europe/Brussels')).strftime('%Y-%m-%d') if variety == 'oneoff': params['facets[programType]'] = 'oneoff' if variety == 'watchlater': self._resumepoints.refresh(ttl=5 * 60) episode_urls = self._resumepoints.watchlater_urls() params['facets[url]'] = '[%s]' % (','.join(episode_urls)) if variety == 'continue': self._resumepoints.refresh(ttl=5 * 60) episode_urls = self._resumepoints.resumepoints_urls() params['facets[url]'] = '[%s]' % (','.join(episode_urls)) if use_favorites: program_urls = [statichelper.program_to_url(p, 'medium') for p in self._favorites.programs()] params['facets[programUrl]'] = '[%s]' % (','.join(program_urls)) elif variety in ('offline', 'recent'): channel_filter = [channel.get('name') for channel in CHANNELS if get_setting(channel.get('name'), 'true') == 'true'] params['facets[programBrands]'] = '[%s]' % (','.join(channel_filter)) if program: params['facets[programUrl]'] = statichelper.program_to_url(program, 'medium') if season and season != 'allseasons': params['facets[seasonTitle]'] = season if episodes: params['facets[episodeNumber]'] = '[%s]' % (','.join(str(episode) for episode in episodes)) if category: params['facets[categories]'] = category if feature: params['facets[programTags.title]'] = feature if programtype: params['facets[programType]'] = programtype if keywords: if not season: season = 'allseasons' params['q'] = quote_plus(statichelper.from_unicode(keywords)) params['highlight'] = 'true' if whatson_id: params['facets[whatsonId]'] = whatson_id if video_id: params['facets[videoId]'] = video_id if video_url: params['facets[url]'] = video_url # Construct VRT NU Search API Url and get api data querystring = '&'.join('{}={}'.format(key, value) for key, value in list(params.items())) search_url = self._VRTNU_SEARCH_URL + '?' + querystring.replace(' ', '%20') # Only encode spaces to minimize url length from json import load if cache_file: # Get api data from cache if it is fresh search_json = get_cache(cache_file, ttl=60 * 60) if not search_json: log(2, 'URL get: {url}', url=unquote(search_url)) req = Request(search_url) try: search_json = load(urlopen(req)) except (TypeError, ValueError): # No JSON object could be decoded return [] except HTTPError as exc: url_length = len(req.get_selector()) if exc.code == 413 and url_length > 8192: ok_dialog(heading='HTTP Error 413', message=localize(30967)) log_error('HTTP Error 413: Exceeded maximum url length: ' 'VRT Search API url has a length of {length} characters.', length=url_length) return [] if exc.code == 400 and 7600 <= url_length <= 8192: ok_dialog(heading='HTTP Error 400', message=localize(30967)) log_error('HTTP Error 400: Probably exceeded maximum url length: ' 'VRT Search API url has a length of {length} characters.', length=url_length) return [] raise update_cache(cache_file, search_json) else: log(2, 'URL get: {url}', url=unquote(search_url)) search_json = load(urlopen(search_url)) # Check for multiple seasons seasons = None if 'facets[seasonTitle]' not in unquote(search_url): facets = search_json.get('facets', dict()).get('facets') seasons = next((f.get('buckets', []) for f in facets if f.get('name') == 'seasons' and len(f.get('buckets', [])) > 1), None) episodes = search_json.get('results', [{}]) show_seasons = bool(season != 'allseasons') # Return seasons if show_seasons and seasons: return (seasons, episodes) api_pages = search_json.get('meta').get('pages').get('total') api_page_size = search_json.get('meta').get('pages').get('size') total_results = search_json.get('meta').get('total_results') if all_items and total_results > api_page_size: for api_page in range(1, api_pages): api_page_url = search_url + '&from=' + str(api_page * api_page_size + 1) api_page_json = load(urlopen(api_page_url)) episodes += api_page_json.get('results', [{}]) # Return episodes return episodes
def run(self): """Main service loop""" self.log('Service started', 0) while not self.abortRequested(): # check every 1 sec if self.waitForAbort(1): # Abort was requested while waiting. We should exit break if not self.player.is_tracking(): continue if bool(get_property('PseudoTVRunning') == 'True'): self.player.disable_tracking() self.playback_manager.demo.hide() continue if get_setting_bool('disableNextUp'): # Next Up is disabled self.player.disable_tracking() self.playback_manager.demo.hide() continue # Method isExternalPlayer() was added in Kodi v18 onward if kodi_version_major() >= 18 and self.player.isExternalPlayer(): self.log('Up Next tracking stopped, external player detected', 2) self.player.disable_tracking() self.playback_manager.demo.hide() continue last_file = self.player.get_last_file() try: current_file = self.player.getPlayingFile() except RuntimeError: self.log( 'Up Next tracking stopped, failed player.getPlayingFile()', 2) self.player.disable_tracking() self.playback_manager.demo.hide() continue if last_file and last_file == from_unicode(current_file): # Already processed this playback before continue try: total_time = self.player.getTotalTime() except RuntimeError: self.log( 'Up Next tracking stopped, failed player.getTotalTime()', 2) self.player.disable_tracking() self.playback_manager.demo.hide() continue if total_time == 0: self.log('Up Next tracking stopped, no file is playing', 2) self.player.disable_tracking() self.playback_manager.demo.hide() continue try: play_time = self.player.getTime() except RuntimeError: self.log('Up Next tracking stopped, failed player.getTime()', 2) self.player.disable_tracking() self.playback_manager.demo.hide() continue notification_time = self.api.notification_time( total_time=total_time) if total_time - play_time > notification_time: # Media hasn't reach notification time yet, waiting a bit longer... continue self.player.set_last_file(from_unicode(current_file)) self.log( 'Show notification as episode (of length %d secs) ends in %d secs' % (total_time, notification_time), 2) self.playback_manager.launch_up_next() self.log('Up Next style autoplay succeeded', 2) self.player.disable_tracking() self.log('Service stopped', 0)
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 set_property(key, value, window_id=constants.WINDOW_HOME): """Set a Window property""" value = statichelper.from_unicode(str(value)) return xbmcgui.Window(window_id).setProperty(key, value)
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 run(self): ''' Main service loop ''' self.log('Service started', 0) while not self.abortRequested(): # check every 1 sec if self.waitForAbort(1): # Abort was requested while waiting. We should exit break if not self.player.is_tracking(): continue up_next_disabled = bool(get_setting('disableNextUp') == 'true') if bool(get_property('PseudoTVRunning') == 'True') or up_next_disabled: continue last_file = self.player.get_last_file() try: current_file = self.player.getPlayingFile() except RuntimeError: self.log( 'Failed getPlayingFile: No file is playing - stop up next tracking', 2) self.player.disable_tracking() continue if last_file and last_file == current_file: continue try: total_time = self.player.getTotalTime() except RuntimeError: self.log( 'Failed getTotalTime: No file is playing - stop up next tracking', 2) self.player.disable_tracking() continue if total_time == 0: continue try: play_time = self.player.getTime() except RuntimeError: self.log( 'Failed getTime: No file is playing - stop up next tracking', 2) self.player.disable_tracking() continue notification_time = self.api.notification_time( total_time=total_time) if total_time - play_time > notification_time: continue self.player.set_last_file(from_unicode(current_file)) self.log( 'Show notification as episode (of length %d secs) ends in %d secs' % (total_time, notification_time), 2) self.playback_manager.launch_up_next() self.log('Up Next style autoplay succeeded', 2) self.player.disable_tracking() self.log('Service stopped', 0)
def show_listing(list_items, category=None, sort='unsorted', ascending=True, content=None, cache=None, selected=None): ''' Show a virtual directory in Kodi ''' from addon import plugin from xbmcgui import ListItem xbmcplugin.setPluginFanart(handle=plugin.handle, image=from_unicode(addon_fanart())) usemenucaching = get_setting('usemenucaching', 'true') == 'true' if cache is None: cache = usemenucaching elif usemenucaching is False: cache = False if content: # content is one of: files, songs, artists, albums, movies, tvshows, episodes, musicvideos xbmcplugin.setContent(plugin.handle, content=content) # Jump through hoops to get a stable breadcrumbs implementation category_label = '' if category: if not content: category_label = 'VRT NU / ' if plugin.path.startswith(('/favorites/', '/resumepoints/')): category_label += localize(30428) + ' / ' # My if isinstance(category, int): category_label += localize(category) else: category_label += category elif not content: category_label = 'VRT NU' xbmcplugin.setPluginCategory(handle=plugin.handle, category=category_label) # FIXME: Since there is no way to influence descending order, we force it here if not ascending: sort = 'unsorted' # NOTE: When showing tvshow listings and 'showoneoff' was set, force 'unsorted' if get_setting( 'showoneoff', 'true') == 'true' and sort == 'label' and content == 'tvshows': sort = 'unsorted' # Add all sort methods to GUI (start with preferred) xbmcplugin.addSortMethod(handle=plugin.handle, sortMethod=SORT_METHODS[sort]) for key in sorted(SORT_METHODS): if key != sort: xbmcplugin.addSortMethod(handle=plugin.handle, sortMethod=SORT_METHODS[key]) # FIXME: This does not appear to be working, we have to order it ourselves # xbmcplugin.setProperty(handle=plugin.handle, key='sort.ascending', value='true' if ascending else 'false') # if ascending: # xbmcplugin.setProperty(handle=plugin.handle, key='sort.order', value=str(SORT_METHODS[sort])) # else: # # NOTE: When descending, use unsorted # xbmcplugin.setProperty(handle=plugin.handle, key='sort.order', value=str(SORT_METHODS['unsorted'])) listing = [] for title_item in list_items: # Three options: # - item is a virtual directory/folder (not playable, path) # - item is a playable file (playable, path) # - item is non-actionable item (not playable, no path) is_folder = bool(not title_item.is_playable and title_item.path) is_playable = bool(title_item.is_playable and title_item.path) list_item = ListItem(label=title_item.title) if title_item.prop_dict: # FIXME: The setProperties method is new in Kodi18, so we cannot use it just yet. # list_item.setProperties(values=title_item.prop_dict) for key, value in list(title_item.prop_dict.items()): list_item.setProperty(key=key, value=str(value)) list_item.setProperty(key='IsInternetStream', value='true' if is_playable else 'false') list_item.setProperty(key='IsPlayable', value='true' if is_playable else 'false') # FIXME: The setIsFolder method is new in Kodi18, so we cannot use it just yet. # list_item.setIsFolder(is_folder) if title_item.art_dict: list_item.setArt(dict(fanart=addon_fanart())) list_item.setArt(title_item.art_dict) if title_item.info_dict: # type is one of: video, music, pictures, game list_item.setInfo(type='video', infoLabels=title_item.info_dict) if title_item.stream_dict: # type is one of: video, audio, subtitle list_item.addStreamInfo('video', title_item.stream_dict) if title_item.context_menu: list_item.addContextMenuItems(title_item.context_menu) url = None if title_item.path: url = title_item.path listing.append((url, list_item, is_folder)) # Jump to specific item if selected is not None: pass # from xbmcgui import getCurrentWindowId, Window # wnd = Window(getCurrentWindowId()) # wnd.getControl(wnd.getFocusId()).selectItem(selected) succeeded = xbmcplugin.addDirectoryItems(plugin.handle, listing, len(listing)) xbmcplugin.endOfDirectory(plugin.handle, succeeded, updateListing=False, cacheToDisc=cache)