Add support for playing from the Guide with IPTV Manager (#29)

This commit is contained in:
Michaël Arnauts 2020-05-25 20:41:38 +02:00 committed by GitHub
parent b5e36047f5
commit c8781424c1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 157 additions and 9 deletions

View File

@ -23,6 +23,7 @@ De volgende features worden ondersteund:
* Programma's rechtstreeks afspelen vanuit de tv-gids * Programma's rechtstreeks afspelen vanuit de tv-gids
* Doorzoeken van alle programma's * Doorzoeken van alle programma's
* Afspelen van gerelateerde Youtube content * Afspelen van gerelateerde Youtube content
* Integratie met [IPTV Manager](https://github.com/add-ons/service.iptv.manager)
## Screenshots ## Screenshots

View File

@ -148,6 +148,10 @@ msgctxt "#30717"
msgid "This program is not available in the catalogue." msgid "This program is not available in the catalogue."
msgstr "" msgstr ""
msgctxt "#30718"
msgid "There is no live stream available for {channel}."
msgstr ""
### SETTINGS ### SETTINGS
msgctxt "#30800" msgctxt "#30800"

View File

@ -149,6 +149,11 @@ msgctxt "#30717"
msgid "This program is not available in the catalogue." msgid "This program is not available in the catalogue."
msgstr "Dit programma is niet beschikbaar in de catalogus." 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 ### SETTINGS
msgctxt "#30800" msgctxt "#30800"

View File

@ -106,8 +106,22 @@ def show_search(query=None):
Search().show_search(query) Search().show_search(query)
@routing.route('/play/live/<channel>')
def play_live(channel):
""" Play the requested item """
from resources.lib.modules.player import Player
Player().live(channel)
@routing.route('/play/epg/<channel>/<timestamp>')
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/<uuid>') @routing.route('/play/catalog/<uuid>')
def play(uuid): def play_catalog(uuid):
""" Play the requested item """ """ Play the requested item """
from resources.lib.modules.player import Player from resources.lib.modules.player import Player
Player().play(uuid) Player().play(uuid)
@ -132,6 +146,20 @@ def metadata_update():
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): def run(params):
""" Run the routing plugin """ """ Run the routing plugin """
routing.run(params) routing.run(params)

View File

@ -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)

View File

@ -124,7 +124,7 @@ class Menu:
else: else:
# We have an UUID and can play this item directly # We have an UUID and can play this item directly
# This is not preferred since we will lack metadata # 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'], return TitleItem(title=info_dict['title'],
path=path, path=path,

View File

@ -7,6 +7,7 @@ import logging
from resources.lib import kodiutils from resources.lib import kodiutils
from resources.lib.modules.menu import Menu 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 import AuthApi
from resources.lib.viervijfzes.auth_awsidp import InvalidLoginException, AuthenticationException from resources.lib.viervijfzes.auth_awsidp import InvalidLoginException, AuthenticationException
from resources.lib.viervijfzes.content import ContentApi, UnavailableException, GeoblockedException from resources.lib.viervijfzes.content import ContentApi, UnavailableException, GeoblockedException
@ -25,6 +26,15 @@ class Player:
# Workaround for Raspberry Pi 3 and older # Workaround for Raspberry Pi 3 and older
kodiutils.set_global_setting('videoplayer.useomxplayer', True) 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): def play_from_page(self, channel, path):
""" Play the requested item. """ Play the requested item.
:type channel: string :type channel: string
@ -34,6 +44,10 @@ class Player:
episode = self._api.get_episode(channel, path) episode = self._api.get_episode(channel, path)
resolved_stream = None resolved_stream = None
if episode is None:
kodiutils.ok_dialog(message=kodiutils.localize(30712))
return
if episode.stream: if episode.stream:
# We already have a resolved stream. Nice! # We already have a resolved stream. Nice!
# We don't need credentials for these streams. # We don't need credentials for these streams.

View File

@ -8,6 +8,7 @@ from datetime import datetime, timedelta
from resources.lib import kodiutils from resources.lib import kodiutils
from resources.lib.kodiutils import TitleItem from resources.lib.kodiutils import TitleItem
from resources.lib.modules.player import Player
from resources.lib.viervijfzes import STREAM_DICT from resources.lib.viervijfzes import STREAM_DICT
from resources.lib.viervijfzes.content import UnavailableException from resources.lib.viervijfzes.content import UnavailableException
from resources.lib.viervijfzes.epg import EpgApi from resources.lib.viervijfzes.epg import EpgApi
@ -171,9 +172,13 @@ class TvGuide:
""" """
broadcast = self._epg.get_broadcast(channel, timestamp) broadcast = self._epg.get_broadcast(channel, timestamp)
if not broadcast: 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() kodiutils.end_of_directory()
return return
kodiutils.container_update( if not broadcast.video_url:
kodiutils.url_for('play', uuid=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)

View File

@ -11,6 +11,8 @@ CHANNELS = OrderedDict([
logo='vier.png', logo='vier.png',
background='vier-background.jpg', background='vier-background.jpg',
studio_icon='vier', studio_icon='vier',
iptv_preset=4,
iptv_id='vier.be',
youtube=[ youtube=[
dict( dict(
label='VIER / VIJF', label='VIER / VIJF',
@ -25,6 +27,8 @@ CHANNELS = OrderedDict([
logo='vijf.png', logo='vijf.png',
background='vijf-background.jpg', background='vijf-background.jpg',
studio_icon='vijf', studio_icon='vijf',
iptv_preset=5,
iptv_id='vijf.be',
youtube=[ youtube=[
dict( dict(
label='VIER / VIJF', label='VIER / VIJF',
@ -39,6 +43,8 @@ CHANNELS = OrderedDict([
logo='zes.png', logo='zes.png',
background='zes-background.jpg', background='zes-background.jpg',
studio_icon='zes', studio_icon='zes',
iptv_preset=6,
iptv_id='zes.be',
youtube=[], youtube=[],
)) ))
]) ])

View File

@ -7,7 +7,8 @@ import json
import logging import logging
from datetime import datetime, timedelta from datetime import datetime, timedelta
import dateutil import dateutil.parser
import dateutil.tz
import requests import requests
_LOGGER = logging.getLogger('epg-api') _LOGGER = logging.getLogger('epg-api')
@ -90,8 +91,8 @@ class EpgApi:
duration = int(data.get('duration')) if data.get('duration') else None duration = int(data.get('duration')) if data.get('duration') else None
# Check if this broadcast is currently airing # Check if this broadcast is currently airing
timestamp = datetime.now() timestamp = datetime.now().replace(tzinfo=dateutil.tz.gettz('CET'))
start = datetime.fromtimestamp(data.get('timestamp')) start = datetime.fromtimestamp(data.get('timestamp')).replace(tzinfo=dateutil.tz.gettz('CET'))
if duration: if duration:
airing = bool(start <= timestamp < (start + timedelta(seconds=duration))) airing = bool(start <= timestamp < (start + timedelta(seconds=duration)))
else: else:
@ -132,7 +133,7 @@ class EpgApi:
:rtype: EpgProgram :rtype: EpgProgram
""" """
# Parse to a real datetime # 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 # Load guide info for this date
programs = self.get_epg(channel=channel, date=timestamp.strftime('%Y-%m-%d')) programs = self.get_epg(channel=channel, date=timestamp.strftime('%Y-%m-%d'))

View File

@ -1,6 +1,9 @@
<?xml version="1.0" encoding="utf-8" standalone="yes"?> <?xml version="1.0" encoding="utf-8" standalone="yes"?>
<settings> <settings>
<setting id="metadata_last_updated" visible="false"/> <setting id="metadata_last_updated" visible="false"/>
<setting id="iptv.enabled" default="true" visible="false"/>
<setting id="iptv.channels_uri" default="plugin://plugin.video.viervijfzes/iptv/channels" visible="false"/>
<setting id="iptv.epg_uri" default="plugin://plugin.video.viervijfzes/iptv/epg" visible="false"/>
<category label="30800"> <!-- Credentials --> <category label="30800"> <!-- Credentials -->
<setting label="30801" type="lsep"/> <!-- Credentials --> <setting label="30801" type="lsep"/> <!-- Credentials -->
<setting label="30803" type="text" id="username"/> <setting label="30803" type="text" id="username"/>