3
.gitignore
vendored
@ -15,3 +15,6 @@ Thumbs.db
|
||||
# Testing
|
||||
tests/home/userdata/addon_data
|
||||
.env
|
||||
|
||||
Pipfile
|
||||
Pipfile.lock
|
||||
|
10
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ë.
|
||||
|
14
addon.xml
@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<addon id="plugin.video.viervijfzes" name="VIER-VIJF-ZES" version="0.3.1" provider-name="Michaël Arnauts">
|
||||
<addon id="plugin.video.viervijfzes" name="GoPlay" version="0.3.1" provider-name="Michaël Arnauts">
|
||||
<requires>
|
||||
<import addon="xbmc.python" version="2.26.0"/>
|
||||
<import addon="script.module.dateutil" version="2.6.0"/>
|
||||
@ -14,12 +14,12 @@
|
||||
</extension>
|
||||
<extension point="xbmc.service" library="service_entry.py"/>
|
||||
<extension point="xbmc.addon.metadata">
|
||||
<summary lang="nl_NL">Bekijk programma's van VIER, VIJF en ZES.</summary>
|
||||
<description lang="nl_NL">Deze add-on geeft toegang tot de programma's die aangeboden worden op de websites van VIER, VIJF en ZES.</description>
|
||||
<disclaimer lang="nl_NL">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ë.</disclaimer>
|
||||
<summary lang="en_GB">Watch content from VIER, VIJF and ZES.</summary>
|
||||
<description lang="en_GB">This add-on gives access to video-on-demand content available on the websites of VIER, VIJF and ZES.</description>
|
||||
<disclaimer lang="en_GB">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.</disclaimer>
|
||||
<summary lang="nl_NL">Bekijk programma's van Play4, Play5 en Play6.</summary>
|
||||
<description lang="nl_NL">Deze add-on geeft toegang tot de programma's die aangeboden worden op de websites van Play4, Play5 en Play6.</description>
|
||||
<disclaimer lang="nl_NL">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ë.</disclaimer>
|
||||
<summary lang="en_GB">Watch content from Play4, Play5 and Play6.</summary>
|
||||
<description lang="en_GB">This add-on gives access to video-on-demand content available on the websites of Play4, Play5 and Play6.</description>
|
||||
<disclaimer lang="en_GB">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.</disclaimer>
|
||||
<platform>all</platform>
|
||||
<license>GPL-3.0-only</license>
|
||||
<news>v0.3.1 (2020-11-28)
|
||||
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 28 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 21 KiB |
@ -35,18 +35,18 @@ def show_channel_menu(channel):
|
||||
Channels().show_channel_menu(channel)
|
||||
|
||||
|
||||
@routing.route('/channels/<channel>/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/<channel>/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/<channel>/categories/<category>')
|
||||
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/<channel>/categories/<category>')
|
||||
# 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/<channel>/tvguide')
|
||||
@ -63,6 +63,13 @@ def show_channel_tvguide_detail(channel=None, date=None):
|
||||
TvGuide().show_detail(channel, date)
|
||||
|
||||
|
||||
@routing.route('/channels/<channel>/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/<channel>')
|
||||
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/<channel>/<program>')
|
||||
def show_catalog_program(channel, program):
|
||||
@routing.route('/catalog/<program>')
|
||||
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/<channel>/<program>/clips')
|
||||
def show_catalog_program_clips(channel, program):
|
||||
@routing.route('/catalog/<program>/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/<channel>/<program>/season/<season>')
|
||||
def show_catalog_program_season(channel, program, season):
|
||||
@routing.route('/catalog/<program>/season/<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/<channel>/<page>')
|
||||
def play_from_page(channel, page):
|
||||
@routing.route('/play/page/<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')
|
||||
|
@ -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()
|
||||
|
@ -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'])
|
||||
|
@ -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)
|
||||
|
@ -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 = {}
|
||||
|
@ -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:
|
||||
|
@ -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)
|
||||
|
@ -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=[],
|
||||
))
|
||||
])
|
||||
|
@ -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'
|
||||
|
@ -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'<a class="program-overview__link" href="(?P<path>[^"]+)">\s+'
|
||||
r'<span class="program-overview__title">\s+(?P<title>[^<]+)</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
|
||||
|
@ -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'
|
||||
|
@ -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):
|
||||
|
BIN
resources/logos/goplay-background.png
Normal file
After Width: | Height: | Size: 28 KiB |
BIN
resources/logos/goplay.png
Normal file
After Width: | Height: | Size: 27 KiB |
BIN
resources/logos/play4-background.png
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
resources/logos/play4.png
Normal file
After Width: | Height: | Size: 7.1 KiB |
BIN
resources/logos/play5-background.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
resources/logos/play5.png
Normal file
After Width: | Height: | Size: 5.0 KiB |
BIN
resources/logos/play6-background.png
Normal file
After Width: | Height: | Size: 18 KiB |
BIN
resources/logos/play6.png
Normal file
After Width: | Height: | Size: 9.4 KiB |
Before Width: | Height: | Size: 45 KiB |
Before Width: | Height: | Size: 7.7 KiB |
Before Width: | Height: | Size: 57 KiB |
Before Width: | Height: | Size: 5.2 KiB |
Before Width: | Height: | Size: 59 KiB |
Before Width: | Height: | Size: 9.9 KiB |
@ -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)
|
||||
|
||||
|
||||
|
@ -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):
|
||||
|
@ -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', ''])
|
||||
|