From 2f0f7dcd47272a56e019cf4d94305865a6b6353f Mon Sep 17 00:00:00 2001 From: Dag Wieers Date: Tue, 30 Apr 2019 02:23:21 +0200 Subject: [PATCH] Include list of channels A central database of all channels is very useful, and opens up the possibility to fall back when web scraping fails. It also makes it possible to start adding audio streams, or studio translations. --- addon.py | 16 +- addon.xml | 2 +- .../resource.language.en_gb/strings.po | 32 +-- .../resource.language.nl_nl/strings.po | 32 +-- resources/lib/kodiwrappers/kodiwrapper.py | 11 +- resources/lib/vrtplayer/__init__.py | 215 ++++++++++++++++++ resources/lib/vrtplayer/actions.py | 1 + resources/lib/vrtplayer/metadatacreator.py | 7 +- resources/lib/vrtplayer/streamservice.py | 4 +- resources/lib/vrtplayer/tvguide.py | 19 +- resources/lib/vrtplayer/vrtapihelper.py | 7 +- resources/lib/vrtplayer/vrtplayer.py | 156 +++++++------ test/vrtplayertests.py | 9 +- 13 files changed, 351 insertions(+), 160 deletions(-) diff --git a/addon.py b/addon.py index e10c23346..8b5974426 100644 --- a/addon.py +++ b/addon.py @@ -25,12 +25,11 @@ 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)) + content_type = params.get('content_type') action = params.get('action') if action == actions.LISTING_AZ_TVSHOWS: vrt_player.show_tvshow_menu_items(path=None) @@ -46,12 +45,11 @@ 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) + elif action == actions.PLAY_RADIO: + vrt_player.play_radio(params) + elif content_type == 'audio': + vrt_player.show_radio_menu_items() else: vrt_player.show_main_menu_items() diff --git a/addon.xml b/addon.xml index 6c01cf51d..8fd0f9ade 100644 --- a/addon.xml +++ b/addon.xml @@ -15,7 +15,7 @@ - video + video audio 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..8b213d041 100644 --- a/resources/lib/kodiwrappers/kodiwrapper.py +++ b/resources/lib/kodiwrappers/kodiwrapper.py @@ -50,7 +50,7 @@ def __init__(self, handle, url, addon): self._addon = addon self._addon_id = addon.getAddonInfo('id') - def show_listing(self, list_items, sort='unsorted', ascending=True, content_type='episodes', cache=True): + def show_listing(self, list_items, sort='unsorted', ascending=True, content_type='episodes', list_type='video', cache=True): listing = [] xbmcplugin.setContent(self._handle, content=content_type) @@ -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=list_type, infoLabels=title_item.video_dict) listing.append((url, list_item, not title_item.is_playable)) @@ -121,6 +121,13 @@ def play(self, video): xbmc.sleep(100) xbmc.Player().showSubtitles(subtitles_visible) + def play_radio(self, stream): + xbmc.Player().play(stream) + play_item = xbmcgui.ListItem(path=stream) + xbmcplugin.setResolvedUrl(self._handle, True, listitem=play_item) + while not xbmc.Player().isPlaying() and not xbmc.Monitor().abortRequested(): + xbmc.sleep(100) + def show_ok_dialog(self, title, message): xbmcgui.Dialog().ok(self._addon.getAddonInfo('name'), title, message) diff --git a/resources/lib/vrtplayer/__init__.py b/resources/lib/vrtplayer/__init__.py index e69de29bb..db4d2b72e 100644 --- a/resources/lib/vrtplayer/__init__.py +++ b/resources/lib/vrtplayer/__init__.py @@ -0,0 +1,215 @@ +# -*- 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', + tagline='', + studio='Een', + website='https://een.be/', + live_stream='https://www.vrt.be/vrtnu/kanalen/een/', + + ), + 'canvas': dict( + id='1H', + type='tv', + name='Canvas', + tagline='', + studio='Canvas', + website='https://canvas.be/', + live_stream='https://www.vrt.be/vrtnu/kanalen/canvas/', + ), + 'ketnet': dict( + id='O9', + type='tv', + name='Ketnet', + tagline='', + studio='Ketnet', + website='https://ketnet.be/', + live_stream='https://www.vrt.be/vrtnu/kanalen/ketnet/', + ), + 'ketnet-jr': dict( + id='1H', + type='tv', + name='Ketnet Junior', + tagline='', + studio='Ketnet Junior', + website='https://ketnet.be/', + ), + 'radio1': dict( + id='11', + type='radio', + name='Radio 1', + tagline='Altijd Benieuwd', + studio='Radio 1', + hls_128='https://live-radio-cf-vrt.akamaized.net/groupb/live/47303075-8243-434b-8199-2e62cf4dd97a/live.isml/.m3u8', + mpeg_dash_128='https://live-radio-cf-vrt.akamaized.net/groupb/live/47303075-8243-434b-8199-2e62cf4dd97a/live.isml/.mpd', + mp3_128='http://icecast.vrtcdn.be/radio1-high.mp3', + mp3_64='http://icecast.vrtcdn.be/radio1-mid.mp3', + aac_128='http://icecast.vrtcdn.be/radio1.aac', + ), + 'radio2': dict( + id='24', + type='radio', + name='Radio 2', + tagline='De grootste familie', + studio='Radio 2', + hls_128='https://live-radio-cf-vrt.akamaized.net/groupb/live/93a8a402-9008-4a97-b473-bc107be7524d/live.isml/.m3u8', + mpeg_dash_128='https://live-radio-cf-vrt.akamaized.net/groupb/live/93a8a402-9008-4a97-b473-bc107be7524d/live.isml/.mpd', + mp3_128='http://icecast.vrtcdn.be/ra2ovl-high.mp3', + mp3_64='http://icecast.vrtcdn.be/ra2ovl-mid.mp3', + aac_128='http://icecast.vrtcdn.be/ra2ovl.aac', + ), + 'klara': dict( + id='31', + type='radio', + tagline='Blijf verwonderd', + name='Klara', + studio='Klara', + hls_128='https://live-radio-cf-vrt.akamaized.net/groupa/live/a9f36fda-cb3c-4b4e-9405-a5bba55654c0/live.isml/.m3u8', + mpeg_dash_128='https://live-radio-cf-vrt.akamaized.net/groupa/live/a9f36fda-cb3c-4b4e-9405-a5bba55654c0/live.isml/.mpd', + mp3_128='http://icecast.vrtcdn.be/klara-high.mp3', + mp3_64='http://icecast.vrtcdn.be/klara-mid.mp3', + aac_128='http://icecast.vrtcdn.be/klara.aac', + ), + 'klara-continuo': dict( + id='32', + type='radio', + tagline='Non-stop klassieke muziek', + name='Klara Continuo', + studio='Klara Continuo', + hls_128='https://live-radio-cf-vrt.akamaized.net/groupa/live/0d06dbbe-92d4-4cfe-a0b3-ccc6b7a32ec4/live.isml/.m3u8', + mpeg_dash_128='https://live-radio-cf-vrt.akamaized.net/groupa/live/0d06dbbe-92d4-4cfe-a0b3-ccc6b7a32ec4/live.isml/.mpd', + mp3_128='http://icecast.vrtcdn.be/klaracontinuo-high.mp3', + mp3_64='http://icecast.vrtcdn.be/klaracontinuo-mid.mp3', + aac_128='http://icecast.vrtcdn.be/klaracontinuo.aac', + ), + 'stubru': dict( + id='41', + type='radio', + name='Studio Brussel', + tagline='Life is Music', + studio='Studio Brussel', + website='https://stubru.be/', + hls_128='https://live-radio-cf-vrt.akamaized.net/groupc/live/f404f0f3-3917-40fd-80b6-a152761072fe/live.isml/.m3u8', + mpeg_dash_128='https://live-radio-cf-vrt.akamaized.net/groupc/live/f404f0f3-3917-40fd-80b6-a152761072fe/live.isml/.mpd', + mp3_128='http://icecast.vrtcdn.be/stubru-high.mp3', + mp3_64='http://icecast.vrtcdn.be/stubru-mid.mp3', + aac_128='http://icecast.vrtcdn.be/stubru.aac', + ), + 'de-tijdloze': dict( + id='44', + type='radio', + name='De Tijdloze', + tagline='Altijd en overal de beste Tijdloze muziek', + studio='Studio Brussel', + website='https://stubru.be/', + hls_128='https://live-radio-cf-vrt.akamaized.net/groupc/live/582109ca-1e71-4330-93fc-e9affee94d7d/live.isml/.m3u8', + mpeg_dash_128='https://live-radio-cf-vrt.akamaized.net/groupc/live/582109ca-1e71-4330-93fc-e9affee94d7d/live.isml/.mpd', + mp3_128='http://icecast.vrtcdn.be/stubru_tijdloze-high.mp3', + mp3_64='http://icecast.vrtcdn.be/stubru_tijdloze-mid.mp3', + aac_128='http://icecast.vrtcdn.be/stubru_tijdloze.aac', + ), + 'mnm': dict( + id='55', + type='radio', + name='MNM', + tagline='Music and More', + studio='MNM', + website='https://mnm.be/', + hls_128='https://live-radio-cf-vrt.akamaized.net/groupa/live/68dc3b80-040e-4a75-a394-72f3bb7aff9a/live.isml/.m3u8', + mpeg_dash_128='https://live-radio-cf-vrt.akamaized.net/groupa/live/68dc3b80-040e-4a75-a394-72f3bb7aff9a/live.isml/.mpd', + mp3_128='http://icecast.vrtcdn.be/mnm-high.mp3', + mp3_64='http://icecast.vrtcdn.be/mnm-mid.mp3', + aac_128='http://icecast.vrtcdn.be/mnm.aac', + ), + 'mnm-hits': dict( + id='56', + type='radio', + name='MNM Hits', + tagline='Music and More - The Hits', + studio='MNM', + website='http://mnm.be/', + hls_128='https://live-radio-cf-vrt.akamaized.net/groupb/live/35dd91de-0352-4865-8632-17e5af8dc6ba/live.isml/.m3u8', + mpeg_dash_128='https://live-radio-cf-vrt.akamaized.net/groupb/live/35dd91de-0352-4865-8632-17e5af8dc6ba/live.isml/.mpd', + mp3_128='http://icecast.vrtcdn.be/mnm_hits-high.mp3', + mp3_64='http://icecast.vrtcdn.be/mnm_hits-mid.mp3', + aac_128='http://icecast.vrtcdn.be/mnm_hits.aac', + ), + 'mnm-urbanice': dict( + id='57', + type='radio', + name='MNM UrbaNice', + tagline='De Online Urban Stream van MNM', + studio='MNM', + website='https://mnm.be/', + hls_128='https://live-radio-cf-vrt.akamaized.net/groupa/live/da0b681c-73db-4c9e-af32-7921591d3fbd/live.isml/.m3u8', + mpeg_dash_128='https://live-radio-cf-vrt.akamaized.net/groupa/live/da0b681c-73db-4c9e-af32-7921591d3fbd/live.isml/.mpd', + mp3_128='http://icecast.vrtcdn.be/mnm_urb-high.mp3', + mp3_64='http://icecast.vrtcdn.be/mnm_urb-mid.mp3', + aac_128='http://icecast.vrtcdn.be/mnm_urb.aac', + ), + 'ketnet-hits': dict( + id='O3', + type='radio', + name='Ketnet Hits', + tagline='De hipste, de coolste én de plezantste hits op een rijtje', + studio='Ketnet', + website='https://ketnet.be/', + hls_128='https://live-radio-cf-vrt.akamaized.net/groupa/live/014a9eea-af85-4da6-aab2-c472ca8d0149/live.isml/.m3u8', + mpeg_dash_128='https://live-radio-cf-vrt.akamaized.net/groupa/live/014a9eea-af85-4da6-aab2-c472ca8d0149/live.isml/.mpd', + mp3_128='http://icecast.vrtcdn.be/ketnetradio-high.mp3', + mp3_64='http://icecast.vrtcdn.be/ketnetradio-mid.mp3', + aac_128='http://icecast.vrtcdn.be/ketnetradio.aac', + ), + 'vrtnws': dict( + id='13', + type='radio', + name='VRT NWS', + tagline='Ieder moment het meest recente nieuws', + studio='VRT NWS', + website='https://www.vrtnieuws.be/', + hls_128='https://ondemand-radio-cf-vrt.akamaized.net/audioonly/content/fixed/11_11niws-snip_hi.mp4/.m3u8', + mpeg_dash_128='https://ondemand-radio-cf-vrt.akamaized.net/audioonly/content/fixed/11_11niws-snip_hi.mp4/.mpd', + mp3_128='https://progressive-audio.lwc.vrtcdn.be/content/fixed/11_11niws-snip_hi.mp3', + ), + 'vrt-event': dict( + id='71', + type='radio', + name='VRT Event', + tagline='', + studio='VRT', + website='https://vrt.be/', + hls_128='https://live-radio-cf-vrt.akamaized.net/groupa/live/779d53fc-9472-4fe8-b62a-1d38c5878c60/live.isml/.m3u8', + mpeg_dash_128='https://live-radio-cf-vrt.akamaized.net/groupa/live/779d53fc-9472-4fe8-b62a-1d38c5878c60/live.isml/.mpd', + mp3_128='http://icecast.vrtcdn.be/vrtevent-high.mp3', + mp3_64='http://icecast.vrtcdn.be/vrtevent-mid.mp3', + aac_128='http://icecast.vrtcdn.be/vrtevent.aac', + ), +} diff --git a/resources/lib/vrtplayer/actions.py b/resources/lib/vrtplayer/actions.py index 96ec0ee99..84ba3b5a3 100644 --- a/resources/lib/vrtplayer/actions.py +++ b/resources/lib/vrtplayer/actions.py @@ -14,3 +14,4 @@ LISTING_TVGUIDE = 'listingtvguide' PLAY = 'play' +PLAY_RADIO = 'play_radio' 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..4463b1d63 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/' +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 '' - VRT_BASE = 'https://www.vrt.be/' - VRTNU_BASE_URL = VRT_BASE + '/vrtnu' + +class VRTPlayer: def __init__(self, addon_path, kodi_wrapper, stream_service, api_helper): self._addon_path = addon_path @@ -63,77 +83,83 @@ 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), + 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('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), + 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_radio_menu_items(self): + radio_items = [] + for channel in CHANNELS: + if CHANNELS[channel]['type'] != 'radio': + continue + radio_items.append(helperobjects.TitleItem( + title=CHANNELS[channel]['name'], + url_dict=dict(action=actions.PLAY_RADIO, radio_stream=CHANNELS[channel]['hls_128']), 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='DefaultAddonMusic.png', icon='DefaultAddonMusic.png', fanart='DefaultAddonMusic.png'), + video_dict=dict( + title=CHANNELS[channel]['name'], + tagline=CHANNELS[channel]['tagline'], + plot=self._kodi_wrapper.get_localized_string(32102) % CHANNELS[channel]['name'], + studio=CHANNELS[channel]['studio'], + mediatype='music', + ), + )) + + self._kodi_wrapper.show_listing(radio_items, sort='label', content_type='music', list_type='video') 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 play_radio(self, params): + if 'channel' in params: + stream = CHANNELS[params.get('channel')]['hls_128'] + else: + stream = params.get('radio_stream') + self._kodi_wrapper.play_radio(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) + def __get_category_menu_items(self): + try: + categories = get_categories(self._proxies) + except Exception: + categories = CATEGORIES + + if not categories: + categories = CATEGORIES + 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), + for category in categories: + thumbnail = category.get('thumbnail', 'DefaultGenre.png') + listing.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=video_dict)) - return listing + video_dict=None)) - @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 + return listing 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()