feat(DSCP): Add option to get UHD tracks

This commit is contained in:
stabbedbybrick 2024-05-28 17:33:42 +02:00
parent 6f31335d6a
commit 79ad7516d1
2 changed files with 43 additions and 138 deletions

View File

@ -26,13 +26,14 @@ class DSCP(Service):
Author: stabbedbybrick Author: stabbedbybrick
Authorization: Cookies Authorization: Cookies
Robustness: Robustness:
L3: 1080p, AAC2.0 L3: 2160p, AAC2.0
\b \b
Tips: Tips:
- Input can be either complete title URL or just the path: '/show/richard-hammonds-workshop' - Input can be either complete title URL or just the path: '/show/richard-hammonds-workshop'
- Use the --lang LANG_RANGE option to request non-english tracks - Use the --lang LANG_RANGE option to request non-english tracks
- Single video URLs are currently not supported - Single video URLs are currently not supported
- use -v H.265 to request H.265 tracks
""" """
@ -48,6 +49,7 @@ class DSCP(Service):
def __init__(self, ctx: Context, title: str): def __init__(self, ctx: Context, title: str):
self.title = title self.title = title
self.vcodec = ctx.parent.params.get("vcodec")
super().__init__(ctx) super().__init__(ctx)
self.license = None self.license = None
@ -65,7 +67,7 @@ class DSCP(Service):
self.configure() self.configure()
def search(self) -> Generator[SearchResult, None, None]: def search(self) -> Generator[SearchResult, None, None]:
r = self.session.get(self.config["endpoints"]["search"].format(region=self.region, query=self.title)) r = self.session.get(self.config["endpoints"]["search"].format(base_api=self.base_api, query=self.title))
r.raise_for_status() r.raise_for_status()
data = r.json() data = r.json()
@ -91,9 +93,15 @@ class DSCP(Service):
sys.exit(1) sys.exit(1)
if kind == "show": if kind == "show":
r = self.session.get(self.config["endpoints"]["show"].format(region=self.region, title_id=content_id)) data = self.session.get(
r.raise_for_status() self.config["endpoints"]["show"].format(base_api=self.base_api, title_id=content_id)
data = r.json() ).json()
if "errors" in data:
if "invalid.token" in data["errors"][0]["code"]:
self.log.error("- Invalid Token. Cookies are invalid or may have expired.")
sys.exit(1)
raise ConnectionError(data["errors"])
content = next(x for x in data["included"] if x["attributes"].get("alias") == "generic-show-episodes") content = next(x for x in data["included"] if x["attributes"].get("alias") == "generic-show-episodes")
content_id = content["id"] content_id = content["id"]
@ -104,7 +112,7 @@ class DSCP(Service):
seasons = [ seasons = [
self.session.get( self.session.get(
self.config["endpoints"]["seasons"].format( self.config["endpoints"]["seasons"].format(
region=self.region, content_id=content_id, season=season, show_id=show_id base_api=self.base_api, content_id=content_id, season=season, show_id=show_id
) )
).json() ).json()
for season in season_params for season in season_params
@ -134,138 +142,32 @@ class DSCP(Service):
) )
def get_tracks(self, title: Union[Movie, Episode]) -> Tracks: def get_tracks(self, title: Union[Movie, Episode]) -> Tracks:
platform = "firetv" if self.vcodec == "H.265" else "desktop"
res = self.session.post( res = self.session.post(
self.config["endpoints"]["playback"].format(region=self.region), self.config["endpoints"]["playback"].format(base_api=self.base_api),
json={ json={
"videoId": title.id, "videoId": title.id,
"wisteriaProperties": {
"advertiser": {
"adId": "|84958235701907329361495486486652228049||17163182474853637414c74993b0cb4f9a42062d41449",
"firstPlay": 0,
"fwDid": "",
"fwIsLat": 0,
"interactiveCapabilities": [
"brightline",
],
},
"appBundle": "undefined",
"device": {
"browser": {
"name": "chrome",
"version": "125.0.0.0",
},
"id": "",
"language": "en",
"make": "",
"model": "",
"name": "chrome",
"os": "Windows",
"osVersion": "NT 10.0",
"player": {
"name": "Discovery Player Web",
"version": "",
},
"type": "desktop",
},
"gdpr": 0,
"platform": "desktop",
"playbackId": str(uuid.uuid4()),
"product": "dplus_se" if self.site_id != "dplus_se" else "dplus_us",
"sessionId": str(uuid.uuid4()),
"siteId": "dplus_se" if self.site_id != "dplus_se" else "dplus_us",
"streamProvider": {
"hlsVersion": 6,
"pingConfig": 0,
"suspendBeaconing": 0,
"version": "1.0.0",
},
},
"deviceCapabilities": {
"manifests": {
"formats": {
"dash": {},
},
},
"segments": {
"formats": {
"fmp4": {},
},
},
"codecs": {
"audio": {
"decoders": [
{
"codec": "aac",
"profiles": [
"lc",
"hev",
"hev2",
],
},
],
},
"video": {
"decoders": [
{
"codec": "h264",
"profiles": [
"high",
"main",
"baseline",
],
"maxLevel": "5.2",
},
{
"codec": "h265",
"profiles": [
"main10",
"main",
],
"maxLevel": "5.2",
},
],
"hdrFormats": [],
},
},
"contentProtection": {
"contentDecryptionModules": [
{
"drmKeySystem": "clearkey",
},
{
"drmKeySystem": "widevine",
"maxSecurityLevel": "l3",
},
],
},
},
"deviceInfo": { "deviceInfo": {
"adBlocker": False, "adBlocker": "false",
"deviceId": "", "drmSupported": "true",
"drmTypes": { "hwDecodingCapabilities": ["H264", "H265"],
"widevine": True, "screen": {"width": 3840, "height": 2160},
"playready": False, "player": {"width": 3840, "height": 2160},
"fairplay": False, },
"clearkey": True, "wisteriaProperties": {
}, "advertiser": {"firstPlay": 0, "fwIsLat": 0},
"drmSupported": True, "device": {"browser": {"name": "chrome", "version": "96.0.4664.55"}, "type": platform},
"hdrCapabilities": [ "platform": platform,
"SDR", "product": "dplus_emea",
], "sessionId": str(uuid.uuid1()),
"hwDecodingCapabilities": [ "streamProvider": {"suspendBeaconing": 0, "hlsVersion": 7, "pingConfig": 1},
"H264",
"H265",
],
"soundCapabilities": [
"STEREO",
],
}, },
}, },
).json() ).json()
if "errors" in res: if "errors" in res:
if "missingpackage" in res["errors"][0]["code"]: if "missingpackage" in res["errors"][0]["code"]:
self.log.error("- Access Denied. Please check your subscription.") self.log.error("- Access Denied. Title is not available for this account.")
sys.exit(1) sys.exit(1)
if "invalid.token" in res["errors"][0]["code"]: if "invalid.token" in res["errors"][0]["code"]:
@ -306,18 +208,21 @@ class DSCP(Service):
def configure(self): def configure(self):
self.session.headers.update( self.session.headers.update(
{ {
"origin": "https://www.discoveryplus.com", "user-agent": "Chrome/96.0.4664.55",
"referer": "https://www.discoveryplus.com/",
"x-disco-client": "WEB:UNKNOWN:dplus_us:2.44.4", "x-disco-client": "WEB:UNKNOWN:dplus_us:2.44.4",
"x-disco-params": "realm=go,siteLookupKey=dplus_us,bid=dplus,hn=www.discoveryplus.com,hth=,uat=false", "x-disco-params": "realm=go,siteLookupKey=dplus_us,bid=dplus,hn=www.discoveryplus.com,hth=,uat=false",
} }
) )
info = self.session.get(self.config["endpoints"]["info"]).json() info = self.session.get(self.config["endpoints"]["info"]).json()
self.region = info["data"]["attributes"]["baseApiUrl"].split("-")[0].split("//")[1] self.base_api = info["data"]["attributes"]["baseApiUrl"]
user = self.session.get(self.config["endpoints"]["user"].format(region=self.region)).json() user = self.session.get(self.config["endpoints"]["user"].format(base_api=self.base_api)).json()
if "errors" in user: if "errors" in user:
if "invalid.token" in user["errors"][0]["code"]:
self.log.error("- Invalid Token. Cookies are invalid or may have expired.")
sys.exit(1)
raise ConnectionError(user["errors"]) raise ConnectionError(user["errors"])
self.territory = user["data"]["attributes"]["currentLocationTerritory"] self.territory = user["data"]["attributes"]["currentLocationTerritory"]

View File

@ -1,8 +1,8 @@
endpoints: endpoints:
info: https://global-prod.disco-api.com/bootstrapInfo info: https://global-prod.disco-api.com/bootstrapInfo
user: https://{region}-prod-direct.discoveryplus.com/users/me user: "{base_api}/users/me"
prod: "https://{region}-prod-direct.discoveryplus.com/cms/configs/web-prod" prod: "{base_api}/cms/configs/web-prod"
show: "https://{region}-prod-direct.discoveryplus.com/cms/routes/show/{title_id}?include=default&decorators=viewingHistory,isFavorite,playbackAllowed" show: "{base_api}/cms/routes/show/{title_id}?include=default&decorators=viewingHistory,isFavorite,playbackAllowed"
seasons: "https://{region}-prod-direct.discoveryplus.com/cms/collections/{content_id}?include=default&decorators=viewingHistory,isFavorite,playbackAllowed,contentAction,badges&{season}&{show_id}" seasons: "{base_api}/cms/collections/{content_id}?include=default&decorators=viewingHistory,isFavorite,playbackAllowed,contentAction,badges&{season}&{show_id}"
playback: "https://{region}-prod-direct.discoveryplus.com/playback/v3/videoPlaybackInfo" playback: "{base_api}/playback/v3/videoPlaybackInfo"
search: "https://{region}-prod-direct.discoveryplus.com/cms/routes/search/result?include=default&decorators=viewingHistory,isFavorite,playbackAllowed,contentAction,badges&contentFilter[query]={query}&page[items.number]=1&page[items.size]=8" search: "{base_api}/cms/routes/search/result?include=default&decorators=viewingHistory,isFavorite,playbackAllowed,contentAction,badges&contentFilter[query]={query}&page[items.number]=1&page[items.size]=8"