def apply_signature(config_args: Dict, fmt: str, js: str) -> None: """Apply the decrypted signature to the stream manifest. :param dict config_args: Details of the media streams available. :param str fmt: Key in stream manifests (``ytplayer_config``) containing progressive download or adaptive streams (e.g.: ``url_encoded_fmt_stream_map`` or ``adaptive_fmts``). :param str js: The contents of the base.js asset file. """ cipher = Cipher(js=js) stream_manifest = config_args[fmt] for i, stream in enumerate(stream_manifest): try: url: str = stream["url"] except KeyError: live_stream = ( json.loads(config_args["player_response"]) .get("playabilityStatus", {},) .get("liveStreamability") ) if live_stream: raise LiveStreamError("UNKNOWN") # 403 Forbidden fix. if "signature" in url or ( "s" not in stream and ("&sig=" in url or "&lsig=" in url) ): # For certain videos, YouTube will just provide them pre-signed, in # which case there's no real magic to download them and we can skip # the whole signature descrambling entirely. logger.debug("signature found, skip decipher") continue signature = cipher.get_signature(ciphered_signature=stream["s"]) logger.debug( "finished descrambling signature for itag=%s", stream["itag"] ) query_params = parse_qs(urlparse(url).query) if 'ratebypass' not in query_params.keys(): # Cipher n to get the updated value initial_n = list(query_params['n'][0]) new_n = cipher.calculate_n(initial_n) query_params['n'][0] = new_n # Update the value parsed = urlparse(url) # The parsed query params are lists of a single element, convert to proper dicts. query_params = { k: v[0] for k,v in query_params.items() } url = f'{parsed.scheme}://{parsed.netloc}{parsed.path}?{urlencode(query_params)}' # 403 forbidden fix stream_manifest[i]["url"] = url + "&sig=" + signature
def apply_signature(stream_manifest: Dict, vid_info: Dict, js: str) -> None: """Apply the decrypted signature to the stream manifest. :param dict stream_manifest: Details of the media streams available. :param str js: The contents of the base.js asset file. """ cipher = Cipher(js=js) for i, stream in enumerate(stream_manifest): try: url: str = stream["url"] except KeyError: live_stream = (vid_info.get( "playabilityStatus", {}, ).get("liveStreamability")) if live_stream: raise LiveStreamError("UNKNOWN") # 403 Forbidden fix. if "signature" in url or ("s" not in stream and ("&sig=" in url or "&lsig=" in url)): # For certain videos, YouTube will just provide them pre-signed, in # which case there's no real magic to download them and we can skip # the whole signature descrambling entirely. logger.debug("signature found, skip decipher") continue signature = cipher.get_signature(ciphered_signature=stream["s"]) logger.debug("finished descrambling signature for itag=%s", stream["itag"]) parsed_url = urlparse(url) # Convert query params off url to dict query_params = parse_qs(urlparse(url).query) query_params = {k: v[0] for k, v in query_params.items()} query_params['sig'] = signature if 'ratebypass' not in query_params.keys(): # Cipher n to get the updated value initial_n = list(query_params['n']) new_n = cipher.calculate_n(initial_n) query_params['n'] = new_n url = f'{parsed_url.scheme}://{parsed_url.netloc}{parsed_url.path}?{urlencode(query_params)}' # noqa:E501 # 403 forbidden fix stream_manifest[i]["url"] = url