diff --git a/yt_dlp/downloader/dash.py b/yt_dlp/downloader/dash.py index 84ff79c8af..f1826333a0 100644 --- a/yt_dlp/downloader/dash.py +++ b/yt_dlp/downloader/dash.py @@ -8,7 +8,7 @@ from .fragment import FragmentFD from ..networking import Request from ..networking.exceptions import RequestError -from ..utils import update_url_query, urljoin +from ..utils import remove_start, traverse_obj, update_url_query, urljoin class DashSegmentsFD(FragmentFD): @@ -54,6 +54,25 @@ def real_download(self, filename, info_dict): if extra_param_to_segment_url: extra_query = urllib.parse.parse_qs(extra_param_to_segment_url) + hls_aes = fmt.get('hls_aes', {}) + if hls_aes: + decrypt_info = {'METHOD', 'AES-128'} + key = hls_aes.get('key') + if key: + key = binascii.unhexlify(remove_start(key, '0x')) + assert len(key) in (16, 24, 32), 'Invalid length for HLS AES-128 key' + decrypt_info['KEY'] = key + iv = hls_aes.get('iv') + if iv: + iv = binascii.unhexlify(remove_start(iv, '0x').zfill(32)) + decrypt_info['IV'] = iv + uri = hls_aes.get('uri') + if uri: + if extra_query: + uri = update_url_query(uri, extra_query) + decrypt_info['URI'] = uri + ctx['decrypt_info'] = decrypt_info + fragments_to_download = self._get_fragments(fmt, ctx, extra_query) if real_downloader: @@ -65,8 +84,11 @@ def real_download(self, filename, info_dict): args.append([ctx, fragments_to_download, fmt]) - if 'dash_cenc' in info_dict and not info_dict['dash_cenc'].get('key'): - self._get_clearkey_cenc(info_dict) + cenc_key = traverse_obj(info_dict, ('dash_cenc', 'key')) + cenc_key_ids = traverse_obj(info_dict, ('dash_cenc', 'key_ids')) + clearkey_laurl = traverse_obj(info_dict, ('dash_cenc', 'laurl')) + if not cenc_key and cenc_key_ids and clearkey_laurl: + self._get_clearkey_cenc(info_dict, clearkey_laurl, cenc_key_ids) return self.download_and_append_fragments_multiple(*args, is_fatal=lambda idx: idx == 0) @@ -95,18 +117,11 @@ def _get_fragments(self, fmt, ctx, extra_query): 'fragment_count': fragment.get('fragment_count'), 'index': i, 'url': fragment_url, + 'decrypt_info': ctx.get('decrypt_info', {'METHOD': 'NONE'}), } - def _get_clearkey_cenc(self, info_dict): + def _get_clearkey_cenc(self, info_dict, laurl, key_ids): dash_cenc = info_dict.get('dash_cenc', {}) - laurl = dash_cenc.get('laurl') - if not laurl: - self.report_error('No Clear Key license server URL for encrypted DASH stream') - return - key_ids = dash_cenc.get('key_ids') - if not key_ids: - self.report_error('No requested CENC KIDs for encrypted DASH stream') - return payload = json.dumps({ 'kids': [ base64.urlsafe_b64encode(bytes.fromhex(k)).decode().rstrip('=') @@ -128,7 +143,7 @@ def _get_clearkey_cenc(self, info_dict): k = key.get('k') if k: try: - dash_cenc['key'] = base64.urlsafe_b64decode(f'{k}==').hex() + dash_cenc.update({'key': base64.urlsafe_b64decode(f'{k}==').hex()}) info_dict['dash_cenc'] = dash_cenc return except (ValueError, binascii.Error):