diff --git a/addon.py b/addon.py index e10c23346..9c773fb89 100644 --- a/addon.py +++ b/addon.py @@ -25,9 +25,7 @@ def router(params_string): addon = xbmcaddon.Addon() kodi_wrapper = kodiwrapper.KodiWrapper(_ADDON_HANDLE, _ADDON_URL, addon) token_resolver = tokenresolver.TokenResolver(kodi_wrapper) - stream_service = streamservice.StreamService(vrtplayer.VRTPlayer.VRT_BASE, - vrtplayer.VRTPlayer.VRTNU_BASE_URL, - kodi_wrapper, token_resolver) + stream_service = streamservice.StreamService(kodi_wrapper, token_resolver) api_helper = vrtapihelper.VRTApiHelper(kodi_wrapper) vrt_player = vrtplayer.VRTPlayer(addon.getAddonInfo('path'), kodi_wrapper, stream_service, api_helper) params = dict(parse_qsl(params_string)) @@ -46,12 +44,7 @@ def router(params_string): tv_guide = tvguide.TVGuide(addon.getAddonInfo('path'), kodi_wrapper) tv_guide.show_tvguide(params) elif action == actions.PLAY: - video = dict( - video_url=params.get('video_url'), - video_id=params.get('video_id'), - publication_id=params.get('publication_id'), - ) - vrt_player.play(video) + vrt_player.play(params) else: vrt_player.show_main_menu_items() diff --git a/resources/language/resource.language.en_gb/strings.po b/resources/language/resource.language.en_gb/strings.po index 27fe0e3a1..6a0fa9237 100644 --- a/resources/language/resource.language.en_gb/strings.po +++ b/resources/language/resource.language.en_gb/strings.po @@ -132,36 +132,12 @@ msgid "Episode" msgstr "Episode" msgctxt "#32101" -msgid "Eén live" -msgstr "Eén live" +msgid "%s live" +msgstr "%s live" msgctxt "#32102" -msgid "Watch Eén live TV stream (via Internet)" -msgstr "Watch Eén live TV stream (via Internet)" - -msgctxt "#32111" -msgid "Canvas live" -msgstr "Canvas live" - -msgctxt "#32112" -msgid "Watch Canvas live TV stream (via Internet)" -msgstr "Watch Canvas live TV stream (via Internet)" - -msgctxt "#32121" -msgid "Ketnet live" -msgstr "Ketnet live" - -msgctxt "#32122" -msgid "Watch Ketnet live TV stream (via Internet)" -msgstr "Watch Ketnet live TV stream (via Internet)" - -msgctxt "#32131" -msgid "Sporza live" -msgstr "Sporza live" - -msgctxt "#32132" -msgid "Watch Sporza live TV stream (via Internet)" -msgstr "Watch Sporza live TV stream via (Internet)" +msgid "Watch %s live TV stream (via Internet)" +msgstr "Watch %s live TV stream (via Internet)" msgctxt "#32201" msgid "[B][COLOR red]Geo-blocked[/COLOR][/B]\n" diff --git a/resources/language/resource.language.nl_nl/strings.po b/resources/language/resource.language.nl_nl/strings.po index c081e7ae8..ebec60d4b 100644 --- a/resources/language/resource.language.nl_nl/strings.po +++ b/resources/language/resource.language.nl_nl/strings.po @@ -133,36 +133,12 @@ msgid "Episode" msgstr "Aflevering" msgctxt "#32101" -msgid "Eén live" -msgstr "Eén live" +msgid "%s live" +msgstr "%s live" msgctxt "#32102" -msgid "Watch Eén live TV stream (via Internet)" -msgstr "Bekijk Eén live tv stream (via Internet)" - -msgctxt "#32111" -msgid "Canvas live" -msgstr "Canvas live" - -msgctxt "#32112" -msgid "Watch Canvas live TV stream (via Internet)" -msgstr "Bekijk Canvas live tv stream (via Internet)" - -msgctxt "#32121" -msgid "Ketnet live" -msgstr "Ketnet live" - -msgctxt "#32122" -msgid "Watch Ketnet live TV stream (via Internet)" -msgstr "Bekijk Ketnet live tv stream (via Internet)" - -msgctxt "#32131" -msgid "Sporza live" -msgstr "Sporza live" - -msgctxt "#32132" -msgid "Watch Sporza live TV stream (via Internet)" -msgstr "Bekijk Sporza live tv stream (via Internet)" +msgid "Watch %s live TV stream (via Internet)" +msgstr "Bekijk %s live tv stream (via Internet)" msgctxt "#32201" msgid "[B][COLOR red]Geo-blocked[/COLOR][/B]\n" diff --git a/resources/lib/kodiwrappers/kodiwrapper.py b/resources/lib/kodiwrappers/kodiwrapper.py index 1f736fa20..3b314be98 100644 --- a/resources/lib/kodiwrappers/kodiwrapper.py +++ b/resources/lib/kodiwrappers/kodiwrapper.py @@ -90,7 +90,7 @@ def show_listing(self, list_items, sort='unsorted', ascending=True, content_type list_item.setArt(title_item.art_dict) if title_item.video_dict: - list_item.setInfo('video', infoLabels=title_item.video_dict) + list_item.setInfo(type='video', infoLabels=title_item.video_dict) listing.append((url, list_item, not title_item.is_playable)) diff --git a/resources/lib/vrtplayer/__init__.py b/resources/lib/vrtplayer/__init__.py index e69de29bb..04af2253e 100644 --- a/resources/lib/vrtplayer/__init__.py +++ b/resources/lib/vrtplayer/__init__.py @@ -0,0 +1,93 @@ +# -*- coding: utf-8 -*- + +# GNU General Public License v3.0 (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, unicode_literals + +# Fallback list of categories so we don't depend on web scraping only +CATEGORIES = [ + dict(name='Audiodescriptie', id='met-audiodescriptie'), + dict(name='Cultuur', id='cultuur'), + dict(name='Docu', id='docu'), + dict(name='Entertainment', id='entertainment'), + dict(name='Films', id='films'), + dict(name='Human interest', id='human-interest'), + dict(name='Humor', id='humor'), + dict(name='Kinderen', id='voor-kinderen'), + dict(name='Koken', id='koken'), + dict(name='Lifestyle', id='lifestyle'), + dict(name='Muziek', id='muziek'), + dict(name='Nieuws en actua', id='nieuws-en-actua'), + dict(name='Series', id='series'), + dict(name='Sport', id='sport'), + dict(name='Talkshows', id='talkshows'), + dict(name='Vlaamse Gebarentaal', id='met-gebarentaal'), + dict(name='Wetenschap en natuur', id='wetenschap-en-natuur'), +] + +CHANNELS = { + 'een': dict( + id='O8', + type='tv', + name='Eén', + studio='Een', + live_stream='https://www.vrt.be/vrtnu/kanalen/een/', + + ), + 'canvas': dict( + id='1H', + type='tv', + name='Canvas', + studio='Canvas', + live_stream='https://www.vrt.be/vrtnu/kanalen/canvas/', + ), + 'ketnet': dict( + id='O9', + type='tv', + name='Ketnet', + studio='Ketnet', + live_stream='https://www.vrt.be/vrtnu/kanalen/ketnet/', + ), + 'ketnet-jr': dict( + id='1H', + type='tv', + name='Ketnet Junior', + studio='Ketnet Junior', + ), + 'radio1': dict( + id='11', + type='radio', + name='Radio 1', + studio='Radio 1', + ), + 'radio2': dict( + id='24', + type='radio', + name='Radio 2', + studio='Radio 2', + ), + 'klara': dict( + id='31', + type='radio', + name='Klara', + studio='Klara', + ), + 'stubru': dict( + id='41', + type='radio', + name='Studio Brussel', + studio='Studio Brussel', + ), + 'mnm': dict( + id='55', + type='radio', + name='MNM', + studio='MNM', + ), + 'vrtnws': dict( + id='13', + type='radio', + name='VRT NWS', + studio='VRT NWS', + ), +} diff --git a/resources/lib/vrtplayer/metadatacreator.py b/resources/lib/vrtplayer/metadatacreator.py index 05e1c8c4c..da746f0ca 100644 --- a/resources/lib/vrtplayer/metadatacreator.py +++ b/resources/lib/vrtplayer/metadatacreator.py @@ -6,6 +6,8 @@ from datetime import datetime import dateutil.tz +from resources.lib.vrtplayer import CHANNELS + class MetadataCreator: @@ -160,8 +162,9 @@ def get_video_dict(self): video_dict = dict() if self.brands: - video_dict['studio'] = self.brands -# video_dict['channelname'] = self.brands[0] if isinstance(self.brands, list) else self.brands + video_dict['studio'] = CHANNELS.get(self.brands[0], {'studio': 'VRT'}).get('studio') + else: + video_dict['studio'] = 'VRT' if self.datetime: video_dict['aired'] = self.datetime.strftime('%Y-%m-%d %H:%M:%S') diff --git a/resources/lib/vrtplayer/streamservice.py b/resources/lib/vrtplayer/streamservice.py index 09525b64b..fcdd65cf2 100644 --- a/resources/lib/vrtplayer/streamservice.py +++ b/resources/lib/vrtplayer/streamservice.py @@ -23,12 +23,10 @@ class StreamService: _VUALTO_API_URL = 'https://media-services-public.vrt.be/vualto-video-aggregator-web/rest/external/v1' _CLIENT = 'vrtvideo' - def __init__(self, vrt_base, vrtnu_base_url, kodi_wrapper, token_resolver): + def __init__(self, kodi_wrapper, token_resolver): self._kodi_wrapper = kodi_wrapper self._proxies = self._kodi_wrapper.get_proxies() self.token_resolver = token_resolver - self._vrt_base = vrt_base - self._vrtnu_base_url = vrtnu_base_url self._create_settings_dir() self._can_play_drm = self._kodi_wrapper.can_play_drm() self._license_url = None diff --git a/resources/lib/vrtplayer/tvguide.py b/resources/lib/vrtplayer/tvguide.py index 91f809df7..9925cf1dd 100644 --- a/resources/lib/vrtplayer/tvguide.py +++ b/resources/lib/vrtplayer/tvguide.py @@ -10,22 +10,7 @@ import requests from resources.lib.helperobjects import helperobjects -from resources.lib.vrtplayer import actions, metadatacreator, statichelper - -CHANNELS = dict( - een=dict( - id='O8', - name='Eén', - ), - canvas=dict( - id='1H', - name='Canvas', - ), - ketnet=dict( - id='O9', - name='Ketnet', - ), -) +from resources.lib.vrtplayer import CHANNELS, actions, metadatacreator, statichelper DATE_STRINGS = { '-2': 32330, # 2 days ago @@ -82,7 +67,7 @@ def show_tvguide(self, params): url_dict=dict(action=actions.LISTING_TVGUIDE, date=date, channel=channel), is_playable=False, art_dict=dict(thumb=self.__get_media(channel + '.png'), icon='DefaultAddonPVRClient.png', fanart='DefaultAddonPVRClient.png'), - video_dict=dict(plot=plot), + video_dict=dict(plot=plot, studio=CHANNELS[channel]['studio']), ), ) self._kodi_wrapper.show_listing(channel_items, content_type='files') diff --git a/resources/lib/vrtplayer/vrtapihelper.py b/resources/lib/vrtplayer/vrtapihelper.py index f2ab1b9c1..ce5d7ede7 100644 --- a/resources/lib/vrtplayer/vrtapihelper.py +++ b/resources/lib/vrtplayer/vrtapihelper.py @@ -20,10 +20,9 @@ class VRTApiHelper: _VRT_BASE = 'https://www.vrt.be' - _VRTNU_API_BASE = 'https://vrtnu-api.vrt.be' - _VRTNU_SEARCH_URL = _VRTNU_API_BASE + '/search' - _VRTNU_SUGGEST_URL = _VRTNU_API_BASE + '/suggest' - _VRTNU_SCREENSHOT_URL = _VRTNU_API_BASE + '/screenshots' + _VRTNU_SEARCH_URL = 'https://vrtnu-api.vrt.be/search' + _VRTNU_SUGGEST_URL = 'https://vrtnu-api.vrt.be/suggest' + _VRTNU_SCREENSHOT_URL = 'https://vrtnu-api.vrt.be/screenshots' def __init__(self, kodi_wrapper): self._kodi_wrapper = kodi_wrapper diff --git a/resources/lib/vrtplayer/vrtplayer.py b/resources/lib/vrtplayer/vrtplayer.py index d8b6e56f3..e465223d0 100644 --- a/resources/lib/vrtplayer/vrtplayer.py +++ b/resources/lib/vrtplayer/vrtplayer.py @@ -8,18 +8,38 @@ import requests from resources.lib.helperobjects import helperobjects -from resources.lib.vrtplayer import actions, statichelper +from resources.lib.vrtplayer import CATEGORIES, CHANNELS, actions, statichelper -class VRTPlayer: +def get_categories(proxies=None): + response = requests.get('https://www.vrt.be/vrtnu/categorieen/', proxies=proxies) + tiles = SoupStrainer('a', {'class': 'nui-tile'}) + soup = BeautifulSoup(response.content, 'html.parser', parse_only=tiles) + + categories = [] + for tile in soup.find_all(class_='nui-tile'): + categories.append(dict( + id=tile.get('href').split('/')[-2], + thumbnail=get_category_thumbnail(tile), + name=get_category_title(tile), + )) + + return categories + + +def get_category_thumbnail(element): + raw_thumbnail = element.find(class_='media').get('data-responsive-image', 'DefaultGenre.png') + return statichelper.add_https_method(raw_thumbnail) - # URLs van https://services.vrt.be/videoplayer/r/live.json - _EEN_LIVESTREAM = 'https://www.vrt.be/vrtnu/kanalen/een/' - _CANVAS_LIVESTREAM = 'https://www.vrt.be/vrtnu/kanalen/canvas/' - _KETNET_LIVESTREAM = 'https://www.vrt.be/vrtnu/kanalen/ketnet/' - VRT_BASE = 'https://www.vrt.be/' - VRTNU_BASE_URL = VRT_BASE + '/vrtnu' +def get_category_title(element): + found_element = element.find('h3') + if found_element is not None: + return statichelper.strip_newlines(found_element.contents[0]) + return '' + + +class VRTPlayer: def __init__(self, addon_path, kodi_wrapper, stream_service, api_helper): self._addon_path = addon_path @@ -63,77 +83,55 @@ def show_tvshow_menu_items(self, path): self._kodi_wrapper.show_listing(tvshow_items, sort='label', content_type='tvshows') def show_category_menu_items(self): - joined_url = self.VRTNU_BASE_URL + '/categorieen/' - category_items = self.__get_category_menu_items(joined_url, {'class': 'nui-tile'}, actions.LISTING_CATEGORY_TVSHOWS) + category_items = self.__get_category_menu_items() self._kodi_wrapper.show_listing(category_items, sort='label', content_type='files') - def play(self, video): - stream = self._stream_service.get_stream(video) - if stream is not None: - self._kodi_wrapper.play(stream) - def show_livestream_items(self): - livestream_items = [ - helperobjects.TitleItem( - title=self._kodi_wrapper.get_localized_string(32101), - url_dict=dict(action=actions.PLAY, video_url=self._EEN_LIVESTREAM), - is_playable=True, - art_dict=dict(thumb=self.__get_media('een.png'), icon='DefaultAddonPVRClient.png', fanart=self._api_helper.get_live_screenshot('een')), - video_dict=dict(plot=self._kodi_wrapper.get_localized_string(32201) + '\n' + self._kodi_wrapper.get_localized_string(32102)), - ), - helperobjects.TitleItem( - title=self._kodi_wrapper.get_localized_string(32111), - url_dict=dict(action=actions.PLAY, video_url=self._CANVAS_LIVESTREAM), + livestream_items = [] + for channel in ['een', 'canvas', 'ketnet']: + livestream_items.append(helperobjects.TitleItem( + title=self._kodi_wrapper.get_localized_string(32101) % CHANNELS[channel]['name'], + url_dict=dict(action=actions.PLAY, video_url=CHANNELS[channel]['live_stream']), is_playable=True, - art_dict=dict(thumb=self.__get_media('canvas.png'), icon='DefaultAddonPVRClient.png', fanart=self._api_helper.get_live_screenshot('canvas')), - video_dict=dict(plot=self._kodi_wrapper.get_localized_string(32201) + '\n' + self._kodi_wrapper.get_localized_string(32112)), - ), - helperobjects.TitleItem( - title=self._kodi_wrapper.get_localized_string(32121), - url_dict=dict(action=actions.PLAY, video_url=self._KETNET_LIVESTREAM), - is_playable=True, - art_dict=dict(thumb=self.__get_media('ketnet.png'), icon='DefaultAddonPVRClient.png', fanart=self._api_helper.get_live_screenshot('ketnet')), - video_dict=dict(plot=self._kodi_wrapper.get_localized_string(32201) + '\n' + self._kodi_wrapper.get_localized_string(32122)), - ), - ] - self._kodi_wrapper.show_listing(livestream_items, content_type='videos') + art_dict=dict(thumb=self.__get_media(channel + '.png'), icon='DefaultAddonPVRClient.png', fanart=self._api_helper.get_live_screenshot(channel)), + video_dict=dict( + title=self._kodi_wrapper.get_localized_string(32101) % CHANNELS[channel]['name'], + plot=self._kodi_wrapper.get_localized_string(32201) + '\n' + self._kodi_wrapper.get_localized_string(32102) % CHANNELS[channel]['name'], + studio=CHANNELS[channel]['studio'], + mediatype='video', + ), + )) + + self._kodi_wrapper.show_listing(livestream_items, sort='unsorted', content_type='videos') def show_episodes(self, path): episode_items, sort, ascending = self._api_helper.get_episode_items(path) self._kodi_wrapper.show_listing(episode_items, sort=sort, ascending=ascending, content_type='episodes', cache=False) + def play(self, params): + stream = self._stream_service.get_stream(params) + if stream is not None: + self._kodi_wrapper.play(stream) + def __get_media(self, file_name): return os.path.join(self._addon_path, 'resources', 'media', file_name) - def __get_category_menu_items(self, url, soupstrainer_parser_selector, routing_action, video_dict_action=None): - response = requests.get(url, proxies=self._proxies) - tiles = SoupStrainer('a', soupstrainer_parser_selector) - soup = BeautifulSoup(response.content, 'html.parser', parse_only=tiles) - listing = [] - for tile in soup.find_all(class_='nui-tile'): - category = tile.get('href').split('/')[-2] - thumbnail, title = self.__get_category_thumbnail_and_title(tile) - video_dict = None - if video_dict_action is not None: - video_dict = video_dict_action(tile) - - listing.append(helperobjects.TitleItem(title=title, - url_dict=dict(action=routing_action, video_url=category), - is_playable=False, - art_dict=dict(thumb=thumbnail, icon='DefaultGenre.png', fanart=thumbnail), - video_dict=video_dict)) - return listing - - @staticmethod - def __format_category_image_url(element): - raw_thumbnail = element.find(class_='media').get('data-responsive-image', 'DefaultGenre.png') - return statichelper.add_https_method(raw_thumbnail) - - @staticmethod - def __get_category_thumbnail_and_title(element): - thumbnail = VRTPlayer.__format_category_image_url(element) - found_element = element.find('h3') - title = '' - if found_element is not None: - title = statichelper.strip_newlines(found_element.contents[0]) - return thumbnail, title + def __get_category_menu_items(self): + try: + categories = get_categories(self._proxies) + except Exception: + categories = [] + + if not categories: + categories = CATEGORIES + + category_items = [] + for category in categories: + thumbnail = category.get('thumbnail', 'DefaultGenre.png') + category_items.append(helperobjects.TitleItem(title=category['name'], + url_dict=dict(action=actions.LISTING_CATEGORY_TVSHOWS, video_url=category['id']), + is_playable=False, + art_dict=dict(thumb=thumbnail, icon='DefaultGenre.png', fanart=thumbnail), + video_dict=None)) + + return category_items diff --git a/test/vrtplayertests.py b/test/vrtplayertests.py index 0a8781819..9c402f5cc 100644 --- a/test/vrtplayertests.py +++ b/test/vrtplayertests.py @@ -5,7 +5,8 @@ from __future__ import absolute_import, division, unicode_literals import mock import unittest -from resources.lib.vrtplayer import vrtapihelper, vrtplayer + +from resources.lib.vrtplayer import CATEGORIES, vrtapihelper, vrtplayer class TestVRTPlayer(unittest.TestCase): @@ -34,6 +35,12 @@ def test_show_videos_specific_seasons_shows_videos(self): player.show_episodes('/vrtnu/a-z/thuis/24.lists.all-episodes.relevant/') self.assertTrue(self._kodi_wrapper.show_listing.called) + def test_categories_scraping(self): + ''' Test to ensure our hardcoded categories conforms to scraped categories ''' + # Remove thumbnails from scraped categories first + categories = [dict(id=c['id'], name=c['name']) for c in vrtplayer.get_categories()] + self.assertTrue(categories == CATEGORIES) + if __name__ == '__main__': unittest.main()