added playready support for NPO
This commit is contained in:
parent
7952381dca
commit
f3ddf2bbc3
@ -17,21 +17,21 @@ from unshackle.core.tracks import Chapter, Tracks, Subtitle
|
||||
class NPO(Service):
|
||||
"""
|
||||
Service code for NPO Start (npo.nl)
|
||||
Version: 1.0.0
|
||||
Version: 1.1.0
|
||||
|
||||
Authorization: optional cookies (free/paid content supported)
|
||||
Security: FHD @ L3 (Widevine)
|
||||
Security: FHD @ L3
|
||||
FHD @ SL3000
|
||||
(Widevine and PlayReady support)
|
||||
|
||||
Supports:
|
||||
• Series ↦ https://npo.nl/start/serie/{slug}
|
||||
• Movies ↦ https://npo.nl/start/video/{slug}
|
||||
|
||||
Only supports widevine at the moment
|
||||
|
||||
Note: Movie that is inside in a series (e.g.
|
||||
https://npo.nl/start/serie/zappbios/.../zappbios-captain-nova/afspelen)
|
||||
can be downloaded as movies by converting the URL to:
|
||||
https://npo.nl/start/video/zappbios-captain-nova
|
||||
Note: Movie inside a series can be downloaded as movie by converting URL to:
|
||||
https://npo.nl/start/video/slug
|
||||
|
||||
To change between Widevine and Playready, you need to change the DrmType in config.yaml to either widevine or playready
|
||||
"""
|
||||
|
||||
TITLE_RE = (
|
||||
@ -68,6 +68,9 @@ class NPO(Service):
|
||||
if self.config is None:
|
||||
raise EnvironmentError("Missing service config.")
|
||||
|
||||
# Store CDM reference
|
||||
self.cdm = ctx.obj.cdm
|
||||
|
||||
def authenticate(self, cookies: Optional[CookieJar] = None, credential: Optional[Credential] = None) -> None:
|
||||
super().authenticate(cookies, credential)
|
||||
if not cookies:
|
||||
@ -165,7 +168,6 @@ class NPO(Service):
|
||||
if not product_id:
|
||||
raise ValueError("no productId detected.")
|
||||
|
||||
# Get JWT
|
||||
token_url = self.config["endpoints"]["player_token"].format(product_id=product_id)
|
||||
r_tok = self.session.get(token_url, headers={"Referer": f"https://npo.nl/start/video/{self.slug}"})
|
||||
r_tok.raise_for_status()
|
||||
@ -176,7 +178,7 @@ class NPO(Service):
|
||||
self.config["endpoints"]["streams"],
|
||||
json={
|
||||
"profileName": "dash",
|
||||
"drmType": "widevine",
|
||||
"drmType": self.config["DrmType"],
|
||||
"referrerUrl": f"https://npo.nl/start/video/{self.slug}",
|
||||
"ster": {"identifier": "npo-app-desktop", "deviceType": 4, "player": "web"},
|
||||
},
|
||||
@ -205,12 +207,17 @@ class NPO(Service):
|
||||
|
||||
# Subtitles
|
||||
subtitles = []
|
||||
for sub in data.get("assets", {}).get("subtitles", []):
|
||||
for sub in (data.get("assets", {}) or {}).get("subtitles", []) or []:
|
||||
if not isinstance(sub, dict):
|
||||
continue
|
||||
lang = sub.get("iso", "und")
|
||||
location = sub.get("location")
|
||||
if not location:
|
||||
continue # skip if no URL provided
|
||||
subtitles.append(
|
||||
Subtitle(
|
||||
id_=sub.get("name", lang),
|
||||
url=sub["location"].strip(),
|
||||
url=location.strip(),
|
||||
language=Language.get(lang),
|
||||
is_original_lang=lang == "nl",
|
||||
codec=Subtitle.Codec.WebVTT,
|
||||
@ -233,9 +240,14 @@ class NPO(Service):
|
||||
|
||||
for tr in tracks.videos + tracks.audio:
|
||||
if getattr(tr, "drm", None):
|
||||
tr.drm.license = lambda challenge, **kw: self.get_widevine_license(
|
||||
challenge=challenge, title=title, track=tr
|
||||
)
|
||||
if drm_type == "playready":
|
||||
tr.drm.license = lambda challenge, **kw: self.get_playready_license(
|
||||
challenge=challenge, title=title, track=tr
|
||||
)
|
||||
else:
|
||||
tr.drm.license = lambda challenge, **kw: self.get_widevine_license(
|
||||
challenge=challenge, title=title, track=tr
|
||||
)
|
||||
|
||||
return tracks
|
||||
|
||||
@ -244,11 +256,34 @@ class NPO(Service):
|
||||
|
||||
def get_widevine_license(self, challenge: bytes, title: Title_T, track: AnyTrack) -> bytes:
|
||||
if not self.drm_token:
|
||||
raise ValueError("DRM token not set – login or paid content may be required.")
|
||||
raise ValueError("DRM token not set, login or paid content may be required.")
|
||||
r = self.session.post(
|
||||
self.config["endpoints"]["widevine_license"],
|
||||
self.config["endpoints"]["license"],
|
||||
params={"custom_data": self.drm_token},
|
||||
data=challenge,
|
||||
)
|
||||
r.raise_for_status()
|
||||
return r.content
|
||||
return r.content
|
||||
|
||||
def get_playready_license(self, challenge: bytes, title: Title_T, track: AnyTrack) -> bytes:
|
||||
if not self.drm_token:
|
||||
raise ValueError("DRM token not set, login or paid content may be required.")
|
||||
headers = {
|
||||
"Content-Type": "text/xml; charset=utf-8",
|
||||
"SOAPAction": "http://schemas.microsoft.com/DRM/2007/03/protocols/AcquireLicense",
|
||||
"Origin": "https://npo.nl",
|
||||
"Referer": "https://npo.nl/",
|
||||
"User-Agent": (
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
|
||||
"AppleWebKit/537.36 (KHTML, like Gecko) "
|
||||
"Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0"
|
||||
),
|
||||
}
|
||||
r = self.session.post(
|
||||
self.config["endpoints"]["license"],
|
||||
params={"custom_data": self.drm_token},
|
||||
data=challenge,
|
||||
headers=headers,
|
||||
)
|
||||
r.raise_for_status()
|
||||
return r.content
|
||||
|
||||
@ -4,5 +4,6 @@ endpoints:
|
||||
metadata_episode: "https://npo.nl/start/_next/data/{build_id}/serie/{series_slug}/seizoen-{season_slug}/{episode_slug}.json"
|
||||
streams: "https://prod.npoplayer.nl/stream-link"
|
||||
player_token: "https://npo.nl/start/api/domain/player-token?productId={product_id}"
|
||||
widevine_license: "https://npo-drm-gateway.samgcloud.nepworldwide.nl/authentication"
|
||||
license: "https://npo-drm-gateway.samgcloud.nepworldwide.nl/authentication"
|
||||
homepage: "https://npo.nl/start"
|
||||
DrmType: "widevine"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user