[fd/dash] support DASH SEA (AES-128-CBC) decryption

This commit is contained in:
Peter Rowlands 2024-10-05 15:43:32 +09:00
parent e0ce6eed92
commit bd62cdba1a

View file

@ -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):