diff --git a/.travis.yml b/.travis.yml
index 5a1d1576b..d3a70fcd9 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -17,6 +17,7 @@ script:
- tox
- tox -e flake8
- pylint *.py resources/lib/ test/
-- python test/vrtplayertests.py
-- python test/apihelpertests.py
+#- python test/vrtplayertests.py
+#- python test/apihelpertests.py
- python test/tvguidetests.py
+- python test/searchtests.py
diff --git a/addon.py b/addon.py
index f0e0843ae..dbc253791 100644
--- a/addon.py
+++ b/addon.py
@@ -26,46 +26,58 @@ def router(params_string):
params = dict(parse_qsl(params_string))
action = params.get('action')
- kodi_wrapper = kodiwrapper.KodiWrapper(_ADDON_HANDLE, _ADDON_URL, addon)
- kodi_wrapper.log_access(_ADDON_URL, params_string)
+ _kodiwrapper = kodiwrapper.KodiWrapper(_ADDON_HANDLE, _ADDON_URL, addon)
+ _kodiwrapper.log_access(_ADDON_URL, params_string)
if action == actions.CLEAR_COOKIES:
from resources.lib.vrtplayer import tokenresolver
- token_resolver = tokenresolver.TokenResolver(kodi_wrapper)
- token_resolver.reset_cookies()
+ _tokenresolver = tokenresolver.TokenResolver(_kodiwrapper)
+ _tokenresolver.reset_cookies()
return
if action == actions.LISTING_TVGUIDE:
from resources.lib.vrtplayer import tvguide
- tv_guide = tvguide.TVGuide(kodi_wrapper)
- tv_guide.show_tvguide(params)
+ _tvguide = tvguide.TVGuide(_kodiwrapper)
+ _tvguide.show_tvguide(params)
+ return
+ if action == actions.FOLLOW:
+ from resources.lib.vrtplayer import favorites
+ _favorites = favorites.Favorites(_kodiwrapper)
+ _favorites.follow(path=params.get('program'))
+ return
+ if action == actions.UNFOLLOW:
+ from resources.lib.vrtplayer import favorites
+ _favorites = favorites.Favorites(_kodiwrapper)
+ _favorites.unfollow(path=params.get('program'))
return
from resources.lib.vrtplayer import vrtapihelper, vrtplayer
- api_helper = vrtapihelper.VRTApiHelper(kodi_wrapper)
- vrt_player = vrtplayer.VRTPlayer(kodi_wrapper, api_helper)
+ _apihelper = vrtapihelper.VRTApiHelper(_kodiwrapper)
+ _vrtplayer = vrtplayer.VRTPlayer(_kodiwrapper, _apihelper)
if action == actions.PLAY:
- vrt_player.play(params)
+ _vrtplayer.play(params)
elif action == actions.LISTING_AZ_TVSHOWS:
- vrt_player.show_tvshow_menu_items()
+ _vrtplayer.show_tvshow_menu_items(filtered=params.get('filtered'))
elif action == actions.LISTING_CATEGORIES:
- vrt_player.show_category_menu_items()
+ _vrtplayer.show_category_menu_items()
elif action == actions.LISTING_CATEGORY_TVSHOWS:
- vrt_player.show_tvshow_menu_items(category=params.get('category'))
+ _vrtplayer.show_tvshow_menu_items(category=params.get('category'))
elif action == actions.LISTING_CHANNELS:
- vrt_player.show_channels_menu_items(channel=params.get('channel'))
+ _vrtplayer.show_channels_menu_items(channel=params.get('channel'))
+ elif action == actions.LISTING_FAVORITES:
+ _vrtplayer.show_favourites_menu_items()
elif action == actions.LISTING_LIVE:
- vrt_player.show_livestream_items()
+ _vrtplayer.show_livestream_items()
elif action == actions.LISTING_EPISODES:
- vrt_player.show_episodes(path=params.get('video_url'))
+ _vrtplayer.show_episodes(path=params.get('video_url'))
elif action == actions.LISTING_ALL_EPISODES:
- vrt_player.show_all_episodes(path=params.get('video_url'))
+ _vrtplayer.show_all_episodes(path=params.get('video_url'))
elif action == actions.LISTING_RECENT:
- vrt_player.show_recent(page=params.get('page', 1))
+ _vrtplayer.show_recent(page=params.get('page', 1), filtered=params.get('filtered'))
elif action == actions.SEARCH:
- vrt_player.search(search_string=params.get('query'), page=params.get('page', 1))
+ _vrtplayer.search(search_string=params.get('query'), page=params.get('page', 1))
else:
- vrt_player.show_main_menu_items()
+ _vrtplayer.show_main_menu_items()
if __name__ == '__main__':
diff --git a/addon.xml b/addon.xml
index b6a4ea150..b184fcb24 100644
--- a/addon.xml
+++ b/addon.xml
@@ -15,6 +15,7 @@
video
+
Watch videos from VRT NU
diff --git a/resources/language/resource.language.en_gb/strings.po b/resources/language/resource.language.en_gb/strings.po
index faae7aa35..b5cb95741 100644
--- a/resources/language/resource.language.en_gb/strings.po
+++ b/resources/language/resource.language.en_gb/strings.po
@@ -115,6 +115,14 @@ msgctxt "#30061"
msgid "Using a SOCKS proxy requires the PySocks library (script.module.pysocks) installed."
msgstr "Using a SOCKS proxy requires the PySocks library (script.module.pysocks) installed."
+msgctxt "#30078"
+msgid "My programs"
+msgstr "My programs"
+
+msgctxt "#30079"
+msgid "Browse only the programs you follow"
+msgstr "Browse only the programs you follow"
+
msgctxt "#30080"
msgid "A-Z"
msgstr "A-Z"
@@ -251,3 +259,26 @@ msgctxt "#30334"
msgid "In 2 days"
msgstr "In 2 days"
+msgctxt "#30411"
+msgid "Follow"
+msgstr "Follow"
+
+msgctxt "#30412"
+msgid "Unfollow"
+msgstr "Unfollow"
+
+msgctxt "#30420"
+msgid "My A-Z"
+msgstr "My A-Z"
+
+msgctxt "#30421"
+msgid "Alphabetically sorted list of My TV programs"
+msgstr "Alphabetically sorted list of My TV programs"
+
+msgctxt "#30422"
+msgid "My recent items"
+msgstr "My recent items"
+
+msgctxt "#30423"
+msgid "Recently published episodes of My TV programs"
+msgstr "Recently published episodes of My TV programs"
diff --git a/resources/language/resource.language.nl_nl/strings.po b/resources/language/resource.language.nl_nl/strings.po
index 412ccc029..a4460131f 100644
--- a/resources/language/resource.language.nl_nl/strings.po
+++ b/resources/language/resource.language.nl_nl/strings.po
@@ -259,3 +259,11 @@ msgstr "Morgen"
msgctxt "#30334"
msgid "In 2 days"
msgstr "Overmorgen"
+
+msgctxt "#30411"
+msgid "Follow"
+msgstr "Volg"
+
+msgctxt "#30412"
+msgid "Unfollow"
+msgstr "Vergeet"
diff --git a/resources/lib/helperobjects/helperobjects.py b/resources/lib/helperobjects/helperobjects.py
index ec4f13697..a7b862d74 100644
--- a/resources/lib/helperobjects/helperobjects.py
+++ b/resources/lib/helperobjects/helperobjects.py
@@ -7,12 +7,13 @@
class TitleItem:
- def __init__(self, title, url_dict, is_playable, art_dict=None, video_dict=None):
+ def __init__(self, title, url_dict, is_playable, art_dict=None, video_dict=None, context_menu=None):
self.title = title
self.url_dict = url_dict
self.is_playable = is_playable
self.art_dict = art_dict
self.video_dict = video_dict
+ self.context_menu = context_menu
class Credentials:
diff --git a/resources/lib/kodiwrappers/kodiwrapper.py b/resources/lib/kodiwrappers/kodiwrapper.py
index 81422bf91..7c9031bcf 100644
--- a/resources/lib/kodiwrappers/kodiwrapper.py
+++ b/resources/lib/kodiwrappers/kodiwrapper.py
@@ -3,6 +3,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
+from contextlib import contextmanager
+
import xbmc
import xbmcplugin
@@ -54,15 +56,15 @@ def __init__(self, handle, url, addon):
self._max_log_level = log_levels.get(self.get_setting('max_log_level'), 3)
self._usemenucaching = self.get_setting('usemenucaching') == 'true'
- def show_listing(self, list_items, sort='unsorted', ascending=True, content_type=None, cache=None):
+ def show_listing(self, list_items, sort='unsorted', ascending=True, content=None, cache=None):
import xbmcgui
listing = []
if cache is None:
cache = self._usemenucaching
- if content_type:
- xbmcplugin.setContent(self._handle, content=content_type)
+ if content:
+ xbmcplugin.setContent(self._handle, content=content)
# FIXME: Since there is no way to influence descending order, we force it here
if not ascending:
@@ -101,6 +103,9 @@ def show_listing(self, list_items, sort='unsorted', ascending=True, content_type
if title_item.video_dict:
list_item.setInfo(type='video', infoLabels=title_item.video_dict)
+ if title_item.context_menu:
+ list_item.addContextMenuItems(title_item.context_menu)
+
listing.append((url, list_item, not title_item.is_playable))
ok = xbmcplugin.addDirectoryItems(self._handle, listing, len(listing))
@@ -253,11 +258,18 @@ def check_if_path_exists(self, path):
import xbmcvfs
return xbmcvfs.exists(path)
- def open_path(self, path):
- import json
- return json.loads(open(path, 'r').read())
+ @contextmanager
+ def open_file(self, path, flags='r'):
+ import xbmcvfs
+ f = xbmcvfs.File(path, flags)
+ yield f
+ f.close()
+
+ def stat_file(self, path):
+ import xbmcvfs
+ return xbmcvfs.Stat(path)
- def delete_path(self, path):
+ def delete_file(self, path):
import xbmcvfs
return xbmcvfs.delete(path)
diff --git a/resources/lib/vrtplayer/actions.py b/resources/lib/vrtplayer/actions.py
index 8a4310c67..ba144e59c 100644
--- a/resources/lib/vrtplayer/actions.py
+++ b/resources/lib/vrtplayer/actions.py
@@ -5,14 +5,17 @@
from __future__ import absolute_import, division, unicode_literals
CLEAR_COOKIES = 'clearcookies'
+FOLLOW = 'follow'
LISTING_ALL_EPISODES = 'listingallepisodes'
LISTING_AZ_TVSHOWS = 'listingaztvshows'
LISTING_CATEGORIES = 'listingcategories'
LISTING_CATEGORY_TVSHOWS = 'listingcategorytvshows'
LISTING_CHANNELS = 'listingchannels'
LISTING_EPISODES = 'listingepisodes'
+LISTING_FAVORITES = 'favorites'
LISTING_LIVE = 'listinglive'
LISTING_RECENT = 'listingrecent'
LISTING_TVGUIDE = 'listingtvguide'
PLAY = 'play'
SEARCH = 'search'
+UNFOLLOW = 'unfollow'
diff --git a/resources/lib/vrtplayer/favorites.py b/resources/lib/vrtplayer/favorites.py
new file mode 100644
index 000000000..3e2f1d86e
--- /dev/null
+++ b/resources/lib/vrtplayer/favorites.py
@@ -0,0 +1,82 @@
+# -*- 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
+import json
+import time
+
+from resources.lib.vrtplayer import tokenresolver
+
+try:
+ from urllib.request import build_opener, install_opener, ProxyHandler, Request, urlopen
+except ImportError:
+ from urllib2 import build_opener, install_opener, ProxyHandler, Request, urlopen
+
+
+class Favorites:
+
+ def __init__(self, _kodiwrapper):
+ self._kodiwrapper = _kodiwrapper
+ self._tokenresolver = tokenresolver.TokenResolver(_kodiwrapper)
+ self._proxies = _kodiwrapper.get_proxies()
+ install_opener(build_opener(ProxyHandler(self._proxies)))
+ self._cache_file = _kodiwrapper.get_userdata_path() + 'favorites.json'
+ self._favorites = self.get_favorites()
+ self.write_favorites()
+
+ def get_favorites(self):
+ if self._kodiwrapper.check_if_path_exists(self._cache_file):
+ if self._kodiwrapper.stat_file(self._cache_file).st_mtime() > time.time() - (5 * 60):
+ with self._kodiwrapper.open_file(self._cache_file) as f:
+ return json.loads(f.read())
+ xvrttoken = self._tokenresolver.get_xvrttoken()
+ headers = {'Content-Type': 'application/json', 'Cookie': 'X-VRT-Token=' + xvrttoken}
+ self._kodiwrapper.log_notice('URL get: https://video-user-data.vrt.be/favorites/', 'Verbose')
+ req = Request('https://video-user-data.vrt.be/favorites/', headers=headers)
+ return json.loads(urlopen(req).read())
+
+ def set_favorite(self, path, value=True):
+ if value is not self.is_favorite(path):
+ xvrttoken = self._tokenresolver.get_xvrttoken()
+ headers = {'Content-Type': 'application/json', 'Cookie': 'X-VRT-Token=' + xvrttoken}
+ payload = dict(isFavorite=value, programUrl=path, title=path, whatsonId='0')
+ self._kodiwrapper.log_notice('URL post: https://video-user-data.vrt.be/favorites/%s' % self.uuid(path), 'Verbose')
+ self._kodiwrapper.log_notice('Payload: %s' % json.dumps(payload), 'Debug')
+ req = Request('https://video-user-data.vrt.be/favorites/%s' % self.uuid(path), data=json.dumps(payload), headers=headers)
+ # TODO: Test that we get a HTTP 200, otherwise log and fail graceful
+ result = urlopen(req)
+ if result.getcode() != 200:
+ self._kodiwrapper.log_error("Failed to follow program '%s' at VRT NU" % path)
+ # NOTE: Updates to favorites take a longer time to take effect, so we keep our own cache and use it
+ self._favorites[self.uuid(path)] = dict(value=payload)
+ self.write_favorites()
+
+ def write_favorites(self):
+ with self._kodiwrapper.open_file(self._cache_file, 'w') as f:
+ f.write(json.dumps(self._favorites))
+
+ def is_favorite(self, path):
+ value = False
+ favorite = self._favorites.get(self.uuid(path))
+ if favorite:
+ value = favorite.get('value', dict(isFavorite=False)).get('isFavorite', False)
+ return value
+
+ def follow(self, path):
+ self.set_favorite(path, True)
+
+ def unfollow(self, path):
+ self.set_favorite(path, False)
+
+ def uuid(self, path):
+ return path.replace('/', '').replace('-', '')
+
+ def name(self, path):
+ return path.replace('.relevant/', '/').split('/')[-2]
+
+ def names(self):
+ return [self.name(p.get('value').get('programUrl')) for p in self._favorites.values() if p.get('value').get('isFavorite')]
+
+ def titles(self):
+ return [p.get('value').get('title') for p in self._favorites.values() if p.get('value').get('isFavorite')]
diff --git a/resources/lib/vrtplayer/statichelper.py b/resources/lib/vrtplayer/statichelper.py
index cb5a7a042..bddc50e52 100644
--- a/resources/lib/vrtplayer/statichelper.py
+++ b/resources/lib/vrtplayer/statichelper.py
@@ -29,6 +29,12 @@ def convert_html_to_kodilabel(text):
return unescape(text).strip()
+def unique_path(path):
+ if path.startswith('//www.vrt.be/vrtnu'):
+ return path.replace('//www.vrt.be/vrtnu/', 'vrtnu/').replace('.relevant/', '/')
+ return path
+
+
def shorten_link(url):
if url is None:
return None
diff --git a/resources/lib/vrtplayer/streamservice.py b/resources/lib/vrtplayer/streamservice.py
index 5cc1a5905..ce3faa5bb 100644
--- a/resources/lib/vrtplayer/streamservice.py
+++ b/resources/lib/vrtplayer/streamservice.py
@@ -13,8 +13,8 @@
from urllib.error import HTTPError
from urllib.request import build_opener, install_opener, urlopen, ProxyHandler
except ImportError:
- from urllib2 import build_opener, install_opener, urlopen, ProxyHandler, quote, HTTPError
from urllib import urlencode # pylint: disable=ungrouped-imports
+ from urllib2 import build_opener, install_opener, urlopen, ProxyHandler, quote, HTTPError
class StreamService:
@@ -23,23 +23,23 @@ class StreamService:
_VUALTO_API_URL = 'https://media-services-public.vrt.be/vualto-video-aggregator-web/rest/external/v1'
_CLIENT = 'vrtvideo'
- def __init__(self, kodi_wrapper, token_resolver):
- self._kodi_wrapper = kodi_wrapper
- self._proxies = self._kodi_wrapper.get_proxies()
+ def __init__(self, _kodiwrapper, _tokenresolver):
+ self._kodiwrapper = _kodiwrapper
+ self._proxies = _kodiwrapper.get_proxies()
install_opener(build_opener(ProxyHandler(self._proxies)))
- self.token_resolver = token_resolver
+ self._tokenresolver = _tokenresolver
self._create_settings_dir()
- self._can_play_drm = self._kodi_wrapper.can_play_drm()
+ self._can_play_drm = _kodiwrapper.can_play_drm()
self._license_url = None
def _get_license_url(self):
- self._kodi_wrapper.log_notice('URL get: ' + self._VUPLAY_API_URL, 'Verbose')
+ self._kodiwrapper.log_notice('URL get: ' + self._VUPLAY_API_URL, 'Verbose')
self._license_url = json.loads(urlopen(self._VUPLAY_API_URL).read()).get('drm_providers', dict()).get('widevine', dict()).get('la_url')
def _create_settings_dir(self):
- settingsdir = self._kodi_wrapper.get_userdata_path()
- if not self._kodi_wrapper.check_if_path_exists(settingsdir):
- self._kodi_wrapper.make_dir(settingsdir)
+ settingsdir = self._kodiwrapper.get_userdata_path()
+ if not self._kodiwrapper.check_if_path_exists(settingsdir):
+ self._kodiwrapper.make_dir(settingsdir)
def _get_license_key(self, key_url, key_type='R', key_headers=None, key_value=None):
''' Generates a propery license key value
@@ -87,7 +87,7 @@ def _get_api_data(self, video):
publication_id = video.get('publication_id')
# Prepare api_data for on demand streams by video_id and publication_id
if video_id and publication_id:
- xvrttoken = self.token_resolver.get_xvrttoken()
+ xvrttoken = self._tokenresolver.get_xvrttoken()
api_data = apidata.ApiData(self._CLIENT, self._VUALTO_API_URL, video_id, publication_id + quote('$'), xvrttoken, False)
# Prepare api_data for livestreams by video_id, e.g. vualto_strubru, vualto_mnm
elif video_id and not video_url:
@@ -100,7 +100,7 @@ def _get_api_data(self, video):
def _webscrape_api_data(self, video_url):
'''Scrape api data from VRT NU html page'''
from bs4 import BeautifulSoup, SoupStrainer
- self._kodi_wrapper.log_notice('URL get: ' + video_url, 'Verbose')
+ self._kodiwrapper.log_notice('URL get: ' + video_url, 'Verbose')
html_page = urlopen(video_url).read()
strainer = SoupStrainer('div', {'class': 'cq-dd-vrtvideo'})
soup = BeautifulSoup(html_page, 'html.parser', parse_only=strainer)
@@ -108,12 +108,12 @@ def _webscrape_api_data(self, video_url):
video_data = soup.find(lambda tag: tag.name == 'div' and tag.get('class') == ['vrtvideo']).attrs
except Exception as e:
# Web scraping failed, log error
- self._kodi_wrapper.log_error('Web scraping api data failed: %s' % e)
+ self._kodiwrapper.log_error('Web scraping api data failed: %s' % e)
return None
# Web scraping failed, log error
if not video_data:
- self._kodi_wrapper.log_error('Web scraping api data failed, empty video_data')
+ self._kodiwrapper.log_error('Web scraping api data failed, empty video_data')
return None
# Store required html data attributes
@@ -129,10 +129,10 @@ def _webscrape_api_data(self, video_url):
else:
is_live_stream = False
publication_id += quote('$')
- xvrttoken = self.token_resolver.get_xvrttoken()
+ xvrttoken = self._tokenresolver.get_xvrttoken()
if client is None or media_api_url is None or (video_id is None and publication_id is None):
- self._kodi_wrapper.log_error('Web scraping api data failed, required attributes missing')
+ self._kodiwrapper.log_error('Web scraping api data failed, required attributes missing')
return None
return apidata.ApiData(client, media_api_url, video_id, publication_id, xvrttoken, is_live_stream)
@@ -140,16 +140,16 @@ def _webscrape_api_data(self, video_url):
def _get_video_json(self, api_data):
token_url = api_data.media_api_url + '/tokens'
if api_data.is_live_stream:
- playertoken = self.token_resolver.get_live_playertoken(token_url, api_data.xvrttoken)
+ playertoken = self._tokenresolver.get_live_playertoken(token_url, api_data.xvrttoken)
else:
- playertoken = self.token_resolver.get_ondemand_playertoken(token_url, api_data.xvrttoken)
+ playertoken = self._tokenresolver.get_ondemand_playertoken(token_url, api_data.xvrttoken)
# Construct api_url and get video json
video_json = None
if playertoken:
api_url = api_data.media_api_url + '/videos/' + api_data.publication_id + \
api_data.video_id + '?vrtPlayerToken=' + playertoken + '&client=' + api_data.client
- self._kodi_wrapper.log_notice('URL get: ' + api_url, 'Verbose')
+ self._kodiwrapper.log_notice('URL get: ' + api_url, 'Verbose')
try:
video_json = json.loads(urlopen(api_url).read())
except HTTPError as e:
@@ -158,9 +158,9 @@ def _get_video_json(self, api_data):
return video_json
def _handle_error(self, video_json):
- self._kodi_wrapper.log_error(video_json.get('message'))
- message = self._kodi_wrapper.get_localized_string(30054)
- self._kodi_wrapper.show_ok_dialog('', message)
+ self._kodiwrapper.log_error(video_json.get('message'))
+ message = self._kodiwrapper.get_localized_string(30054)
+ self._kodiwrapper.show_ok_dialog('', message)
@staticmethod
def _fix_virtualsubclip(stream_dict, duration):
@@ -205,19 +205,19 @@ def get_stream(self, video, retry=False, api_data=None):
return self._select_stream(stream_dict, vudrm_token)
if video_json.get('code') in ('INCOMPLETE_ROAMING_CONFIG', 'INVALID_LOCATION'):
- self._kodi_wrapper.log_error(video_json.get('message'))
- roaming_xvrttoken = self.token_resolver.get_xvrttoken(True)
+ self._kodiwrapper.log_error(video_json.get('message'))
+ roaming_xvrttoken = self._tokenresolver.get_xvrttoken(True)
if not retry and roaming_xvrttoken is not None:
# Delete cached playertokens
if api_data.is_live_stream:
- self._kodi_wrapper.delete_path(self._kodi_wrapper.get_userdata_path() + 'live_vrtPlayerToken')
+ self._kodiwrapper.delete_file(self._kodiwrapper.get_userdata_path() + 'live_vrtPlayerToken')
else:
- self._kodi_wrapper.delete_path(self._kodi_wrapper.get_userdata_path() + 'ondemand_vrtPlayerToken')
+ self._kodiwrapper.delete_file(self._kodiwrapper.get_userdata_path() + 'ondemand_vrtPlayerToken')
# Update api_data with roaming_xvrttoken and try again
api_data.xvrttoken = roaming_xvrttoken
return self.get_stream(video, retry=True, api_data=api_data)
- message = self._kodi_wrapper.get_localized_string(30053)
- self._kodi_wrapper.show_ok_dialog('', message)
+ message = self._kodiwrapper.get_localized_string(30053)
+ self._kodiwrapper.show_ok_dialog('', message)
else:
self._handle_error(video_json)
@@ -237,24 +237,24 @@ def _try_get_drm_stream(self, stream_dict, vudrm_token):
def _select_stream(self, stream_dict, vudrm_token):
stream_url = None
protocol = None
- if vudrm_token and self._can_play_drm and self._kodi_wrapper.get_setting('usedrm') == 'true':
+ if vudrm_token and self._can_play_drm and self._kodiwrapper.get_setting('usedrm') == 'true':
protocol = 'mpeg_dash drm'
- self._kodi_wrapper.log_notice('Protocol: ' + protocol)
+ self._kodiwrapper.log_notice('Protocol: ' + protocol)
stream_url = self._try_get_drm_stream(stream_dict, vudrm_token)
if vudrm_token and stream_url is None:
protocol = 'hls_aes'
- self._kodi_wrapper.log_notice('Protocol: ' + protocol)
+ self._kodiwrapper.log_notice('Protocol: ' + protocol)
stream_url = streamurls.StreamURLS(*self._select_hls_substreams(stream_dict[protocol])) if protocol in stream_dict else None
- if self._kodi_wrapper.has_inputstream_adaptive_installed() and stream_url is None:
+ if self._kodiwrapper.has_inputstream_adaptive_installed() and stream_url is None:
protocol = 'mpeg_dash'
- self._kodi_wrapper.log_notice('Protocol: ' + protocol)
+ self._kodiwrapper.log_notice('Protocol: ' + protocol)
stream_url = streamurls.StreamURLS(stream_dict[protocol], use_inputstream_adaptive=True) if protocol in stream_dict else None
if stream_url is None:
protocol = 'hls'
- self._kodi_wrapper.log_notice('Protocol: ' + protocol)
+ self._kodiwrapper.log_notice('Protocol: ' + protocol)
# No if-else statement because this is the last resort stream selection
stream_url = streamurls.StreamURLS(*self._select_hls_substreams(stream_dict[protocol]))
@@ -267,9 +267,9 @@ def _select_hls_substreams(self, master_hls_url):
hls_audio_id = None
hls_subtitle_id = None
hls_base_url = master_hls_url.split('.m3u8')[0]
- self._kodi_wrapper.log_notice('URL get: ' + master_hls_url + '?hd', 'Verbose')
+ self._kodiwrapper.log_notice('URL get: ' + master_hls_url + '?hd', 'Verbose')
hls_playlist = urlopen(master_hls_url + '?hd').read()
- max_bandwidth = self._kodi_wrapper.get_max_bandwidth()
+ max_bandwidth = self._kodiwrapper.get_max_bandwidth()
stream_bandwidth = None
# Get hls variant url based on max_bandwith setting
@@ -287,9 +287,9 @@ def _select_hls_substreams(self, master_hls_url):
break
if not hls_variant_url:
- message = self._kodi_wrapper.get_localized_string(30057) % (str(max_bandwidth), stream_bandwidth)
- self._kodi_wrapper.show_ok_dialog('', message)
- self._kodi_wrapper.open_settings()
+ message = self._kodiwrapper.get_localized_string(30057) % (str(max_bandwidth), stream_bandwidth)
+ self._kodiwrapper.show_ok_dialog('', message)
+ self._kodiwrapper.open_settings()
# Get audio url
if hls_audio_id:
@@ -299,7 +299,7 @@ def _select_hls_substreams(self, master_hls_url):
hls_variant_url = hls_base_url + match_audio.group(1) + '-' + hls_variant_url.split('-')[-1]
# Get subtitle url, works only for on demand streams
- if self._kodi_wrapper.get_setting('showsubtitles') == 'true' and '/live/' not in master_hls_url and hls_subtitle_id:
+ if self._kodiwrapper.get_setting('showsubtitles') == 'true' and '/live/' not in master_hls_url and hls_subtitle_id:
subtitle_regex = re.compile(r'#EXT-X-MEDIA:TYPE=SUBTITLES[\w\-=,\.\"\/]+GROUP-ID=\"' + hls_subtitle_id + r'\"[\w\-=,\.\"\/]+URI=\"([\w\-=]+)\.m3u8\"')
match_subtitle = re.search(subtitle_regex, hls_playlist)
if match_subtitle:
diff --git a/resources/lib/vrtplayer/tokenresolver.py b/resources/lib/vrtplayer/tokenresolver.py
index 851c930ce..3aba22f15 100644
--- a/resources/lib/vrtplayer/tokenresolver.py
+++ b/resources/lib/vrtplayer/tokenresolver.py
@@ -5,11 +5,12 @@
from __future__ import absolute_import, division, unicode_literals
from resources.lib.helperobjects import helperobjects
+
try:
- from urllib.parse import urlencode
+ from urllib.parse import urlencode, unquote
from urllib.request import build_opener, install_opener, ProxyHandler, HTTPErrorProcessor, urlopen, Request
except ImportError:
- from urllib2 import build_opener, install_opener, ProxyHandler, HTTPErrorProcessor, urlopen, Request
+ from urllib2 import build_opener, install_opener, ProxyHandler, HTTPErrorProcessor, unquote, urlopen, Request
from urllib import urlencode # pylint: disable=ungrouped-imports
@@ -31,13 +32,13 @@ class TokenResolver:
_ROAMING_XVRTTOKEN_COOKIE = 'roaming_XVRTToken'
_XVRT_TOKEN_COOKIE = 'XVRTToken'
- def __init__(self, kodi_wrapper):
- self._kodi_wrapper = kodi_wrapper
- self._proxies = self._kodi_wrapper.get_proxies()
+ def __init__(self, _kodiwrapper):
+ self._kodiwrapper = _kodiwrapper
+ self._proxies = _kodiwrapper.get_proxies()
install_opener(build_opener(ProxyHandler(self._proxies)))
def get_ondemand_playertoken(self, token_url, xvrttoken):
- token_path = self._kodi_wrapper.get_userdata_path() + self._ONDEMAND_COOKIE
+ token_path = self._kodiwrapper.get_userdata_path() + self._ONDEMAND_COOKIE
token = self._get_cached_token(token_path, 'vrtPlayerToken')
if xvrttoken and token is None:
@@ -47,7 +48,7 @@ def get_ondemand_playertoken(self, token_url, xvrttoken):
return token
def get_live_playertoken(self, token_url, xvrttoken):
- token_path = self._kodi_wrapper.get_userdata_path() + self._LIVE_COOKIE
+ token_path = self._kodiwrapper.get_userdata_path() + self._LIVE_COOKIE
token = self._get_cached_token(token_path, 'vrtPlayerToken')
if token is None:
if xvrttoken is not None:
@@ -60,7 +61,7 @@ def get_live_playertoken(self, token_url, xvrttoken):
def get_xvrttoken(self, get_roaming_token=False):
token_filename = self._ROAMING_XVRTTOKEN_COOKIE if get_roaming_token else self._XVRT_TOKEN_COOKIE
- token_path = self._kodi_wrapper.get_userdata_path() + token_filename
+ token_path = self._kodiwrapper.get_userdata_path() + token_filename
token = self._get_cached_token(token_path, 'X-VRT-Token')
if token is None:
@@ -75,36 +76,38 @@ def get_cookie_from_cookiejar(cookiename, cookiejar):
def _get_new_playertoken(self, path, token_url, headers):
import json
- self._kodi_wrapper.log_notice('URL post: ' + token_url, 'Verbose')
+ self._kodiwrapper.log_notice('URL post: ' + token_url, 'Verbose')
req = Request(token_url, data='', headers=headers)
playertoken = json.loads(urlopen(req).read())
- json.dump(playertoken, open(path, 'w'))
+ with self._kodiwrapper.open_file(path, 'w') as f:
+ json.dump(playertoken, f)
return playertoken.get('vrtPlayerToken')
def _get_cached_token(self, path, token_name):
cached_token = None
- if self._kodi_wrapper.check_if_path_exists(path):
+ if self._kodiwrapper.check_if_path_exists(path):
from datetime import datetime
import dateutil.parser
import dateutil.tz
import json
- token = json.loads(open(path, 'r').read())
+ with self._kodiwrapper.open_file(path) as f:
+ token = json.loads(f.read())
now = datetime.now(dateutil.tz.tzlocal())
exp = dateutil.parser.parse(token.get('expirationDate'))
if exp > now:
- self._kodi_wrapper.log_notice('Got cached token', 'Verbose')
+ self._kodiwrapper.log_notice('Got cached token', 'Verbose')
cached_token = token.get(token_name)
else:
- self._kodi_wrapper.log_notice('Cached token deleted', 'Info')
- self._kodi_wrapper.delete_path(path)
+ self._kodiwrapper.log_notice('Cached token deleted', 'Info')
+ self._kodiwrapper.delete_file(path)
return cached_token
def _get_new_xvrttoken(self, path, get_roaming_token):
import json
- cred = helperobjects.Credentials(self._kodi_wrapper)
+ cred = helperobjects.Credentials(self._kodiwrapper)
if not cred.are_filled_in():
- self._kodi_wrapper.open_settings()
+ self._kodiwrapper.open_settings()
cred.reload()
data = dict(
loginID=cred.username,
@@ -113,7 +116,7 @@ def _get_new_xvrttoken(self, path, get_roaming_token):
APIKey=self._API_KEY,
targetEnv='jssdk',
)
- self._kodi_wrapper.log_notice('URL post: ' + self._LOGIN_URL, 'Verbose')
+ self._kodiwrapper.log_notice('URL post: ' + unquote(self._LOGIN_URL), 'Verbose')
req = Request(self._LOGIN_URL, data=urlencode(data))
logon_json = json.loads(urlopen(req).read())
token = None
@@ -127,7 +130,7 @@ def _get_new_xvrttoken(self, path, get_roaming_token):
email=cred.username,
)
headers = {'Content-Type': 'application/json', 'Cookie': login_cookie}
- self._kodi_wrapper.log_notice('URL post: ' + self._TOKEN_GATEWAY_URL, 'Verbose')
+ self._kodiwrapper.log_notice('URL post: ' + unquote(self._TOKEN_GATEWAY_URL), 'Verbose')
req = Request(self._TOKEN_GATEWAY_URL, data=json.dumps(payload), headers=headers)
cookie_data = urlopen(req).info().getheader('Set-Cookie').split('X-VRT-Token=')[1].split('; ')
xvrttoken = TokenResolver._create_token_dictionary_from_urllib(cookie_data)
@@ -135,24 +138,25 @@ def _get_new_xvrttoken(self, path, get_roaming_token):
xvrttoken = self._get_roaming_xvrttoken(xvrttoken)
if xvrttoken is not None:
token = xvrttoken.get('X-VRT-Token')
- json.dump(xvrttoken, open(path, 'w'))
+ with self._kodiwrapper.open_file(path, 'w') as f:
+ json.dump(xvrttoken, f)
else:
self._handle_error(logon_json, cred)
return token
def _handle_error(self, logon_json, cred):
error_message = logon_json.get('errorDetails')
- title = self._kodi_wrapper.get_localized_string(30051)
+ title = self._kodiwrapper.get_localized_string(30051)
if error_message == 'invalid loginID or password':
cred.reset()
- message = self._kodi_wrapper.get_localized_string(30052)
+ message = self._kodiwrapper.get_localized_string(30052)
elif error_message == 'loginID must be provided':
- message = self._kodi_wrapper.get_localized_string(30055)
+ message = self._kodiwrapper.get_localized_string(30055)
elif error_message == 'Missing required parameter: password':
- message = self._kodi_wrapper.get_localized_string(30056)
+ message = self._kodiwrapper.get_localized_string(30056)
else:
message = error_message
- self._kodi_wrapper.show_ok_dialog(title, message)
+ self._kodiwrapper.show_ok_dialog(title, message)
def _get_roaming_xvrttoken(self, xvrttoken):
roaming_xvrttoken = None
@@ -160,7 +164,7 @@ def _get_roaming_xvrttoken(self, xvrttoken):
cookie_value = 'X-VRT-Token=' + xvrttoken.get('X-VRT-Token')
headers = {'Cookie': cookie_value}
opener = build_opener(NoRedirection, ProxyHandler(self._proxies))
- self._kodi_wrapper.log_notice('URL post: ' + url, 'Verbose')
+ self._kodiwrapper.log_notice('URL post: ' + unquote(url), 'Verbose')
req = Request(url, headers=headers)
req_info = opener.open(req).info()
cookie_value += '; state=' + req_info.getheader('Set-Cookie').split('state=')[1].split('; ')[0]
@@ -168,7 +172,7 @@ def _get_roaming_xvrttoken(self, xvrttoken):
url = opener.open(url).info().getheader('Location')
headers = {'Cookie': cookie_value}
if url is not None:
- self._kodi_wrapper.log_notice('URL post: ' + url, 'Verbose')
+ self._kodiwrapper.log_notice('URL post: ' + unquote(url), 'Verbose')
req = Request(url, headers=headers)
cookie_data = opener.open(req).info().getheader('Set-Cookie').split('X-VRT-Token=')[1].split('; ')
roaming_xvrttoken = TokenResolver._create_token_dictionary_from_urllib(cookie_data)
@@ -198,12 +202,12 @@ def _create_token_dictionary_from_urllib(cookie_data):
return token_dictionary
def reset_cookies(self):
- user_data_path = self._kodi_wrapper.get_userdata_path()
+ user_data_path = self._kodiwrapper.get_userdata_path()
ondemand = user_data_path + self._ONDEMAND_COOKIE
live = user_data_path + self._LIVE_COOKIE
xvrt = user_data_path + self._XVRT_TOKEN_COOKIE
roaming = user_data_path + self._ROAMING_XVRTTOKEN_COOKIE
- self._kodi_wrapper.delete_path(ondemand)
- self._kodi_wrapper.delete_path(live)
- self._kodi_wrapper.delete_path(xvrt)
- self._kodi_wrapper.delete_path(roaming)
+ self._kodiwrapper.delete_file(ondemand)
+ self._kodiwrapper.delete_file(live)
+ self._kodiwrapper.delete_file(xvrt)
+ self._kodiwrapper.delete_file(roaming)
diff --git a/resources/lib/vrtplayer/tvguide.py b/resources/lib/vrtplayer/tvguide.py
index b2cd697fd..eb170c158 100644
--- a/resources/lib/vrtplayer/tvguide.py
+++ b/resources/lib/vrtplayer/tvguide.py
@@ -29,11 +29,11 @@ class TVGuide:
VRT_TVGUIDE = 'https://www.vrt.be/bin/epg/schedule.%Y-%m-%d.json'
- def __init__(self, kodi_wrapper):
- self._kodi_wrapper = kodi_wrapper
- self._proxies = self._kodi_wrapper.get_proxies()
+ def __init__(self, _kodiwrapper):
+ self._kodiwrapper = _kodiwrapper
+ self._proxies = _kodiwrapper.get_proxies()
install_opener(build_opener(ProxyHandler(self._proxies)))
- kodi_wrapper.set_locale()
+ _kodiwrapper.set_locale()
def show_tvguide(self, params):
date = params.get('date')
@@ -41,39 +41,39 @@ def show_tvguide(self, params):
if not date:
date_items = self.show_date_menu()
- self._kodi_wrapper.show_listing(date_items, content_type='files')
+ self._kodiwrapper.show_listing(date_items, content='files')
elif not channel:
channel_items = self.show_channel_menu(date)
- self._kodi_wrapper.show_listing(channel_items)
+ self._kodiwrapper.show_listing(channel_items)
else:
episode_items = self.show_episodes(date, channel)
- self._kodi_wrapper.show_listing(episode_items, content_type='episodes', cache=False)
+ self._kodiwrapper.show_listing(episode_items, content='episodes', cache=False)
def show_date_menu(self):
now = datetime.now(dateutil.tz.tzlocal())
date_items = []
for i in range(7, -31, -1):
day = now + timedelta(days=i)
- title = day.strftime(self._kodi_wrapper.get_localized_datelong())
+ title = day.strftime(self._kodiwrapper.get_localized_datelong())
if str(i) in DATE_STRINGS:
if i == 0:
- title = '[COLOR yellow][B]%s[/B], %s[/COLOR]' % (self._kodi_wrapper.get_localized_string(DATE_STRINGS[str(i)]), title)
+ title = '[COLOR yellow][B]%s[/B], %s[/COLOR]' % (self._kodiwrapper.get_localized_string(DATE_STRINGS[str(i)]), title)
else:
- title = '[B]%s[/B], %s' % (self._kodi_wrapper.get_localized_string(DATE_STRINGS[str(i)]), title)
+ title = '[B]%s[/B], %s' % (self._kodiwrapper.get_localized_string(DATE_STRINGS[str(i)]), title)
date_items.append(helperobjects.TitleItem(
title=title,
url_dict=dict(action=actions.LISTING_TVGUIDE, date=day.strftime('%Y-%m-%d')),
is_playable=False,
art_dict=dict(thumb='DefaultYear.png', icon='DefaultYear.png', fanart='DefaultYear.png'),
- video_dict=dict(plot=day.strftime(self._kodi_wrapper.get_localized_datelong())),
+ video_dict=dict(plot=day.strftime(self._kodiwrapper.get_localized_datelong())),
))
return date_items
def show_channel_menu(self, date):
dateobj = dateutil.parser.parse(date)
- datelong = dateobj.strftime(self._kodi_wrapper.get_localized_datelong())
+ datelong = dateobj.strftime(self._kodiwrapper.get_localized_datelong())
fanart_path = 'resource://resource.images.studios.white/%(studio)s.png'
icon_path = 'resource://resource.images.studios.white/%(studio)s.png'
@@ -87,7 +87,7 @@ def show_channel_menu(self, date):
icon = icon_path % channel
fanart = fanart_path % channel
- plot = self._kodi_wrapper.get_localized_string(30301) % channel.get('label') + '\n' + datelong
+ plot = self._kodiwrapper.get_localized_string(30301) % channel.get('label') + '\n' + datelong
channel_items.append(helperobjects.TitleItem(
title=channel.get('label'),
url_dict=dict(action=actions.LISTING_TVGUIDE, date=date, channel=channel.get('name')),
@@ -100,9 +100,9 @@ def show_channel_menu(self, date):
def show_episodes(self, date, channel):
now = datetime.now(dateutil.tz.tzlocal())
dateobj = dateutil.parser.parse(date)
- datelong = dateobj.strftime(self._kodi_wrapper.get_localized_datelong())
+ datelong = dateobj.strftime(self._kodiwrapper.get_localized_datelong())
api_url = dateobj.strftime(self.VRT_TVGUIDE)
- self._kodi_wrapper.log_notice('URL get: ' + api_url, 'Verbose')
+ self._kodiwrapper.log_notice('URL get: ' + api_url, 'Verbose')
schedule = json.loads(urlopen(api_url).read())
name = channel
try:
@@ -137,14 +137,14 @@ def show_episodes(self, date, channel):
video_url = statichelper.add_https_method(url)
url_dict = dict(action=actions.PLAY, video_url=video_url)
if start_date < now <= end_date: # Now playing
- metadata.title = '[COLOR yellow]%s[/COLOR] %s' % (label, self._kodi_wrapper.get_localized_string(30302))
+ metadata.title = '[COLOR yellow]%s[/COLOR] %s' % (label, self._kodiwrapper.get_localized_string(30302))
else:
metadata.title = label
else:
# FIXME: Find a better solution for non-actionable items
url_dict = dict(action=actions.LISTING_TVGUIDE, date=date, channel=channel)
if start_date < now <= end_date: # Now playing
- metadata.title = '[COLOR brown]%s[/COLOR] %s' % (label, self._kodi_wrapper.get_localized_string(30302))
+ metadata.title = '[COLOR brown]%s[/COLOR] %s' % (label, self._kodiwrapper.get_localized_string(30302))
else:
metadata.title = '[COLOR gray]%s[/COLOR]' % label
episode_items.append(helperobjects.TitleItem(
diff --git a/resources/lib/vrtplayer/vrtapihelper.py b/resources/lib/vrtplayer/vrtapihelper.py
index 17a210684..d05db8692 100644
--- a/resources/lib/vrtplayer/vrtapihelper.py
+++ b/resources/lib/vrtplayer/vrtapihelper.py
@@ -4,7 +4,7 @@
from __future__ import absolute_import, division, unicode_literals
from resources.lib.helperobjects.helperobjects import TitleItem
-from resources.lib.vrtplayer import actions, metadatacreator, statichelper
+from resources.lib.vrtplayer import actions, favorites, metadatacreator, statichelper
try:
from urllib.parse import urlencode, unquote
@@ -21,13 +21,14 @@ class VRTApiHelper:
_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
- self._proxies = self._kodi_wrapper.get_proxies()
+ def __init__(self, _kodiwrapper):
+ self._kodiwrapper = _kodiwrapper
+ self._proxies = _kodiwrapper.get_proxies()
install_opener(build_opener(ProxyHandler(self._proxies)))
- self._showpermalink = kodi_wrapper.get_setting('showpermalink') == 'true'
+ self._showpermalink = _kodiwrapper.get_setting('showpermalink') == 'true'
+ self._favorites = favorites.Favorites(self._kodiwrapper)
- def get_tvshow_items(self, category=None, channel=None):
+ def get_tvshow_items(self, category=None, channel=None, filtered=False):
import json
params = dict()
@@ -41,13 +42,17 @@ def get_tvshow_items(self, category=None, channel=None):
params['facets[programBrands]'] = channel
api_url = self._VRTNU_SUGGEST_URL + '?' + urlencode(params)
- self._kodi_wrapper.log_notice('URL get: ' + api_url, 'Verbose')
+ self._kodiwrapper.log_notice('URL get: ' + unquote(api_url), 'Verbose')
api_json = json.loads(urlopen(api_url).read())
- return self._map_to_tvshow_items(api_json)
+ return self._map_to_tvshow_items(api_json, filtered=filtered)
- def _map_to_tvshow_items(self, tvshows):
+ def _map_to_tvshow_items(self, tvshows, filtered=False):
tvshow_items = []
+ if filtered:
+ favorite_names = self._favorites.names()
for tvshow in tvshows:
+ if filtered and tvshow.get('programName') not in favorite_names:
+ continue
metadata = metadatacreator.MetadataCreator()
metadata.mediatype = 'tvshow'
metadata.tvshowtitle = tvshow.get('title', '???')
@@ -58,6 +63,13 @@ def _map_to_tvshow_items(self, tvshows):
# title = '%s [LIGHT][COLOR yellow]%s[/COLOR][/LIGHT]' % (tvshow.get('title', '???'), tvshow.get('episode_count', '?'))
label = tvshow.get('title', '???')
thumbnail = statichelper.add_https_method(tvshow.get('thumbnail', 'DefaultAddonVideo.png'))
+ program_path = statichelper.unique_path(tvshow.get('targetUrl'))
+ if self._favorites.is_favorite(program_path):
+ params = dict(action='unfollow', program=program_path)
+ context_menu = [(self._kodiwrapper.get_localized_string(30412), 'RunPlugin(plugin://plugin.video.vrt.nu?%s)' % urlencode(params))]
+ else:
+ params = dict(action='follow', program=program_path)
+ context_menu = [(self._kodiwrapper.get_localized_string(30411), 'RunPlugin(plugin://plugin.video.vrt.nu?%s)' % urlencode(params))]
# Cut vrtbase url off since it will be added again when searching for episodes
# (with a-z we dont have the full url)
video_url = statichelper.add_https_method(tvshow.get('targetUrl')).replace(self._VRT_BASE, '')
@@ -67,6 +79,7 @@ def _map_to_tvshow_items(self, tvshows):
is_playable=False,
art_dict=dict(thumb=thumbnail, icon='DefaultAddonVideo.png', fanart=thumbnail),
video_dict=metadata.get_video_dict(),
+ context_menu=context_menu,
))
return tvshow_items
@@ -86,7 +99,7 @@ def _get_season_items(self, api_url, api_json):
season_items, sort, ascending = self._map_to_season_items(api_url, facet.get('buckets', []), episode)
return season_items, sort, ascending
- def get_episode_items(self, path=None, page=None, all_seasons=False):
+ def get_episode_items(self, path=None, page=None, all_seasons=False, filtered=False):
import json
episode_items = []
sort = 'episode'
@@ -101,10 +114,15 @@ def get_episode_items(self, path=None, page=None, all_seasons=False):
'facets[transcodingStatus]': 'AVAILABLE',
'facets[programBrands]': '[een,canvas,sporza,vrtnws,vrtnxt,radio1,radio2,klara,stubru,mnm]',
}
+
+ if filtered:
+ params['facets[program]'] = '[%s]' % (','.join(self._favorites.names()))
+
api_url = self._VRTNU_SEARCH_URL + '?' + urlencode(params)
- self._kodi_wrapper.log_notice('URL get: ' + api_url, 'Verbose')
+ self._kodiwrapper.log_notice('URL get: ' + unquote(api_url), 'Verbose')
api_json = json.loads(urlopen(api_url).read())
episode_items, sort, ascending = self._map_to_episode_items(api_json.get('results', []), titletype='recent')
+ content = 'episodes'
if path:
if '.relevant/' in path:
@@ -116,7 +134,7 @@ def get_episode_items(self, path=None, page=None, all_seasons=False):
api_url = self._VRTNU_SEARCH_URL + '?' + urlencode(params)
else:
api_url = path
- self._kodi_wrapper.log_notice('URL get: ' + api_url, 'Verbose')
+ self._kodiwrapper.log_notice('URL get: ' + unquote(api_url), 'Verbose')
api_json = json.loads(urlopen(api_url).read())
episodes = api_json.get('results', [{}])
@@ -131,20 +149,24 @@ def get_episode_items(self, path=None, page=None, all_seasons=False):
# Look for seasons items if not yet done
season_key = None
+
# path = requests.utils.unquote(path)
path = unquote(path)
if all_seasons is True:
episode_items, sort, ascending = self._map_to_episode_items(episodes, season_key=None)
+ content = 'episodes'
elif 'facets[seasonTitle]' in path:
season_key = path.split('facets[seasonTitle]=')[1]
elif display_options.get('showSeason') is True:
episode_items, sort, ascending = self._get_season_items(api_url, api_json)
+ content = 'seasons'
# No season items, generate episode items
if not episode_items:
episode_items, sort, ascending = self._map_to_episode_items(episodes, season_key=season_key)
+ content = 'episodes'
- return episode_items, sort, ascending
+ return episode_items, sort, ascending, content
def _map_to_episode_items(self, episodes, titletype=None, season_key=None):
from datetime import datetime
@@ -197,16 +219,16 @@ def _map_to_episode_items(self, episodes, titletype=None, season_key=None):
plot_meta = ''
if metadata.geolocked:
# Show Geo-locked
- plot_meta += self._kodi_wrapper.get_localized_string(30201)
+ plot_meta += self._kodiwrapper.get_localized_string(30201)
# Only display when a video disappears if it is within the next 3 months
if metadata.offtime is not None and (metadata.offtime - now).days < 93:
# Show date when episode is removed
- plot_meta += self._kodi_wrapper.get_localized_string(30202) % metadata.offtime.strftime(self._kodi_wrapper.get_localized_dateshort())
+ plot_meta += self._kodiwrapper.get_localized_string(30202) % metadata.offtime.strftime(self._kodiwrapper.get_localized_dateshort())
# Show the remaining days/hours the episode is still available
if (metadata.offtime - now).days > 0:
- plot_meta += self._kodi_wrapper.get_localized_string(30203) % (metadata.offtime - now).days
+ plot_meta += self._kodiwrapper.get_localized_string(30203) % (metadata.offtime - now).days
else:
- plot_meta += self._kodi_wrapper.get_localized_string(30204) % int((metadata.offtime - now).seconds / 3600)
+ plot_meta += self._kodiwrapper.get_localized_string(30204) % int((metadata.offtime - now).seconds / 3600)
if plot_meta:
metadata.plot = '%s\n%s' % (plot_meta, metadata.plot)
@@ -214,6 +236,14 @@ def _map_to_episode_items(self, episodes, titletype=None, season_key=None):
if self._showpermalink and metadata.permalink:
metadata.plot = '%s\n\n[COLOR yellow]%s[/COLOR]' % (metadata.plot, metadata.permalink)
+ program_path = statichelper.unique_path(episode.get('programUrl'))
+ if self._favorites.is_favorite(program_path):
+ params = dict(action='unfollow', program=program_path)
+ context_menu = [(self._kodiwrapper.get_localized_string(30412), 'RunPlugin(plugin://plugin.video.vrt.nu?%s)' % urlencode(params))]
+ else:
+ params = dict(action='follow', program=program_path)
+ context_menu = [(self._kodiwrapper.get_localized_string(30411), 'RunPlugin(plugin://plugin.video.vrt.nu?%s)' % urlencode(params))]
+
thumb = statichelper.add_https_method(episode.get('videoThumbnailUrl', 'DefaultAddonVideo.png'))
fanart = statichelper.add_https_method(episode.get('programImageUrl', thumb))
video_url = statichelper.add_https_method(episode.get('url'))
@@ -225,6 +255,7 @@ def _map_to_episode_items(self, episodes, titletype=None, season_key=None):
is_playable=True,
art_dict=dict(thumb=thumb, icon='DefaultAddonVideo.png', fanart=fanart),
video_dict=metadata.get_video_dict(),
+ context_menu=context_menu,
))
return episode_items, sort, ascending
@@ -244,9 +275,9 @@ def _map_to_season_items(self, api_url, seasons, episode):
ascending = False
# Add an "* All seasons" list item
- if self._kodi_wrapper.get_global_setting('videolibrary.showallitems') is True:
+ if self._kodiwrapper.get_global_setting('videolibrary.showallitems') is True:
season_items.append(TitleItem(
- title=self._kodi_wrapper.get_localized_string(30096),
+ title=self._kodiwrapper.get_localized_string(30096),
url_dict=dict(action=actions.LISTING_ALL_EPISODES, video_url=api_url),
is_playable=False,
art_dict=dict(thumb=fanart, icon='DefaultSets.png', fanart=fanart),
@@ -258,7 +289,7 @@ def _map_to_season_items(self, api_url, seasons, episode):
for season in seasons:
season_key = season.get('key')
- label = '%s %s' % (self._kodi_wrapper.get_localized_string(30094), season_key)
+ label = '%s %s' % (self._kodiwrapper.get_localized_string(30094), season_key)
params = {'facets[seasonTitle]': season_key}
path = api_url + '&' + urlencode(params)
season_items.append(TitleItem(
@@ -280,7 +311,7 @@ def search(self, search_string, page=1):
'q': search_string,
}
api_url = 'https://search.vrt.be/search?' + urlencode(params)
- self._kodi_wrapper.log_notice('URL get: ' + api_url, 'Verbose')
+ self._kodiwrapper.log_notice('URL get: ' + unquote(api_url), 'Verbose')
api_json = json.loads(urlopen(api_url).read())
episodes = api_json.get('results', [{}])
@@ -296,7 +327,7 @@ def __delete_cached_thumbnail(self, url):
crc = self.__get_crc32(url)
ext = url.split('.')[-1]
path = 'special://thumbnails/%s/%s.%s' % (crc[0], crc, ext)
- self._kodi_wrapper.delete_path(path)
+ self._kodiwrapper.delete_file(path)
@staticmethod
def __get_crc32(string):
@@ -349,7 +380,7 @@ def _make_label(self, result, titletype, options=None):
# NOTE: Sort the episodes ourselves, because Kodi does not allow to set to 'descending'
# sort = 'episode'
sort = 'label'
- label = '%s %s: %s' % (self._kodi_wrapper.get_localized_string(30095), result.get('episodeNumber'), label)
+ label = '%s %s: %s' % (self._kodiwrapper.get_localized_string(30095), result.get('episodeNumber'), label)
elif options.get('showBroadcastDate') and result.get('formattedBroadcastShortDate'):
sort = 'dateadded'
label = '%s - %s' % (result.get('formattedBroadcastShortDate'), label)
diff --git a/resources/lib/vrtplayer/vrtplayer.py b/resources/lib/vrtplayer/vrtplayer.py
index a0768f3a2..498970027 100644
--- a/resources/lib/vrtplayer/vrtplayer.py
+++ b/resources/lib/vrtplayer/vrtplayer.py
@@ -14,64 +14,84 @@
class VRTPlayer:
- def __init__(self, kodi_wrapper, api_helper):
- self._kodi_wrapper = kodi_wrapper
- self._proxies = self._kodi_wrapper.get_proxies()
+ def __init__(self, _kodiwrapper, _apihelper):
+ self._kodiwrapper = _kodiwrapper
+ self._proxies = _kodiwrapper.get_proxies()
install_opener(build_opener(ProxyHandler(self._proxies)))
- self._api_helper = api_helper
+ self._apihelper = _apihelper
def show_main_menu_items(self):
main_items = [
- TitleItem(title=self._kodi_wrapper.get_localized_string(30080),
+ TitleItem(title=self._kodiwrapper.get_localized_string(30078),
+ url_dict=dict(action=actions.LISTING_FAVORITES),
+ is_playable=False,
+ art_dict=dict(thumb='icons/settings/profiles.png', icon='icons/settings/profiles.png', fanart='icons/settings/profiles.png'),
+ video_dict=dict(plot=self._kodiwrapper.get_localized_string(30079))),
+ TitleItem(title=self._kodiwrapper.get_localized_string(30080),
url_dict=dict(action=actions.LISTING_AZ_TVSHOWS),
is_playable=False,
art_dict=dict(thumb='DefaultMovieTitle.png', icon='DefaultMovieTitle.png', fanart='DefaultMovieTitle.png'),
- video_dict=dict(plot=self._kodi_wrapper.get_localized_string(30081))),
- TitleItem(title=self._kodi_wrapper.get_localized_string(30082),
+ video_dict=dict(plot=self._kodiwrapper.get_localized_string(30081))),
+ TitleItem(title=self._kodiwrapper.get_localized_string(30082),
url_dict=dict(action=actions.LISTING_CATEGORIES),
is_playable=False,
art_dict=dict(thumb='DefaultGenre.png', icon='DefaultGenre.png', fanart='DefaultGenre.png'),
- video_dict=dict(plot=self._kodi_wrapper.get_localized_string(30083))),
- TitleItem(title=self._kodi_wrapper.get_localized_string(30084),
+ video_dict=dict(plot=self._kodiwrapper.get_localized_string(30083))),
+ TitleItem(title=self._kodiwrapper.get_localized_string(30084),
url_dict=dict(action=actions.LISTING_CHANNELS),
is_playable=False,
art_dict=dict(thumb='DefaultTags.png', icon='DefaultTags.png', fanart='DefaultTags.png'),
- video_dict=dict(plot=self._kodi_wrapper.get_localized_string(30085))),
- TitleItem(title=self._kodi_wrapper.get_localized_string(30086),
+ video_dict=dict(plot=self._kodiwrapper.get_localized_string(30085))),
+ TitleItem(title=self._kodiwrapper.get_localized_string(30086),
url_dict=dict(action=actions.LISTING_LIVE),
is_playable=False,
art_dict=dict(thumb='DefaultAddonPVRClient.png', icon='DefaultAddonPVRClient.png', fanart='DefaultAddonPVRClient.png'),
- video_dict=dict(plot=self._kodi_wrapper.get_localized_string(30087))),
- TitleItem(title=self._kodi_wrapper.get_localized_string(30088),
+ video_dict=dict(plot=self._kodiwrapper.get_localized_string(30087))),
+ TitleItem(title=self._kodiwrapper.get_localized_string(30088),
url_dict=dict(action=actions.LISTING_RECENT, page='1'),
is_playable=False,
art_dict=dict(thumb='DefaultYear.png', icon='DefaultYear.png', fanart='DefaultYear.png'),
- video_dict=dict(plot=self._kodi_wrapper.get_localized_string(30089))),
- TitleItem(title=self._kodi_wrapper.get_localized_string(30090),
+ video_dict=dict(plot=self._kodiwrapper.get_localized_string(30089))),
+ TitleItem(title=self._kodiwrapper.get_localized_string(30090),
url_dict=dict(action=actions.LISTING_TVGUIDE),
is_playable=False,
art_dict=dict(thumb='DefaultAddonTvInfo.png', icon='DefaultAddonTvInfo.png', fanart='DefaultAddonTvInfo.png'),
- video_dict=dict(plot=self._kodi_wrapper.get_localized_string(30091))),
- TitleItem(title=self._kodi_wrapper.get_localized_string(30092),
+ video_dict=dict(plot=self._kodiwrapper.get_localized_string(30091))),
+ TitleItem(title=self._kodiwrapper.get_localized_string(30092),
url_dict=dict(action=actions.SEARCH),
is_playable=False,
art_dict=dict(thumb='DefaultAddonsSearch.png', icon='DefaultAddonsSearch.png', fanart='DefaultAddonsSearch.png'),
- video_dict=dict(plot=self._kodi_wrapper.get_localized_string(30093))),
+ video_dict=dict(plot=self._kodiwrapper.get_localized_string(30093))),
+ ]
+ self._kodiwrapper.show_listing(main_items)
+
+ def show_favourites_menu_items(self):
+ favourites_items = [
+ TitleItem(title=self._kodiwrapper.get_localized_string(30420),
+ url_dict=dict(action=actions.LISTING_AZ_TVSHOWS, filtered=True),
+ is_playable=False,
+ art_dict=dict(thumb='DefaultMovieTitle.png', icon='DefaultMovieTitle.png', fanart='DefaultMovieTitle.png'),
+ video_dict=dict(plot=self._kodiwrapper.get_localized_string(30421))),
+ TitleItem(title=self._kodiwrapper.get_localized_string(30422),
+ url_dict=dict(action=actions.LISTING_RECENT, page='1', filtered=True),
+ is_playable=False,
+ art_dict=dict(thumb='DefaultYear.png', icon='DefaultYear.png', fanart='DefaultYear.png'),
+ video_dict=dict(plot=self._kodiwrapper.get_localized_string(30423))),
]
- self._kodi_wrapper.show_listing(main_items)
+ self._kodiwrapper.show_listing(favourites_items)
- def show_tvshow_menu_items(self, category=None):
- tvshow_items = self._api_helper.get_tvshow_items(category=category)
- self._kodi_wrapper.show_listing(tvshow_items, sort='label', content_type='tvshows')
+ def show_tvshow_menu_items(self, category=None, filtered=False):
+ tvshow_items = self._apihelper.get_tvshow_items(category=category, filtered=filtered)
+ self._kodiwrapper.show_listing(tvshow_items, sort='label', content='tvshows')
def show_category_menu_items(self):
category_items = self.__get_category_menu_items()
- self._kodi_wrapper.show_listing(category_items, sort='label', content_type='files')
+ self._kodiwrapper.show_listing(category_items, sort='label', content='files')
def show_channels_menu_items(self, channel=None):
if channel:
- tvshow_items = self._api_helper.get_tvshow_items(channel=channel)
- self._kodi_wrapper.show_listing(tvshow_items, sort='label', content_type='tvshows')
+ tvshow_items = self._apihelper.get_tvshow_items(channel=channel)
+ self._kodiwrapper.show_listing(tvshow_items, sort='label', content='tvshows')
else:
from resources.lib.vrtplayer import CHANNELS
self.show_channels(action=actions.LISTING_CHANNELS, channels=[c.get('name') for c in CHANNELS])
@@ -102,14 +122,14 @@ def show_channels(self, action=actions.PLAY, channels=None):
is_playable = False
else:
url_dict = dict(action=action)
- label = self._kodi_wrapper.get_localized_string(30101) % channel.get('label')
+ label = self._kodiwrapper.get_localized_string(30101) % channel.get('label')
is_playable = True
if channel.get('name') in ['een', 'canvas', 'ketnet']:
- fanart = self._api_helper.get_live_screenshot(channel.get('name'))
- plot = '%s\n%s' % (self._kodi_wrapper.get_localized_string(30201),
- self._kodi_wrapper.get_localized_string(30102) % channel.get('label'))
+ fanart = self._apihelper.get_live_screenshot(channel.get('name'))
+ plot = '%s\n%s' % (self._kodiwrapper.get_localized_string(30201),
+ self._kodiwrapper.get_localized_string(30102) % channel.get('label'))
else:
- plot = self._kodi_wrapper.get_localized_string(30102) % channel.get('label')
+ plot = self._kodiwrapper.get_localized_string(30102) % channel.get('label')
if channel.get('live_stream_url'):
url_dict['video_url'] = channel.get('live_stream_url')
elif channel.get('live_stream'):
@@ -130,42 +150,42 @@ def show_channels(self, action=actions.PLAY, channels=None):
),
))
- self._kodi_wrapper.show_listing(channel_items, cache=False)
+ self._kodiwrapper.show_listing(channel_items, cache=False)
def show_episodes(self, path):
- episode_items, sort, ascending = self._api_helper.get_episode_items(path=path)
- self._kodi_wrapper.show_listing(episode_items, sort=sort, ascending=ascending, content_type='episodes')
+ episode_items, sort, ascending, content = self._apihelper.get_episode_items(path=path)
+ self._kodiwrapper.show_listing(episode_items, sort=sort, ascending=ascending, content=content)
def show_all_episodes(self, path):
- episode_items, sort, ascending = self._api_helper.get_episode_items(path=path, all_seasons=True)
- self._kodi_wrapper.show_listing(episode_items, sort=sort, ascending=ascending, content_type='episodes')
+ episode_items, sort, ascending, content = self._apihelper.get_episode_items(path=path, all_seasons=True)
+ self._kodiwrapper.show_listing(episode_items, sort=sort, ascending=ascending, content=content)
- def show_recent(self, page):
+ def show_recent(self, page, filtered=False):
try:
page = int(page)
except TypeError:
page = 1
- episode_items, sort, ascending = self._api_helper.get_episode_items(page=page)
+ episode_items, sort, ascending, content = self._apihelper.get_episode_items(page=page, filtered=filtered)
# Add 'More...' entry at the end
episode_items.append(TitleItem(
- title=self._kodi_wrapper.get_localized_string(30300),
+ title=self._kodiwrapper.get_localized_string(30300),
url_dict=dict(action=actions.LISTING_RECENT, page=page + 1),
is_playable=False,
art_dict=dict(thumb='DefaultYear.png', icon='DefaultYear.png', fanart='DefaultYear.png'),
video_dict=dict(),
))
- self._kodi_wrapper.show_listing(episode_items, sort=sort, ascending=ascending, content_type='episodes', cache=False)
+ self._kodiwrapper.show_listing(episode_items, sort=sort, ascending=ascending, content=content, cache=False)
def play(self, params):
from resources.lib.vrtplayer import streamservice, tokenresolver
- token_resolver = tokenresolver.TokenResolver(self._kodi_wrapper)
- stream_service = streamservice.StreamService(self._kodi_wrapper, token_resolver)
+ token_resolver = tokenresolver.TokenResolver(self._kodiwrapper)
+ stream_service = streamservice.StreamService(self._kodiwrapper, token_resolver)
stream = stream_service.get_stream(params)
if stream is not None:
- self._kodi_wrapper.play(stream)
+ self._kodiwrapper.play(stream)
def search(self, search_string=None, page=1):
try:
@@ -174,27 +194,27 @@ def search(self, search_string=None, page=1):
page = 1
if search_string is None:
- search_string = self._kodi_wrapper.get_search_string()
+ search_string = self._kodiwrapper.get_search_string()
if not search_string:
return
- search_items, sort, ascending = self._api_helper.search(search_string, page=page)
+ search_items, sort, ascending = self._apihelper.search(search_string, page=page)
if not search_items:
- self._kodi_wrapper.show_ok_dialog(self._kodi_wrapper.get_localized_string(30098), self._kodi_wrapper.get_localized_string(30099) % search_string)
+ self._kodiwrapper.show_ok_dialog(self._kodiwrapper.get_localized_string(30098), self._kodiwrapper.get_localized_string(30099) % search_string)
return
# Add 'More...' entry at the end
if len(search_items) == 50:
search_items.append(TitleItem(
- title=self._kodi_wrapper.get_localized_string(30300),
+ title=self._kodiwrapper.get_localized_string(30300),
url_dict=dict(action=actions.SEARCH, query=search_string, page=page + 1),
is_playable=False,
art_dict=dict(thumb='DefaultAddonSearch.png', icon='DefaultAddonSearch.png', fanart='DefaultAddonSearch.png'),
video_dict=dict(),
))
- self._kodi_wrapper.show_listing(search_items, sort=sort, ascending=ascending, content_type='episodes', cache=False)
+ self._kodiwrapper.show_listing(search_items, sort=sort, ascending=ascending, content='episodes', cache=False)
def __get_category_menu_items(self):
try:
@@ -221,7 +241,7 @@ def __get_category_menu_items(self):
def get_categories(self, proxies=None):
from bs4 import BeautifulSoup, SoupStrainer
- self._kodi_wrapper.log_notice('URL get: https://www.vrt.be/vrtnu/categorieen/', 'Verbose')
+ self._kodiwrapper.log_notice('URL get: https://www.vrt.be/vrtnu/categorieen/', 'Verbose')
response = urlopen('https://www.vrt.be/vrtnu/categorieen/')
tiles = SoupStrainer('a', {'class': 'nui-tile'})
soup = BeautifulSoup(response.read(), 'html.parser', parse_only=tiles)
diff --git a/test/apihelpertests.py b/test/apihelpertests.py
index 9b9fcc1ab..80f6f0a09 100644
--- a/test/apihelpertests.py
+++ b/test/apihelpertests.py
@@ -2,37 +2,67 @@
# GNU General Public License v3.0 (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
-# pylint: disable=unused-variable
-
-from __future__ import absolute_import, division, unicode_literals
+from __future__ import absolute_import, division, print_function, unicode_literals
+from contextlib import contextmanager
import mock
-from resources.lib.vrtplayer import vrtapihelper
+import os
import unittest
+from resources.lib.vrtplayer import vrtapihelper
+
+
+@contextmanager
+def open_file(path, flags='r'):
+ f = open(path, flags)
+ yield f
+ f.close()
+
+
+def log_notice(msg, level):
+ print('%s: %s' % (level, msg))
+
class ApiHelperTests(unittest.TestCase):
- _kodi_wrapper = mock.MagicMock()
- _kodi_wrapper.get_proxies = mock.MagicMock(return_value=dict())
- _kodi_wrapper.get_localized_dateshort = mock.MagicMock(return_value='%d-%m-%Y')
- _kodi_wrapper.get_localized_string.return_value = '"[B][COLOR blue]Available until %s[/COLOR][/B]\n"'
- _api_helper = vrtapihelper.VRTApiHelper(_kodi_wrapper)
+ _kodiwrapper = mock.MagicMock()
+ _kodiwrapper.check_if_path_exists = mock.MagicMock(side_effect=os.path.exists)
+ _kodiwrapper.get_localized_dateshort = mock.MagicMock(return_value='%d-%m-%Y')
+ _kodiwrapper.get_localized_string.return_value = '"[B][COLOR blue]Available until %s[/COLOR][/B]\n"'
+ _kodiwrapper.get_proxies = mock.MagicMock(return_value=dict())
+ _kodiwrapper.get_userdata_path.return_value = './userdata/'
+ _kodiwrapper.log_notice = mock.MagicMock(side_effect=log_notice)
+ _kodiwrapper.make_dir.return_value = None
+ _kodiwrapper.open_file = mock.MagicMock(side_effect=open_file)
+ # _kodiwrapper.stat_file = mock.MagicMock(side_effect=os.stat)
+ _apihelper = vrtapihelper.VRTApiHelper(_kodiwrapper)
def test_get_api_data_single_season(self):
- title_items, sort, ascending = self._api_helper.get_episode_items(path='/vrtnu/a-z/het-journaal.relevant/')
- self.assertTrue(122 < len(title_items) < 126)
+ title_items, sort, ascending, content = self._apihelper.get_episode_items(path='/vrtnu/a-z/het-journaal.relevant/')
+ self.assertTrue(123 < len(title_items) < 129, 'We got %s items instead.' % len(title_items))
+ self.assertEqual(sort, 'dateadded')
+ self.assertFalse(ascending)
+ self.assertEqual(content, 'episodes')
def test_get_api_data_multiple_seasons(self):
- title_items, sort, ascending = self._api_helper.get_episode_items(path='/vrtnu/a-z/thuis.relevant/')
+ title_items, sort, ascending, content = self._apihelper.get_episode_items(path='/vrtnu/a-z/thuis.relevant/')
self.assertTrue(len(title_items) < 5)
+ self.assertEqual(sort, 'label')
+ self.assertFalse(ascending)
+ self.assertEqual(content, 'seasons')
def test_get_api_data_specific_season(self):
- title_items, sort, ascending = self._api_helper.get_episode_items(path='/vrtnu/a-z/pano.relevant/')
+ title_items, sort, ascending, content = self._apihelper.get_episode_items(path='/vrtnu/a-z/pano.relevant/')
self.assertEqual(len(title_items), 4)
+ self.assertEqual(sort, 'label')
+ self.assertFalse(ascending)
+ self.assertEqual(content, 'seasons')
def test_get_api_data_specific_season_without_broadcastdate(self):
- title_items, sort, ascending = self._api_helper.get_episode_items(path='/vrtnu/a-z/postbus-x.relevant/')
+ title_items, sort, ascending, content = self._apihelper.get_episode_items(path='/vrtnu/a-z/postbus-x.relevant/')
self.assertEqual(len(title_items), 3)
+ self.assertEqual(sort, 'label')
+ self.assertTrue(ascending)
+ self.assertEqual(content, 'seasons')
if __name__ == '__main__':
diff --git a/test/favoritestests.py b/test/favoritestests.py
new file mode 100644
index 000000000..489368b4e
--- /dev/null
+++ b/test/favoritestests.py
@@ -0,0 +1,60 @@
+# -*- 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, print_function, unicode_literals
+from contextlib import contextmanager
+import mock
+import os
+import unittest
+
+from resources.lib.vrtplayer import favorites
+
+
+@contextmanager
+def open_file(path, flags='r'):
+ f = open(path, flags)
+ yield f
+ f.close()
+
+
+def log_notice(msg, level):
+ print('%s: %s' % (level, msg))
+
+
+class TestFavorites(unittest.TestCase):
+
+ _kodiwrapper = mock.MagicMock()
+ _kodiwrapper.get_proxies = mock.MagicMock(return_value=dict())
+ _kodiwrapper.log_notice = mock.MagicMock(side_effect=log_notice)
+ _kodiwrapper.get_userdata_path.return_value = './userdata/'
+ _kodiwrapper.check_if_path_exists = mock.MagicMock(side_effect=os.path.exists)
+ _kodiwrapper.open_file = mock.MagicMock(side_effect=open_file)
+ # _kodiwrapper.stat_file = mock.MagicMock(side_effect=os.stat)
+ _kodiwrapper.make_dir.return_value = None
+ _favorites = favorites.Favorites(_kodiwrapper)
+
+ def test_follow_unfollow(self):
+ program = '/vrtnu/a-z/winteruur/'
+ self._favorites.follow(program)
+ self.assertTrue(self._favorites.is_favorite(program))
+
+ self._favorites.unfollow(program)
+ self.assertFalse(self._favorites.is_favorite(program))
+
+ self._favorites.follow(program)
+ self.assertTrue(self._favorites.is_favorite(program))
+
+ def test_names(self):
+ names = self._favorites.names()
+ self.assertTrue(names)
+ print(names)
+
+ def test_titles(self):
+ titles = self._favorites.titles()
+ self.assertTrue(titles)
+ print(sorted(titles))
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/test/searchtests.py b/test/searchtests.py
index ec8967257..ddf0a35e5 100644
--- a/test/searchtests.py
+++ b/test/searchtests.py
@@ -2,23 +2,43 @@
# 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
+from __future__ import absolute_import, division, print_function, unicode_literals
+from contextlib import contextmanager
import mock
+import os
import unittest
from resources.lib.vrtplayer import vrtapihelper
+@contextmanager
+def open_file(path, flags='r'):
+ f = open(path, flags)
+ yield f
+ f.close()
+
+
+def log_notice(msg, level):
+ print('%s: %s' % (level, msg))
+
+
class TestVRTPlayer(unittest.TestCase):
- _kodi_wrapper = mock.MagicMock()
- _kodi_wrapper.get_proxies = mock.MagicMock(return_value=dict())
- _kodi_wrapper.get_localized_dateshort = mock.MagicMock(return_value='%d-%m-%Y')
- _api_helper = vrtapihelper.VRTApiHelper(_kodi_wrapper)
+ _kodiwrapper = mock.MagicMock()
+ _kodiwrapper.check_if_path_exists = mock.MagicMock(side_effect=os.path.exists)
+ _kodiwrapper.get_localized_dateshort = mock.MagicMock(return_value='%d-%m-%Y')
+ _kodiwrapper.get_localized_string.return_value = '"[B][COLOR blue]Available until %s[/COLOR][/B]\n"'
+ _kodiwrapper.get_proxies = mock.MagicMock(return_value=dict())
+ _kodiwrapper.get_userdata_path.return_value = './userdata/'
+ _kodiwrapper.log_notice = mock.MagicMock(side_effect=log_notice)
+ _kodiwrapper.make_dir.return_value = None
+ _kodiwrapper.open_file = mock.MagicMock(side_effect=open_file)
+ # _kodiwrapper.stat_file = mock.MagicMock(side_effect=os.stat)
+ _apihelper = vrtapihelper.VRTApiHelper(_kodiwrapper)
def test_search_journaal(self):
''' Test for journaal '''
- search_items, sort, ascending = self._api_helper.search('journaal', page=1)
+ search_items, sort, ascending = self._apihelper.search('journaal', page=1)
# Test we get a non-empty search result
self.assertEqual(len(search_items), 50)
@@ -27,7 +47,7 @@ def test_search_journaal(self):
def test_search_journaal_page2(self):
''' Test for journaal '''
- search_items, sort, ascending = self._api_helper.search('journaal', page=2)
+ search_items, sort, ascending = self._apihelper.search('journaal', page=2)
# Test we get a non-empty search result
self.assertEqual(len(search_items), 50)
@@ -36,7 +56,7 @@ def test_search_journaal_page2(self):
def test_search_weer(self):
''' Test for journaal '''
- search_items, sort, ascending = self._api_helper.search('weer', page=1)
+ search_items, sort, ascending = self._apihelper.search('weer', page=1)
# Test we get a non-empty search result
self.assertEqual(len(search_items), 50)
diff --git a/test/streamservicetests.py b/test/streamservicetests.py
index 095969751..b675e4d0b 100644
--- a/test/streamservicetests.py
+++ b/test/streamservicetests.py
@@ -43,19 +43,18 @@ def get_localized_string(msgctxt):
class StreamServiceTests(unittest.TestCase):
- _kodi_wrapper = mock.MagicMock()
- _kodi_wrapper.get_proxies = mock.MagicMock(return_value=dict())
- _kodi_wrapper.get_localized_dateshort = mock.MagicMock(return_value='%d-%m-%Y')
- _kodi_wrapper.get_localized_string.return_value = mock.MagicMock(side_effect=get_localized_string)
- _kodi_wrapper.get_setting = mock.MagicMock(side_effect=get_settings)
- _kodi_wrapper.get_userdata_path.return_value = 'vrttest'
- _kodi_wrapper.check_if_path_exists.return_value = False
- _kodi_wrapper.make_dir.return_value = None
- _kodi_wrapper.open_path.return_value = False
- _kodi_wrapper.check_inputstream_adaptive.return_value = True
- _api_helper = vrtapihelper.VRTApiHelper(_kodi_wrapper)
- _token_resolver = tokenresolver.TokenResolver(_kodi_wrapper)
- _stream_service = streamservice.StreamService(_kodi_wrapper, _token_resolver)
+ _kodiwrapper = mock.MagicMock()
+ _kodiwrapper.get_proxies = mock.MagicMock(return_value=dict())
+ _kodiwrapper.get_localized_dateshort = mock.MagicMock(return_value='%d-%m-%Y')
+ _kodiwrapper.get_localized_string.return_value = mock.MagicMock(side_effect=get_localized_string)
+ _kodiwrapper.get_setting = mock.MagicMock(side_effect=get_settings)
+ _kodiwrapper.get_userdata_path.return_value = 'vrttest'
+ _kodiwrapper.check_if_path_exists.return_value = False
+ _kodiwrapper.make_dir.return_value = None
+ _kodiwrapper.check_inputstream_adaptive.return_value = True
+ _apihelper = vrtapihelper.VRTApiHelper(_kodiwrapper)
+ _tokenresolver = tokenresolver.TokenResolver(_kodiwrapper)
+ _streamservice = streamservice.StreamService(_kodiwrapper, _tokenresolver)
def test_get_ondemand_stream_from_invalid_url(self):
video = dict(
@@ -64,7 +63,7 @@ def test_get_ondemand_stream_from_invalid_url(self):
publication_id=None,
)
try:
- stream = self._stream_service.get_stream(video)
+ stream = self._streamservice.get_stream(video)
self.assertEqual(stream.stream_url, video['video_url'])
except HTTPError:
pass
@@ -76,7 +75,7 @@ def test_get_ondemand_stream_from_url_gets_stream_does_not_crash(self):
video_id=None,
publication_id=None,
)
- stream = self._stream_service.get_stream(video)
+ stream = self._streamservice.get_stream(video)
self.assertTrue(stream is not None)
def test_get_live_stream_from_url_does_not_crash_returns_stream_and_licensekey(self):
@@ -86,7 +85,7 @@ def test_get_live_stream_from_url_does_not_crash_returns_stream_and_licensekey(s
video_id=None,
publication_id=None,
)
- stream = self._stream_service.get_stream(video)
+ stream = self._streamservice.get_stream(video)
self.assertTrue(stream is not None)
self.assertTrue(stream.license_key is not None)
@@ -96,7 +95,7 @@ def test_get_live_stream_from_url_does_not_crash(self):
video_id=None,
publication_id=None,
)
- stream = self._stream_service.get_stream(video)
+ stream = self._streamservice.get_stream(video)
self.assertTrue(stream is not None)
def test_get_live_stream_from_id_does_not_crash(self):
@@ -105,7 +104,7 @@ def test_get_live_stream_from_id_does_not_crash(self):
video_id=CHANNELS[1]['live_stream_id'],
publication_id=None,
)
- stream = self._stream_service.get_stream(video)
+ stream = self._streamservice.get_stream(video)
self.assertTrue(stream is not None)
diff --git a/test/tvguidetests.py b/test/tvguidetests.py
index a3c302c2f..b23a6747a 100644
--- a/test/tvguidetests.py
+++ b/test/tvguidetests.py
@@ -16,10 +16,10 @@
class TestTVGuide(unittest.TestCase):
- _kodi_wrapper = mock.MagicMock()
- _kodi_wrapper.get_proxies = mock.MagicMock(return_value=dict())
- _kodi_wrapper.get_localized_datelong = mock.MagicMock(return_value='%a %d-%m-%Y')
- _tv_guide = tvguide.TVGuide(_kodi_wrapper)
+ _kodiwrapper = mock.MagicMock()
+ _kodiwrapper.get_proxies = mock.MagicMock(return_value=dict())
+ _kodiwrapper.get_localized_datelong = mock.MagicMock(return_value='%a %d-%m-%Y')
+ _tv_guide = tvguide.TVGuide(_kodiwrapper)
def test_tvguide_date_menu(self):
''' Test TV guide main menu '''
diff --git a/test/vrtplayertests.py b/test/vrtplayertests.py
index 5231b7fca..5e2b8d2d6 100644
--- a/test/vrtplayertests.py
+++ b/test/vrtplayertests.py
@@ -4,24 +4,42 @@
# pylint: disable=unused-variable
-from __future__ import absolute_import, division, unicode_literals
+from __future__ import absolute_import, division, print_function, unicode_literals
+from contextlib import contextmanager
import mock
+import os
import random
import unittest
from resources.lib.vrtplayer import CATEGORIES, CHANNELS, vrtapihelper, vrtplayer
+@contextmanager
+def open_file(path, flags='r'):
+ f = open(path, flags)
+ yield f
+ f.close()
+
+
+def log_notice(msg, level):
+ print('%s: %s' % (level, msg))
+
+
class TestVRTPlayer(unittest.TestCase):
- _kodi_wrapper = mock.MagicMock()
- _kodi_wrapper.get_proxies = mock.MagicMock(return_value=dict())
- _kodi_wrapper.get_localized_dateshort = mock.MagicMock(return_value='%d-%m-%Y')
- _api_helper = vrtapihelper.VRTApiHelper(_kodi_wrapper)
+ _kodiwrapper = mock.MagicMock()
+ _kodiwrapper.check_if_path_exists = mock.MagicMock(side_effect=os.path.exists)
+ _kodiwrapper.get_localized_dateshort = mock.MagicMock(return_value='%d-%m-%Y')
+ _kodiwrapper.get_proxies = mock.MagicMock(return_value=dict())
+ _kodiwrapper.get_userdata_path.return_value = './userdata/'
+ _kodiwrapper.log_notice = mock.MagicMock(side_effect=log_notice)
+ _kodiwrapper.open_file = mock.MagicMock(side_effect=open_file)
+ # _kodiwrapper.stat_file = mock.MagicMock(side_effect=os.stat)
+ _apihelper = vrtapihelper.VRTApiHelper(_kodiwrapper)
def test_tvshows(self):
''' Test A-Z tvshow listing and CHANNELS list '''
- tvshow_items = self._api_helper.get_tvshow_items(category=None)
+ tvshow_items = self._apihelper.get_tvshow_items(category=None)
# Test we get a non-empty A-Z listing back
self.assertTrue(tvshow_items)
@@ -34,89 +52,96 @@ def test_tvshows(self):
def test_show_videos_single_episode_shows_videos(self):
path = '/vrtnu/a-z/marathonradio.relevant/'
- episode_items, sort, ascending = self._api_helper.get_episode_items(path=path)
+ episode_items, sort, ascending, content = self._apihelper.get_episode_items(path=path)
self.assertTrue(episode_items, msg=path)
self.assertEqual(sort, 'dateadded')
self.assertFalse(ascending)
+ self.assertEqual(content, 'episodes')
- player = vrtplayer.VRTPlayer(self._kodi_wrapper, self._api_helper)
+ player = vrtplayer.VRTPlayer(self._kodiwrapper, self._apihelper)
player.show_episodes(path)
- self.assertTrue(self._kodi_wrapper.show_listing.called)
+ self.assertTrue(self._kodiwrapper.show_listing.called)
def test_show_videos_single_season_shows_videos(self):
path = '/vrtnu/a-z/het-weer.relevant/'
- episode_items, sort, ascending = self._api_helper.get_episode_items(path=path)
+ episode_items, sort, ascending, content = self._apihelper.get_episode_items(path=path)
self.assertTrue(episode_items, msg=path)
self.assertEqual(sort, 'dateadded')
self.assertFalse(ascending)
+ self.assertEqual(content, 'episodes')
- player = vrtplayer.VRTPlayer(self._kodi_wrapper, self._api_helper)
+ player = vrtplayer.VRTPlayer(self._kodiwrapper, self._apihelper)
player.show_episodes(path)
- self.assertTrue(self._kodi_wrapper.show_listing.called)
+ self.assertTrue(self._kodiwrapper.show_listing.called)
def test_show_videos_multiple_seasons_shows_videos(self):
path = '/vrtnu/a-z/pano.relevant/'
- episode_items, sort, ascending = self._api_helper.get_episode_items(path=path)
+ episode_items, sort, ascending, content = self._apihelper.get_episode_items(path=path)
self.assertTrue(episode_items)
self.assertEqual(sort, 'label')
self.assertFalse(ascending)
+ self.assertEqual(content, 'seasons')
- player = vrtplayer.VRTPlayer(self._kodi_wrapper, self._api_helper)
+ player = vrtplayer.VRTPlayer(self._kodiwrapper, self._apihelper)
player.show_episodes(path)
- self.assertTrue(self._kodi_wrapper.show_listing.called)
+ self.assertTrue(self._kodiwrapper.show_listing.called)
def test_show_videos_specific_seasons_shows_videos(self):
path = '/vrtnu/a-z/thuis.relevant/'
- episode_items, sort, ascending = self._api_helper.get_episode_items(path=path)
+ episode_items, sort, ascending, content = self._apihelper.get_episode_items(path=path)
self.assertTrue(episode_items, msg=path)
self.assertEqual(sort, 'label')
self.assertFalse(ascending)
+ self.assertEqual(content, 'seasons')
- player = vrtplayer.VRTPlayer(self._kodi_wrapper, self._api_helper)
+ player = vrtplayer.VRTPlayer(self._kodiwrapper, self._apihelper)
player.show_episodes(path)
- self.assertTrue(self._kodi_wrapper.show_listing.called)
+ self.assertTrue(self._kodiwrapper.show_listing.called)
def test_get_recent_episodes(self):
''' Test items, sort and order '''
- episode_items, sort, ascending = self._api_helper.get_episode_items(page=1)
+ episode_items, sort, ascending, content = self._apihelper.get_episode_items(page=1)
self.assertEqual(len(episode_items), 50)
self.assertEqual(sort, 'dateadded')
self.assertFalse(ascending)
+ self.assertEqual(content, 'episodes')
def test_get_program_episodes(self):
''' Test items, sort and order '''
path = '/vrtnu/a-z/het-journaal.relevant/'
- episode_items, sort, ascending = self._api_helper.get_episode_items(path=path)
+ episode_items, sort, ascending, content = self._apihelper.get_episode_items(path=path)
self.assertTrue(episode_items)
self.assertEqual(sort, 'dateadded')
self.assertFalse(ascending)
+ self.assertEqual(content, 'episodes')
def test_get_tvshows(self):
''' Test items, sort and order '''
path = 'nieuws-en-actua'
- tvshow_items = self._api_helper.get_tvshow_items(path)
+ tvshow_items = self._apihelper.get_tvshow_items(path)
self.assertTrue(tvshow_items)
def test_categories_scraping(self):
''' Test to ensure our hardcoded categories conforms to scraped categories '''
# Remove thumbnails from scraped categories first
- vrt_player = vrtplayer.VRTPlayer(self._kodi_wrapper, self._api_helper)
+ vrt_player = vrtplayer.VRTPlayer(self._kodiwrapper, self._apihelper)
categories = [dict(id=c['id'], name=c['name']) for c in vrt_player.get_categories()]
self.assertEqual(categories, CATEGORIES)
def test_random_tvshow_episodes(self):
''' Rest episode from a random tvshow in a random category '''
- vrt_player = vrtplayer.VRTPlayer(self._kodi_wrapper, self._api_helper)
+ vrt_player = vrtplayer.VRTPlayer(self._kodiwrapper, self._apihelper)
categories = vrt_player.get_categories()
self.assertTrue(categories)
category = random.choice(categories)
- tvshow_items = self._api_helper.get_tvshow_items(category['id'])
+ tvshow_items = self._apihelper.get_tvshow_items(category['id'])
self.assertTrue(tvshow_items, msg=category['id'])
tvshow = random.choice(tvshow_items)
- episode_items, sort, ascending = self._api_helper.get_episode_items(tvshow.url_dict['video_url'])
+ episode_items, sort, ascending, content = self._apihelper.get_episode_items(tvshow.url_dict['video_url'])
self.assertTrue(episode_items, msg=tvshow.url_dict['video_url'])
+ self.assertTrue(content in ['episodes', 'seasons'], "Content for '%s' is '%s'" % (tvshow.title, content))
if __name__ == '__main__':