From 9cdebb6c7f21ce64d49c1a2d02841e5377455487 Mon Sep 17 00:00:00 2001 From: mediaminister <45148099+mediaminister@users.noreply.github.com> Date: Mon, 16 Jan 2023 17:59:29 +0100 Subject: [PATCH] Update api (#114) --- resources/lib/addon.py | 7 +++- resources/lib/modules/menu.py | 2 +- resources/lib/modules/player.py | 12 +++--- resources/lib/viervijfzes/content.py | 57 ++++++++++++++++++---------- tests/test_api.py | 4 +- 5 files changed, 51 insertions(+), 31 deletions(-) diff --git a/resources/lib/addon.py b/resources/lib/addon.py index 4410a1d..a55e072 100644 --- a/resources/lib/addon.py +++ b/resources/lib/addon.py @@ -161,10 +161,13 @@ def play_epg(channel, timestamp): @routing.route('/play/catalog') @routing.route('/play/catalog/') -def play_catalog(uuid=None): +@routing.route('/play/catalog//') +def play_catalog(uuid=None, islongform=False): """ Play the requested item """ + from ast import literal_eval from resources.lib.modules.player import Player - Player().play(uuid) + # Convert string to bool using literal_eval + Player().play(uuid, literal_eval(islongform)) @routing.route('/play/page/') diff --git a/resources/lib/modules/menu.py b/resources/lib/modules/menu.py index 6ea515e..d434e7e 100644 --- a/resources/lib/modules/menu.py +++ b/resources/lib/modules/menu.py @@ -183,7 +183,7 @@ class Menu: if item.uuid: # We have an UUID and can play this item directly - path = kodiutils.url_for('play_catalog', uuid=item.uuid) + path = kodiutils.url_for('play_catalog', uuid=item.uuid, islongform=item.islongform) else: # We don't have an UUID, and first need to fetch the video information from the page path = kodiutils.url_for('play_from_page', page=quote(item.path, safe='')) diff --git a/resources/lib/modules/player.py b/resources/lib/modules/player.py index c993f26..437e0a6 100644 --- a/resources/lib/modules/player.py +++ b/resources/lib/modules/player.py @@ -74,7 +74,7 @@ class Player: if episode.uuid: # Lookup the stream - resolved_stream = self._resolve_stream(episode.uuid) + resolved_stream = self._resolve_stream(episode.uuid, episode.islongform) _LOGGER.debug('Resolved stream: %s', resolved_stream) if resolved_stream: @@ -95,16 +95,17 @@ class Player: art_dict=titleitem.art_dict, prop_dict=titleitem.prop_dict) - def play(self, uuid): + def play(self, uuid, islongform): """ Play the requested item. :type uuid: string + :type islongform: bool """ if not uuid: kodiutils.ok_dialog(message=kodiutils.localize(30712)) # The video is unavailable... return # Lookup the stream - resolved_stream = self._resolve_stream(uuid) + resolved_stream = self._resolve_stream(uuid, islongform) if resolved_stream.license_url: # Generate license key license_key = self.create_license_key(resolved_stream.license_url, key_headers=dict( @@ -116,9 +117,10 @@ class Player: kodiutils.play(resolved_stream.url, resolved_stream.stream_type, license_key) @staticmethod - def _resolve_stream(uuid): + def _resolve_stream(uuid, islongform): """ Resolve the stream for the requested item :type uuid: string + :type islongform: bool """ try: # Check if we have credentials @@ -135,7 +137,7 @@ class Player: auth = AuthApi(kodiutils.get_setting('username'), kodiutils.get_setting('password'), kodiutils.get_tokens_path()) # Get stream information - resolved_stream = ContentApi(auth).get_stream_by_uuid(uuid) + resolved_stream = ContentApi(auth).get_stream_by_uuid(uuid, islongform) return resolved_stream except (InvalidLoginException, AuthenticationException) as ex: diff --git a/resources/lib/viervijfzes/content.py b/resources/lib/viervijfzes/content.py index 4c9f8cd..1913e24 100644 --- a/resources/lib/viervijfzes/content.py +++ b/resources/lib/viervijfzes/content.py @@ -109,7 +109,7 @@ class Episode: """ Defines an Episode. """ def __init__(self, uuid=None, nodeid=None, path=None, channel=None, program_title=None, title=None, description=None, thumb=None, duration=None, - season=None, season_uuid=None, number=None, rating=None, aired=None, expiry=None, stream=None): + season=None, season_uuid=None, number=None, rating=None, aired=None, expiry=None, stream=None, islongform=False): """ :type uuid: str :type nodeid: str @@ -127,6 +127,7 @@ class Episode: :type aired: datetime :type expiry: datetime :type stream: string + :type islongform: bool """ self.uuid = uuid self.nodeid = nodeid @@ -144,6 +145,7 @@ class Episode: self.aired = aired self.expiry = expiry self.stream = stream + self.islongform = islongform def __repr__(self): return "%r" % self.__dict__ @@ -349,50 +351,63 @@ class ContentApi: return None - def get_stream_by_uuid(self, uuid): + def get_stream_by_uuid(self, uuid, islongform): """ Get the stream URL to use for this video. :type uuid: str + :type islongform: bool :rtype str """ - response = self._get_url(self.API_VIERVIJFZES + '/content/%s' % uuid, authentication=self._auth.get_token()) + mode = 'long-form' if islongform else 'short-form' + response = self._get_url(self.API_GOPLAY + '/web/v1/videos/%s/%s' % (mode, uuid), authentication='Bearer %s' % self._auth.get_token()) data = json.loads(response) if not data: raise UnavailableException - if 'videoDash' in data: + if data.get('manifestUrls'): - if 'drmKey' in data: + if data.get('drmXml'): # DRM protected stream # See https://docs.unified-streaming.com/documentation/drm/buydrm.html#setting-up-the-client - drm_key = data['drmKey']['S'] - - _LOGGER.debug('Fetching Authentication XML with drm_key %s', drm_key) - response_drm = self._get_url(self.API_GOPLAY + '/video/xml/%s' % drm_key, authentication=self._auth.get_token()) - data_drm = json.loads(response_drm) # DRM protected DASH stream return ResolvedStream( uuid=uuid, - url=data['videoDash']['S'], + url=data['manifestUrls']['dash'], stream_type=STREAM_DASH, license_url='https://wv-keyos.licensekeyserver.com/', - auth=data_drm.get('auth'), + auth=data['drmXml'], ) + if data.get('manifestUrls').get('dash'): + # Unprotected DASH stream + return ResolvedStream( + uuid=uuid, + url=data['manifestUrls']['dash'], + stream_type=STREAM_DASH, + ) + + # Unprotected HLS stream + return ResolvedStream( + uuid=uuid, + url=data['manifestUrls']['hls'], + stream_type=STREAM_HLS, + ) + + # No manifest url found, get manifest from Server-Side Ad Insertion service + if data.get('adType') == 'SSAI' and data.get('ssai'): + url = 'https://pubads.g.doubleclick.net/ondemand/dash/content/%s/vid/%s/streams' % (data.get('ssai').get('contentSourceID'), data.get('ssai').get('videoID')) + ad_data = json.loads(self._post_url(url, data='')) + # Unprotected DASH stream return ResolvedStream( uuid=uuid, - url=data['videoDash']['S'], + url=ad_data['stream_manifest'], stream_type=STREAM_DASH, ) - # Unprotected HLS stream - return ResolvedStream( - uuid=uuid, - url=data['video']['S'], - stream_type=STREAM_HLS, - ) + raise UnavailableException + def get_program_tree(self, cache=CACHE_AUTO): """ Get a content tree with information about all the programs. @@ -675,7 +690,6 @@ class ContentApi: :type season_uuid: str :rtype Episode """ - if data.get('episodeNumber'): episode_number = data.get('episodeNumber') else: @@ -703,6 +717,7 @@ class ContentApi: expiry=datetime.fromtimestamp(int(data.get('unpublishDate'))) if data.get('unpublishDate') else None, rating=data.get('parentalRating'), stream=data.get('path'), + islongform=data.get('isLongForm'), ) return episode @@ -751,7 +766,7 @@ class ContentApi: else: response = self._session.post(url, params=params, json=data) - if response.status_code != 200: + if response.status_code not in (200, 201): _LOGGER.error(response.text) raise Exception('Could not fetch data') diff --git a/tests/test_api.py b/tests/test_api.py index 56ce607..bd600d2 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -70,12 +70,12 @@ class TestApi(unittest.TestCase): self.assertIsInstance(program, Program) episode = program.episodes[0] - resolved_stream = self._api.get_stream_by_uuid(episode.uuid) + resolved_stream = self._api.get_stream_by_uuid(episode.uuid, episode.islongform) self.assertIsInstance(resolved_stream, ResolvedStream) @unittest.skipUnless(kodiutils.get_setting('username') and kodiutils.get_setting('password'), 'Skipping since we have no credentials.') def test_get_drm_stream(self): - resolved_stream = self._api.get_stream_by_uuid('e7faa457-5768-4abd-bf4f-5a0e1055bbd3') # https://www.goplay.be/video/ncis-los-angeles/ncis-los-angeles-s13/ncis-los-angeles-s13-aflevering-1 + resolved_stream = self._api.get_stream_by_uuid('e7faa457-5768-4abd-bf4f-5a0e1055bbd3', True) # https://www.goplay.be/video/ncis-los-angeles/ncis-los-angeles-s13/ncis-los-angeles-s13-aflevering-1 self.assertIsInstance(resolved_stream, ResolvedStream)