diff --git a/resources/language/resource.language.en_gb/strings.po b/resources/language/resource.language.en_gb/strings.po index aedd6d9..5234c1b 100644 --- a/resources/language/resource.language.en_gb/strings.po +++ b/resources/language/resource.language.en_gb/strings.po @@ -60,6 +60,10 @@ msgstr "" ### SUBMENUS +msgctxt "#30052" +msgid "Watch live [B]{channel}[/B]" +msgstr "" + msgctxt "#30053" msgid "TV Guide for [B]{channel}[/B]" msgstr "" diff --git a/resources/language/resource.language.nl_nl/strings.po b/resources/language/resource.language.nl_nl/strings.po index 833e3d2..6c3a86f 100644 --- a/resources/language/resource.language.nl_nl/strings.po +++ b/resources/language/resource.language.nl_nl/strings.po @@ -61,6 +61,10 @@ msgstr "Tv-gids" ### SUBMENUS +msgctxt "#30052" +msgid "Watch live [B]{channel}[/B]" +msgstr "Kijk live [B]{channel}[/B]" + msgctxt "#30053" msgid "TV Guide for [B]{channel}[/B]" msgstr "Tv-gids voor [B]{channel}[/B]" diff --git a/resources/lib/addon.py b/resources/lib/addon.py index a55e072..bc403ea 100644 --- a/resources/lib/addon.py +++ b/resources/lib/addon.py @@ -160,14 +160,11 @@ def play_epg(channel, timestamp): @routing.route('/play/catalog') -@routing.route('/play/catalog/') -@routing.route('/play/catalog//') -def play_catalog(uuid=None, islongform=False): +@routing.route('/play/catalog//') +def play_catalog(uuid=None, content_type=None): """ Play the requested item """ - from ast import literal_eval from resources.lib.modules.player import Player - # Convert string to bool using literal_eval - Player().play(uuid, literal_eval(islongform)) + Player().play(uuid, content_type) @routing.route('/play/page/') diff --git a/resources/lib/modules/channels.py b/resources/lib/modules/channels.py index 88b4eee..a5482e6 100644 --- a/resources/lib/modules/channels.py +++ b/resources/lib/modules/channels.py @@ -71,9 +71,27 @@ class Channels: # Lookup the high resolution logo based on the channel name fanart = '{path}/resources/logos/{logo}'.format(path=kodiutils.addon_path(), logo=channel_info.get('background')) + icon = '{path}/resources/logos/{logo}'.format(path=kodiutils.addon_path(), logo=channel_info.get('logo')) listing = [] + listing.append( + TitleItem( + title=kodiutils.localize(30052, channel=channel_info.get('name')), # Watch live {channel} + path=kodiutils.url_for('play_live', channel=channel_info.get('name')) + '?.pvr', + art_dict={ + 'icon': icon, + 'fanart': fanart, + }, + info_dict={ + 'plot': kodiutils.localize(30052, channel=channel_info.get('name')), # Watch live {channel} + 'playcount': 0, + 'mediatype': 'video', + }, + is_playable=True, + ) + ) + if channel_info.get('epg_id'): listing.append( TitleItem( diff --git a/resources/lib/modules/menu.py b/resources/lib/modules/menu.py index 0bacfff..c30ecd0 100644 --- a/resources/lib/modules/menu.py +++ b/resources/lib/modules/menu.py @@ -183,7 +183,7 @@ class Menu: if item.uuid: # We have an UUID and can play this item directly - path = kodiutils.url_for('play_catalog', uuid=item.uuid, islongform=item.islongform) + path = kodiutils.url_for('play_catalog', uuid=item.uuid, content_type=item.content_type) else: # We don't have an UUID, and first need to fetch the video information from the page path = kodiutils.url_for('play_from_page', page=quote(item.path, safe='')) diff --git a/resources/lib/modules/player.py b/resources/lib/modules/player.py index 5497f93..e03329e 100644 --- a/resources/lib/modules/player.py +++ b/resources/lib/modules/player.py @@ -26,8 +26,7 @@ class Player: # Workaround for Raspberry Pi 3 and older kodiutils.set_global_setting('videoplayer.useomxplayer', True) - @staticmethod - def live(channel): + def live(self, channel): """ Play the live channel. :type channel: string """ @@ -38,9 +37,9 @@ class Player: # self.play_from_page(broadcast.video_url) # return - channel_name = CHANNELS.get(channel, {'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() + channel_url = CHANNELS.get(channel, {'url': channel}).get('url') + + self.play_from_page(channel_url) def play_from_page(self, path): """ Play the requested item. @@ -69,7 +68,7 @@ class Player: if episode.uuid: # Lookup the stream - resolved_stream = self._resolve_stream(episode.uuid, episode.islongform) + resolved_stream = self._resolve_stream(episode.uuid, episode.content_type) _LOGGER.debug('Resolved stream: %s', resolved_stream) if resolved_stream: @@ -81,24 +80,24 @@ class Player: art_dict=titleitem.art_dict, prop_dict=titleitem.prop_dict) - def play(self, uuid, islongform): + def play(self, uuid, content_type): """ Play the requested item. :type uuid: string - :type islongform: bool + :type content_type: 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, islongform) + resolved_stream = self._resolve_stream(uuid, content_type) kodiutils.play(resolved_stream.url, resolved_stream.stream_type, resolved_stream.license_key) @staticmethod - def _resolve_stream(uuid, islongform): + def _resolve_stream(uuid, content_type): """ Resolve the stream for the requested item :type uuid: string - :type islongform: bool + :type content_type: string """ try: # Check if we have credentials @@ -115,7 +114,7 @@ class Player: auth = AuthApi(kodiutils.get_setting('username'), kodiutils.get_setting('password'), kodiutils.get_tokens_path()) # Get stream information - resolved_stream = ContentApi(auth).get_stream_by_uuid(uuid, islongform) + resolved_stream = ContentApi(auth).get_stream_by_uuid(uuid, content_type) return resolved_stream except (InvalidLoginException, AuthenticationException) as ex: diff --git a/resources/lib/viervijfzes/__init__.py b/resources/lib/viervijfzes/__init__.py index 91fe30b..16947b0 100644 --- a/resources/lib/viervijfzes/__init__.py +++ b/resources/lib/viervijfzes/__init__.py @@ -7,6 +7,7 @@ from collections import OrderedDict CHANNELS = OrderedDict([ ('Play4', { 'name': 'Play4', + 'url': 'live-kijken/play-4', 'epg_id': 'vier', 'logo': 'play4.png', 'background': 'play4-background.png', @@ -18,6 +19,7 @@ CHANNELS = OrderedDict([ }), ('Play5', { 'name': 'Play5', + 'url': 'live-kijken/play-5', 'epg_id': 'vijf', 'logo': 'play5.png', 'background': 'play5-background.png', @@ -29,6 +31,7 @@ CHANNELS = OrderedDict([ }), ('Play6', { 'name': 'Play6', + 'url': 'live-kijken/play-6', 'epg_id': 'zes', 'logo': 'play6.png', 'background': 'play6-background.png', @@ -40,8 +43,8 @@ CHANNELS = OrderedDict([ }), ('Play7', { 'name': 'Play7', + 'url': 'live-kijken/play-7', 'epg_id': 'zeven', - 'url': 'https://www.goplay.be', 'logo': 'play7.png', 'background': 'play7-background.png', 'iptv_preset': 17, diff --git a/resources/lib/viervijfzes/content.py b/resources/lib/viervijfzes/content.py index 219278c..6b15885 100644 --- a/resources/lib/viervijfzes/content.py +++ b/resources/lib/viervijfzes/content.py @@ -112,7 +112,7 @@ class Episode: """ Defines an Episode. """ def __init__(self, uuid=None, nodeid=None, path=None, channel=None, program_title=None, title=None, description=None, thumb=None, duration=None, - season=None, season_uuid=None, number=None, rating=None, aired=None, expiry=None, stream=None, islongform=False): + season=None, season_uuid=None, number=None, rating=None, aired=None, expiry=None, stream=None, content_type=None): """ :type uuid: str :type nodeid: str @@ -130,7 +130,7 @@ class Episode: :type aired: datetime :type expiry: datetime :type stream: string - :type islongform: bool + :type content_type: string """ self.uuid = uuid self.nodeid = nodeid @@ -148,7 +148,7 @@ class Episode: self.aired = aired self.expiry = expiry self.stream = stream - self.islongform = islongform + self.content_type = content_type def __repr__(self): return "%r" % self.__dict__ @@ -338,6 +338,14 @@ class ContentApi: if not data: return None + if 'episode' in data and data['episode']['pageInfo']['type'] == 'live_channel': + episode = Episode( + uuid=data['episode']['pageInfo']['nodeUuid'], + program_title=data['episode']['pageInfo']['title'], + content_type=data['episode']['pageInfo']['type'], + ) + return episode + if 'video' in data and data['video']: # We have found detailed episode information episode = self._parse_clip_data(data['video']) @@ -353,14 +361,19 @@ class ContentApi: return None - def get_stream_by_uuid(self, uuid, islongform): + def get_stream_by_uuid(self, uuid, content_type): """ Return a ResolvedStream for this video. - :type uuid: str - :type islongform: bool + :type uuid: string + :type content_type: string :rtype: ResolvedStream """ - mode = 'long-form' if islongform else 'short-form' - response = self._get_url(self.API_GOPLAY + '/web/v1/videos/%s/%s' % (mode, uuid), authentication='Bearer %s' % self._auth.get_token()) + if content_type in ('video-long_form', 'long_form'): + mode = 'videos/long-form' + elif content_type == 'video-short_form': + mode = 'videos/short-form' + elif content_type == 'live_channel': + mode = 'liveStreams' + response = self._get_url(self.API_GOPLAY + '/web/v1/%s/%s' % (mode, uuid), authentication='Bearer %s' % self._auth.get_token()) data = json.loads(response) if not data: @@ -482,8 +495,8 @@ class ContentApi: raw_html = self._get_url(self.SITE_URL) # Categories regexes - regex_articles = re.compile(r']+>(.*?)', re.DOTALL) - regex_category = re.compile(r'(.*?)(?:.*?
(.*?)
)?', re.DOTALL) + regex_articles = re.compile(r']+>([\s\S]*?)', re.DOTALL) + regex_category = re.compile(r'(.*?)(?:.*?
(.*?)
)?', re.DOTALL) categories = [] for result in regex_articles.finditer(raw_html): @@ -492,9 +505,9 @@ class ContentApi: match_category = regex_category.search(article_html) category_title = None if match_category: - category_title = match_category.group(1).strip() + category_title = unescape(match_category.group(1).strip()) if match_category.group(2): - category_title += ' [B]%s[/B]' % match_category.group(2).strip() + category_title += ' [B]%s[/B]' % unescape(match_category.group(2).strip()) if category_title: # Extract programs and lookup in all_programs so we have more metadata @@ -547,8 +560,8 @@ class ContentApi: :rtype list[Program] """ # Item regexes - regex_item = re.compile(r']+?href="(?P[^"]+)"[^>]+?>' - r'.*?

(?P[^<]*)</h3>.*?data-background-image="(?P<image>.*?)".*?' + regex_item = re.compile(r'<a[^>]+?href=\"(?P<path>[^\"]+)\"[^>]+?>' + r'[\s\S]*?<h3 class=\"poster-teaser__title\">(?P<title>[^<]*)</h3>[\s\S]*?poster-teaser__image\" src=\"(?P<image>[\s\S]*?)\"[\s\S]*?' r'</a>', re.DOTALL) # Extract items @@ -574,20 +587,21 @@ class ContentApi: :rtype list[Episode] """ # Item regexes - regex_item = re.compile(r'<a[^>]+?href="(?P<path>[^"]+)"[^>]+?>.*?</a>', re.DOTALL) + regex_item = re.compile(r'<a[^>]+?class=\"(?P<item_type>[^\"]+)\"[^>]+?href=\"(?P<path>[^\"]+)\"[^>]+?>[\s\S]*?</a>', re.DOTALL) - regex_episode_program = re.compile(r'<h3 class="episode-teaser__subtitle">([^<]*)</h3>') - regex_episode_title = re.compile(r'<(?:div|h3) class="(?:poster|card|image|episode)-teaser__title">(?:<span>)?([^<]*)(?:</span>)?</(?:div|h3)>') - regex_episode_duration = re.compile(r'data-duration="([^"]*)"') - regex_episode_video_id = re.compile(r'data-video-id="([^"]*)"') - regex_episode_image = re.compile(r'data-background-image="([^"]*)"') - regex_episode_badge = re.compile(r'<div class="(?:poster|card|image|episode)-teaser__badge badge">([^<]*)</div>') + regex_episode_program = re.compile(r'<(?:div|h3) class=\"episode-teaser__subtitle\">([^<]*)</(?:div|h3)>') + regex_episode_title = re.compile(r'<(?:div|h3) class=\"(?:poster|card|image|episode)-teaser__title\">(?:<span>)?([^<]*)(?:</span>)?</(?:div|h3)>') + regex_episode_duration = re.compile(r'data-duration=\"([^\"]*)\"') + regex_episode_video_id = re.compile(r'data-video-id=\"([^\"]*)\"') + regex_episode_image = re.compile(r'<img class=\"episode-teaser__header\" src=\"([^<\"]*)\"') + regex_episode_badge = re.compile(r'<div class=\"badge (?:poster|card|image|episode)-teaser__badge (?:poster|card|image|episode)-teaser__badge--default\">([^<]*)</div>') # Extract items episodes = [] for item in regex_item.finditer(html): item_html = item.group(0) path = item.group('path') + item_type = item.group('item_type') # Extract title try: @@ -632,6 +646,8 @@ class ContentApi: if episode_badge: description += "\n\n[B]%s[/B]" % episode_badge + content_type = 'video-short_form' if 'card-' in item_type else 'video-long_form' + # Episode episodes.append(Episode( path=path.lstrip('/'), @@ -642,6 +658,7 @@ class ContentApi: uuid=episode_video_id, thumb=episode_image, program_title=episode_program, + content_type=content_type )) return episodes @@ -721,7 +738,7 @@ class ContentApi: expiry=datetime.fromtimestamp(int(data.get('unpublishDate'))) if data.get('unpublishDate') else None, rating=data.get('parentalRating'), stream=data.get('path'), - islongform=data.get('isLongForm'), + content_type=data.get('type'), ) return episode