diff --git a/unshackle/commands/dl.py b/unshackle/commands/dl.py index a00a1322..6470e6f8 100644 --- a/unshackle/commands/dl.py +++ b/unshackle/commands/dl.py @@ -1561,6 +1561,9 @@ class dl: missing_ranges = [r for r in range_ if not any(x.range == r for x in title.tracks.videos)] for color_range in missing_ranges: self.log.warning(f"Skipping {color_range.name} video tracks as none are available.") + if not title.tracks.videos: + self.log.error(f"There's no {', '.join(r.name for r in range_)} Video Track...") + sys.exit(1) if vbitrate: if any(r == Video.Range.HYBRID for r in range_): diff --git a/unshackle/core/api/handlers.py b/unshackle/core/api/handlers.py index f14f6898..dc5c52c0 100644 --- a/unshackle/core/api/handlers.py +++ b/unshackle/core/api/handlers.py @@ -1231,7 +1231,39 @@ def _create_service_instance( cdm = _resolve_server_cdm(normalized_service, profile, data.get("cdm_type")) ctx.obj = ContextData(config=service_config, cdm=cdm, proxy_providers=proxy_providers, profile=profile) - ctx.params = {"proxy": proxy_param, "no_proxy": data.get("no_proxy", False)} + # Reconstruct track selection params from client data + from unshackle.core.tracks import Video + + range_names = data.get("range_") + range_values = None + if range_names: + range_values = [] + for name in range_names: + try: + range_values.append(Video.Range[name]) + except KeyError: + pass + range_values = range_values or None + + vcodec_names = data.get("vcodec") + vcodec_values = None + if vcodec_names: + vcodec_values = [] + for name in vcodec_names: + try: + vcodec_values.append(Video.Codec[name]) + except KeyError: + pass + vcodec_values = vcodec_values or None + + ctx.params = { + "proxy": proxy_param, + "no_proxy": data.get("no_proxy", False), + "range_": range_values, + "vcodec": vcodec_values, + "quality": data.get("quality"), + "best_available": data.get("best_available", False), + } service_module = Services.load(normalized_service) @@ -1258,6 +1290,10 @@ def _create_service_instance( "client_region", "no_proxy", "cdm_type", + "range_", + "vcodec", + "quality", + "best_available", } service_init_params = inspect.signature(service_module.__init__).parameters diff --git a/unshackle/core/remote_service.py b/unshackle/core/remote_service.py index 30854c39..99e56be8 100644 --- a/unshackle/core/remote_service.py +++ b/unshackle/core/remote_service.py @@ -68,7 +68,8 @@ class RemoteClient: if resp.status_code >= 400: error_msg = result.get("message", resp.text) error_code = result.get("error_code", "UNKNOWN") - raise click.ClickException(f"Server error [{error_code}]: {error_msg}") + log.error(f"Server error [{error_code}]: {error_msg}") + raise SystemExit(1) return result def post(self, endpoint: str, data: Dict[str, Any]) -> Dict[str, Any]: @@ -492,6 +493,20 @@ class RemoteService: create_data["profile"] = profile if no_proxy: create_data["no_proxy"] = True + # Forward track selection params so the server fetches the right manifests + if self.ctx.parent: + range_ = self.ctx.parent.params.get("range_") + if range_: + create_data["range_"] = [r.name for r in range_] + vcodec = self.ctx.parent.params.get("vcodec") + if vcodec: + create_data["vcodec"] = [c.name for c in vcodec] + quality = self.ctx.parent.params.get("quality") + if quality: + create_data["quality"] = list(quality) + if self.ctx.parent.params.get("best_available"): + create_data["best_available"] = True + if self._service_params: create_data.update(self._service_params) @@ -530,7 +545,8 @@ class RemoteService: if status == "failed": error = resp.get("error", "Authentication failed on server") - raise click.ClickException(f"Remote auth failed: {error}") + log.error(f"Remote auth failed: {error}") + raise SystemExit(1) if status == "pending_input": prompt = resp.get("prompt", "Enter input: ") @@ -543,7 +559,8 @@ class RemoteService: time.sleep(poll_interval) - raise click.ClickException("Remote authentication timed out") + log.error("Remote authentication timed out") + raise SystemExit(1) def get_titles(self) -> Titles_T: if self._titles is not None: