Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add IPTV setting and display catchup data #56

Merged
merged 4 commits into from
Aug 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
# 2.x

## [2.1.4](https://github.com/f-lawe/plugin.video.orange.fr/releases/tag/v2.1.4) - 2024-08-08

## Changed
- Better UI items management

### Fixed
- InputStream Helper is now called properly ([#50](https://github.com/f-lawe/plugin.video.orange.fr/issues/50))
- Avoid plugin to be runned twice on catchup TV videos ([#55](https://github.com/f-lawe/plugin.video.orange.fr/issues/55))

## [2.1.3](https://github.com/f-lawe/plugin.video.orange.fr/releases/tag/v2.1.3) - 2024-07-21

### Fixed
Expand Down
2 changes: 1 addition & 1 deletion addon.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<addon id="plugin.video.orange.fr" name="Orange TV France" version="2.1.3" provider-name="Flawe">
<addon id="plugin.video.orange.fr" name="Orange TV France" version="2.1.4" provider-name="Flawe">
<requires>
<import addon="xbmc.python" version="3.0.1"/>
<import addon="script.module.routing" version="0.2.3"/>
Expand Down
22 changes: 13 additions & 9 deletions resources/language/resource.language.en_gb/strings.po
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
# Kodi Media Center language file
# Addon Name: Orange TV France
# Addon id: plugin.video.orange.fr
# Addon Provider: BreizhReloaded
# Addon Provider: Flawe
msgid ""
msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Last-Translator: BreizhReloaded\n"
"Last-Translator: Flawe\n"
"Language-Team: English\n"
"Language: en\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
Expand All @@ -23,17 +23,25 @@ msgid "Install IPTV Manager…"
msgstr ""

msgctxt "#30102"
msgid "Help 30104"
msgstr "
msgid "Help 30102"
msgstr ""

msgctxt "#30103"
msgid "Go to IPTV Manager settings…"
msgid "Enable IPTV Manager integration"
msgstr ""

msgctxt "#30104"
msgid "Help 30104"
msgstr ""

msgctxt "#30105"
msgid "Go to IPTV Manager settings…"
msgstr ""

msgctxt "#30106"
msgid "Help 30106"
msgstr ""

# Provider settings (from 30200 to 30299)

msgctxt "#30200"
Expand Down Expand Up @@ -103,7 +111,3 @@ msgstr ""
msgctxt "#30901"
msgid "InputStream cannot be loaded."
msgstr ""

msgctxt "#30902"
msgid "Orange TV France must be used from the Kodi TV section."
msgstr ""
26 changes: 15 additions & 11 deletions resources/language/resource.language.fr_fr/strings.po
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
# Kodi Media Center language file
# Addon Name: Orange TV France
# Addon id: plugin.video.orange.fr
# Addon Provider: BreizhReloaded
# Addon Provider: Flawe
msgid ""
msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Last-Translator: BreizhReloaded\n"
"Last-Translator: Flawe\n"
"Language-Team: Français\n"
"Language: fr\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
Expand All @@ -23,17 +23,25 @@ msgid "Install IPTV Manager…"
msgstr "Installer IPTV Manager…"

msgctxt "#30102"
msgid "Help 30104"
msgid "Help 30102"
msgstr ""

msgctxt "#30103"
msgid "Go to IPTV Manager settings…"
msgstr "Ouvrir les paramètres de IPTV Manager"
msgid "Enable IPTV Manager integration"
msgstr "Activer l'intégration avec IPTV Manager"

msgctxt "#30104"
msgid "Help 30104"
msgstr ""

msgctxt "#30105"
msgid "Go to IPTV Manager settings…"
msgstr "Ouvrir les paramètres de IPTV Manager…"

msgctxt "#30106"
msgid "Help 30106"
msgstr ""

# Provider settings (from 30200 to 30299)

msgctxt "#30200"
Expand Down Expand Up @@ -63,8 +71,8 @@ msgid "Proxy"
msgstr "Proxy"

msgctxt "#30301"
msgid "Activer"
msgstr ""
msgid "Enable"
msgstr "Activer"

msgctxt "#30302"
msgid "Help 30302"
Expand Down Expand Up @@ -103,7 +111,3 @@ msgstr "Cette chaîne ne fait pas partie de votre abonnement."
msgctxt "#30901"
msgid "InputStream cannot be loaded."
msgstr "InputStream n'a pas pu être chargé."

msgctxt "#30902"
msgid "Orange TV France must be used from the Kodi TV section."
msgstr "Orange TV France doit être utilisé depuis la section TV de Kodi."
35 changes: 16 additions & 19 deletions resources/lib/managers/catchup_manager.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
"""Catchup TV Manager."""

import xbmc
import xbmcplugin

from lib.providers import get_provider
from lib.router import router
from lib.utils.gui import create_directory_items
from lib.utils.kodi import build_addon_url
from lib.utils.gui import create_list_item


class CatchupManager:
Expand All @@ -19,36 +17,35 @@ def __init__(self):
def get_channels(self) -> list:
"""Return channels available for catchup TV."""
channels = self.provider.get_catchup_channels()
directory_items = create_directory_items(channels)

succeeded = xbmcplugin.addDirectoryItems(router.handle, directory_items, len(directory_items))
xbmcplugin.endOfDirectory(router.handle, succeeded)
for channel in channels:
xbmcplugin.addDirectoryItem(router.handle, channel["path"], create_list_item(channel, True), True)

xbmcplugin.endOfDirectory(router.handle)

def get_categories(self, catchup_channel_id: str) -> list:
"""Return content categories for the required channel."""
categories = self.provider.get_catchup_categories(catchup_channel_id)
directory_items = create_directory_items(categories)

succeeded = xbmcplugin.addDirectoryItems(router.handle, directory_items, len(directory_items))
xbmcplugin.endOfDirectory(router.handle, succeeded)
for category in categories:
xbmcplugin.addDirectoryItem(router.handle, category["path"], create_list_item(category, True), True)

xbmcplugin.endOfDirectory(router.handle)

def get_articles(self, catchup_channel_id: str, category_id: str) -> list:
"""Return content (TV show, movie, etc) for the required channel and category."""
articles = self.provider.get_catchup_articles(catchup_channel_id, category_id)
directory_items = create_directory_items(articles)

succeeded = xbmcplugin.addDirectoryItems(router.handle, directory_items, len(directory_items))
xbmcplugin.endOfDirectory(router.handle, succeeded)
for article in articles:
xbmcplugin.addDirectoryItem(router.handle, article["path"], create_list_item(article, True), True)

xbmcplugin.endOfDirectory(router.handle)

def get_videos(self, catchup_channel_id: str, article_id: str) -> list:
"""Return the video list for the required show."""
videos = self.provider.get_catchup_videos(catchup_channel_id, article_id)
directory_items = create_directory_items(videos)

succeeded = xbmcplugin.addDirectoryItems(router.handle, directory_items, len(directory_items))
xbmcplugin.endOfDirectory(router.handle, succeeded)
for video in videos:
xbmcplugin.addDirectoryItem(router.handle, video["path"], create_list_item(video))

def play_video(self, video_id: str):
"""Play catchup video."""
player = xbmc.Player()
player.play(build_addon_url(f"/catchup-streams/{video_id}"))
xbmcplugin.endOfDirectory(router.handle)
38 changes: 17 additions & 21 deletions resources/lib/managers/stream_manager.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
"""Video stream manager."""

import inputstreamhelper
import xbmcgui
import xbmcplugin

from lib.providers import get_provider
from lib.router import router
from lib.utils.gui import create_video_item
from lib.utils.gui import create_play_item
from lib.utils.kodi import localize, ok_dialog


Expand All @@ -17,32 +16,29 @@ def __init__(self):
"""Initialize Stream Manager object."""
self.provider = get_provider()

def load_live_stream(self, stream_id: str) -> xbmcgui.ListItem:
def load_live_stream(self, stream_id: str) -> None:
"""Load live TV stream."""
stream_info = self.provider.get_live_stream_info(stream_id)
if not stream_info:
ok_dialog(localize(30900))
return

is_helper = inputstreamhelper.Helper(stream_info["manifest_type"], drm=stream_info["drm"])
if not is_helper.check_inputstream():
ok_dialog(localize(30901))
return
self._load_stream(stream_info)

list_item = create_video_item(stream_info)
xbmcplugin.setResolvedUrl(router.handle, True, list_item)

def load_chatchup_stream(self, stream_id: str) -> xbmcgui.ListItem:
def load_chatchup_stream(self, stream_id: str) -> None:
"""Load catchup TV stream."""
stream_info = self.provider.get_catchup_stream_info(stream_id)
if not stream_info:
self._load_stream(stream_info)

def _load_stream(self, stream_info: dict = None) -> None:
"""Load stream."""
if stream_info is None:
ok_dialog(localize(30900))
xbmcplugin.setResolvedUrl(router.handle, False)
return

is_helper = inputstreamhelper.Helper(stream_info["manifest_type"], drm=stream_info["drm"])
if not is_helper.check_inputstream():
ok_dialog(localize(30901))
is_helper = inputstreamhelper.Helper(stream_info["manifest_type"], drm=stream_info["license_type"])

if is_helper.check_inputstream():
play_item = create_play_item(stream_info, is_helper.inputstream_addon)
xbmcplugin.setResolvedUrl(router.handle, True, play_item)
return

list_item = create_video_item(stream_info)
xbmcplugin.setResolvedUrl(router.handle, True, list_item)
ok_dialog(localize(30901))
xbmcplugin.setResolvedUrl(router.handle, False)
47 changes: 20 additions & 27 deletions resources/lib/providers/abstract_orange_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

from lib.providers.abstract_provider import AbstractProvider
from lib.utils.kodi import build_addon_url, get_drm, get_global_setting, log
from lib.utils.request import build_request, get_random_ua
from lib.utils.request import build_request, get_random_ua, open_request

_PROGRAMS_ENDPOINT = "https://rp-ott-mediation-tv.woopic.com/api-gw/live/v3/applications/STB4PC/programs?period={period}&epgIds=all&mco={mco}"
_CATCHUP_CHANNELS_ENDPOINT = "https://rp-ott-mediation-tv.woopic.com/api-gw/catchup/v4/applications/PC/channels"
Expand Down Expand Up @@ -43,9 +43,7 @@ def get_catchup_stream_info(self, stream_id: str) -> dict:
def get_streams(self) -> list:
"""Load stream data from Orange and convert it to JSON-STREAMS format."""
req = build_request(_CHANNELS_ENDPOINT)

with urlopen(req) as res:
channels = list(json.loads(res.read())["channels"])
channels = dict(open_request(req, {"channels": {}}))["channels"]

log(f"{len(channels)} channels found", xbmc.LOGINFO)
channels.sort(key=lambda channel: channel["displayOrder"])
Expand Down Expand Up @@ -121,15 +119,12 @@ def get_epg(self) -> dict:
def get_catchup_channels(self) -> list:
"""Load available catchup channels."""
req = build_request(_CATCHUP_CHANNELS_ENDPOINT)

with urlopen(req) as res:
channels = list(json.loads(res.read()))
channels = open_request(req, [])

log(f"{len(channels)} catchup channels found", xbmc.LOGINFO)

return [
{
"is_folder": True,
"label": str(channel["name"]).upper(),
"path": build_addon_url(f"/channels/{channel['id']}/categories"),
"art": {"thumb": channel["logos"]["ref_millenials_partner_white_logo"]},
Expand All @@ -140,13 +135,10 @@ def get_catchup_channels(self) -> list:
def get_catchup_categories(self, catchup_channel_id: str) -> list:
"""Return a list of catchup categories for the specified channel id."""
req = build_request(_CATCHUP_CHANNELS_ENDPOINT + "/" + catchup_channel_id)

with urlopen(req) as res:
categories = list(json.loads(res.read())["categories"])
categories = dict(open_request(req, {"categories": {}}))["categories"]

return [
{
"is_folder": True,
"label": category["name"][0].upper() + category["name"][1:],
"path": build_addon_url(f"/channels/{catchup_channel_id}/categories/{category['id']}/articles"),
}
Expand All @@ -155,35 +147,37 @@ def get_catchup_categories(self, catchup_channel_id: str) -> list:

def get_catchup_articles(self, catchup_channel_id: str, category_id: str) -> list:
"""Return a list of catchup groups for the specified channel id and category id."""
req = build_request(
_CATCHUP_ARTICLES_ENDPOINT.format(catchup_channel_id=catchup_channel_id, category_id=category_id)
)
url = _CATCHUP_ARTICLES_ENDPOINT.format(catchup_channel_id=catchup_channel_id, category_id=category_id)
req = build_request(url)

with urlopen(req) as res:
articles = list(json.loads(res.read())["articles"])
articles = dict(open_request(req, {"articles": {}}))["articles"]

return [
{
"is_folder": True,
"label": article["title"],
"path": build_addon_url(f"/channels/{catchup_channel_id}/articles/{article['id']}/videos"),
"art": {"poster": article["covers"]["ref_16_9"]},
}
for article in articles
]

def get_catchup_videos(self, catchup_channel_id: str, article_id: str) -> list:
"""Return a list of catchup videos for the specified channel id and article id."""
req = build_request(_CATCHUP_VIDEOS_ENDPOINT.format(group_id=article_id))

with urlopen(req) as res:
videos = list(json.loads(res.read())["videos"])
videos = dict(open_request(req, {"videos": {}}))["videos"]

return [
{
"is_folder": False,
"label": video["title"],
"path": build_addon_url(f"/videos/{video['id']}"),
"art": {"thumb": video["covers"]["ref_4_3"]},
"path": build_addon_url(f"/catchup-streams/{video['id']}"),
"art": {"poster": video["covers"]["ref_16_9"]},
"info": {
"duration": int(video["duration"]) * 60,
"genres": video["genres"],
"plot": video["longSummary"],
"premiered": datetime.fromtimestamp(int(video["broadcastDate"]) / 1000).strftime("%Y-%m-%d"),
"year": int(video["productionDate"]),
},
}
for video in videos
]
Expand All @@ -197,7 +191,7 @@ def _get_stream_info(self, stream_type: str, version: str, item_type: str, strea
item_type=item_type,
stream_id=stream_id,
)
req, tv_token = self._build_request(url, auth_url=auth_url)
req, tv_token = self._build_auth_request(url, auth_url=auth_url)

try:
with urlopen(req) as res:
Expand Down Expand Up @@ -232,15 +226,14 @@ def _get_stream_info(self, stream_type: str, version: str, item_type: str, strea
"path": stream_info["url"],
"mime_type": "application/xml+dash",
"manifest_type": "mpd",
"drm": drm.name.lower(),
"license_type": drm.value,
"license_key": f"{license_server_url}|{headers}|{post_data}|{response}",
}

log(stream_info, xbmc.LOGDEBUG)
return stream_info

def _build_request(self, url: str, additional_headers: dict = None, auth_url: str = None) -> (Request, str):
def _build_auth_request(self, url: str, additional_headers: dict = None, auth_url: str = None) -> (Request, str):
"""Build HTTP request."""
tv_token = None

Expand Down
Loading