Skip to content

Commit

Permalink
Merge pull request #4449 from basrieter/retrospect-matrix
Browse files Browse the repository at this point in the history
[plugin.video.retrospect] v5.7.4
  • Loading branch information
basrieter authored Jan 18, 2024
2 parents 42a9230 + 7167664 commit 72f5eaf
Show file tree
Hide file tree
Showing 93 changed files with 1,807 additions and 183 deletions.
17 changes: 11 additions & 6 deletions plugin.video.retrospect/addon.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<addon id="plugin.video.retrospect"
version="5.7.4"
version="5.7.5"
name="Retrospect"
provider-name="Bas Rieter">

Expand All @@ -14,6 +14,7 @@
<import addon="script.module.pyscrypt" version="1.6.2" />
<import addon="script.module.pyaes" version="1.6.1" />
<import addon="resource.images.retrospect" version="1.0.12" />
<import addon="script.module.pyjwt" version="2.8.0" />
</requires>

<extension point="xbmc.python.pluginsource"
Expand Down Expand Up @@ -122,18 +123,22 @@
<platform>all</platform>
<license>GPL-3.0-or-later</license>
<language>en nl de sv no lt lv fi</language>
<news>[B]Retrospect v5.7.4 - Changelog - 2023-12-20[/B]
<news>[B]Retrospect v5.7.5 - Changelog - 2024-01-18[/B]

Minor fix for the NPO `Recent` items.
This release fixes issues with MTV and NPO. It also restores the IPTV functionality for NPO and introduces Videoland as a replacement for RTL XL.

[B]Framework related[/B]
* None
* Fixed: For search results, keep the sort order as &quot;unsorted&quot;.
* Changed: Various fixes for log-in and log-out.

[B]GUI/Settings/Language related[/B]
* None
* Added: Option to have a channel dedicated filter of premium items.

[B]Channel related[/B]
* Fixed: Recent times broke (Fixes #1750).
* Fixed: NPO Recent items did not display videos unrelated to shows.
* Fixed: MTV channels.
* Fixed: NPO IPTV Support.
* Fixed: SVT `new`-items

</news>
<assets>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,21 +36,25 @@ def __init__(self, channel_info):
self.noImage = "nickelodeonimage.png"
self.mainListUri = "https://www.nickelodeon.nl/shows"
self.baseUrl = "https://www.nickelodeon.nl"
self.__mgid = ""

elif self.channelCode == "nickno":
self.noImage = "nickelodeonimage.png"
self.mainListUri = "https://www.nickelodeon.no/shows"
self.baseUrl = "https://www.nickelodeon.no"
self.__mgid = ""

elif self.channelCode == "mtvnl":
self.mainListUri = "https://www.mtv.nl/shows"
self.baseUrl = "https://www.mtv.nl"
self.noImage = "mtvnlimage.png"
self.__mgid = "mtv.nl"

elif self.channelCode == "mtvde":
self.mainListUri = "https://www.mtv.de/shows"
self.baseUrl = "https://www.mtv.de"
self.noImage = "mtvnlimage.png"
self.__mgid = "mtv.de"
else:
raise NotImplementedError("Unknown channel code")

Expand Down Expand Up @@ -287,7 +291,8 @@ def create_json_video_item(self, result_set):
if sub_heading:
name = "{} - {}".format(name, sub_heading)

url = "{}{}".format(self.baseUrl, result_set["url"])
url = f"https://topaz.viacomcbs.digital/topaz/api/mgid:arc:episode:{self.__mgid}:{result_set['id']}/mica.json?clientPlatform=desktop"
url = f"https://topaz.viacomcbs.digital/topaz/api/mgid:arc:episode:mtv.nl:84c9904e-6fed-11e9-9fb2-70df2f866ace/mica.json?clientPlatform=desktop&ssus=44545c3d-6208-45e5-953e-801abf27ae7b&browser=Chrome&device=Desktop&os=Windows+10"
item = MediaItem(name, url, media_type=EPISODE)
item.description = meta.get("description")
item.thumb = result_set.get("media", {}).get("image", {}).get("url")
Expand Down Expand Up @@ -333,26 +338,9 @@ def update_video_item(self, item):
Logger.debug('Starting update_video_item for %s (%s)', item.name, self.channelName)
from resources.lib.streams.m3u8 import M3u8

data = UriHandler.open(item.url)
video_id = Regexer.do_regex(r'{"video":{"config":{"uri":"([^"]+)', data)[0]
url = "http://media.mtvnservices.com/pmt/e1/access/index.html?uri={}&configtype=edge".format(video_id)
meta_data = UriHandler.open(url, referer=self.baseUrl)
meta = JsonHelper(meta_data)
stream_parts = meta.get_value("feed", "items")
for stream_part in stream_parts:
stream_url = stream_part["group"]["content"]
stream_url = stream_url.replace("&device={device}", "")
stream_url = "%s&format=json&acceptMethods=hls" % (stream_url,)
stream_data = UriHandler.open(stream_url)
stream = JsonHelper(stream_data)

# subUrls = stream.get_value("package", "video", "item", 0, "transcript", 0, "typographic") # NOSONAR

hls_streams = stream.get_value("package", "video", "item", 0, "rendition")
for hls_stream in hls_streams:
hls_url = hls_stream["src"]
item.complete |= M3u8.update_part_with_m3u8_streams(item, hls_url)

item.complete = True
data = JsonHelper(UriHandler.open(item.url))
stream_url = data.get_value("stitchedstream", "source")
item.complete |= M3u8.update_part_with_m3u8_streams(item, stream_url)

Logger.trace("Media url: %s", item)
return item
148 changes: 82 additions & 66 deletions plugin.video.retrospect/channels/channel.nos/nos2010/chn_nos2010.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
from resources.lib.mediaitem import MediaItem, FolderItem
from resources.lib.xbmcwrapper import XbmcWrapper
from resources.lib.actions import action
from resources.lib.textures import TextureHandler


class Channel(chn_class.Channel):
Expand Down Expand Up @@ -117,6 +118,10 @@ def __init__(self, channel_info: ChannelInfo):
name="Season content API parser", json=True,
parser=[], creator=self.create_api_episode_item)

self._add_data_parser("https://npo.nl/start/video/",
name="Single video items from recent guid",
updater=self.update_single_video)

# Standard updater
self._add_data_parser("*", requires_logon=True,
updater=self.update_video_item)
Expand Down Expand Up @@ -711,27 +716,35 @@ def load_all_epg_channels(self, data: Union[str, JsonHelper]) -> Tuple[Union[str

def create_api_epg_item(self, result_set: dict) -> Optional[MediaItem]:
series_slug = (result_set["series"] or {}).get("slug")
if not series_slug:
return None
program_guid = (result_set["program"] or {}).get("guid")
if not program_guid:
season_slug = None

if not series_slug and program_guid:
# It is a single video not belonging to a series.
program_slug = result_set["program"]["slug"]
url = f"https://npo.nl/start/video/{program_slug}"
elif series_slug and program_guid:
url = f"https://npo.nl/start/api/domain/series-seasons?slug={series_slug}"
season_slug = result_set["season"]["slug"]
else:
return None

url = f"https://npo.nl/start/api/domain/series-seasons?slug={series_slug}"
name = result_set["title"]
season_slug = result_set["season"]["slug"]
start = result_set["programStart"]
channel = result_set["channel"]

date_stamp = DateHelper.get_date_from_posix(start, tz=self.__timezone)
if date_stamp > datetime.datetime.now(tz=pytz.UTC):
return None
# Check not needed. Programs in the future that are unavailable don't have a result_set.program property, which is already checked above.
# https://github.com/retrospect-addon/plugin.video.retrospect/pull/1754#issuecomment-1884550951
# if date_stamp > datetime.datetime.now(tz=pytz.UTC):
# return None

item = MediaItem(f"{date_stamp.hour:02d}:{date_stamp.minute:02d} - {channel} - {name}", url, media_type=mediatype.EPISODE)
item.metaData = {
"season_slug": season_slug,
"program_guid": program_guid
}
if season_slug and program_guid:
item.metaData = {
"season_slug": season_slug,
"program_guid": program_guid
}
item.set_date(date_stamp.year, date_stamp.month, date_stamp.day, date_stamp.hour, date_stamp.minute, date_stamp.second)

duration = result_set.get("durationInSeconds")
Expand All @@ -744,6 +757,17 @@ def create_api_epg_item(self, result_set: dict) -> Optional[MediaItem]:
item.description = image_data.get("description")
return item

def update_single_video(self, item: MediaItem) -> MediaItem:
data = UriHandler.open(item.url)
whatson_info = Regexer.do_regex(r'"productId"\W+"([^"]+)"', data)
if not whatson_info:
# Retry as with a login it might fail
data = UriHandler.open(item.url)
whatson_info = Regexer.do_regex(r'"productId"\W+"([^"]+)"', data)

whatson_id = whatson_info[0]
return self.__update_video_item(item, whatson_id)

def update_epg_series_item(self, item: MediaItem) -> MediaItem:
# Go from season slug, show slug & program guid ->
# ?? https://npo.nl/start/api/domain/series-detail?slug=boer-zoekt-vrouw
Expand Down Expand Up @@ -1144,45 +1168,42 @@ def update_video_item_live(self, item: MediaItem) -> MediaItem:
return item

def create_iptv_streams(self, parameter_parser):
""" Fetch the available live channels using EPG endpoint and format them into JSON-STREAMS
""" Fetch the available live channels using guide-channels endpoint and format them into JSON-STREAMS
:param ActionParser parameter_parser: a ActionParser object to is used to parse and
create urls
:return: Formatted stations
:rtype: list
"""
epg_url = datetime.datetime.now().strftime("https://start-api.npo.nl/epg/%Y-%m-%d?type=tv")
epg_data = UriHandler.open(epg_url,
no_cache=True,
additional_headers=self.__jsonApiKeyHeader)
epg = JsonHelper(epg_data)

channel_data = JsonHelper(UriHandler.open(f"https://npo.nl/start/api/domain/guide-channels"))
parent_item = MediaItem("Live", "https://www.npostart.nl/live", media_type=mediatype.FOLDER)
items = []
iptv_streams = []

for stations in epg.get_value("epg"):
livestream = JsonHelper.get_from(stations, "channel", "liveStream")
item = MediaItem(JsonHelper.get_from(livestream, "title"),
JsonHelper.get_from(livestream, "shareUrl"),
media_type=mediatype.VIDEO)
item.isLive = True
item.isGeoLocked = True
logo_sources = {
"NPO1": TextureHandler.instance().get_texture_uri(self, "npo1.png"),
"NPO2": TextureHandler.instance().get_texture_uri(self, "npo2.png"),
"NPO3": TextureHandler.instance().get_texture_uri(self, "npo3.png"),
"NPO1 Extra": TextureHandler.instance().get_texture_uri(self, "npo1extra.png"),
"NPO2 Extra": TextureHandler.instance().get_texture_uri(self, "npo2extra.png"),
"NPO Politiek en Nieuws": TextureHandler.instance().get_texture_uri(self, "npopolitiekennieuws.png")
}

for livestream in channel_data.json:
item = self.create_api_live_tv(livestream)
items.append(item)

iptv_streams.append(dict(
id=JsonHelper.get_from(livestream, "id"),
id=JsonHelper.get_from(livestream, "guid"),
name=JsonHelper.get_from(livestream, "title"),
logo=JsonHelper.get_from(livestream, "images", "original", "formats", "tv",
"source"),
logo=logo_sources[JsonHelper.get_from(livestream, "title")],
group=self.channelName,
stream=parameter_parser.create_action_url(self, action=action.PLAY_VIDEO, item=item,
store_id=parent_item.guid),
))

parameter_parser.pickler.store_media_items(parent_item.guid, parent_item, items)

return iptv_streams

def create_iptv_epg(self, parameter_parser):
Expand All @@ -1195,46 +1216,41 @@ def create_iptv_epg(self, parameter_parser):
:rtype: dict
"""

channel_data = JsonHelper(UriHandler.open(f"https://npo.nl/start/api/domain/guide-channels"))
parent = MediaItem("EPG", "https://start-api.npo.nl/epg/", media_type=mediatype.FOLDER)
iptv_epg = dict()
media_items = []

for livestream in channel_data.json:
iptv_epg[livestream["guid"]] = []

# Fetch 3 days in the past and in the future
start = datetime.datetime.now() - datetime.timedelta(days=3)
for i in range(0, 6, 1):
air_date = start + datetime.timedelta(i)
date = air_date.strftime("%d-%m-%Y")
guid = livestream["guid"]
guide_data = JsonHelper(UriHandler.open(f"https://npo.nl/start/api/domain/guide-channel?guid={guid}&date={date}"))

for item in guide_data.json:
item["channel"] = livestream["title"]
media_item = self.create_api_epg_item(item)
iptv_epg_item = dict(
start=datetime.datetime.fromtimestamp(JsonHelper.get_from(item, "programStart"), datetime.timezone.utc).isoformat(),
stop=datetime.datetime.fromtimestamp(JsonHelper.get_from(item, "programEnd"), datetime.timezone.utc).isoformat(),
title=JsonHelper.get_from(item, "title"))

if len(JsonHelper.get_from(item, "images")) > 0:
iptv_epg_item["image"] = JsonHelper.get_from(item, "images")[0].get("url")

if media_item is not None:
iptv_epg_item["stream"] = parameter_parser.create_action_url(self, action=action.PLAY_VIDEO,
item=media_item,
store_id=parent.guid)
media_items.append(media_item)

iptv_epg[livestream["guid"]].append(iptv_epg_item)

start = datetime.datetime.now() - datetime.timedelta(days=3)
for i in range(0, 7, 1):
air_date = start + datetime.timedelta(i)
data = UriHandler.open(
air_date.strftime("https://start-api.npo.nl/epg/%Y-%m-%d?type=tv"),
no_cache=True,
additional_headers=self.__jsonApiKeyHeader)

json_data = JsonHelper.loads(data)
for epg_item in JsonHelper.get_from(json_data, "epg"):
epg_id = JsonHelper.get_from(epg_item, "channel", 'liveStream', 'id')
iptv_epg[epg_id] = iptv_epg.get(epg_id, [])
for program in JsonHelper.get_from(epg_item, "schedule"):
media_item = MediaItem(JsonHelper.get_from(program, "program", "title"),
JsonHelper.get_from(program, "program", "id"),
media_type=mediatype.EPISODE)
region_restrictions = JsonHelper.get_from(program, "program",
"regionRestrictions")
media_item.isGeoBlocked = any(
[r for r in region_restrictions if r != "PLUSVOD:EU"])
media_items.append(media_item)
iptv_epg[epg_id].append(dict(
start=JsonHelper.get_from(program, "startsAt"),
stop=JsonHelper.get_from(program, "endsAt"),
title=JsonHelper.get_from(program, "program", "title"),
description=JsonHelper.get_from(program, "program", "descriptionLong"),
image=JsonHelper.get_from(program, "program", "images", "header", "formats",
"tv", "source"),
genre=JsonHelper.get_from(program, "program", "genres", 0,
"terms") if JsonHelper.get_from(program,
"program",
"genres") else [],
stream=parameter_parser.create_action_url(self, action=action.PLAY_VIDEO,
item=media_item,
store_id=parent.guid),
))
parameter_parser.pickler.store_media_items(parent.guid, parent, media_items)
return iptv_epg

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
"language": "nl",
"fanart": "rtlfanart.png",
"poster": "rtlposter_white.png",
"adaptiveAddonSelectable": false
"adaptiveAddonSelectable": false,
"ignore": true
}
],
"settings": [
Expand Down
Loading

0 comments on commit 72f5eaf

Please sign in to comment.