Add support for playing from the Guide with IPTV Manager (#29)
This commit is contained in:
parent
b5e36047f5
commit
c8781424c1
@ -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
|
||||
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -106,8 +106,22 @@ def show_search(query=None):
|
||||
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>')
|
||||
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)
|
||||
|
81
resources/lib/modules/iptvmanager.py
Normal file
81
resources/lib/modules/iptvmanager.py
Normal 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)
|
@ -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,
|
||||
|
@ -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.
|
||||
|
@ -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)
|
||||
|
@ -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=[],
|
||||
))
|
||||
])
|
||||
|
@ -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'))
|
||||
|
@ -1,6 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
|
||||
<settings>
|
||||
<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 -->
|
||||
<setting label="30801" type="lsep"/> <!-- Credentials -->
|
||||
<setting label="30803" type="text" id="username"/>
|
||||
|
Loading…
Reference in New Issue
Block a user