This commit is contained in:
chu23465 2025-04-13 07:52:20 +05:30
parent c9e04e3499
commit 4eb7ddedb8
4 changed files with 359 additions and 359 deletions

Binary file not shown.

View File

@ -9,7 +9,6 @@ from langcodes import Language
from vinetrimmer.objects import TextTrack, Title, Tracks from vinetrimmer.objects import TextTrack, Title, Tracks
from vinetrimmer.services.BaseService import BaseService from vinetrimmer.services.BaseService import BaseService
from vinetrimmer.utils.pyhulu import Device, HuluClient from vinetrimmer.utils.pyhulu import Device, HuluClient
from vinetrimmer.utils.widevine.device import LocalDevice
class Hulu(BaseService): class Hulu(BaseService):
""" """

View File

@ -13,424 +13,425 @@ import jsonpickle
from vinetrimmer.objects import Title, Tracks from vinetrimmer.objects import Title, Tracks
from vinetrimmer.objects.tracks import AudioTrack, MenuTrack, TextTrack, VideoTrack from vinetrimmer.objects.tracks import AudioTrack, MenuTrack, TextTrack, VideoTrack
from vinetrimmer.services.BaseService import BaseService from vinetrimmer.services.BaseService import BaseService
from vinetrimmer.utils.widevine.device import LocalDevice
class ParamountPlus(BaseService): class ParamountPlus(BaseService):
""" """
Service code for Paramount's Paramount+ streaming service (https://paramountplus.com). Service code for Paramount's Paramount+ streaming service (https://paramountplus.com).
\b \b
Authorization: Credentials Authorization: Credentials
Security: UHD@L3, doesn't care about releases. Security: UHD@L3, doesn't care about releases.
""" """
ALIASES = ["PMTP", "paramountplus", "paramount+"] ALIASES = ["PMTP", "paramountplus", "paramount+"]
TITLE_RE = [ TITLE_RE = [
r"^https?://(?:www\.)?paramountplus\.com/(?P<type>movies)/[a-z0-9_-]+/(?P<id>\w+)", r"^https?://(?:www\.)?paramountplus\.com/(?P<type>movies)/[a-z0-9_-]+/(?P<id>\w+)",
r"^https?://(?:www\.)?paramountplus\.com/(?P<type>shows)/(?P<id>[a-zA-Z0-9_-]+)(/)?", r"^https?://(?:www\.)?paramountplus\.com/(?P<type>shows)/(?P<id>[a-zA-Z0-9_-]+)(/)?",
r"^https?://(?:www\.)?paramountplus\.com(?:/[a-z]{2})?/(?P<type>movies)/[a-z0-9_-]+/(?P<id>\w+)", r"^https?://(?:www\.)?paramountplus\.com(?:/[a-z]{2})?/(?P<type>movies)/[a-z0-9_-]+/(?P<id>\w+)",
r"^https?://(?:www\.)?paramountplus\.com(?:/[a-z]{2})?/(?P<type>shows)/(?P<id>[a-zA-Z0-9_-]+)(/)?", r"^https?://(?:www\.)?paramountplus\.com(?:/[a-z]{2})?/(?P<type>shows)/(?P<id>[a-zA-Z0-9_-]+)(/)?",
r"^(?P<id>\d+)$", r"^(?P<id>\d+)$",
] ]
VIDEO_CODEC_MAP = {"H264": ["avc", "avc1"], "H265": ["hvc", "dvh", "hvc1", "hev1", "dvh1", "dvhe"]} VIDEO_CODEC_MAP = {"H264": ["avc", "avc1"], "H265": ["hvc", "dvh", "hvc1", "hev1", "dvh1", "dvhe"]}
AUDIO_CODEC_MAP = {"AAC": "mp4a", "AC3": "ac-3", "EC3": "ec-3"} AUDIO_CODEC_MAP = {"AAC": "mp4a", "AC3": "ac-3", "EC3": "ec-3"}
@staticmethod @staticmethod
@click.command(name="ParamountPlus", short_help="https://paramountplus.com") @click.command(name="ParamountPlus", short_help="https://paramountplus.com")
@click.argument("title", type=str, required=False) @click.argument("title", type=str, required=False)
@click.option("-m", "--movie", is_flag=True, default=False, help="Title is a Movie.") @click.option("-m", "--movie", is_flag=True, default=False, help="Title is a Movie.")
@click.option( @click.option(
"-c", "--clips", is_flag=True, default=False, help="Download clips instead of episodes (for TV shows)" "-c", "--clips", is_flag=True, default=False, help="Download clips instead of episodes (for TV shows)"
) )
@click.pass_context @click.pass_context
def cli(ctx: click.Context, **kwargs): def cli(ctx: click.Context, **kwargs):
return ParamountPlus(ctx, **kwargs) return ParamountPlus(ctx, **kwargs)
def __init__(self, ctx: click.Context, title: str, movie: bool, clips: bool): def __init__(self, ctx: click.Context, title: str, movie: bool, clips: bool):
super().__init__(ctx) super().__init__(ctx)
m = self.parse_title(ctx, title) m = self.parse_title(ctx, title)
self.movie = movie or m.get("type") == "movies" self.movie = movie or m.get("type") == "movies"
self.clips = clips self.clips = clips
self.vcodec = ctx.parent.params["vcodec"] self.vcodec = ctx.parent.params["vcodec"]
self.acodec = ctx.parent.params["acodec"] self.acodec = ctx.parent.params["acodec"]
self.range = ctx.parent.params["range_"] self.range = ctx.parent.params["range_"]
self.wanted = ctx.parent.params["wanted"] self.wanted = ctx.parent.params["wanted"]
self.shorts = False self.shorts = False
self.profile = ctx.obj.profile self.profile = ctx.obj.profile
self.playready = ctx.obj.cdm.device.type == LocalDevice.Types.PLAYREADY self.playready = True if "certificate_chain" in dir(ctx.obj.cdm) else False # ctx.obj.cdm.device.type == LocalDevice.Types.PLAYREADY
ctx.parent.params["acodec"] = "EC3"
ctx.parent.params["acodec"] = "EC3"
if self.range != "SDR":
# vcodec must be H265 for High Dynamic Range if self.range != "SDR":
self.vcodec = "H265" # vcodec must be H265 for High Dynamic Range
self.vcodec = "H265"
self.configure()
self.configure()
def get_titles(self):
if self.movie: def get_titles(self):
res = self.session.get( if self.movie:
url=self.config[self.region]["movie"].format(title_id=self.title), res = self.session.get(
params={ url=self.config[self.region]["movie"].format(title_id=self.title),
"includeTrailerInfo": "true", params={
"includeContentInfo": "true", "includeTrailerInfo": "true",
"locale": "en-us", "includeContentInfo": "true",
"at": self.config[self.region]["at_token"], "locale": "en-us",
}, "at": self.config[self.region]["at_token"],
).json() },
if not res["success"]: ).json()
if res["message"] == "No movie found for contentId.": if not res["success"]:
raise self.log.exit(" - Unable to find movie. For TV shows, use the numeric ID.") if res["message"] == "No movie found for contentId.":
else: raise self.log.exit(" - Unable to find movie. For TV shows, use the numeric ID.")
raise self.log.exit(f" - Failed to get title information: {res['message']}") else:
raise self.log.exit(f" - Failed to get title information: {res['message']}")
title = res["movie"]["movieContent"]
title = res["movie"]["movieContent"]
return Title(
id_=title["contentId"], return Title(
type_=Title.Types.MOVIE, id_=title["contentId"],
name=title["title"], type_=Title.Types.MOVIE,
year=title["_airDateISO"][:4], # todo: find a way to get year, this api doesnt return it name=title["title"],
original_lang="en", # TODO: Don't assume year=title["_airDateISO"][:4], # todo: find a way to get year, this api doesnt return it
source=self.ALIASES[0], original_lang="en", # TODO: Don't assume
service_data=title, source=self.ALIASES[0],
) service_data=title,
else: )
res = self.session.get( else:
url=self.config[self.region]["shows"].format(title=self.title) res = self.session.get(
).json() url=self.config[self.region]["shows"].format(title=self.title)
links = next((x.get("links") for x in res["showMenu"] if x.get("device_app_id") == "all_platforms"), None) ).json()
config = next((x.get("videoConfigUniqueName") for x in links if x.get("title").strip() == "Episodes"), None) links = next((x.get("links") for x in res["showMenu"] if x.get("device_app_id") == "all_platforms"), None)
show = next((x for x in res["show"]["results"] if x.get("type") == "show"), None) config = next((x.get("videoConfigUniqueName") for x in links if x.get("title").strip() == "Episodes"), None)
seasons = [x["seasonNum"] for x in res["available_video_seasons"]["itemList"] if x.get("seasonNum")] show = next((x for x in res["show"]["results"] if x.get("type") == "show"), None)
showId = show.get("show_id") seasons = [x["seasonNum"] for x in res["available_video_seasons"]["itemList"] if x.get("seasonNum")]
showId = show.get("show_id")
show_data = self.session.get(
url=self.config[self.region]["section"].format(showId=showId, config=config), show_data = self.session.get(
params={"platformType": "apps", "rows": "1", "begin": "0"}, url=self.config[self.region]["section"].format(showId=showId, config=config),
).json() params={"platformType": "apps", "rows": "1", "begin": "0"},
).json()
section = next(
(x["sectionId"] for x in show_data["videoSectionMetadata"] if x["title"] == "Full Episodes"), None section = next(
) (x["sectionId"] for x in show_data["videoSectionMetadata"] if x["title"] == "Full Episodes"), None
)
episodes = []
for season in seasons: episodes = []
res = self.session.get( for season in seasons:
url=self.config[self.region]["seasons"].format(section=section), res = self.session.get(
params={"begin": "0", "rows": "999", "params": f"seasonNum={season}", "seasonNum": season}, url=self.config[self.region]["seasons"].format(section=section),
).json() params={"begin": "0", "rows": "999", "params": f"seasonNum={season}", "seasonNum": season},
episodes.extend(res["sectionItems"].get("itemList")) ).json()
episodes.extend(res["sectionItems"].get("itemList"))
titles = []
for episode in episodes: titles = []
titles.append( for episode in episodes:
Title( titles.append(
id_=episode.get("contentId") or episode.get("content_id"), Title(
type_=Title.Types.TV, id_=episode.get("contentId") or episode.get("content_id"),
name=episode.get("seriesTitle") or episode.get("series_title"), type_=Title.Types.TV,
season=episode.get("seasonNum") or episode.get("season_number") or 0, name=episode.get("seriesTitle") or episode.get("series_title"),
episode=episode["episodeNum"] if episode["fullEpisode"] else episode["positionNum"], season=episode.get("seasonNum") or episode.get("season_number") or 0,
episode_name=episode["label"], episode=episode["episodeNum"] if episode["fullEpisode"] else episode["positionNum"],
original_lang="en", # TODO: Don't assume episode_name=episode["label"],
source=self.ALIASES[0], original_lang="en", # TODO: Don't assume
service_data=episode, source=self.ALIASES[0],
) service_data=episode,
) )
)
return titles
return titles
def get_tracks(self, title: Title):
assets = ( def get_tracks(self, title: Title):
["DASH_CENC_HDR10"], assets = (
[ ["DASH_CENC_HDR10"],
"HLS_AES", [
"DASH_LIVE", "HLS_AES",
"DASH_CENC_HDR10", "DASH_LIVE",
"DASH_TA", "DASH_CENC_HDR10",
"DASH_CENC", "DASH_TA",
"DASH_CENC_PRECON", "DASH_CENC",
"DASH_CENC_PS4", "DASH_CENC_PRECON",
], "DASH_CENC_PS4",
) ],
for asset in assets: )
r = requests.Request( for asset in assets:
"GET", r = requests.Request(
url=self.config["LINK_PLATFORM_URL"].format(video_id=title.id), "GET",
params={ url=self.config["LINK_PLATFORM_URL"].format(video_id=title.id),
"format": "redirect", params={
"formats": "MPEG-DASH", "format": "redirect",
"assetTypes": "|".join(asset), "formats": "MPEG-DASH",
"manifest": "M3U", "assetTypes": "|".join(asset),
"Tracking": "true", "manifest": "M3U",
"mbr": "true", "Tracking": "true",
}, "mbr": "true",
) },
req = self.session.send(self.session.prepare_request(r), allow_redirects=False) )
if req.ok: req = self.session.send(self.session.prepare_request(r), allow_redirects=False)
break if req.ok:
else: break
raise ValueError(f"Manifest Error: {req.text}") else:
raise ValueError(f"Manifest Error: {req.text}")
mpd_url = req.headers.get('location')
mpd_url = req.headers.get('location')
try:
tracks: Tracks = Tracks.from_mpd( try:
url=mpd_url.replace("cenc_precon_dash", "cenc_dash"), tracks: Tracks = Tracks.from_mpd(
source=self.ALIASES[0], url=mpd_url.replace("cenc_precon_dash", "cenc_dash"),
session=self.session, source=self.ALIASES[0],
) session=self.session,
except: )
tracks: Tracks = Tracks.from_mpd( except:
url=mpd_url, tracks: Tracks = Tracks.from_mpd(
source=self.ALIASES[0], url=mpd_url,
session=self.session, source=self.ALIASES[0],
) session=self.session,
tracks.subtitles.clear() )
tracks.subtitles.clear()
req = self.session.get(
url=self.config["LINK_PLATFORM_URL"].format(video_id=title.id), req = self.session.get(
params={ url=self.config["LINK_PLATFORM_URL"].format(video_id=title.id),
"format": "redirect", params={
"formats": "M3U", "format": "redirect",
"assetTypes": "|".join(["HLS_FPS_PRECON"]), "formats": "M3U",
"manifest": "M3U", "assetTypes": "|".join(["HLS_FPS_PRECON"]),
"Tracking": "true", "manifest": "M3U",
"mbr": "true", "Tracking": "true",
}, "mbr": "true",
) },
hls_url = req.url )
hls_url = req.url
tracks_m3u8 = Tracks.from_m3u8(
m3u8.load(hls_url), tracks_m3u8 = Tracks.from_m3u8(
source=self.ALIASES[0], m3u8.load(hls_url),
) source=self.ALIASES[0],
tracks.subtitles = tracks_m3u8.subtitles )
tracks.subtitles = tracks_m3u8.subtitles
for track in tracks:
# track.id = track.id for track in tracks:
if isinstance(track, VideoTrack): # track.id = track.id
track.hdr10 = ( if isinstance(track, VideoTrack):
track.codec[:4] in ("hvc1", "hev1") and track.extra[0].attrib.get("codecs")[5] == "2" track.hdr10 = (
) or (track.codec[:4] in ("hvc1", "hev1") and "HDR10plus" in track.url) track.codec[:4] in ("hvc1", "hev1") and track.extra[0].attrib.get("codecs")[5] == "2"
) or (track.codec[:4] in ("hvc1", "hev1") and "HDR10plus" in track.url)
track.dv = track.codec[:4] in ("dvh1", "dvhe")
track.dv = track.codec[:4] in ("dvh1", "dvhe")
if isinstance(track, VideoTrack) or isinstance(track, AudioTrack):
if self.shorts: if isinstance(track, VideoTrack) or isinstance(track, AudioTrack):
track.encrypted = False if self.shorts:
track.encrypted = False
if isinstance(track, TextTrack):
track.codec = "vtt" if isinstance(track, TextTrack):
#if track.language.language == "en": track.codec = "vtt"
# track.sdh = True # TODO: don't assume SDH #if track.language.language == "en":
# track.sdh = True # TODO: don't assume SDH
if self.vcodec:
tracks.videos = [x for x in tracks.videos if (x.codec or "")[:4] in self.VIDEO_CODEC_MAP[self.vcodec]] if self.vcodec:
tracks.videos = [x for x in tracks.videos if (x.codec or "")[:4] in self.VIDEO_CODEC_MAP[self.vcodec]]
if self.acodec:
tracks.audios = [x for x in tracks.audios if (x.codec or "")[:4] == self.AUDIO_CODEC_MAP[self.acodec]] if self.acodec:
tracks.audios = [x for x in tracks.audios if (x.codec or "")[:4] == self.AUDIO_CODEC_MAP[self.acodec]]
return tracks
return tracks
def get_chapters(self, title: Title):
chapters = [] def get_chapters(self, title: Title):
events = title.service_data.get("playbackEvents") chapters = []
events = {k: v for k, v in events.items() if v is not None} events = title.service_data.get("playbackEvents")
events = dict(sorted(events.items(), key=lambda item: item[1])) events = {k: v for k, v in events.items() if v is not None}
if not events: events = dict(sorted(events.items(), key=lambda item: item[1]))
return chapters if not events:
return chapters
chapters_titles = {
"endCreditChapterTimeMs": "Credits", chapters_titles = {
"previewStartTimeMs": "Preview Start", "endCreditChapterTimeMs": "Credits",
"previewEndTimeMs": "Preview End", "previewStartTimeMs": "Preview Start",
"openCreditEndTimeMs": "openCreditEnd", "previewEndTimeMs": "Preview End",
"openCreditStartTime": "openCreditStart", "openCreditEndTimeMs": "openCreditEnd",
} "openCreditStartTime": "openCreditStart",
}
for name, time_ in events.items():
if isinstance(time_, (int, float)): for name, time_ in events.items():
chapters.append( if isinstance(time_, (int, float)):
MenuTrack( chapters.append(
number=len(chapters) + 1, MenuTrack(
title=chapters_titles.get(name), number=len(chapters) + 1,
timecode=MenuTrack.format_duration(time_ / 1000), title=chapters_titles.get(name),
) timecode=MenuTrack.format_duration(time_ / 1000),
) )
)
# chapters = sorted(chapters, key=self.converter_timecode)
# chapters = sorted(chapters, key=self.converter_timecode)
return chapters
return chapters
def certificate(self, **_):
return None # will use common privacy cert def certificate(self, **_):
return None # will use common privacy cert
def license(self, challenge, title, **_):
contentId = title.service_data.get("contentId") or title.service_data.get("content_id") def license(self, challenge, title, **_):
if not contentId: contentId = title.service_data.get("contentId") or title.service_data.get("content_id")
raise self.log.exit("Error") if not contentId:
raise self.log.exit("Error")
r = self.session.post(
url=self.config["license_pr"] if self.playready else self.config["license"], r = self.session.post(
params={ url=self.config["license_pr"] if self.playready else self.config["license"],
"CrmId": "cbsi", params={
"AccountId": "cbsi", "CrmId": "cbsi",
"SubContentType": "Default", "AccountId": "cbsi",
"ContentId": title.service_data.get("contentId") or title.service_data.get("content_id"), "SubContentType": "Default",
}, "ContentId": title.service_data.get("contentId") or title.service_data.get("content_id"),
headers={"Authorization": f"Bearer {self.get_barrear(content_id=contentId)}"}, },
data=challenge, # expects bytes headers={"Authorization": f"Bearer {self.get_barrear(content_id=contentId)}"},
) data=challenge, # expects bytes
)
if r.headers["Content-Type"].startswith("application/json"):
res = r.json() if r.headers["Content-Type"].startswith("application/json"):
raise self.log.exit(res["message"]) res = r.json()
raise self.log.exit(res["message"])
return base64.b64encode(r.content).decode() if self.playready else r.content
return base64.b64encode(r.content).decode() if self.playready else r.content
def configure(self):
self.region = self.session.get("https://ipinfo.io/json").json()["country"] def configure(self):
if self.region != "US": self.region = self.session.get("https://ipinfo.io/json").json()["country"]
if self.region != "FR": if self.region != "US":
self.region = "INTL" if self.region != "FR":
self.region = "INTL"
#self.device_cache_path = Path(self.get_cache("device_tokens_{profile}.json".format(
#profile=self.profile, #self.device_cache_path = Path(self.get_cache("device_tokens_{profile}.json".format(
#))) #profile=self.profile,
#)))
#if self.device_cache_path.exists():
#with open(self.device_cache_path, encoding="utf-8") as fd: #if self.device_cache_path.exists():
#date = jsonpickle.decode(fd.read()) #with open(self.device_cache_path, encoding="utf-8") as fd:
#if "expiry" in date and datetime.fromisoformat(date["expiry"]) > datetime.now(): #date = jsonpickle.decode(fd.read())
#self.log.warning(" + Using cached device tokens") #if "expiry" in date and datetime.fromisoformat(date["expiry"]) > datetime.now():
#cache = date #self.log.warning(" + Using cached device tokens")
#else: #cache = date
#self.log.warning(" + Refreshing cookies") #else:
#self.device_cache_path.unlink() #self.log.warning(" + Refreshing cookies")
#if not self.credentials: #self.device_cache_path.unlink()
#raise self.log.exit(" - No credentials provided, unable to log in.") #if not self.credentials:
#self.session.headers.update({"user-agent": self.config["Android"]["UserAgent"]}) #raise self.log.exit(" - No credentials provided, unable to log in.")
#self.session.params.update({"at": self.config[self.region]["at_token"]}) #self.session.headers.update({"user-agent": self.config["Android"]["UserAgent"]})
#username = self.credentials.username #self.session.params.update({"at": self.config[self.region]["at_token"]})
#password = self.credentials.password #username = self.credentials.username
#expiry = (datetime.now() + timedelta(minutes=3)).isoformat() #password = self.credentials.password
#cookie = self.login(username=username, password=password) #expiry = (datetime.now() + timedelta(minutes=3)).isoformat()
#cache = {"cookie": cookie, "expiry": expiry} #cookie = self.login(username=username, password=password)
#self.device_cache_path.parent.mkdir(exist_ok=True, parents=True) #cache = {"cookie": cookie, "expiry": expiry}
#with open(self.device_cache_path, "w", encoding="utf-8") as fd: #self.device_cache_path.parent.mkdir(exist_ok=True, parents=True)
#fd.write(jsonpickle.encode(cache)) #with open(self.device_cache_path, "w", encoding="utf-8") as fd:
#else: #fd.write(jsonpickle.encode(cache))
if not self.credentials: #else:
raise self.log.exit(" - No credentials provided, unable to log in.") if not self.credentials:
self.log.warning(" + Logging in") raise self.log.exit(" - No credentials provided, unable to log in.")
self.session.headers.update({"user-agent": self.config["Android"]["UserAgent"]}) self.log.warning(" + Logging in")
self.session.params.update({"at": self.config[self.region]["at_token"]}) self.session.headers.update({"user-agent": self.config["Android"]["UserAgent"]})
username = self.credentials.username self.session.params.update({"at": self.config[self.region]["at_token"]})
password = self.credentials.password username = self.credentials.username
#expiry = (datetime.now() + timedelta(minutes=3)).isoformat() password = self.credentials.password
cookie = self.login(username=username, password=password) #expiry = (datetime.now() + timedelta(minutes=3)).isoformat()
#cache = {"cookie": cookie, "expiry": expiry} cookie = self.login(username=username, password=password)
#self.device_cache_path.parent.mkdir(exist_ok=True, parents=True) #cache = {"cookie": cookie, "expiry": expiry}
#with open(self.device_cache_path, "w", encoding="utf-8") as fd: #self.device_cache_path.parent.mkdir(exist_ok=True, parents=True)
#fd.write(jsonpickle.encode(cache)) #with open(self.device_cache_path, "w", encoding="utf-8") as fd:
#cookie = cache["cookie"] #fd.write(jsonpickle.encode(cache))
self.session.headers.update({"cookie": cookie}) #cookie = cache["cookie"]
else: self.session.headers.update({"cookie": cookie})
self.session.headers.update( else:
{ self.session.headers.update(
"Origin": "https://www.paramountplus.com", {
} "Origin": "https://www.paramountplus.com",
) }
self.session.params.update({"at": self.config[self.region]["at_token"]}) )
self.session.params.update({"at": self.config[self.region]["at_token"]})
#if not self.is_logged_in():
#raise ValueError("InvalidCookies") #if not self.is_logged_in():
#raise ValueError("InvalidCookies")
#if not self.is_subscribed():
#raise ValueError("NotEntitled") #if not self.is_subscribed():
#raise ValueError("NotEntitled")
# Service specific functions
# Service specific functions
def get_prop(self, prop):
res = self.session.get("https://www.paramountplus.com") def get_prop(self, prop):
prop_re = prop.replace(".", r"\.") res = self.session.get("https://www.paramountplus.com")
search = re.search(rf"{prop_re} ?= ?[\"']?([^\"';]+)", res.text) prop_re = prop.replace(".", r"\.")
if not search: search = re.search(rf"{prop_re} ?= ?[\"']?([^\"';]+)", res.text)
raise ValueError("InvalidCookies") if not search:
raise ValueError("InvalidCookies")
return search.group(1)
return search.group(1)
def is_logged_in(self):
return self.get_prop("CBS.UserAuthStatus") == "true" def is_logged_in(self):
return self.get_prop("CBS.UserAuthStatus") == "true"
def is_subscribed(self):
return self.get_prop("CBS.Registry.user.sub_status") == "SUBSCRIBER" def is_subscribed(self):
return self.get_prop("CBS.Registry.user.sub_status") == "SUBSCRIBER"
def login(self, username, password):
login_params = { def login(self, username, password):
"j_username": username, login_params = {
"j_password": password "j_username": username,
} "j_password": password
}
response = self.session.post(url=self.config[self.region]["login"], params=login_params)
response = self.session.post(url=self.config[self.region]["login"], params=login_params)
status_response = self.session.get(url=self.config[self.region]["status"]).json()
self.log.debug(status_response) status_response = self.session.get(url=self.config[self.region]["status"]).json()
if status_response["success"] == False: self.log.debug(status_response)
raise self.log.exit("InvalidCredentials") if status_response["success"] == False:
#if not status_response["userStatus"]["description"] == "SUBSCRIBER": raise self.log.exit("InvalidCredentials")
#raise ValueError("NotEntitled") #if not status_response["userStatus"]["description"] == "SUBSCRIBER":
#raise ValueError("NotEntitled")
cookies = ";".join([f"{key}={value}" for key, value in response.cookies.get_dict().items()])
cookies = ";".join([f"{key}={value}" for key, value in response.cookies.get_dict().items()])
return cookies
return cookies
def get_barrear(self, content_id):
#license_data = self.session.get(url="https://www.intl.paramountplus.com/apps-api/v3.0/androidphone/irdeto-control/session-token.json?contentId=%s&locale=en-us&at=ABATOpD5wXyjhjIMO0BaNh/gW0iCu0ISRy2U7/tyGiKZTQTlYDFL1NPD58CcuJLOQYY=" % (content_id)).json() def get_barrear(self, content_id):
try: #license_data = self.session.get(url="https://www.intl.paramountplus.com/apps-api/v3.0/androidphone/irdeto-control/session-token.json?contentId=%s&locale=en-us&at=ABATOpD5wXyjhjIMO0BaNh/gW0iCu0ISRy2U7/tyGiKZTQTlYDFL1NPD58CcuJLOQYY=" % (content_id)).json()
res = self.session.get( try:
url=self.config[self.region]["barrearUrl"].replace("iphone", "androidtv") if self.playready else self.config[self.region]["barrearUrl"], res = self.session.get(
params={"contentId": content_id} url=self.config[self.region]["barrearUrl"].replace("iphone", "androidtv") if self.playready else self.config[self.region]["barrearUrl"],
) params={"contentId": content_id}
res.raise_for_status() )
except requests.HTTPError as e: res.raise_for_status()
if e.response.status_code == 401: except requests.HTTPError as e:
self.log.warning("Received a 401 error, deleting cached cookies") if e.response.status_code == 401:
self.device_cache_path.unlink() self.log.warning("Received a 401 error, deleting cached cookies")
self.session.headers.clear() self.device_cache_path.unlink()
self.session.params = {} self.session.headers.clear()
self.configure() self.session.params = {}
self.retrying = True self.configure()
self.retrying = True
res = res.json()
res = res.json()
if not res["success"]:
raise self.log.exit("Unable to get license token: %s" % (res["errors"])) if not res["success"]:
raise self.log.exit("Unable to get license token: %s" % (res["errors"]))
self.license_url = res["url"]
ls_session = res["ls_session"] self.license_url = res["url"]
ls_session = res["ls_session"]
return ls_session
return ls_session
def parse_movie_year(self, url):
html_raw = self.session.get(url) def parse_movie_year(self, url):
html_raw = self.session.get(url)
if html_raw.status_code != 200:
return None if html_raw.status_code != 200:
return None
self.year = int(
re.findall('"movie__air-year">[0-9]+<', html_raw.text)[0].replace('"movie__air-year">', "").replace("<", "") self.year = int(
) re.findall('"movie__air-year">[0-9]+<', html_raw.text)[0].replace('"movie__air-year">', "").replace("<", "")
)
def parse_show_id(self, url):
html_raw = self.session.get(url) def parse_show_id(self, url):
html_raw = self.session.get(url)
if html_raw.status_code != 200:
self.log.exit("Could not parse Show Id.") if html_raw.status_code != 200:
self.log.exit("Could not parse Show Id.")
show = json.loads('{"' + re.search('CBS.Registry.Show = {"(.*)"}', html_raw.text).group(1) + '"}')
show = json.loads('{"' + re.search('CBS.Registry.Show = {"(.*)"}', html_raw.text).group(1) + '"}')
return str(show["id"])
return str(show["id"])