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