Added HLS Support

This commit is contained in:
FairTrade 2026-03-08 15:18:09 +01:00
parent 6509198610
commit fa776a590a
2 changed files with 68 additions and 17 deletions

View File

@ -286,39 +286,69 @@ class KNPY(Service):
if response_json and response_json.get("errorSubcode") == "playRegionRestricted": if response_json and response_json.get("errorSubcode") == "playRegionRestricted":
self.log.error("Kanopy reports: This video is not available in your country.") self.log.error("Kanopy reports: This video is not available in your country.")
raise PermissionError( raise PermissionError(
"Playback blocked by region restriction. Try connecting through a supported country or verify your librarys access region." "Playback blocked by region restriction. Try connecting through a supported country or verify your library's access region."
) )
else: else:
self.log.error(f"Access forbidden (HTTP 403). Response: {response_json}") 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 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() r.raise_for_status()
play_data = response_json or r.json() play_data = response_json or r.json()
manifest_url = None manifest_url = None
manifest_type = None
drm_info = {}
# Iterate through manifests: prefer DASH, fallback to HLS
for manifest in play_data.get("manifests", []): for manifest in play_data.get("manifests", []):
if manifest["manifestType"] == "dash": manifest_type_raw = manifest["manifestType"]
url = manifest["url"] url = manifest["url"].strip() # Strip whitespace from URLs
manifest_url = f"https://kanopy.com{url}" if url.startswith("/") else url
drm_type = manifest.get("drmType") # Construct full URL if relative
if url.startswith("/"):
url = f"https://kanopy.com{url}"
drm_type = manifest.get("drmType")
if manifest_type_raw == "dash":
manifest_url = url
manifest_type = "dash"
if drm_type == "kanopyDrm": if drm_type == "kanopyDrm":
play_id = play_data.get("playId") play_id = play_data.get("playId")
self.widevine_license_url = self.config["endpoints"]["widevine_license"].format(license_id=f"{play_id}-0") self.widevine_license_url = self.config["endpoints"]["widevine_license"].format(
license_id=f"{play_id}-0"
)
elif drm_type == "studioDrm": elif drm_type == "studioDrm":
license_id = manifest.get("drmLicenseID", f"{play_data.get('playId')}-1") license_id = manifest.get("drmLicenseID", f"{play_data.get('playId')}-1")
self.widevine_license_url = self.config["endpoints"]["widevine_license"].format(license_id=license_id) self.widevine_license_url = self.config["endpoints"]["widevine_license"].format(
license_id=license_id
)
else: else:
self.log.warning(f"Unknown drmType: {drm_type}") self.log.warning(f"Unknown DASH drmType: {drm_type}")
self.widevine_license_url = None self.widevine_license_url = None
break break # Prefer DASH, exit loop
elif manifest_type_raw == "hls" and not manifest_url:
# Store HLS as fallback if DASH not found
manifest_url = url
manifest_type = "hls"
if drm_type == "fairplay":
self.log.warning("HLS with FairPlay DRM detected - not currently supported by this service")
self.widevine_license_url = None
drm_info["fairplay"] = True
else:
# HLS with no DRM or unsupported DRM type
self.widevine_license_url = None
drm_info["clear"] = True
if not manifest_url: if not manifest_url:
raise ValueError("Could not find a DASH manifest for this title.") raise ValueError("Could not find a DASH or HLS manifest for this title.")
if not self.widevine_license_url: if manifest_type == "dash" and not self.widevine_license_url:
raise ValueError("Could not construct Widevine license URL.") raise ValueError("Could not construct Widevine license URL for DASH manifest.")
self.log.info(f"Fetching DASH manifest from: {manifest_url}") self.log.info(f"Fetching {manifest_type.upper()} manifest from: {manifest_url}")
r = self.session.get(manifest_url) r = self.session.get(manifest_url)
r.raise_for_status() r.raise_for_status()
@ -331,14 +361,35 @@ class KNPY(Service):
"Connection": "keep-alive", "Connection": "keep-alive",
}) })
tracks = DASH.from_text(r.text, url=manifest_url).to_tracks(language=title.language) # Parse manifest based on type
if manifest_type == "dash":
tracks = DASH.from_text(r.text, url=manifest_url).to_tracks(language=title.language)
elif manifest_type == "hls":
# Try to import HLS parser from unshackle
try:
from unshackle.core.manifests import HLS
tracks = HLS.from_text(r.text, url=manifest_url).to_tracks(language=title.language)
self.log.info("Successfully parsed HLS manifest")
except ImportError:
self.log.error(
"HLS manifest parser not available in unshackle.core.manifests. "
"Ensure your unshackle installation supports HLS parsing."
)
raise
except Exception as e:
self.log.error(f"Failed to parse HLS manifest: {e}")
raise
else:
raise ValueError(f"Unsupported manifest type: {manifest_type}")
# Add subtitles/captions from play_data (works for both DASH and HLS)
for caption_data in play_data.get("captions", []): for caption_data in play_data.get("captions", []):
lang = caption_data.get("language", "en") lang = caption_data.get("language", "en")
for file_info in caption_data.get("files", []): for file_info in caption_data.get("files", []):
if file_info.get("type") == "webvtt": if file_info.get("type") == "webvtt":
tracks.add(Subtitle( tracks.add(Subtitle(
id_=f"caption-{lang}", id_=f"caption-{lang}",
url=file_info["url"], url=file_info["url"].strip(),
codec=Subtitle.Codec.WebVTT, codec=Subtitle.Codec.WebVTT,
language=Language.get(lang) language=Language.get(lang)
)) ))