def show_recommendations_category(self, storefront, category): """ Show the items in a recommendations category. :type storefront: str :type category: str """ try: result = self._vtm_go.get_storefront_category(storefront, category) except ApiUpdateRequired: kodiutils.ok_dialog(message=kodiutils.localize( 30705)) # The VTM GO Service has been updated... return except Exception as ex: # pylint: disable=broad-except _LOGGER.error("%s", ex) kodiutils.ok_dialog(message="%s" % ex) return listing = [] for item in result.content: listing.append(Menu.generate_titleitem(item)) if storefront == STOREFRONT_SERIES: content = 'tvshows' elif storefront == STOREFRONT_MOVIES: content = 'movies' else: content = 'tvshows' # Fallback to a list of tvshows kodiutils.show_listing(listing, result.title, content=content, sort=['unsorted', 'label', 'year', 'duration'])
def index(): """ Ask to login, or go to the main menu. """ if not kodiutils.has_credentials(): if not kodiutils.yesno_dialog(message=kodiutils.localize( 30701)): # You need to configure your credentials... # We have no credentials, return to the Home Menu kodiutils.end_of_directory() kodiutils.execute_builtin('ActivateWindow(Home)') return kodiutils.open_settings() try: # Try authentication AuthApi(username=kodiutils.get_setting('username'), password=kodiutils.get_setting('password'), tenant=kodiutils.get_setting('tenant'), token_path=kodiutils.get_tokens_path()) except InvalidLoginException: kodiutils.ok_dialog(message=kodiutils.localize( 30203)) # Your credentials are not valid! kodiutils.open_settings() kodiutils.execute_builtin('ActivateWindow(Home)') kodiutils.end_of_directory() return except HTTPError as exc: kodiutils.ok_dialog(message=kodiutils.localize( 30702, code='HTTP %d' % exc.response.status_code) ) # Unknown error while logging in: {code} kodiutils.end_of_directory() return show_main_menu()
def play_asset(self, asset_id): """ Play an asset (can be a program of a live channel). :param string asset_id: The ID of the asset to play. """ # Get asset info asset = self._channel_api.get_asset(asset_id) item = Menu.generate_titleitem(asset) # Get stream info try: stream_info = self._channel_api.get_stream(asset_id) except NotAvailableInOfferException as exc: _LOGGER.error(exc) kodiutils.ok_dialog(message=kodiutils.localize(30712)) return license_key = self._create_license_key( stream_info.drm_license_url, key_headers={'Content-Type': 'application/octet-stream'}) _LOGGER.debug('Starting playing %s with license key %s', stream_info.url, license_key) kodiutils.play(stream_info.url, license_key, item.title, item.art_dict, item.info_dict, item.prop_dict)
def show_recommendations_category(self, storefront, category): """ Show the items in a recommendations category. :type storefront: str :type category: str """ try: recommendations = self._vtm_go.get_recommendations(storefront) except ApiUpdateRequired: kodiutils.ok_dialog(message=kodiutils.localize( 30705)) # The VTM GO Service has been updated... return except Exception as ex: # pylint: disable=broad-except _LOGGER.error("%s", ex) kodiutils.ok_dialog(message="%s" % ex) return listing = [] for cat in recommendations: # Only show the requested category if cat.category_id != category: continue for item in cat.content: listing.append(self._menu.generate_titleitem(item)) # Sort categories by default like in VTM GO. kodiutils.show_listing(listing, 30015, content='tvshows')
def show_program_season(self, program, season): """ Show the episodes of a program from the catalog :type program: str :type season: int """ try: program_obj = self._vtm_go.get_program( program ) # Use CACHE_AUTO since the data is just refreshed in show_program except UnavailableException: kodiutils.ok_dialog( message=kodiutils.localize(30717) ) # This program is not available in the VTM GO catalogue. kodiutils.end_of_directory() return if season == -1: # Show all seasons seasons = list(program_obj.seasons.values()) else: # Show the season that was selected seasons = [program_obj.seasons[season]] listing = [ self._menu.generate_titleitem(e) for s in seasons for e in list(s.episodes.values()) ] # Sort by episode number by default. Takes seasons into account. kodiutils.show_listing(listing, 30003, content='episodes', sort=['episode', 'duration'])
def show_catalog(self): """ Show the catalog """ try: categories = self._vtm_go.get_categories() except ApiUpdateRequired: kodiutils.ok_dialog(message=kodiutils.localize( 30705)) # The VTM GO Service has been updated... return except Exception as ex: # pylint: disable=broad-except _LOGGER.error("%s", ex) kodiutils.ok_dialog(message="%s" % ex) return listing = [] for cat in categories: listing.append( kodiutils.TitleItem( title=cat.title, path=kodiutils.url_for('show_catalog_category', category=cat.category_id), info_dict=dict( plot='[B]{category}[/B]'.format(category=cat.title), ), )) # Sort categories by default like in VTM GO. kodiutils.show_listing(listing, 30003, content='files')
def show_continuewatching(self): """ Show the items in "Continue Watching" """ try: mylist = self._vtm_go.get_swimlane('continue-watching') except ApiUpdateRequired: kodiutils.ok_dialog(message=kodiutils.localize( 30705)) # The VTM GO Service has been updated... return except Exception as ex: # pylint: disable=broad-except _LOGGER.error("%s", ex) kodiutils.ok_dialog(message="%s" % ex) return listing = [] for item in mylist: titleitem = self._menu.generate_titleitem(item, progress=True) # Add Program Name to title since this list contains episodes from multiple programs title = '%s - %s' % (titleitem.info_dict.get('tvshowtitle'), titleitem.info_dict.get('title')) titleitem.info_dict['title'] = title listing.append(titleitem) # Sort categories by default like in VTM GO. kodiutils.show_listing(listing, 30019, content='episodes', sort='label')
def show_catalog_category(self, category=None): """ Show a category in the catalog :type category: str """ try: items = self._vtm_go.get_items(category) except ApiUpdateRequired: kodiutils.ok_dialog(message=kodiutils.localize( 30705)) # The VTM GO Service has been updated... return except Exception as ex: # pylint: disable=broad-except _LOGGER.error("%s", ex) kodiutils.ok_dialog(message="%s" % ex) return listing = [] for item in items: listing.append(self._menu.generate_titleitem(item)) # Sort items by label, but don't put folders at the top. # Used for A-Z listing or when movies and episodes are mixed. kodiutils.show_listing( listing, 30003, content='movies' if category == 'films' else 'tvshows', sort=['label', 'year', 'duration'])
def show_program_season(self, program_id, season_uuid): """ Show the episodes of a program from the catalog :type program_id: str :type season_uuid: str """ try: program = self._api.get_program(program_id) except UnavailableException: kodiutils.ok_dialog(message=kodiutils.localize( 30717)) # This program is not available in the catalogue. kodiutils.end_of_directory() return if season_uuid == "-1": # Show all episodes episodes = program.episodes else: # Show the episodes of the season that was selected episodes = [ e for e in program.episodes if e.season_uuid == season_uuid ] listing = [Menu.generate_titleitem(episode) for episode in episodes] # Sort by episode number by default. Takes seasons into account. kodiutils.show_listing(listing, 30003, content='episodes', sort=['episode', 'duration'])
def get_selection(cls): """Retrieve information about the selected ListItem.""" # The selected ListItem is available in sys.listitem, but there is not enough data that we can use to know what # exact item was selected. Therefore, we use xbmc.getInfoLabel(ListItem.xxx), that references the currently # selected ListItem. This is not always the same as the item where the Context Menu was opened on when the # selection was moved really quick before the Python code was started. This often happens when using the mouse, # but should not happen when using the keyboard or a remote control. Therefore, we do a check to see if the # sys.listitem.getPath is the same as xbmc.getInfoLabel(ListItem.FolderPath) before continuing. # For now, this is the best we can do. # # The sys.listitem.getPath() returns a string like "pvr://guide/0016/2020-05-28 09:24:47.epg". We could use the # date to find out what item was selected, but we can't match the channel with something that makes sense. It's # not the same ID as the ID in the JSON-RPC "PVR.GetChannels" or "PVR.GetChannelDetails" commands. # # The available fields are: # * sys.listitem.getLabel() # Universiteit van Vlaanderen # * sys.listitem.getPath() # pvr://guide/0016/2020-05-28 09:24:47.epg # # I would have preferred to use the Channel ID we use for for the epg (like een.be), but that isn't available. # We only have a name (ListItem.ChannelName), or the channel number (ListItem.ChannelNumberLabel). # Check if the selected item is also the intended item if sys.listitem.getPath() != kodiutils.get_info_label('ListItem.FolderPath'): # pylint: disable=no-member # We are in trouble. We know that the data we want to use is invalid, but there is nothing we can do. kodiutils.ok_dialog(message=kodiutils.localize(30712)) # Could not determine the selected program... return None # Load information from the ListItem date = kodiutils.get_info_label('ListItem.Date') duration = kodiutils.get_info_label('ListItem.Duration') channel = kodiutils.to_unicode(kodiutils.get_info_label('ListItem.ChannelName')) # Parse begin to a datetime. The ListItem.Date doesn't contain seconds date_format = kodiutils.get_region('dateshort') + ' ' + kodiutils.get_region('time').replace(':%S', '') try: start = datetime.strptime(date, date_format) except TypeError: start = datetime(*(time.strptime(date, date_format)[0:6])) # Parse duration to seconds splitted = duration.split(':') if len(splitted) == 1: # %S seconds = int(splitted[0]) elif len(splitted) == 2: # %M:%S seconds = int(splitted[0]) * 60 + int(splitted[1]) elif len(splitted) == 3: # %H:%M:%S seconds = int(splitted[0]) * 3600 + int(splitted[1]) * 60 + int(splitted[2]) else: raise Exception('Unknown duration %s' % duration) return dict( start=start, duration=seconds, channel=channel, )
def refresh(cls, show_progress=False): """ Update channels and EPG data """ channels = [] epg = dict() if show_progress: progress = kodiutils.progress( message=kodiutils.localize(30703)) # Detecting IPTV add-ons... else: progress = None addons = cls.get_iptv_addons() for index, addon in enumerate(addons): _LOGGER.info('Updating IPTV data for %s...', addon.addon_id) if progress: progress.update(int(100 * index / len(addons)), kodiutils.localize(30704).format( addon=kodiutils.addon_name(addon.addon_obj) )) # Fetching channels and guide of {addon}... # Fetch channels channels.extend(addon.get_channels()) if progress and progress.iscanceled(): progress.close() return # Fetch EPG data epg.update(addon.get_epg()) if progress and progress.iscanceled(): progress.close() return # Write files if show_progress: progress.update( 100, kodiutils.localize(30705)) # Updating channels and guide... IptvSimple.write_playlist(channels) IptvSimple.write_epg(epg) if kodiutils.get_setting_bool('iptv_simple_restart'): if show_progress: # Try to restart now. We will schedule it if the user is watching TV. IptvSimple.restart(True) else: # Schedule a restart IptvSimple.restart(False) # Update last_refreshed kodiutils.set_setting_int('last_refreshed', int(time.time())) if show_progress: progress.close() kodiutils.ok_dialog(message=kodiutils.localize( 30706)) # The channels and guide are updated successfully!
def play_from_contextmenu(): """Play an item from the Context Menu in Kodi 18""" stream = ContextMenu.get_direct_uri() if stream is None: kodiutils.ok_dialog(message=kodiutils.localize(30706)) return _LOGGER.debug('Playing using direct URI: %s', stream) kodiutils.execute_builtin('PlayMedia', stream)
def live(channel): """ Play the live channel. :type channel: string """ channel_name = CHANNELS.get(channel, dict(name=channel)) kodiutils.ok_dialog(message=kodiutils.localize( 30718, channel=channel_name.get( 'name'))) # There is no live stream available for {channel}. kodiutils.end_of_directory()
def setup_iptv_simple(): """ Setup IPTV Simple """ reply = kodiutils.yesno_dialog( message=kodiutils.localize(30700)) # Are you sure... if reply: if IptvSimple.setup(): kodiutils.ok_dialog(message=kodiutils.localize( 30701)) # The configuration of IPTV Simple is completed! else: kodiutils.ok_dialog(message=kodiutils.localize( 30702)) # The configuration of IPTV Simple has failed!
def clean(): """ Clear metadata (called from settings) """ cache_path = kodiutils.get_cache_path() _, files = kodiutils.listdir(cache_path) for filename in files: if not kodiutils.delete(os.path.join(cache_path, filename)): return kodiutils.ok_dialog(message=kodiutils.localize( 30721)) # Clearing local metadata failed kodiutils.set_setting('metadata_last_updated', '0') return kodiutils.ok_dialog( message=kodiutils.localize(30714)) # Local metadata is cleared
def verify_credentials(): """ Verify if the user has credentials and if they are valid. """ retry = False while True: # Check if the user has credentials if retry or not kodiutils.get_setting( 'username') or not kodiutils.get_setting('password'): # Ask user to fill in his credentials if not kodiutils.yesno_dialog(message=kodiutils.localize( 30701)): # You need to configure your credentials... # The user doesn't want to do this. Abort to Home screen. return False kodiutils.open_settings() try: # Create Auth object so we actually do a login. Auth(kodiutils.get_setting('username'), kodiutils.get_setting('password'), kodiutils.get_setting('loginprovider'), kodiutils.get_setting('profile'), kodiutils.get_tokens_path()) return True except InvalidLoginException: kodiutils.ok_dialog(message=kodiutils.localize( 30203)) # Your credentials are not valid! retry = True except NoStreamzSubscriptionException: kodiutils.ok_dialog(message=kodiutils.localize( 30201)) # Your Streamz account has no valid subscription! retry = True except NoTelenetSubscriptionException: kodiutils.ok_dialog(message=kodiutils.localize( 30202)) # Your Telenet account has no valid subscription! retry = True except LoginErrorException as exc: kodiutils.ok_dialog(message=kodiutils.localize( 30702, code=exc.code)) # Unknown error while logging in: {code} return False except HTTPError as exc: kodiutils.ok_dialog(message=kodiutils.localize( 30702, code='HTTP %d' % exc.response.status_code) ) # Unknown error while logging in: {code} return False
def play_epg_datetime(self, channel, timestamp): """ Play a program based on the channel and the timestamp when it was aired :type channel: str :type timestamp: str """ broadcast = self._vtm_go_epg.get_broadcast(channel, timestamp) if not broadcast: kodiutils.ok_dialog(heading=kodiutils.localize(30711), message=kodiutils.localize(30713)) # The requested video was not found in the guide. kodiutils.end_of_directory() return kodiutils.redirect( kodiutils.url_for('play', category=broadcast.playable_type, item=broadcast.playable_uuid))
def live(channel): """ Play the live channel. :type channel: string """ # TODO: this doesn't work correctly, playing a live program from the PVR won't play something from the beginning # Lookup current program # broadcast = self._epg.get_broadcast(channel, datetime.datetime.now().isoformat()) # if broadcast and broadcast.video_url: # self.play_from_page(broadcast.video_url) # return channel_name = CHANNELS.get(channel, dict(name=channel)) kodiutils.ok_dialog(message=kodiutils.localize(30718, channel=channel_name.get('name'))) # There is no live stream available for {channel}. kodiutils.end_of_directory()
def onPlayBackStopped(self): # pylint: disable=invalid-name """Called when user stops Kodi playing a file""" if not self.listen: return _LOGGER.debug('KodiPlayer onPlayBackStopped') if not self.av_started: # Check stream path import requests response = requests.get(self.stream_path) if response.status_code == 403: message_id = 30720 else: message_id = 30719 kodiutils.ok_dialog(message=kodiutils.localize(message_id))
def _check_inputstream(): """ Check if inputstreamhelper and inputstream.adaptive are fine. :rtype boolean """ try: from inputstreamhelper import Helper is_helper = Helper('mpd', drm='com.widevine.alpha') if not is_helper.check_inputstream(): # inputstreamhelper has already shown an error return False except ImportError: kodiutils.ok_dialog(message=kodiutils.localize(30708)) # Please reboot Kodi return False return True
def play_from_page(self, path): """ Play the requested item. :type path: string """ if not path: kodiutils.ok_dialog(message=kodiutils.localize(30712)) # The video is unavailable... return # Get episode information episode = self._api.get_episode(path, cache=CACHE_PREVENT) resolved_stream = None if episode is None: kodiutils.ok_dialog(message=kodiutils.localize(30712)) return if episode.stream: # We already have a resolved stream. Nice! # We don't need credentials for these streams. resolved_stream = ResolvedStream( uuid=episode.uuid, url=episode.stream, ) _LOGGER.debug('Already got a resolved stream: %s', resolved_stream) if episode.uuid: # Lookup the stream resolved_stream = self._resolve_stream(episode.uuid) _LOGGER.debug('Resolved stream: %s', resolved_stream) if resolved_stream: titleitem = Menu.generate_titleitem(episode) if resolved_stream.license_url: # Generate license key license_key = self.create_license_key(resolved_stream.license_url, key_headers=dict( customdata=resolved_stream.auth, )) else: license_key = None kodiutils.play(resolved_stream.url, resolved_stream.stream_type, license_key, info_dict=titleitem.info_dict, art_dict=titleitem.art_dict, prop_dict=titleitem.prop_dict)
def play_epg_datetime(self, channel, timestamp): """ Play a program based on the channel and the timestamp when it was aired :type channel: str :type timestamp: str """ broadcast = self._epg.get_broadcast(channel, timestamp) if not broadcast: kodiutils.ok_dialog(message=kodiutils.localize(30713)) # The requested video was not found in the guide. kodiutils.end_of_directory() return if not broadcast.video_url: kodiutils.ok_dialog(message=kodiutils.localize(30712)) # The video is unavailable and can't be played right now. kodiutils.end_of_directory() return Player().play_from_page(channel, broadcast.video_url)
def show_program_clips(self, channel, program_id): """ Show the clips of a program from the catalog :type channel: str :type program_id: str """ try: # We need to query the backend, since we don't cache clips. program = self._api.get_program(channel, program_id, extract_clips=True, cache=CACHE_PREVENT) except UnavailableException: kodiutils.ok_dialog(message=kodiutils.localize(30717)) # This program is not available in the catalogue. kodiutils.end_of_directory() return listing = [Menu.generate_titleitem(episode) for episode in program.clips] # Sort like we get our results back. kodiutils.show_listing(listing, 30003, content='episodes')
def play_epg_datetime(self, channel, timestamp): """ Play a program based on the channel and the timestamp when it was aired :type channel: str :type timestamp: str """ broadcast = self._epg.get_broadcast(channel, timestamp) if not broadcast: kodiutils.ok_dialog( heading=kodiutils.localize(30711), message=kodiutils.localize( 30713)) # The requested video was not found in the guide. kodiutils.end_of_directory() return kodiutils.container_update( kodiutils.url_for('play', channel=channel, uuid=broadcast.video_url))
def play(self, channel, item): """ Play the requested item. :type channel: string :type item: string """ try: # Get stream information resolved_stream = self._api.get_stream(channel, item) except GeoblockedException: kodiutils.ok_dialog(heading=kodiutils.localize(30709), message=kodiutils.localize(30710)) # This video is geo-blocked... return except UnavailableException: kodiutils.ok_dialog(heading=kodiutils.localize(30711), message=kodiutils.localize(30712)) # The video is unavailable... return # Play this item kodiutils.play(resolved_stream)
def play(self, uuid): """ Play the requested item. :type uuid: string """ if not uuid: kodiutils.ok_dialog(message=kodiutils.localize(30712)) # The video is unavailable... return # Lookup the stream resolved_stream = self._resolve_stream(uuid) if resolved_stream.license_url: # Generate license key license_key = self.create_license_key(resolved_stream.license_url, key_headers=dict( customdata=resolved_stream.auth, )) else: license_key = None kodiutils.play(resolved_stream.url, resolved_stream.stream_type, license_key)
def play_asset(self, asset_id): """ Play an asset (can be a program of a live channel). :param string asset_id: The ID of the asset to play. """ # Get asset info if len(asset_id) == 32: # a locId is 32 chars asset = self._channel_api.get_asset_by_locid(asset_id) else: # an asset_id is 40 chars asset = self._channel_api.get_asset(asset_id) if isinstance(asset, Program): item = Menu.generate_titleitem_program(asset) elif isinstance(asset, Channel): item = Menu.generate_titleitem_channel(asset) else: raise Exception('Unknown asset type: %s' % asset) # Get stream info try: stream_info = self._channel_api.get_stream(asset.uid) except InvalidTokenException: # Retry with fresh tokens self._auth.login(True) stream_info = self._channel_api.get_stream(asset.uid) except (NotAvailableInOfferException, UnavailableException) as exc: _LOGGER.error(exc) kodiutils.ok_dialog( message=kodiutils.localize(30712) ) # The video is unavailable and can't be played right now. kodiutils.end_of_directory() return license_key = self._create_license_key( stream_info.drm_license_url, key_headers={'Content-Type': 'application/octet-stream'}) _LOGGER.debug('Starting playing %s with license key %s', stream_info.url, license_key) kodiutils.play(stream_info.url, license_key, item.title, item.art_dict, item.info_dict, item.prop_dict)
def login(self): """ Start the authorisation flow. """ auth_info = self._auth.authorize() # Show the authorization message progress_dialog = kodiutils.progress( message=kodiutils.localize(30701, url=auth_info.get('verification_uri'), code=auth_info.get('user_code'))) progress_dialog.update(0) # Check the authorization until it succeeds or the user cancels. delay = auth_info.get('interval') expiry = auth_info.get('expires_in') time_start = datetime.now() time_end = time_start + timedelta(seconds=expiry) while datetime.now() < time_end: # Update progress progress_dialog.update( int((datetime.now() - time_start).seconds * 100 / expiry)) # Check if the users has cancelled the login flow if progress_dialog.iscanceled(): progress_dialog.close() return # Check if we are authorized now check = self._auth.authorize_check() if check: progress_dialog.close() kodiutils.notification(kodiutils.localize(30702)) kodiutils.redirect(kodiutils.url_for('show_main_menu')) return # Sleep a bit sleep(delay) # Close progress indicator progress_dialog.close() kodiutils.ok_dialog(message=kodiutils.localize(30703))
def select_profile(self, key=None): """ Show your profiles. :type key: str """ profiles = self._auth.get_profiles() # Show warning when you have no profiles if not profiles: # Your account has no profiles defined. Please login on www.streamz.be/streamz and create a profile. kodiutils.ok_dialog(message=kodiutils.localize(30703)) kodiutils.end_of_directory() return # Select the first profile when you only have one if len(profiles) == 1: key = profiles[0].key # Save the selected profile if key: profile = [x for x in profiles if x.key == key][0] _LOGGER.debug('Setting profile to %s', profile) kodiutils.set_setting('profile', '%s:%s' % (profile.key, profile.product)) kodiutils.set_setting('profile_name', profile.name) kodiutils.redirect(kodiutils.url_for('show_main_menu')) return # Show profile selection when you have multiple profiles listing = [ TitleItem( title=self._get_profile_name(p), path=kodiutils.url_for('select_profile', key=p.key), art_dict=dict(icon='DefaultUser.png'), info_dict=dict(plot=p.name, ), ) for p in profiles ] kodiutils.show_listing(listing, sort=['unsorted'], category=30057) # Select Profile
def index(): """ Show the profile selection, or go to the main menu. """ try: if (kodiutils.get_setting_bool('auto_login') and kodiutils.get_setting('username') and kodiutils.get_setting('password') and kodiutils.get_setting('profile')): # We have credentials show_main_menu() else: # Ask the user for the profile to use select_profile() except NoLoginException: kodiutils.ok_dialog(message=kodiutils.localize( 30701)) # You need to configure your credentials... kodiutils.open_settings() kodiutils.container_refresh() except InvalidLoginException: kodiutils.ok_dialog(message=kodiutils.localize( 30203)) # Your credentials are not valid! kodiutils.open_settings() kodiutils.end_of_directory() except NoStreamzSubscriptionException: kodiutils.ok_dialog(message=kodiutils.localize( 30201)) # Your Streamz account has no valid subscription! kodiutils.end_of_directory() except NoTelenetSubscriptionException: kodiutils.ok_dialog(message=kodiutils.localize( 30202)) # Your Telenet account has no valid subscription! kodiutils.end_of_directory() except LoginErrorException as exc: kodiutils.ok_dialog(message=kodiutils.localize( 30702, code=exc.code)) # Unknown error while logging in: {code} kodiutils.open_settings() kodiutils.end_of_directory()