diff --git a/KNPY/__init__.py b/KNPY/__init__.py index 3096c55..8647d03 100644 --- a/KNPY/__init__.py +++ b/KNPY/__init__.py @@ -269,29 +269,38 @@ class KNPY(Service): "visitorId": self._visitor_id } - if "authorization" not in self.session.headers: - self.session.headers["authorization"] = f"Bearer {self._jwt}" - self.session.headers["x-version"] = self.API_VERSION - self.session.headers["user-agent"] = self.USER_AGENT + self.session.headers.setdefault("authorization", f"Bearer {self._jwt}") + self.session.headers.setdefault("x-version", self.API_VERSION) + self.session.headers.setdefault("user-agent", self.USER_AGENT) - r = self.session.post( - self.config["endpoints"]["plays"], - json=play_payload, - ) + r = self.session.post(self.config["endpoints"]["plays"], json=play_payload) + response_json = None + try: + response_json = r.json() + except Exception: + pass + + # Handle known errors gracefully + if r.status_code == 403: + if response_json and response_json.get("errorSubcode") == "playRegionRestricted": + self.log.error("Kanopy reports: This video is not available in your country.") + raise PermissionError( + "Playback blocked by region restriction. Try connecting through a supported country or verify your library’s access region." + ) + else: + self.log.error(f"Access forbidden (HTTP 403). Response: {response_json}") + raise PermissionError("Kanopy denied access to this video. It may require a different library membership or authentication.") + + # Raise for any other HTTP errors r.raise_for_status() - play_data = r.json() + play_data = response_json or r.json() manifest_url = None for manifest in play_data.get("manifests", []): if manifest["manifestType"] == "dash": - manifest_relative_url = manifest["url"] - if manifest_relative_url.startswith("/"): - manifest_url = f"https://kanopy.com{manifest_relative_url}" - else: - manifest_url = manifest_relative_url - + url = manifest["url"] + manifest_url = f"https://kanopy.com{url}" if url.startswith("/") else url drm_type = manifest.get("drmType") - if drm_type == "kanopyDrm": play_id = play_data.get("playId") self.widevine_license_url = self.config["endpoints"]["widevine_license"].format(license_id=f"{play_id}-0") @@ -311,7 +320,8 @@ class KNPY(Service): self.log.info(f"Fetching DASH manifest from: {manifest_url}") r = self.session.get(manifest_url) r.raise_for_status() - + + # Refresh headers for manifest parsing self.session.headers.clear() self.session.headers.update({ "User-Agent": self.WIDEVINE_UA, @@ -319,23 +329,23 @@ class KNPY(Service): "Accept-Encoding": "gzip, deflate", "Connection": "keep-alive", }) - + tracks = DASH.from_text(r.text, url=manifest_url).to_tracks(language=title.language) - for caption_data in play_data.get("captions", []): - lang_code = caption_data.get("language", "en") + lang = caption_data.get("language", "en") for file_info in caption_data.get("files", []): if file_info.get("type") == "webvtt": tracks.add(Subtitle( - id_=f"caption-{lang_code}", + id_=f"caption-{lang}", url=file_info["url"], codec=Subtitle.Codec.WebVTT, - language=Language.get(lang_code) + language=Language.get(lang) )) break - + return tracks + def get_widevine_license(self, *, challenge: bytes, title: Title_T, track: AnyTrack) -> bytes: if not self.widevine_license_url: raise ValueError("Widevine license URL was not set. Call get_tracks first.") @@ -394,4 +404,4 @@ class KNPY(Service): # return results def get_chapters(self, title: Title_T) -> list: - return [] \ No newline at end of file + return []