diff --git a/.gitignore b/.gitignore index c7c4c3b..bad9394 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,6 @@ Thumbs.db # Testing tests/home/userdata/addon_data .env + +Pipfile +Pipfile.lock diff --git a/README.md b/README.md index e25b7c8..e5c02d5 100644 --- a/README.md +++ b/README.md @@ -4,18 +4,18 @@ [![License: GPLv3](https://img.shields.io/badge/License-GPLv3-yellow.svg)](https://opensource.org/licenses/GPL-3.0) [![Contributors](https://img.shields.io/github/contributors/add-ons/plugin.video.viervijfzes.svg)](https://github.com/add-ons/plugin.video.viervijfzes/graphs/contributors) -# VIER-VIJF-ZES Kodi add-on +# GoPlay Kodi add-on -*plugin.video.viervijfzes* is een Kodi add-on om de video-on-demand content van [vier.be](https://www.vier.be/), [vijf.be](https://www.vijf.be/) en [zestv.be](https://www.zestv.be/) te bekijken. +*plugin.video.viervijfzes* is een Kodi add-on om de video-on-demand content van [goplay.be](https://www.goplay.be/) te bekijken. -> Note: Je moet eerst een account aanmaken op één van bovenstaande websites. +> Note: Je moet eerst een account aanmaken. Meer informatie kan je vinden op de [Wiki pagina](https://github.com/add-ons/plugin.video.viervijfzes/wiki). ## Features De volgende features worden ondersteund: -* Bekijk on-demand content van VIER, VIJF en ZES +* Bekijk on-demand content van Play4, Play5 en Play6 * Programma's rechtstreeks afspelen vanuit de tv-gids * Doorzoeken van alle programma's * Afspelen van gerelateerde Youtube content @@ -34,4 +34,4 @@ De volgende features worden ondersteund: ## Disclaimer Deze add-on wordt niet ondersteund door SBS Belgium, en wordt aangeboden 'as is', zonder enige garantie. -De logo's van VIER, VIJF en ZES zijn eigendom van SBS België. +De logo's van GoPlay, Play4, Play5 en Play6 zijn eigendom van SBS België. diff --git a/addon.xml b/addon.xml index 26903b7..a1c3435 100644 --- a/addon.xml +++ b/addon.xml @@ -1,5 +1,5 @@ - + @@ -14,12 +14,12 @@ - Bekijk programma's van VIER, VIJF en ZES. - Deze add-on geeft toegang tot de programma's die aangeboden worden op de websites van VIER, VIJF en ZES. - Deze add-on wordt niet ondersteund door SBS België, en wordt aangeboden 'as is', zonder enige garantie. De logo's van VIER, VIJF en ZES zijn eigendom van SBS België. - Watch content from VIER, VIJF and ZES. - This add-on gives access to video-on-demand content available on the websites of VIER, VIJF and ZES. - This add-on is not officially commissioned/supported by SBS Belgium and is provided 'as is' without any warranty of any kind. The VIER, VIJF and ZES logos are property of SBS Belgium. + Bekijk programma's van Play4, Play5 en Play6. + Deze add-on geeft toegang tot de programma's die aangeboden worden op de websites van Play4, Play5 en Play6. + Deze add-on wordt niet ondersteund door SBS België, en wordt aangeboden 'as is', zonder enige garantie. De logo's van Play4, Play5 en Play6 zijn eigendom van SBS België. + Watch content from Play4, Play5 and Play6. + This add-on gives access to video-on-demand content available on the websites of Play4, Play5 and Play6. + This add-on is not officially commissioned/supported by SBS Belgium and is provided 'as is' without any warranty of any kind. The Play4, Play5 and Play6 logos are property of SBS Belgium. all GPL-3.0-only v0.3.1 (2020-11-28) diff --git a/resources/fanart.png b/resources/fanart.png index 967703f..560bbd8 100644 Binary files a/resources/fanart.png and b/resources/fanart.png differ diff --git a/resources/icon.png b/resources/icon.png index eff68e6..3cdff4f 100644 Binary files a/resources/icon.png and b/resources/icon.png differ diff --git a/resources/lib/addon.py b/resources/lib/addon.py index 70ab9f1..7cdd181 100644 --- a/resources/lib/addon.py +++ b/resources/lib/addon.py @@ -35,18 +35,18 @@ def show_channel_menu(channel): Channels().show_channel_menu(channel) -@routing.route('/channels//categories') -def show_channel_categories(channel): - """ Shows TV Channel categories """ - from resources.lib.modules.channels import Channels - Channels().show_channel_categories(channel) +# @routing.route('/channels//categories') +# def show_channel_categories(channel): +# """ Shows TV Channel categories """ +# from resources.lib.modules.channels import Channels +# Channels().show_channel_categories(channel) -@routing.route('/channels//categories/') -def show_channel_category(channel, category): - """ Shows TV Channel categories """ - from resources.lib.modules.channels import Channels - Channels().show_channel_category(channel, category) +# @routing.route('/channels//categories/') +# def show_channel_category(channel, category): +# """ Shows TV Channel categories """ +# from resources.lib.modules.channels import Channels +# Channels().show_channel_category(channel, category) @routing.route('/channels//tvguide') @@ -63,6 +63,13 @@ def show_channel_tvguide_detail(channel=None, date=None): TvGuide().show_detail(channel, date) +@routing.route('/channels//catalog') +def show_channel_catalog(channel): + """ Show the catalog of a channel """ + from resources.lib.modules.catalog import Catalog + Catalog().show_catalog_channel(channel) + + @routing.route('/catalog') def show_catalog(): """ Show the catalog """ @@ -70,32 +77,25 @@ def show_catalog(): Catalog().show_catalog() -@routing.route('/catalog/') -def show_channel_catalog(channel): - """ Show a category in the catalog """ - from resources.lib.modules.catalog import Catalog - Catalog().show_catalog_channel(channel) - - -@routing.route('/catalog//') -def show_catalog_program(channel, program): +@routing.route('/catalog/') +def show_catalog_program(program): """ Show a program from the catalog """ from resources.lib.modules.catalog import Catalog - Catalog().show_program(channel, program) + Catalog().show_program(program) -@routing.route('/catalog///clips') -def show_catalog_program_clips(channel, program): +@routing.route('/catalog//clips') +def show_catalog_program_clips(program): """ Show the clips from a program """ from resources.lib.modules.catalog import Catalog - Catalog().show_program_clips(channel, program) + Catalog().show_program_clips(program) -@routing.route('/catalog///season/') -def show_catalog_program_season(channel, program, season): +@routing.route('/catalog//season/') +def show_catalog_program_season(program, season): """ Show a season from a program """ from resources.lib.modules.catalog import Catalog - Catalog().show_program_season(channel, program, season) + Catalog().show_program_season(program, season) @routing.route('/search') @@ -127,8 +127,8 @@ def play_catalog(uuid): Player().play(uuid) -@routing.route('/play/page//') -def play_from_page(channel, page): +@routing.route('/play/page/') +def play_from_page(page): """ Play the requested item """ try: # Python 3 from urllib.parse import unquote @@ -136,7 +136,7 @@ def play_from_page(channel, page): from urllib import unquote from resources.lib.modules.player import Player - Player().play_from_page(channel, unquote(page)) + Player().play_from_page(unquote(page)) @routing.route('/metadata/update') diff --git a/resources/lib/modules/catalog.py b/resources/lib/modules/catalog.py index 4dd7093..8018088 100644 --- a/resources/lib/modules/catalog.py +++ b/resources/lib/modules/catalog.py @@ -8,7 +8,6 @@ import logging from resources.lib import kodiutils from resources.lib.kodiutils import TitleItem from resources.lib.modules.menu import Menu -from resources.lib.viervijfzes import CHANNELS from resources.lib.viervijfzes.auth import AuthApi from resources.lib.viervijfzes.content import CACHE_PREVENT, ContentApi, UnavailableException @@ -26,9 +25,7 @@ class Catalog: def show_catalog(self): """ Show all the programs of all channels """ try: - items = [] - for channel in list(CHANNELS): - items.extend(self._api.get_programs(channel)) + items = self._api.get_programs() except Exception as ex: kodiutils.notification(message=str(ex)) raise @@ -57,13 +54,12 @@ class Catalog: # Used for A-Z listing or when movies and episodes are mixed. kodiutils.show_listing(listing, 30003, content='tvshows', sort='title') - def show_program(self, channel, program_id): + def show_program(self, program_id): """ Show a program from the catalog - :type channel: str :type program_id: str """ try: - program = self._api.get_program(channel, program_id, extract_clips=True, cache=CACHE_PREVENT) # Use CACHE_PREVENT since we want fresh data + program = self._api.get_program(program_id, extract_clips=True, cache=CACHE_PREVENT) # Use CACHE_PREVENT since we want fresh data except UnavailableException: kodiutils.ok_dialog(message=kodiutils.localize(30717)) # This program is not available in the catalogue. kodiutils.end_of_directory() @@ -76,11 +72,9 @@ class Catalog: # Go directly to the season when we have only one season and no clips if not program.clips and len(program.seasons) == 1: - self.show_program_season(channel, program_id, list(program.seasons.values())[0].uuid) + self.show_program_season(program_id, list(program.seasons.values())[0].uuid) return - studio = CHANNELS.get(program.channel, {}).get('studio_icon') - listing = [] # Add an '* All seasons' entry when configured in Kodi @@ -88,7 +82,7 @@ class Catalog: listing.append( TitleItem( title='* %s' % kodiutils.localize(30204), # * All seasons - path=kodiutils.url_for('show_catalog_program_season', channel=channel, program=program_id, season='-1'), + path=kodiutils.url_for('show_catalog_program_season', program=program_id, season='-1'), art_dict={ 'fanart': program.background, }, @@ -97,7 +91,6 @@ class Catalog: 'title': kodiutils.localize(30204), # All seasons 'plot': program.description, 'set': program.title, - 'studio': studio, } ) ) @@ -107,7 +100,7 @@ class Catalog: listing.append( TitleItem( title=season.title, # kodiutils.localize(30205, season=season.number), # Season {season} - path=kodiutils.url_for('show_catalog_program_season', channel=channel, program=program_id, season=season.uuid), + path=kodiutils.url_for('show_catalog_program_season', program=program_id, season=season.uuid), art_dict={ 'fanart': program.background, }, @@ -116,7 +109,6 @@ class Catalog: 'title': kodiutils.localize(30205, season=season.number), # Season {season} 'plot': season.description, 'set': program.title, - 'studio': studio, } ) ) @@ -126,7 +118,7 @@ class Catalog: listing.append( TitleItem( title=kodiutils.localize(30059, program=program.title), # Clips for {program} - path=kodiutils.url_for('show_catalog_program_clips', channel=channel, program=program_id), + path=kodiutils.url_for('show_catalog_program_clips', program=program_id), art_dict={ 'fanart': program.background, }, @@ -135,7 +127,6 @@ class Catalog: 'title': kodiutils.localize(30059, program=program.title), # Clips for {program} 'plot': kodiutils.localize(30060, program=program.title), # Watch short clips of {program} 'set': program.title, - 'studio': studio, } ) ) @@ -143,14 +134,13 @@ class Catalog: # Sort by label. Some programs return seasons unordered. kodiutils.show_listing(listing, 30003, content='tvshows') - def show_program_season(self, channel, program_id, season_uuid): + def show_program_season(self, program_id, season_uuid): """ Show the episodes of a program from the catalog - :type channel: str :type program_id: str :type season_uuid: str """ try: - program = self._api.get_program(channel, program_id) + program = self._api.get_program(program_id) except UnavailableException: kodiutils.ok_dialog(message=kodiutils.localize(30717)) # This program is not available in the catalogue. kodiutils.end_of_directory() @@ -168,14 +158,13 @@ class Catalog: # Sort by episode number by default. Takes seasons into account. kodiutils.show_listing(listing, 30003, content='episodes', sort=['episode', 'duration']) - def show_program_clips(self, channel, program_id): + def show_program_clips(self, program_id): """ Show the clips of a program from the catalog - :type channel: str :type program_id: str """ try: # We need to query the backend, since we don't cache clips. - program = self._api.get_program(channel, program_id, extract_clips=True, cache=CACHE_PREVENT) + program = self._api.get_program(program_id, extract_clips=True, cache=CACHE_PREVENT) except UnavailableException: kodiutils.ok_dialog(message=kodiutils.localize(30717)) # This program is not available in the catalogue. kodiutils.end_of_directory() diff --git a/resources/lib/modules/channels.py b/resources/lib/modules/channels.py index afb9642..3cb3649 100644 --- a/resources/lib/modules/channels.py +++ b/resources/lib/modules/channels.py @@ -7,10 +7,9 @@ import logging from resources.lib import kodiutils from resources.lib.kodiutils import TitleItem -from resources.lib.modules.menu import Menu from resources.lib.viervijfzes import CHANNELS, STREAM_DICT from resources.lib.viervijfzes.auth import AuthApi -from resources.lib.viervijfzes.content import CACHE_AUTO, CACHE_ONLY, ContentApi +from resources.lib.viervijfzes.content import ContentApi _LOGGER = logging.getLogger(__name__) @@ -55,7 +54,6 @@ class Channels: 'plot': None, 'playcount': 0, 'mediatype': 'video', - 'studio': channel.get('studio_icon'), }, stream_dict=STREAM_DICT, context_menu=context_menu @@ -74,18 +72,24 @@ class Channels: # Lookup the high resolution logo based on the channel name fanart = '{path}/resources/logos/{logo}'.format(path=kodiutils.addon_path(), logo=channel_info.get('background')) - listing = [ - TitleItem( - title=kodiutils.localize(30053, channel=channel_info.get('name')), # TV Guide for {channel} - path=kodiutils.url_for('show_channel_tvguide', channel=channel), - art_dict={ - 'icon': 'DefaultAddonTvInfo.png', - 'fanart': fanart, - }, - info_dict={ - 'plot': kodiutils.localize(30054, channel=channel_info.get('name')), # Browse the TV Guide for {channel} - } - ), + listing = [] + + if channel_info.get('epg_id'): + listing.append( + TitleItem( + title=kodiutils.localize(30053, channel=channel_info.get('name')), # TV Guide for {channel} + path=kodiutils.url_for('show_channel_tvguide', channel=channel), + art_dict={ + 'icon': 'DefaultAddonTvInfo.png', + 'fanart': fanart, + }, + info_dict={ + 'plot': kodiutils.localize(30054, channel=channel_info.get('name')), # Browse the TV Guide for {channel} + } + ) + ) + + listing.append( TitleItem( title=kodiutils.localize(30055, channel=channel_info.get('name')), # Catalog for {channel} path=kodiutils.url_for('show_channel_catalog', channel=channel), @@ -96,19 +100,22 @@ class Channels: info_dict={ 'plot': kodiutils.localize(30056, channel=channel_info.get('name')), # Browse the Catalog for {channel} } - ), - TitleItem( - title=kodiutils.localize(30057, channel=channel_info.get('name')), # Categories for {channel} - path=kodiutils.url_for('show_channel_categories', channel=channel), - art_dict={ - 'icon': 'DefaultGenre.png', - 'fanart': fanart, - }, - info_dict={ - 'plot': kodiutils.localize(30058, channel=channel_info.get('name')), # Browse the Categories for {channel} - } - ), - ] + ) + ) + + # listing.append( + # TitleItem( + # title=kodiutils.localize(30057, channel=channel_info.get('name')), # Categories for {channel} + # path=kodiutils.url_for('show_channel_categories', channel=channel), + # art_dict={ + # 'icon': 'DefaultGenre.png', + # 'fanart': fanart, + # }, + # info_dict={ + # 'plot': kodiutils.localize(30058, channel=channel_info.get('name')), # Browse the Categories for {channel} + # } + # ) + # ) # Add YouTube channels if kodiutils.get_cond_visibility('System.HasAddon(plugin.video.youtube)') != 0: @@ -125,57 +132,57 @@ class Channels: kodiutils.show_listing(listing, 30007, sort=['unsorted']) - def show_channel_categories(self, channel): - """ Shows the categories of a channel - :type channel: str - """ - categories = self._api.get_categories(channel) + # def show_channel_categories(self, channel): + # """ Shows the categories of a channel + # :type channel: str + # """ + # categories = self._api.get_categories(channel) + # + # listing = [ + # TitleItem( + # title=category.title, + # path=kodiutils.url_for('show_channel_category', channel=category.channel, category=category.uuid), + # art_dict={ + # 'icon': 'DefaultGenre.png', + # }, + # ) for category in categories + # ] + # + # kodiutils.show_listing(listing, 30007, sort=['unsorted']) - listing = [ - TitleItem( - title=category.title, - path=kodiutils.url_for('show_channel_category', channel=category.channel, category=category.uuid), - art_dict={ - 'icon': 'DefaultGenre.png', - }, - ) for category in categories - ] - - kodiutils.show_listing(listing, 30007, sort=['unsorted']) - - def show_channel_category(self, channel, category_id): - """ Shows a selected category of a channel - :type channel: str - :type category_id: str - """ - categories = self._api.get_categories(channel) - - # Extract selected category - category = next(category for category in categories if category.uuid == category_id) - if not category: - raise Exception('Unknown category') - - # Add programs - listing_programs = [] - for item in category.programs: - program = self._api.get_program(channel, item.path, CACHE_ONLY) # Get program details, but from cache only - - if program: - listing_programs.append(Menu.generate_titleitem(program)) - else: - listing_programs.append(Menu.generate_titleitem(item)) - - # Add episodes - listing_episodes = [] - for item in category.episodes: - # We don't have the Program Name without making a request to the page, so we use CACHE_AUTO instead of CACHE_ONLY. - # This will make a request for each item in this view (about 12 items), but it goes quite fast. - # Results are cached, so this will only happen once. - episode = self._api.get_episode(channel, item.path, CACHE_AUTO) - - if episode: - listing_episodes.append(Menu.generate_titleitem(episode)) - else: - listing_episodes.append(Menu.generate_titleitem(item)) - - kodiutils.show_listing(listing_programs + listing_episodes, 30007, content='tvshows', sort=['unsorted']) + # def show_channel_category(self, channel, category_id): + # """ Shows a selected category of a channel + # :type channel: str + # :type category_id: str + # """ + # categories = self._api.get_categories(channel) + # + # # Extract selected category + # category = next(category for category in categories if category.uuid == category_id) + # if not category: + # raise Exception('Unknown category') + # + # # Add programs + # listing_programs = [] + # for item in category.programs: + # program = self._api.get_program(item.path, CACHE_ONLY) # Get program details, but from cache only + # + # if program: + # listing_programs.append(Menu.generate_titleitem(program)) + # else: + # listing_programs.append(Menu.generate_titleitem(item)) + # + # # Add episodes + # listing_episodes = [] + # for item in category.episodes: + # # We don't have the Program Name without making a request to the page, so we use CACHE_AUTO instead of CACHE_ONLY. + # # This will make a request for each item in this view (about 12 items), but it goes quite fast. + # # Results are cached, so this will only happen once. + # episode = self._api.get_episode(item.path, CACHE_AUTO) + # + # if episode: + # listing_episodes.append(Menu.generate_titleitem(episode)) + # else: + # listing_episodes.append(Menu.generate_titleitem(item)) + # + # kodiutils.show_listing(listing_programs + listing_episodes, 30007, content='tvshows', sort=['unsorted']) diff --git a/resources/lib/modules/iptvmanager.py b/resources/lib/modules/iptvmanager.py index 1824c75..6f45efe 100644 --- a/resources/lib/modules/iptvmanager.py +++ b/resources/lib/modules/iptvmanager.py @@ -41,17 +41,16 @@ class IPTVManager: """Return JSON-STREAMS formatted information to IPTV Manager""" streams = [] for key, channel in CHANNELS.items(): - item = dict( - id=channel.get('iptv_id'), - name=channel.get('name'), - logo='special://home/addons/{addon}/resources/logos/{logo}'.format(addon=kodiutils.addon_id(), - logo=channel.get('logo')), - preset=channel.get('iptv_preset'), - stream='plugin://plugin.video.viervijfzes/play/live/{channel}'.format(channel=key), - vod='plugin://plugin.video.viervijfzes/play/epg/{channel}/{{date}}'.format(channel=key) - ) - - streams.append(item) + if channel.get('iptv_id'): + streams.append(dict( + id=channel.get('iptv_id'), + name=channel.get('name'), + logo='special://home/addons/{addon}/resources/logos/{logo}'.format(addon=kodiutils.addon_id(), + logo=channel.get('logo')), + preset=channel.get('iptv_preset'), + stream='plugin://plugin.video.viervijfzes/play/live/{channel}'.format(channel=key), + vod='plugin://plugin.video.viervijfzes/play/epg/{channel}/{{date}}'.format(channel=key) + )) return dict(version=1, streams=streams) @@ -69,25 +68,26 @@ class IPTVManager: for key, channel in CHANNELS.items(): iptv_id = channel.get('iptv_id') - results[iptv_id] = [] - for date in ['yesterday', 'today', 'tomorrow']: - epg = epg_api.get_epg(key, date) + if channel.get('iptv_id'): + results[iptv_id] = [] + for date in ['yesterday', 'today', 'tomorrow']: + epg = epg_api.get_epg(key, date) - results[iptv_id].extend([ - dict( - start=program.start.isoformat(), - stop=(program.start + timedelta(seconds=program.duration)).isoformat(), - title=program.program_title, - subtitle=program.episode_title, - description=program.description, - episode='S%sE%s' % (program.season, program.number) if program.season and program.number else None, - genre=program.genre, - genre_id=program.genre_id, - image=program.cover, - stream=kodiutils.url_for('play_from_page', - channel=key, - page=quote(program.video_url, safe='')) if program.video_url else None) - for program in epg if program.duration - ]) + results[iptv_id].extend([ + dict( + start=program.start.isoformat(), + stop=(program.start + timedelta(seconds=program.duration)).isoformat(), + title=program.program_title, + subtitle=program.episode_title, + description=program.description, + episode='S%sE%s' % (program.season, program.number) if program.season and program.number else None, + genre=program.genre, + genre_id=program.genre_id, + image=program.cover, + stream=kodiutils.url_for('play_from_page', + channel=key, + page=quote(program.video_url, safe='')) if program.video_url else None) + for program in epg if program.duration + ]) return dict(version=1, epg=results) diff --git a/resources/lib/modules/menu.py b/resources/lib/modules/menu.py index b79e46a..d6575fb 100644 --- a/resources/lib/modules/menu.py +++ b/resources/lib/modules/menu.py @@ -5,7 +5,7 @@ from __future__ import absolute_import, division, unicode_literals from resources.lib import kodiutils from resources.lib.kodiutils import TitleItem -from resources.lib.viervijfzes import CHANNELS, STREAM_DICT +from resources.lib.viervijfzes import STREAM_DICT from resources.lib.viervijfzes.content import Episode, Program @@ -70,7 +70,6 @@ class Menu: info_dict = { 'title': item.title, 'plot': item.description, - 'studio': CHANNELS.get(item.channel, {}).get('studio_icon'), 'aired': item.aired.strftime('%Y-%m-%d') if item.aired else None, } prop_dict = {} diff --git a/resources/lib/modules/player.py b/resources/lib/modules/player.py index 3ee1b18..851d837 100644 --- a/resources/lib/modules/player.py +++ b/resources/lib/modules/player.py @@ -35,13 +35,12 @@ class Player: kodiutils.ok_dialog(message=kodiutils.localize(30718, channel=channel_name.get('name'))) # There is no live stream available for {channel}. kodiutils.end_of_directory() - def play_from_page(self, channel, path): + def play_from_page(self, path): """ Play the requested item. - :type channel: string :type path: string """ # Get episode information - episode = self._api.get_episode(channel, path) + episode = self._api.get_episode(path) resolved_stream = None if episode is None: diff --git a/resources/lib/modules/tvguide.py b/resources/lib/modules/tvguide.py index aac88b1..7225e0c 100644 --- a/resources/lib/modules/tvguide.py +++ b/resources/lib/modules/tvguide.py @@ -181,4 +181,4 @@ class TvGuide: kodiutils.end_of_directory() return - Player().play_from_page(channel, broadcast.video_url) + Player().play_from_page(broadcast.video_url) diff --git a/resources/lib/viervijfzes/__init__.py b/resources/lib/viervijfzes/__init__.py index 1936c1f..0dcb357 100644 --- a/resources/lib/viervijfzes/__init__.py +++ b/resources/lib/viervijfzes/__init__.py @@ -1,50 +1,70 @@ # -*- coding: utf-8 -*- -""" SBS API """ +""" GoPlay API """ from __future__ import absolute_import, division, unicode_literals from collections import OrderedDict CHANNELS = OrderedDict([ - ('vier', dict( - name='VIER', - url='https://www.vier.be', - logo='vier.png', - background='vier-background.jpg', - studio_icon='vier', + ('Play4', dict( + name='Play4', + epg_id='vier', + logo='play4.png', + background='play4-background.png', iptv_preset=4, - iptv_id='vier.be', + iptv_id='play4.be', youtube=[ dict( - label='VIER / VIJF', - logo='vier.png', + label='GoPlay', + logo='goplay.png', path='plugin://plugin.video.youtube/user/viertv/', ), ], )), - ('vijf', dict( - name='VIJF', - url='https://www.vijf.be', - logo='vijf.png', - background='vijf-background.jpg', - studio_icon='vijf', + ('Play5', dict( + name='Play5', + epg_id='vijf', + logo='play5.png', + background='play5-background.png', iptv_preset=5, - iptv_id='vijf.be', + iptv_id='play5.be', youtube=[ dict( - label='VIER / VIJF', - logo='vijf.png', + label='GoPlay', + logo='goplay.png', path='plugin://plugin.video.youtube/user/viertv/', ), ], )), - ('zes', dict( - name='ZES', - url='https://www.zestv.be', - logo='zes.png', - background='zes-background.jpg', - studio_icon='zes', + ('Play6', dict( + name='Play6', + epg_id='zes', + logo='play6.png', + background='play6-background.png', iptv_preset=6, - iptv_id='zes.be', + iptv_id='play6.be', + youtube=[ + dict( + label='GoPlay', + logo='goplay.png', + path='plugin://plugin.video.youtube/user/viertv/', + ), + ], + )), + # ('Play7', dict( + # name='Play7', + # epg_id='zeven', + # url='https://www.goplay.be', + # logo='play7.png', + # background='play7-background.png', + # iptv_preset=7, + # iptv_id='play7.be', + # youtube=[], + # )), + ('GoPlay', dict( + name='Go Play', + url='https://www.goplay.be', + logo='goplay.png', + background='goplay-background.png', youtube=[], )) ]) diff --git a/resources/lib/viervijfzes/auth.py b/resources/lib/viervijfzes/auth.py index ebd5964..79ce3e2 100644 --- a/resources/lib/viervijfzes/auth.py +++ b/resources/lib/viervijfzes/auth.py @@ -15,7 +15,7 @@ _LOGGER = logging.getLogger(__name__) class AuthApi: - """ VIER/VIJF/ZES Authentication API """ + """ GoPlay Authentication API """ COGNITO_REGION = 'eu-west-1' COGNITO_POOL_ID = 'eu-west-1_dViSsKM5Y' COGNITO_CLIENT_ID = '6s1h851s8uplco5h6mqh1jac8m' diff --git a/resources/lib/viervijfzes/content.py b/resources/lib/viervijfzes/content.py index 7744465..08f55d6 100644 --- a/resources/lib/viervijfzes/content.py +++ b/resources/lib/viervijfzes/content.py @@ -13,7 +13,7 @@ from datetime import datetime import requests from resources.lib.kodiutils import STREAM_DASH, STREAM_HLS -from resources.lib.viervijfzes import CHANNELS, ResolvedStream +from resources.lib.viervijfzes import ResolvedStream try: # Python 3 from html import unescape @@ -169,14 +169,10 @@ class Category: class ContentApi: - """ VIER/VIJF/ZES Content API""" - API_ENDPOINT = 'https://api.viervijfzes.be' - API2_ENDPOINT = 'https://api2.viervijfzes.be' - SITE_APIS = { - 'vier': 'https://www.vier.be/api', - 'vijf': 'https://www.vijf.be/api', - 'zes': 'https://www.zestv.be/api', - } + """ GoPlay Content API""" + SITE_URL = 'https://www.goplay.be' + API_VIERVIJFZES = 'https://api.viervijfzes.be' + API_GOPLAY = 'https://api.goplay.be' def __init__(self, auth=None, cache_path=None): """ Initialise object """ @@ -184,64 +180,53 @@ class ContentApi: self._auth = auth self._cache_path = cache_path - def get_programs(self, channel, cache=CACHE_AUTO): + def get_programs(self, channel=None, cache=CACHE_AUTO): """ Get a list of all programs of the specified channel. - :type channel: str :type cache: str :rtype list[Program] """ - if channel not in CHANNELS: - raise Exception('Unknown channel %s' % channel) def update(): """ Fetch the program listing by scraping """ # Load webpage - raw_html = self._get_url(CHANNELS[channel]['url']) + raw_html = self._get_url(self.SITE_URL + '/programmas') # Parse programs - regex_programs = re.compile(r'\s+' - r'\s+(?P[^<]+)</span>.*?' - r'</a>', re.DOTALL) - data = { - item.group('path').lstrip('/'): unescape(item.group('title').strip()) + regex_programs = re.compile(r'data-program="(?P<json>[^"]+)"', re.DOTALL) + + data = [ + json.loads(unescape(item.group('json'))) for item in regex_programs.finditer(raw_html) - } + ] if not data: - raise Exception('No programs found for %s' % channel) + raise Exception('No programs found') return data # Fetch listing from cache or update if needed - data = self._handle_cache(key=['programs', channel], cache_mode=cache, update=update, ttl=30 * 5) + data = self._handle_cache(key=['programs'], cache_mode=cache, update=update, ttl=30 * 5) if not data: return [] - programs = [] - for path in data: - title = data[path] - program = self.get_program(channel, path, cache=CACHE_ONLY) # Get program details, but from cache only - if program: - # Use program with metadata from cache - programs.append(program) - else: - # Use program with the values that we've parsed from the page - programs.append(Program(channel=channel, - path=path, - title=title)) + if channel: + programs = [ + self._parse_program_data(record) for record in data if record['pageInfo']['brand'] == channel + ] + else: + programs = [ + self._parse_program_data(record) for record in data + ] + return programs - def get_program(self, channel, path, extract_clips=False, cache=CACHE_AUTO): + def get_program(self, path, extract_clips=False, cache=CACHE_AUTO): """ Get a Program object from the specified page. - :type channel: str :type path: str :type extract_clips: bool :type cache: int :rtype Program """ - if channel not in CHANNELS: - raise Exception('Unknown channel %s' % channel) - # We want to use the html to extract clips # This is the worst hack, since Python 2.7 doesn't support nonlocal raw_html = [None] @@ -249,7 +234,7 @@ class ContentApi: def update(): """ Fetch the program metadata by scraping """ # Fetch webpage - page = self._get_url(CHANNELS[channel]['url'] + '/' + path) + page = self._get_url(self.SITE_URL + '/' + path) # Store a copy in the parent's raw_html var. raw_html[0] = page @@ -262,7 +247,7 @@ class ContentApi: return data # Fetch listing from cache or update if needed - data = self._handle_cache(key=['program', channel, path], cache_mode=cache, update=update) + data = self._handle_cache(key=['program', path], cache_mode=cache, update=update) if not data: return None @@ -270,25 +255,22 @@ class ContentApi: # Also extract clips if we did a real HTTP call if extract_clips and raw_html[0]: - clips = self._extract_videos(raw_html[0], channel) + clips = self._extract_videos(raw_html[0]) program.clips = clips return program - def get_episode(self, channel, path, cache=CACHE_AUTO): + def get_episode(self, path, cache=CACHE_AUTO): """ Get a Episode object from the specified page. - :type channel: str :type path: str :type cache: str :rtype Episode """ - if channel not in CHANNELS: - raise Exception('Unknown channel %s' % channel) def update(): """ Fetch the program metadata by scraping """ # Load webpage - page = self._get_url(CHANNELS[channel]['url'] + '/' + path) + page = self._get_url(self.SITE_URL + '/' + path) program_json = None episode_json = None @@ -299,7 +281,7 @@ class ContentApi: result = regex_video_data.search(page) if result: video_id = json.loads(unescape(result.group(1)))['id'] - video_json_data = self._get_url('%s/video/%s' % (self.SITE_APIS[channel], video_id)) + video_json_data = self._get_url('%s/api/video/%s' % (self.SITE_URL, video_id)) video_json = json.loads(video_json_data) return dict(video=video_json) @@ -320,7 +302,7 @@ class ContentApi: return dict(program=program_json, episode=episode_json) # Fetch listing from cache or update if needed - data = self._handle_cache(key=['episode', channel, path], cache_mode=cache, update=update) + data = self._handle_cache(key=['episode', path], cache_mode=cache, update=update) if not data: return None @@ -344,7 +326,7 @@ class ContentApi: :type uuid: str :rtype str """ - response = self._get_url(self.API_ENDPOINT + '/content/%s' % uuid, authentication=True) + response = self._get_url(self.API_VIERVIJFZES + '/content/%s' % uuid, authentication=True) data = json.loads(response) if 'videoDash' in data: @@ -353,7 +335,7 @@ class ContentApi: drm_key = data['drmKey']['S'] _LOGGER.debug('Fetching Authentication XML with drm_key %s', drm_key) - response_drm = self._get_url(self.API2_ENDPOINT + '/decode/%s' % drm_key, authentication=True) + response_drm = self._get_url(self.API_GOPLAY + '/restricted/decode/%s' % drm_key, authentication=True) data_drm = json.loads(response_drm) return ResolvedStream( @@ -371,68 +353,64 @@ class ContentApi: stream_type=STREAM_HLS, ) - def get_categories(self, channel): - """ Get a list of all categories of the specified channel. - :type channel: str - :rtype list[Category] - """ - if channel not in CHANNELS: - raise Exception('Unknown channel %s' % channel) + # def get_categories(self): + # """ Get a list of all categories. + # :rtype list[Category] + # """ + # # Load webpage + # raw_html = self._get_url(self.SITE_URL) + # + # # Categories regexes + # regex_articles = re.compile(r'<article([^>]+)>(.*?)</article>', re.DOTALL) + # regex_submenu_id = re.compile(r'data-submenu-id="([^"]*)"') # splitted since the order might change + # regex_submenu_title = re.compile(r'data-submenu-title="([^"]*)"') + # + # categories = [] + # for result in regex_articles.finditer(raw_html): + # article_info_html = result.group(1) + # article_html = result.group(2) + # category_title = regex_submenu_title.search(article_info_html).group(1) + # category_id = regex_submenu_id.search(article_info_html).group(1) + # + # # Skip empty categories or 'All programs' + # if not category_id or category_id == 'programmas': + # continue + # + # # Extract items + # programs = self._extract_programs(article_html, channel) + # episodes = self._extract_videos(article_html) + # categories.append(Category(uuid=category_id, channel=channel, title=category_title, programs=programs, episodes=episodes)) + # + # return categories - # Load webpage - raw_html = self._get_url(CHANNELS[channel]['url']) - - # Categories regexes - regex_articles = re.compile(r'<article([^>]+)>(.*?)</article>', re.DOTALL) - regex_submenu_id = re.compile(r'data-submenu-id="([^"]*)"') # splitted since the order might change - regex_submenu_title = re.compile(r'data-submenu-title="([^"]*)"') - - categories = [] - for result in regex_articles.finditer(raw_html): - article_info_html = result.group(1) - article_html = result.group(2) - category_title = regex_submenu_title.search(article_info_html).group(1) - category_id = regex_submenu_id.search(article_info_html).group(1) - - # Skip empty categories or 'All programs' - if not category_id or category_id == 'programmas': - continue - - # Extract items - programs = self._extract_programs(article_html, channel) - episodes = self._extract_videos(article_html, channel) - categories.append(Category(uuid=category_id, channel=channel, title=category_title, programs=programs, episodes=episodes)) - - return categories + # @staticmethod + # def _extract_programs(html, channel): + # """ Extract Programs from HTML code """ + # # Item regexes + # regex_item = re.compile(r'<a[^>]+?href="(?P<path>[^"]+)"[^>]+?>' + # r'.*?<h3 class="poster-teaser__title"><span>(?P<title>[^<]*)</span></h3>.*?' + # r'</a>', re.DOTALL) + # + # # Extract items + # programs = [] + # for item in regex_item.finditer(html): + # path = item.group('path') + # if path.startswith('/video'): + # continue + # + # title = unescape(item.group('title')) + # + # # Program + # programs.append(Program( + # path=path.lstrip('/'), + # channel=channel, + # title=title, + # )) + # + # return programs @staticmethod - def _extract_programs(html, channel): - """ Extract Programs from HTML code """ - # Item regexes - regex_item = re.compile(r'<a[^>]+?href="(?P<path>[^"]+)"[^>]+?>' - r'.*?<h3 class="poster-teaser__title"><span>(?P<title>[^<]*)</span></h3>.*?' - r'</a>', re.DOTALL) - - # Extract items - programs = [] - for item in regex_item.finditer(html): - path = item.group('path') - if path.startswith('/video'): - continue - - title = unescape(item.group('title')) - - # Program - programs.append(Program( - path=path.lstrip('/'), - channel=channel, - title=title, - )) - - return programs - - @staticmethod - def _extract_videos(html, channel): + def _extract_videos(html): """ Extract videos from HTML code """ # Item regexes regex_item = re.compile(r'<a[^>]+?href="(?P<path>[^"]+)"[^>]+?>.*?</a>', re.DOTALL) @@ -490,7 +468,7 @@ class ContentApi: # Episode episodes.append(Episode( path=path.lstrip('/'), - channel=channel, + channel='', # TODO title=title, duration=episode_duration, uuid=episode_video_id, @@ -511,7 +489,7 @@ class ContentApi: program = Program( uuid=data['id'], path=data['link'].lstrip('/'), - channel=data['pageInfo']['site'], + channel=data['pageInfo']['brand'], title=data['title'], description=data['description'], aired=datetime.fromtimestamp(data.get('pageInfo', {}).get('publishDate')), @@ -524,7 +502,7 @@ class ContentApi: key: Season( uuid=playlist['id'], path=playlist['link'].lstrip('/'), - channel=playlist['pageInfo']['site'], + channel=playlist['pageInfo']['brand'], title=playlist['title'], description=playlist['pageInfo']['description'], number=playlist['episodes'][0]['seasonNumber'], # You did not see this diff --git a/resources/lib/viervijfzes/epg.py b/resources/lib/viervijfzes/epg.py index e74d8c7..967d4f7 100644 --- a/resources/lib/viervijfzes/epg.py +++ b/resources/lib/viervijfzes/epg.py @@ -67,12 +67,12 @@ class EpgProgram: class EpgApi: - """ VIER/VIJF/ZES EPG API """ + """ GoPlay EPG API """ EPG_ENDPOINTS = { - 'vier': 'https://www.vier.be/api/epg/{date}', - 'vijf': 'https://www.vijf.be/api/epg/{date}', - 'zes': 'https://www.zestv.be/api/epg/{date}', + 'Play4': 'https://www.goplay.be/api/epg/vier/{date}', + 'Play5': 'https://www.goplay.be/api/epg/vijf/{date}', + 'Play6': 'https://www.goplay.be/api/epg/zes/{date}' } EPG_NO_BROADCAST = 'Geen uitzending' diff --git a/resources/lib/viervijfzes/search.py b/resources/lib/viervijfzes/search.py index adea586..48b62a3 100644 --- a/resources/lib/viervijfzes/search.py +++ b/resources/lib/viervijfzes/search.py @@ -14,7 +14,7 @@ _LOGGER = logging.getLogger(__name__) class SearchApi: - """ VIER/VIJF/ZES Search API """ + """ GoPlay Search API """ API_ENDPOINT = 'https://api.viervijfzes.be/search' def __init__(self): diff --git a/resources/logos/goplay-background.png b/resources/logos/goplay-background.png new file mode 100644 index 0000000..560bbd8 Binary files /dev/null and b/resources/logos/goplay-background.png differ diff --git a/resources/logos/goplay.png b/resources/logos/goplay.png new file mode 100644 index 0000000..6bbc39b Binary files /dev/null and b/resources/logos/goplay.png differ diff --git a/resources/logos/play4-background.png b/resources/logos/play4-background.png new file mode 100644 index 0000000..80f7f51 Binary files /dev/null and b/resources/logos/play4-background.png differ diff --git a/resources/logos/play4.png b/resources/logos/play4.png new file mode 100644 index 0000000..3b5b639 Binary files /dev/null and b/resources/logos/play4.png differ diff --git a/resources/logos/play5-background.png b/resources/logos/play5-background.png new file mode 100644 index 0000000..db00d8f Binary files /dev/null and b/resources/logos/play5-background.png differ diff --git a/resources/logos/play5.png b/resources/logos/play5.png new file mode 100644 index 0000000..aa2fe74 Binary files /dev/null and b/resources/logos/play5.png differ diff --git a/resources/logos/play6-background.png b/resources/logos/play6-background.png new file mode 100644 index 0000000..0c59764 Binary files /dev/null and b/resources/logos/play6-background.png differ diff --git a/resources/logos/play6.png b/resources/logos/play6.png new file mode 100644 index 0000000..534a7d7 Binary files /dev/null and b/resources/logos/play6.png differ diff --git a/resources/logos/vier-background.jpg b/resources/logos/vier-background.jpg deleted file mode 100644 index c27edc4..0000000 Binary files a/resources/logos/vier-background.jpg and /dev/null differ diff --git a/resources/logos/vier.png b/resources/logos/vier.png deleted file mode 100644 index 164b086..0000000 Binary files a/resources/logos/vier.png and /dev/null differ diff --git a/resources/logos/vijf-background.jpg b/resources/logos/vijf-background.jpg deleted file mode 100644 index a89b5ea..0000000 Binary files a/resources/logos/vijf-background.jpg and /dev/null differ diff --git a/resources/logos/vijf.png b/resources/logos/vijf.png deleted file mode 100644 index 64ef960..0000000 Binary files a/resources/logos/vijf.png and /dev/null differ diff --git a/resources/logos/zes-background.jpg b/resources/logos/zes-background.jpg deleted file mode 100644 index 3a60b9a..0000000 Binary files a/resources/logos/zes-background.jpg and /dev/null differ diff --git a/resources/logos/zes.png b/resources/logos/zes.png deleted file mode 100644 index ef422b5..0000000 Binary files a/resources/logos/zes.png and /dev/null differ diff --git a/tests/test_api.py b/tests/test_api.py index 5381012..6fcb3d0 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -11,7 +11,7 @@ import unittest import resources.lib.kodiutils as kodiutils from resources.lib.viervijfzes import ResolvedStream from resources.lib.viervijfzes.auth import AuthApi -from resources.lib.viervijfzes.content import ContentApi, Program, Episode, Category, CACHE_PREVENT +from resources.lib.viervijfzes.content import ContentApi, Program, Episode, CACHE_PREVENT _LOGGER = logging.getLogger(__name__) @@ -23,39 +23,36 @@ class TestApi(unittest.TestCase): self._api = ContentApi(auth, cache_path=kodiutils.get_cache_path()) def test_programs(self): - for channel in ['vier', 'vijf', 'zes']: - programs = self._api.get_programs(channel) - self.assertIsInstance(programs, list) - self.assertIsInstance(programs[0], Program) + programs = self._api.get_programs() + self.assertIsInstance(programs, list) + self.assertIsInstance(programs[0], Program) - def test_categories(self): - for channel in ['vier', 'vijf', 'zes']: - categories = self._api.get_categories(channel) - self.assertIsInstance(categories, list) - if categories: - self.assertIsInstance(categories[0], Category) + # def test_categories(self): + # categories = self._api.get_categories() + # self.assertIsInstance(categories, list) + # self.assertIsInstance(categories[0], Category) def test_episodes(self): - for channel, program in [('vier', 'auwch'), ('vijf', 'zo-man-zo-vrouw')]: - program = self._api.get_program(channel, program, cache=CACHE_PREVENT) + for program in ['auwch', 'zo-man-zo-vrouw']: + program = self._api.get_program(program, cache=CACHE_PREVENT) self.assertIsInstance(program, Program) self.assertIsInstance(program.seasons, dict) self.assertIsInstance(program.episodes, list) self.assertIsInstance(program.episodes[0], Episode) def test_clips(self): - for channel, program in [('vier', 'gert-late-night')]: - program = self._api.get_program(channel, program, extract_clips=True, cache=CACHE_PREVENT) + for program in ['gert-late-night']: + program = self._api.get_program(program, extract_clips=True, cache=CACHE_PREVENT) self.assertIsInstance(program.clips, list) self.assertIsInstance(program.clips[0], Episode) - episode = self._api.get_episode(channel, program.clips[0].path, cache=CACHE_PREVENT) + episode = self._api.get_episode(program.clips[0].path, cache=CACHE_PREVENT) self.assertIsInstance(episode, Episode) @unittest.skipUnless(kodiutils.get_setting('username') and kodiutils.get_setting('password'), 'Skipping since we have no credentials.') def test_get_stream(self): - program = self._api.get_program('vier', 'auwch') + program = self._api.get_program('auwch') self.assertIsInstance(program, Program) episode = program.episodes[0] @@ -64,7 +61,7 @@ class TestApi(unittest.TestCase): @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('b56a41d9-5a6a-4ff0-b16a-56dc7b2e1f25') + resolved_stream = self._api.get_stream_by_uuid('a545bb4f-0f4c-4588-92a2-5086041c0e08') # Hawaii Five-O 8x12 self.assertIsInstance(resolved_stream, ResolvedStream) diff --git a/tests/test_epg.py b/tests/test_epg.py index 794e0ab..cef75b5 100644 --- a/tests/test_epg.py +++ b/tests/test_epg.py @@ -22,17 +22,17 @@ class TestEpg(unittest.TestCase): self._epg = EpgApi() def test_vier_today(self): - programs = self._epg.get_epg('vier', date.today().strftime('%Y-%m-%d')) + programs = self._epg.get_epg('Play4', date.today().strftime('%Y-%m-%d')) self.assertIsInstance(programs, list) self.assertIsInstance(programs[0], EpgProgram) def test_vijf_today(self): - programs = self._epg.get_epg('vijf', date.today().strftime('%Y-%m-%d')) + programs = self._epg.get_epg('Play5', date.today().strftime('%Y-%m-%d')) self.assertIsInstance(programs, list) self.assertIsInstance(programs[0], EpgProgram) def test_zes_today(self): - programs = self._epg.get_epg('zes', date.today().strftime('%Y-%m-%d')) + programs = self._epg.get_epg('Play6', date.today().strftime('%Y-%m-%d')) self.assertIsInstance(programs, list) self.assertIsInstance(programs[0], EpgProgram) @@ -41,16 +41,16 @@ class TestEpg(unittest.TestCase): self._epg.get_epg('vtm', date.today().strftime('%Y-%m-%d')) def test_vier_out_of_range(self): - programs = self._epg.get_epg('vier', '2020-01-01') + programs = self._epg.get_epg('Play4', '2020-01-01') self.assertEqual(programs, []) def test_play_video_from_epg(self): - epg_programs = self._epg.get_epg('vier', 'yesterday') + epg_programs = self._epg.get_epg('Play4', 'yesterday') epg_program = [program for program in epg_programs if program.video_url][0] # Lookup the Episode data since we don't have an UUID api = ContentApi(cache_path=kodiutils.get_cache_path()) - episode = api.get_episode(epg_program.channel, epg_program.video_url) + episode = api.get_episode(epg_program.video_url) self.assertIsInstance(episode, Episode) # def test_map_epg_genre(self): diff --git a/tests/test_routing.py b/tests/test_routing.py index 91e1c24..8171bf7 100644 --- a/tests/test_routing.py +++ b/tests/test_routing.py @@ -29,27 +29,27 @@ class TestRouting(unittest.TestCase): def test_channels_menu(self): routing.run([routing.url_for(addon.show_channels), '0', '']) - routing.run([routing.url_for(addon.show_channel_menu, channel='vier'), '0', '']) + routing.run([routing.url_for(addon.show_channel_menu, channel='Play4'), '0', '']) def test_catalog_menu(self): routing.run([routing.url_for(addon.show_catalog), '0', '']) def test_catalog_channel_menu(self): - routing.run([routing.url_for(addon.show_channel_catalog, channel='vier'), '0', '']) + routing.run([routing.url_for(addon.show_channel_catalog, channel='Play4'), '0', '']) def test_catalog_program_menu(self): - routing.run([routing.url_for(addon.show_catalog_program, channel='vier', program='de-mol'), '0', '']) + routing.run([routing.url_for(addon.show_catalog_program, channel='Play4', program='de-mol'), '0', '']) def test_catalog_program_season_menu(self): - routing.run([routing.url_for(addon.show_catalog_program_season, channel='vier', program='de-mol', season=-1), '0', '']) + routing.run([routing.url_for(addon.show_catalog_program_season, channel='Play4', program='de-mol', season=-1), '0', '']) def test_search_menu(self): routing.run([routing.url_for(addon.show_search), '0', '']) routing.run([routing.url_for(addon.show_search, query='de mol'), '0', '']) def test_tvguide_menu(self): - routing.run([routing.url_for(addon.show_channel_tvguide, channel='vier'), '0', '']) - routing.run([routing.url_for(addon.show_channel_tvguide_detail, channel='vier', date='today'), '0', '']) + routing.run([routing.url_for(addon.show_channel_tvguide, channel='Play4'), '0', '']) + routing.run([routing.url_for(addon.show_channel_tvguide_detail, channel='Play4', date='today'), '0', '']) # def test_metadata_update(self): # routing.run([routing.url_for(addon.metadata_update), '0', ''])