From c8781424c1b5469318ed139daec7efff8fcc2cb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Arnauts?= Date: Mon, 25 May 2020 20:41:38 +0200 Subject: [PATCH] Add support for playing from the Guide with IPTV Manager (#29) --- README.md | 1 + .../resource.language.en_gb/strings.po | 4 + .../resource.language.nl_nl/strings.po | 5 ++ resources/lib/addon.py | 30 ++++++- resources/lib/modules/iptvmanager.py | 81 +++++++++++++++++++ resources/lib/modules/menu.py | 2 +- resources/lib/modules/player.py | 14 ++++ resources/lib/modules/tvguide.py | 11 ++- resources/lib/viervijfzes/__init__.py | 6 ++ resources/lib/viervijfzes/epg.py | 9 ++- resources/settings.xml | 3 + 11 files changed, 157 insertions(+), 9 deletions(-) create mode 100644 resources/lib/modules/iptvmanager.py diff --git a/README.md b/README.md index e095777..4b82c6b 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,7 @@ De volgende features worden ondersteund: * Programma's rechtstreeks afspelen vanuit de tv-gids * Doorzoeken van alle programma's * Afspelen van gerelateerde Youtube content +* Integratie met [IPTV Manager](https://github.com/add-ons/service.iptv.manager) ## Screenshots diff --git a/resources/language/resource.language.en_gb/strings.po b/resources/language/resource.language.en_gb/strings.po index 54b37de..06d2225 100644 --- a/resources/language/resource.language.en_gb/strings.po +++ b/resources/language/resource.language.en_gb/strings.po @@ -148,6 +148,10 @@ msgctxt "#30717" msgid "This program is not available in the catalogue." msgstr "" +msgctxt "#30718" +msgid "There is no live stream available for {channel}." +msgstr "" + ### SETTINGS msgctxt "#30800" diff --git a/resources/language/resource.language.nl_nl/strings.po b/resources/language/resource.language.nl_nl/strings.po index f7f7d2f..1dbc5bd 100644 --- a/resources/language/resource.language.nl_nl/strings.po +++ b/resources/language/resource.language.nl_nl/strings.po @@ -149,6 +149,11 @@ msgctxt "#30717" msgid "This program is not available in the catalogue." msgstr "Dit programma is niet beschikbaar in de catalogus." +msgctxt "#30718" +msgid "There is no live stream available for {channel}." +msgstr "Er is geen live stream beschikbaar voor {channel}." + + ### SETTINGS msgctxt "#30800" diff --git a/resources/lib/addon.py b/resources/lib/addon.py index 2140fa3..ac1b853 100644 --- a/resources/lib/addon.py +++ b/resources/lib/addon.py @@ -106,8 +106,22 @@ def show_search(query=None): Search().show_search(query) +@routing.route('/play/live/') +def play_live(channel): + """ Play the requested item """ + from resources.lib.modules.player import Player + Player().live(channel) + + +@routing.route('/play/epg//') +def play_epg(channel, timestamp): + """ Play the requested item """ + from resources.lib.modules.tvguide import TvGuide + TvGuide().play_epg_datetime(channel, timestamp) + + @routing.route('/play/catalog/') -def play(uuid): +def play_catalog(uuid): """ Play the requested item """ from resources.lib.modules.player import Player Player().play(uuid) @@ -132,6 +146,20 @@ def metadata_update(): Metadata().update() +@routing.route('/iptv/channels') +def iptv_channels(): + """ Generate channel data for the Kodi PVR integration """ + from resources.lib.modules.iptvmanager import IPTVManager + IPTVManager(int(routing.args['port'][0])).send_channels() # pylint: disable=too-many-function-args + + +@routing.route('/iptv/epg') +def iptv_epg(): + """ Generate EPG data for the Kodi PVR integration """ + from resources.lib.modules.iptvmanager import IPTVManager + IPTVManager(int(routing.args['port'][0])).send_epg() # pylint: disable=too-many-function-args + + def run(params): """ Run the routing plugin """ routing.run(params) diff --git a/resources/lib/modules/iptvmanager.py b/resources/lib/modules/iptvmanager.py new file mode 100644 index 0000000..fdeafd4 --- /dev/null +++ b/resources/lib/modules/iptvmanager.py @@ -0,0 +1,81 @@ +# -*- coding: utf-8 -*- +"""Implementation of IPTVManager class""" + +from __future__ import absolute_import, division, unicode_literals + +import logging +from datetime import timedelta + +from resources.lib import kodiutils +from resources.lib.viervijfzes import CHANNELS +from resources.lib.viervijfzes.epg import EpgApi + +_LOGGER = logging.getLogger(__name__) + + +class IPTVManager: + """Interface to IPTV Manager""" + + def __init__(self, port): + """Initialize IPTV Manager object""" + self.port = port + + def via_socket(func): # pylint: disable=no-self-argument + """Send the output of the wrapped function to socket""" + + def send(self): + """Decorator to send over a socket""" + import json + import socket + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.connect(('127.0.0.1', self.port)) + try: + sock.send(json.dumps(func())) # pylint: disable=not-callable + finally: + sock.close() + + return send + + @via_socket + def send_channels(): # pylint: disable=no-method-argument + """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) + + return dict(version=1, streams=streams) + + @via_socket + def send_epg(): # pylint: disable=no-method-argument + """Return JSON-EPG formatted information to IPTV Manager""" + epg_api = EpgApi() + + results = dict() + 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) + + results[iptv_id].extend([ + dict( + start=program.start.isoformat(), + stop=(program.start + timedelta(seconds=program.duration)).isoformat(), + title=program.program_title, + description=program.description, + image=program.cover, + available=bool(program.video_url)) + 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 ae7acdc..4e0660e 100644 --- a/resources/lib/modules/menu.py +++ b/resources/lib/modules/menu.py @@ -124,7 +124,7 @@ class Menu: else: # We have an UUID and can play this item directly # This is not preferred since we will lack metadata - path = kodiutils.url_for('play', uuid=item.uuid) + path = kodiutils.url_for('play_catalog', uuid=item.uuid) return TitleItem(title=info_dict['title'], path=path, diff --git a/resources/lib/modules/player.py b/resources/lib/modules/player.py index 28eac26..5feeea5 100644 --- a/resources/lib/modules/player.py +++ b/resources/lib/modules/player.py @@ -7,6 +7,7 @@ import logging from resources.lib import kodiutils from resources.lib.modules.menu import Menu +from resources.lib.viervijfzes import CHANNELS from resources.lib.viervijfzes.auth import AuthApi from resources.lib.viervijfzes.auth_awsidp import InvalidLoginException, AuthenticationException from resources.lib.viervijfzes.content import ContentApi, UnavailableException, GeoblockedException @@ -25,6 +26,15 @@ class Player: # Workaround for Raspberry Pi 3 and older kodiutils.set_global_setting('videoplayer.useomxplayer', True) + @staticmethod + def live(channel): + """ Play the live channel. + :type channel: string + """ + channel_name = CHANNELS.get(channel, dict(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() + def play_from_page(self, channel, path): """ Play the requested item. :type channel: string @@ -34,6 +44,10 @@ class Player: episode = self._api.get_episode(channel, path) resolved_stream = None + if episode is None: + kodiutils.ok_dialog(message=kodiutils.localize(30712)) + return + if episode.stream: # We already have a resolved stream. Nice! # We don't need credentials for these streams. diff --git a/resources/lib/modules/tvguide.py b/resources/lib/modules/tvguide.py index ab0ae1c..5f02d0b 100644 --- a/resources/lib/modules/tvguide.py +++ b/resources/lib/modules/tvguide.py @@ -8,6 +8,7 @@ from datetime import datetime, timedelta from resources.lib import kodiutils from resources.lib.kodiutils import TitleItem +from resources.lib.modules.player import Player from resources.lib.viervijfzes import STREAM_DICT from resources.lib.viervijfzes.content import UnavailableException from resources.lib.viervijfzes.epg import EpgApi @@ -171,9 +172,13 @@ class TvGuide: """ broadcast = self._epg.get_broadcast(channel, timestamp) if not broadcast: - kodiutils.ok_dialog(heading=kodiutils.localize(30711), message=kodiutils.localize(30713)) # The requested video was not found in the guide. + kodiutils.ok_dialog(message=kodiutils.localize(30713)) # The requested video was not found in the guide. kodiutils.end_of_directory() return - kodiutils.container_update( - kodiutils.url_for('play', uuid=broadcast.video_url)) + if not broadcast.video_url: + kodiutils.ok_dialog(message=kodiutils.localize(30712)) # The video is unavailable and can't be played right now. + kodiutils.end_of_directory() + return + + Player().play_from_page(channel, broadcast.video_url) diff --git a/resources/lib/viervijfzes/__init__.py b/resources/lib/viervijfzes/__init__.py index e3c9c24..c5df88f 100644 --- a/resources/lib/viervijfzes/__init__.py +++ b/resources/lib/viervijfzes/__init__.py @@ -11,6 +11,8 @@ CHANNELS = OrderedDict([ logo='vier.png', background='vier-background.jpg', studio_icon='vier', + iptv_preset=4, + iptv_id='vier.be', youtube=[ dict( label='VIER / VIJF', @@ -25,6 +27,8 @@ CHANNELS = OrderedDict([ logo='vijf.png', background='vijf-background.jpg', studio_icon='vijf', + iptv_preset=5, + iptv_id='vijf.be', youtube=[ dict( label='VIER / VIJF', @@ -39,6 +43,8 @@ CHANNELS = OrderedDict([ logo='zes.png', background='zes-background.jpg', studio_icon='zes', + iptv_preset=6, + iptv_id='zes.be', youtube=[], )) ]) diff --git a/resources/lib/viervijfzes/epg.py b/resources/lib/viervijfzes/epg.py index 2cf0e21..4375668 100644 --- a/resources/lib/viervijfzes/epg.py +++ b/resources/lib/viervijfzes/epg.py @@ -7,7 +7,8 @@ import json import logging from datetime import datetime, timedelta -import dateutil +import dateutil.parser +import dateutil.tz import requests _LOGGER = logging.getLogger('epg-api') @@ -90,8 +91,8 @@ class EpgApi: duration = int(data.get('duration')) if data.get('duration') else None # Check if this broadcast is currently airing - timestamp = datetime.now() - start = datetime.fromtimestamp(data.get('timestamp')) + timestamp = datetime.now().replace(tzinfo=dateutil.tz.gettz('CET')) + start = datetime.fromtimestamp(data.get('timestamp')).replace(tzinfo=dateutil.tz.gettz('CET')) if duration: airing = bool(start <= timestamp < (start + timedelta(seconds=duration))) else: @@ -132,7 +133,7 @@ class EpgApi: :rtype: EpgProgram """ # Parse to a real datetime - timestamp = dateutil.parser.parse(timestamp) + timestamp = dateutil.parser.parse(timestamp).replace(tzinfo=dateutil.tz.gettz('CET')) # Load guide info for this date programs = self.get_epg(channel=channel, date=timestamp.strftime('%Y-%m-%d')) diff --git a/resources/settings.xml b/resources/settings.xml index 404e5b6..1a7b5c0 100644 --- a/resources/settings.xml +++ b/resources/settings.xml @@ -1,6 +1,9 @@ + + +