Skip to content
This repository was archived by the owner on Oct 21, 2024. It is now read-only.

Commit 0ddcdb1

Browse files
committed
Add live channels
1 parent e01b6b5 commit 0ddcdb1

File tree

8 files changed

+84
-42
lines changed

8 files changed

+84
-42
lines changed

resources/language/resource.language.en_gb/strings.po

+4
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,10 @@ msgstr ""
6060

6161

6262
### SUBMENUS
63+
msgctxt "#30052"
64+
msgid "Watch live [B]{channel}[/B]"
65+
msgstr ""
66+
6367
msgctxt "#30053"
6468
msgid "TV Guide for [B]{channel}[/B]"
6569
msgstr ""

resources/language/resource.language.nl_nl/strings.po

+4
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,10 @@ msgstr "Tv-gids"
6161

6262

6363
### SUBMENUS
64+
msgctxt "#30052"
65+
msgid "Watch live [B]{channel}[/B]"
66+
msgstr "Kijk live [B]{channel}[/B]"
67+
6468
msgctxt "#30053"
6569
msgid "TV Guide for [B]{channel}[/B]"
6670
msgstr "Tv-gids voor [B]{channel}[/B]"

resources/lib/addon.py

+3-6
Original file line numberDiff line numberDiff line change
@@ -160,14 +160,11 @@ def play_epg(channel, timestamp):
160160

161161

162162
@routing.route('/play/catalog')
163-
@routing.route('/play/catalog/<uuid>')
164-
@routing.route('/play/catalog/<uuid>/<islongform>')
165-
def play_catalog(uuid=None, islongform=False):
163+
@routing.route('/play/catalog/<uuid>/<content_type>')
164+
def play_catalog(uuid=None, content_type=None):
166165
""" Play the requested item """
167-
from ast import literal_eval
168166
from resources.lib.modules.player import Player
169-
# Convert string to bool using literal_eval
170-
Player().play(uuid, literal_eval(islongform))
167+
Player().play(uuid, content_type)
171168

172169

173170
@routing.route('/play/page/<page>')

resources/lib/modules/channels.py

+18
Original file line numberDiff line numberDiff line change
@@ -71,9 +71,27 @@ def show_channel_menu(channel):
7171

7272
# Lookup the high resolution logo based on the channel name
7373
fanart = '{path}/resources/logos/{logo}'.format(path=kodiutils.addon_path(), logo=channel_info.get('background'))
74+
icon = '{path}/resources/logos/{logo}'.format(path=kodiutils.addon_path(), logo=channel_info.get('logo'))
7475

7576
listing = []
7677

78+
listing.append(
79+
TitleItem(
80+
title=kodiutils.localize(30052, channel=channel_info.get('name')), # Watch live {channel}
81+
path=kodiutils.url_for('play_live', channel=channel_info.get('name')) + '?.pvr',
82+
art_dict={
83+
'icon': icon,
84+
'fanart': fanart,
85+
},
86+
info_dict={
87+
'plot': kodiutils.localize(30052, channel=channel_info.get('name')), # Watch live {channel}
88+
'playcount': 0,
89+
'mediatype': 'video',
90+
},
91+
is_playable=True,
92+
)
93+
)
94+
7795
if channel_info.get('epg_id'):
7896
listing.append(
7997
TitleItem(

resources/lib/modules/menu.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ def generate_titleitem(item):
183183

184184
if item.uuid:
185185
# We have an UUID and can play this item directly
186-
path = kodiutils.url_for('play_catalog', uuid=item.uuid, islongform=item.islongform)
186+
path = kodiutils.url_for('play_catalog', uuid=item.uuid, content_type=item.content_type)
187187
else:
188188
# We don't have an UUID, and first need to fetch the video information from the page
189189
path = kodiutils.url_for('play_from_page', page=quote(item.path, safe=''))

resources/lib/modules/player.py

+11-12
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,7 @@ def __init__(self):
2626
# Workaround for Raspberry Pi 3 and older
2727
kodiutils.set_global_setting('videoplayer.useomxplayer', True)
2828

29-
@staticmethod
30-
def live(channel):
29+
def live(self, channel):
3130
""" Play the live channel.
3231
:type channel: string
3332
"""
@@ -38,9 +37,9 @@ def live(channel):
3837
# self.play_from_page(broadcast.video_url)
3938
# return
4039

41-
channel_name = CHANNELS.get(channel, {'name': channel})
42-
kodiutils.ok_dialog(message=kodiutils.localize(30718, channel=channel_name.get('name'))) # There is no live stream available for {channel}.
43-
kodiutils.end_of_directory()
40+
channel_url = CHANNELS.get(channel, {'url': channel}).get('url')
41+
42+
self.play_from_page(channel_url)
4443

4544
def play_from_page(self, path):
4645
""" Play the requested item.
@@ -69,7 +68,7 @@ def play_from_page(self, path):
6968

7069
if episode.uuid:
7170
# Lookup the stream
72-
resolved_stream = self._resolve_stream(episode.uuid, episode.islongform)
71+
resolved_stream = self._resolve_stream(episode.uuid, episode.content_type)
7372
_LOGGER.debug('Resolved stream: %s', resolved_stream)
7473

7574
if resolved_stream:
@@ -81,24 +80,24 @@ def play_from_page(self, path):
8180
art_dict=titleitem.art_dict,
8281
prop_dict=titleitem.prop_dict)
8382

84-
def play(self, uuid, islongform):
83+
def play(self, uuid, content_type):
8584
""" Play the requested item.
8685
:type uuid: string
87-
:type islongform: bool
86+
:type content_type: string
8887
"""
8988
if not uuid:
9089
kodiutils.ok_dialog(message=kodiutils.localize(30712)) # The video is unavailable...
9190
return
9291

9392
# Lookup the stream
94-
resolved_stream = self._resolve_stream(uuid, islongform)
93+
resolved_stream = self._resolve_stream(uuid, content_type)
9594
kodiutils.play(resolved_stream.url, resolved_stream.stream_type, resolved_stream.license_key)
9695

9796
@staticmethod
98-
def _resolve_stream(uuid, islongform):
97+
def _resolve_stream(uuid, content_type):
9998
""" Resolve the stream for the requested item
10099
:type uuid: string
101-
:type islongform: bool
100+
:type content_type: string
102101
"""
103102
try:
104103
# Check if we have credentials
@@ -115,7 +114,7 @@ def _resolve_stream(uuid, islongform):
115114
auth = AuthApi(kodiutils.get_setting('username'), kodiutils.get_setting('password'), kodiutils.get_tokens_path())
116115

117116
# Get stream information
118-
resolved_stream = ContentApi(auth).get_stream_by_uuid(uuid, islongform)
117+
resolved_stream = ContentApi(auth).get_stream_by_uuid(uuid, content_type)
119118
return resolved_stream
120119

121120
except (InvalidLoginException, AuthenticationException) as ex:

resources/lib/viervijfzes/__init__.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
CHANNELS = OrderedDict([
88
('Play4', {
99
'name': 'Play4',
10+
'url': 'live-kijken/play-4',
1011
'epg_id': 'vier',
1112
'logo': 'play4.png',
1213
'background': 'play4-background.png',
@@ -18,6 +19,7 @@
1819
}),
1920
('Play5', {
2021
'name': 'Play5',
22+
'url': 'live-kijken/play-5',
2123
'epg_id': 'vijf',
2224
'logo': 'play5.png',
2325
'background': 'play5-background.png',
@@ -29,6 +31,7 @@
2931
}),
3032
('Play6', {
3133
'name': 'Play6',
34+
'url': 'live-kijken/play-6',
3235
'epg_id': 'zes',
3336
'logo': 'play6.png',
3437
'background': 'play6-background.png',
@@ -40,8 +43,8 @@
4043
}),
4144
('Play7', {
4245
'name': 'Play7',
46+
'url': 'live-kijken/play-7',
4347
'epg_id': 'zeven',
44-
'url': 'https://www.goplay.be',
4548
'logo': 'play7.png',
4649
'background': 'play7-background.png',
4750
'iptv_preset': 17,

resources/lib/viervijfzes/content.py

+39-22
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ class Episode:
112112
""" Defines an Episode. """
113113

114114
def __init__(self, uuid=None, nodeid=None, path=None, channel=None, program_title=None, title=None, description=None, thumb=None, duration=None,
115-
season=None, season_uuid=None, number=None, rating=None, aired=None, expiry=None, stream=None, islongform=False):
115+
season=None, season_uuid=None, number=None, rating=None, aired=None, expiry=None, stream=None, content_type=None):
116116
"""
117117
:type uuid: str
118118
:type nodeid: str
@@ -130,7 +130,7 @@ def __init__(self, uuid=None, nodeid=None, path=None, channel=None, program_titl
130130
:type aired: datetime
131131
:type expiry: datetime
132132
:type stream: string
133-
:type islongform: bool
133+
:type content_type: string
134134
"""
135135
self.uuid = uuid
136136
self.nodeid = nodeid
@@ -148,7 +148,7 @@ def __init__(self, uuid=None, nodeid=None, path=None, channel=None, program_titl
148148
self.aired = aired
149149
self.expiry = expiry
150150
self.stream = stream
151-
self.islongform = islongform
151+
self.content_type = content_type
152152

153153
def __repr__(self):
154154
return "%r" % self.__dict__
@@ -338,6 +338,14 @@ def update():
338338
if not data:
339339
return None
340340

341+
if 'episode' in data and data['episode']['pageInfo']['type'] == 'live_channel':
342+
episode = Episode(
343+
uuid=data['episode']['pageInfo']['nodeUuid'],
344+
program_title=data['episode']['pageInfo']['title'],
345+
content_type=data['episode']['pageInfo']['type'],
346+
)
347+
return episode
348+
341349
if 'video' in data and data['video']:
342350
# We have found detailed episode information
343351
episode = self._parse_clip_data(data['video'])
@@ -353,14 +361,19 @@ def update():
353361

354362
return None
355363

356-
def get_stream_by_uuid(self, uuid, islongform):
364+
def get_stream_by_uuid(self, uuid, content_type):
357365
""" Return a ResolvedStream for this video.
358-
:type uuid: str
359-
:type islongform: bool
366+
:type uuid: string
367+
:type content_type: string
360368
:rtype: ResolvedStream
361369
"""
362-
mode = 'long-form' if islongform else 'short-form'
363-
response = self._get_url(self.API_GOPLAY + '/web/v1/videos/%s/%s' % (mode, uuid), authentication='Bearer %s' % self._auth.get_token())
370+
if content_type in ('video-long_form', 'long_form'):
371+
mode = 'videos/long-form'
372+
elif content_type == 'video-short_form':
373+
mode = 'videos/short-form'
374+
elif content_type == 'live_channel':
375+
mode = 'liveStreams'
376+
response = self._get_url(self.API_GOPLAY + '/web/v1/%s/%s' % (mode, uuid), authentication='Bearer %s' % self._auth.get_token())
364377
data = json.loads(response)
365378

366379
if not data:
@@ -482,8 +495,8 @@ def get_recommendation_categories(self):
482495
raw_html = self._get_url(self.SITE_URL)
483496

484497
# Categories regexes
485-
regex_articles = re.compile(r'<article[^>]+>(.*?)</article>', re.DOTALL)
486-
regex_category = re.compile(r'<h2.*?>(.*?)</h2>(?:.*?<div class="visually-hidden">(.*?)</div>)?', re.DOTALL)
498+
regex_articles = re.compile(r'<article[^>]+>([\s\S]*?)</article>', re.DOTALL)
499+
regex_category = re.compile(r'<h2.*?>(.*?)</h2>(?:.*?<div class=\"visually-hidden\">(.*?)</div>)?', re.DOTALL)
487500

488501
categories = []
489502
for result in regex_articles.finditer(raw_html):
@@ -492,9 +505,9 @@ def get_recommendation_categories(self):
492505
match_category = regex_category.search(article_html)
493506
category_title = None
494507
if match_category:
495-
category_title = match_category.group(1).strip()
508+
category_title = unescape(match_category.group(1).strip())
496509
if match_category.group(2):
497-
category_title += ' [B]%s[/B]' % match_category.group(2).strip()
510+
category_title += ' [B]%s[/B]' % unescape(match_category.group(2).strip())
498511

499512
if category_title:
500513
# Extract programs and lookup in all_programs so we have more metadata
@@ -547,8 +560,8 @@ def _extract_programs(html):
547560
:rtype list[Program]
548561
"""
549562
# Item regexes
550-
regex_item = re.compile(r'<a[^>]+?href="(?P<path>[^"]+)"[^>]+?>'
551-
r'.*?<h3 class="poster-teaser__title">(?P<title>[^<]*)</h3>.*?data-background-image="(?P<image>.*?)".*?'
563+
regex_item = re.compile(r'<a[^>]+?href=\"(?P<path>[^\"]+)\"[^>]+?>'
564+
r'[\s\S]*?<h3 class=\"poster-teaser__title\">(?P<title>[^<]*)</h3>[\s\S]*?poster-teaser__image\" src=\"(?P<image>[\s\S]*?)\"[\s\S]*?'
552565
r'</a>', re.DOTALL)
553566

554567
# Extract items
@@ -574,20 +587,21 @@ def _extract_videos(html):
574587
:rtype list[Episode]
575588
"""
576589
# Item regexes
577-
regex_item = re.compile(r'<a[^>]+?href="(?P<path>[^"]+)"[^>]+?>.*?</a>', re.DOTALL)
590+
regex_item = re.compile(r'<a[^>]+?class=\"(?P<item_type>[^\"]+)\"[^>]+?href=\"(?P<path>[^\"]+)\"[^>]+?>[\s\S]*?</a>', re.DOTALL)
578591

579-
regex_episode_program = re.compile(r'<h3 class="episode-teaser__subtitle">([^<]*)</h3>')
580-
regex_episode_title = re.compile(r'<(?:div|h3) class="(?:poster|card|image|episode)-teaser__title">(?:<span>)?([^<]*)(?:</span>)?</(?:div|h3)>')
581-
regex_episode_duration = re.compile(r'data-duration="([^"]*)"')
582-
regex_episode_video_id = re.compile(r'data-video-id="([^"]*)"')
583-
regex_episode_image = re.compile(r'data-background-image="([^"]*)"')
584-
regex_episode_badge = re.compile(r'<div class="(?:poster|card|image|episode)-teaser__badge badge">([^<]*)</div>')
592+
regex_episode_program = re.compile(r'<(?:div|h3) class=\"episode-teaser__subtitle\">([^<]*)</(?:div|h3)>')
593+
regex_episode_title = re.compile(r'<(?:div|h3) class=\"(?:poster|card|image|episode)-teaser__title\">(?:<span>)?([^<]*)(?:</span>)?</(?:div|h3)>')
594+
regex_episode_duration = re.compile(r'data-duration=\"([^\"]*)\"')
595+
regex_episode_video_id = re.compile(r'data-video-id=\"([^\"]*)\"')
596+
regex_episode_image = re.compile(r'<img class=\"episode-teaser__header\" src=\"([^<\"]*)\"')
597+
regex_episode_badge = re.compile(r'<div class=\"badge (?:poster|card|image|episode)-teaser__badge (?:poster|card|image|episode)-teaser__badge--default\">([^<]*)</div>')
585598

586599
# Extract items
587600
episodes = []
588601
for item in regex_item.finditer(html):
589602
item_html = item.group(0)
590603
path = item.group('path')
604+
item_type = item.group('item_type')
591605

592606
# Extract title
593607
try:
@@ -632,6 +646,8 @@ def _extract_videos(html):
632646
if episode_badge:
633647
description += "\n\n[B]%s[/B]" % episode_badge
634648

649+
content_type = 'video-short_form' if 'card-' in item_type else 'video-long_form'
650+
635651
# Episode
636652
episodes.append(Episode(
637653
path=path.lstrip('/'),
@@ -642,6 +658,7 @@ def _extract_videos(html):
642658
uuid=episode_video_id,
643659
thumb=episode_image,
644660
program_title=episode_program,
661+
content_type=content_type
645662
))
646663

647664
return episodes
@@ -721,7 +738,7 @@ def _parse_episode_data(data, season_uuid=None):
721738
expiry=datetime.fromtimestamp(int(data.get('unpublishDate'))) if data.get('unpublishDate') else None,
722739
rating=data.get('parentalRating'),
723740
stream=data.get('path'),
724-
islongform=data.get('isLongForm'),
741+
content_type=data.get('type'),
725742
)
726743
return episode
727744

0 commit comments

Comments
 (0)