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

Commit

Permalink
Add live channels
Browse files Browse the repository at this point in the history
  • Loading branch information
mediaminister committed Sep 21, 2023
1 parent e01b6b5 commit afa3718
Show file tree
Hide file tree
Showing 8 changed files with 84 additions and 42 deletions.
4 changes: 4 additions & 0 deletions resources/language/resource.language.en_gb/strings.po
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ msgstr ""


### SUBMENUS
msgctxt "#30052"
msgid "Watch live [B]{channel}[/B]"
msgstr ""

msgctxt "#30053"
msgid "TV Guide for [B]{channel}[/B]"
msgstr ""
Expand Down
4 changes: 4 additions & 0 deletions resources/language/resource.language.nl_nl/strings.po
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ msgstr "Tv-gids"


### SUBMENUS
msgctxt "#30052"
msgid "Watch live [B]{channel}[/B]"
msgstr "Kijk live [B]{channel}[/B]"

msgctxt "#30053"
msgid "TV Guide for [B]{channel}[/B]"
msgstr "Tv-gids voor [B]{channel}[/B]"
Expand Down
9 changes: 3 additions & 6 deletions resources/lib/addon.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,14 +160,11 @@ def play_epg(channel, timestamp):


@routing.route('/play/catalog')
@routing.route('/play/catalog/<uuid>')
@routing.route('/play/catalog/<uuid>/<islongform>')
def play_catalog(uuid=None, islongform=False):
@routing.route('/play/catalog/<uuid>/<content_type>')
def play_catalog(uuid=None, content_type=None):
""" Play the requested item """
from ast import literal_eval
from resources.lib.modules.player import Player
# Convert string to bool using literal_eval
Player().play(uuid, literal_eval(islongform))
Player().play(uuid, content_type)


@routing.route('/play/page/<page>')
Expand Down
18 changes: 18 additions & 0 deletions resources/lib/modules/channels.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,27 @@ def show_channel_menu(channel):

# 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'))
icon = '{path}/resources/logos/{logo}'.format(path=kodiutils.addon_path(), logo=channel_info.get('logo'))

listing = []

listing.append(
TitleItem(
title=kodiutils.localize(30052, channel=channel_info.get('name')), # Watch live {channel}
path=kodiutils.url_for('play_live', channel=channel_info.get('name')) + '?.pvr',
art_dict={
'icon': icon,
'fanart': fanart,
},
info_dict={
'plot': kodiutils.localize(30052, channel=channel_info.get('name')), # Watch live {channel}
'playcount': 0,
'mediatype': 'video',
},
is_playable=True,
)
)

if channel_info.get('epg_id'):
listing.append(
TitleItem(
Expand Down
2 changes: 1 addition & 1 deletion resources/lib/modules/menu.py
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ def generate_titleitem(item):

if item.uuid:
# We have an UUID and can play this item directly
path = kodiutils.url_for('play_catalog', uuid=item.uuid, islongform=item.islongform)
path = kodiutils.url_for('play_catalog', uuid=item.uuid, content_type=item.content_type)
else:
# We don't have an UUID, and first need to fetch the video information from the page
path = kodiutils.url_for('play_from_page', page=quote(item.path, safe=''))
Expand Down
23 changes: 11 additions & 12 deletions resources/lib/modules/player.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,7 @@ def __init__(self):
# Workaround for Raspberry Pi 3 and older
kodiutils.set_global_setting('videoplayer.useomxplayer', True)

@staticmethod
def live(channel):
def live(self, channel):
""" Play the live channel.
:type channel: string
"""
Expand All @@ -38,9 +37,9 @@ def live(channel):
# self.play_from_page(broadcast.video_url)
# return

channel_name = CHANNELS.get(channel, {'name': channel})
kodiutils.ok_dialog(message=kodiutils.localize(30718, channel=channel_name.get('name'))) # There is no live stream available for {channel}.
kodiutils.end_of_directory()
channel_url = CHANNELS.get(channel, {'url': channel}).get('url')

self.play_from_page(channel_url)

def play_from_page(self, path):
""" Play the requested item.
Expand Down Expand Up @@ -69,7 +68,7 @@ def play_from_page(self, path):

if episode.uuid:
# Lookup the stream
resolved_stream = self._resolve_stream(episode.uuid, episode.islongform)
resolved_stream = self._resolve_stream(episode.uuid, episode.content_type)
_LOGGER.debug('Resolved stream: %s', resolved_stream)

if resolved_stream:
Expand All @@ -81,24 +80,24 @@ def play_from_page(self, path):
art_dict=titleitem.art_dict,
prop_dict=titleitem.prop_dict)

def play(self, uuid, islongform):
def play(self, uuid, content_type):
""" Play the requested item.
:type uuid: string
:type islongform: bool
:type content_type: string
"""
if not uuid:
kodiutils.ok_dialog(message=kodiutils.localize(30712)) # The video is unavailable...
return

# Lookup the stream
resolved_stream = self._resolve_stream(uuid, islongform)
resolved_stream = self._resolve_stream(uuid, content_type)
kodiutils.play(resolved_stream.url, resolved_stream.stream_type, resolved_stream.license_key)

@staticmethod
def _resolve_stream(uuid, islongform):
def _resolve_stream(uuid, content_type):
""" Resolve the stream for the requested item
:type uuid: string
:type islongform: bool
:type content_type: string
"""
try:
# Check if we have credentials
Expand All @@ -115,7 +114,7 @@ def _resolve_stream(uuid, islongform):
auth = AuthApi(kodiutils.get_setting('username'), kodiutils.get_setting('password'), kodiutils.get_tokens_path())

# Get stream information
resolved_stream = ContentApi(auth).get_stream_by_uuid(uuid, islongform)
resolved_stream = ContentApi(auth).get_stream_by_uuid(uuid, content_type)
return resolved_stream

except (InvalidLoginException, AuthenticationException) as ex:
Expand Down
5 changes: 4 additions & 1 deletion resources/lib/viervijfzes/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
CHANNELS = OrderedDict([
('Play4', {
'name': 'Play4',
'url': 'live-kijken/play-4',
'epg_id': 'vier',
'logo': 'play4.png',
'background': 'play4-background.png',
Expand All @@ -18,6 +19,7 @@
}),
('Play5', {
'name': 'Play5',
'url': 'live-kijken/play-5',
'epg_id': 'vijf',
'logo': 'play5.png',
'background': 'play5-background.png',
Expand All @@ -29,6 +31,7 @@
}),
('Play6', {
'name': 'Play6',
'url': 'live-kijken/play-6',
'epg_id': 'zes',
'logo': 'play6.png',
'background': 'play6-background.png',
Expand All @@ -40,8 +43,8 @@
}),
('Play7', {
'name': 'Play7',
'url': 'live-kijken/play-7',
'epg_id': 'zeven',
'url': 'https://www.goplay.be',
'logo': 'play7.png',
'background': 'play7-background.png',
'iptv_preset': 17,
Expand Down
61 changes: 39 additions & 22 deletions resources/lib/viervijfzes/content.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ class Episode:
""" Defines an Episode. """

def __init__(self, uuid=None, nodeid=None, path=None, channel=None, program_title=None, title=None, description=None, thumb=None, duration=None,
season=None, season_uuid=None, number=None, rating=None, aired=None, expiry=None, stream=None, islongform=False):
season=None, season_uuid=None, number=None, rating=None, aired=None, expiry=None, stream=None, content_type=None):
"""
:type uuid: str
:type nodeid: str
Expand All @@ -130,7 +130,7 @@ def __init__(self, uuid=None, nodeid=None, path=None, channel=None, program_titl
:type aired: datetime
:type expiry: datetime
:type stream: string
:type islongform: bool
:type content_type: string
"""
self.uuid = uuid
self.nodeid = nodeid
Expand All @@ -148,7 +148,7 @@ def __init__(self, uuid=None, nodeid=None, path=None, channel=None, program_titl
self.aired = aired
self.expiry = expiry
self.stream = stream
self.islongform = islongform
self.content_type = content_type

def __repr__(self):
return "%r" % self.__dict__
Expand Down Expand Up @@ -338,6 +338,14 @@ def update():
if not data:
return None

if 'episode' in data and data['episode']['pageInfo']['type'] == 'live_channel':
episode = Episode(
uuid=data['episode']['pageInfo']['nodeUuid'],
program_title=data['episode']['pageInfo']['title'],
content_type=data['episode']['pageInfo']['type'],
)
return episode

if 'video' in data and data['video']:
# We have found detailed episode information
episode = self._parse_clip_data(data['video'])
Expand All @@ -353,14 +361,19 @@ def update():

return None

def get_stream_by_uuid(self, uuid, islongform):
def get_stream_by_uuid(self, uuid, content_type):
""" Return a ResolvedStream for this video.
:type uuid: str
:type islongform: bool
:type uuid: string
:type content_type: string
:rtype: ResolvedStream
"""
mode = 'long-form' if islongform else 'short-form'
response = self._get_url(self.API_GOPLAY + '/web/v1/videos/%s/%s' % (mode, uuid), authentication='Bearer %s' % self._auth.get_token())
if content_type in ('video-long_form', 'long_form'):
mode = 'videos/long-form'
elif content_type == 'video-short_form':
mode = 'videos/short-form'
elif content_type == 'live_channel':
mode = 'liveStreams'
response = self._get_url(self.API_GOPLAY + '/web/v1/%s/%s' % (mode, uuid), authentication='Bearer %s' % self._auth.get_token())
data = json.loads(response)

if not data:
Expand Down Expand Up @@ -482,8 +495,8 @@ def get_recommendation_categories(self):
raw_html = self._get_url(self.SITE_URL)

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

categories = []
for result in regex_articles.finditer(raw_html):
Expand All @@ -492,9 +505,9 @@ def get_recommendation_categories(self):
match_category = regex_category.search(article_html)
category_title = None
if match_category:
category_title = match_category.group(1).strip()
category_title = unescape(match_category.group(1).strip())
if match_category.group(2):
category_title += ' [B]%s[/B]' % match_category.group(2).strip()
category_title += ' [B]%s[/B]' % unescape(match_category.group(2).strip())

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

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

regex_episode_program = re.compile(r'<h3 class="episode-teaser__subtitle">([^<]*)</h3>')
regex_episode_title = re.compile(r'<(?:div|h3) class="(?:poster|card|image|episode)-teaser__title">(?:<span>)?([^<]*)(?:</span>)?</(?:div|h3)>')
regex_episode_duration = re.compile(r'data-duration="([^"]*)"')
regex_episode_video_id = re.compile(r'data-video-id="([^"]*)"')
regex_episode_image = re.compile(r'data-background-image="([^"]*)"')
regex_episode_badge = re.compile(r'<div class="(?:poster|card|image|episode)-teaser__badge badge">([^<]*)</div>')
regex_episode_program = re.compile(r'<(?:div|h3) class=\"episode-teaser__subtitle\">([^<]*)</(?:div|h3)>')
regex_episode_title = re.compile(r'<(?:div|h3) class=\"(?:poster|card|image|episode)-teaser__title\">(?:<span>)?([^<]*)(?:</span>)?</(?:div|h3)>')
regex_episode_duration = re.compile(r'data-duration=\"([^\"]*)\"')
regex_episode_video_id = re.compile(r'data-video-id=\"([^\"]*)\"')
regex_episode_image = re.compile(r'<img class=\"episode-teaser__header\" src=\"([^<\"]*)\"')
regex_episode_badge = re.compile(r'<div class=\"badge (?:poster|card|image|episode)-teaser__badge (?:poster|card|image|episode)-teaser__badge--default\">([^<]*)</div>')

# Extract items
episodes = []
for item in regex_item.finditer(html):
item_html = item.group(0)
path = item.group('path')
item_type = item.group('item_type')

# Extract title
try:
Expand Down Expand Up @@ -631,6 +645,8 @@ def _extract_videos(html):
description = title
if episode_badge:
description += "\n\n[B]%s[/B]" % episode_badge

content_type = 'video-short_form' if 'card-' in item_type else 'video-long_form'

# Episode
episodes.append(Episode(
Expand All @@ -642,6 +658,7 @@ def _extract_videos(html):
uuid=episode_video_id,
thumb=episode_image,
program_title=episode_program,
content_type=content_type
))

return episodes
Expand Down Expand Up @@ -721,7 +738,7 @@ def _parse_episode_data(data, season_uuid=None):
expiry=datetime.fromtimestamp(int(data.get('unpublishDate'))) if data.get('unpublishDate') else None,
rating=data.get('parentalRating'),
stream=data.get('path'),
islongform=data.get('isLongForm'),
content_type=data.get('type'),
)
return episode

Expand Down

0 comments on commit afa3718

Please sign in to comment.