diff options
Diffstat (limited to 'youtube_dl/downloader/hls.py')
| -rw-r--r-- | youtube_dl/downloader/hls.py | 78 | 
1 files changed, 56 insertions, 22 deletions
diff --git a/youtube_dl/downloader/hls.py b/youtube_dl/downloader/hls.py index 54f2108e9..1d5f178a0 100644 --- a/youtube_dl/downloader/hls.py +++ b/youtube_dl/downloader/hls.py @@ -2,14 +2,24 @@ from __future__ import unicode_literals  import os.path  import re +import binascii +try: +    from Crypto.Cipher import AES +    can_decrypt_frag = True +except ImportError: +    can_decrypt_frag = False  from .fragment import FragmentFD  from .external import FFmpegFD -from ..compat import compat_urlparse +from ..compat import ( +    compat_urlparse, +    compat_struct_pack, +)  from ..utils import (      encodeFilename,      sanitize_open, +    parse_m3u8_attributes,  ) @@ -21,7 +31,7 @@ class HlsFD(FragmentFD):      @staticmethod      def can_download(manifest):          UNSUPPORTED_FEATURES = ( -            r'#EXT-X-KEY:METHOD=(?!NONE)',  # encrypted streams [1] +            r'#EXT-X-KEY:METHOD=(?!NONE|AES-128)',  # encrypted streams [1]              r'#EXT-X-BYTERANGE',  # playlists composed of byte ranges of media files [2]              # Live streams heuristic does not always work (e.g. geo restricted to Germany @@ -39,7 +49,9 @@ class HlsFD(FragmentFD):              # 3. https://tools.ietf.org/html/draft-pantos-http-live-streaming-17#section-4.3.3.2              # 4. https://tools.ietf.org/html/draft-pantos-http-live-streaming-17#section-4.3.3.5          ) -        return all(not re.search(feature, manifest) for feature in UNSUPPORTED_FEATURES) +        check_results = [not re.search(feature, manifest) for feature in UNSUPPORTED_FEATURES] +        check_results.append(not (re.search(r'#EXT-X-KEY:METHOD=AES-128', manifest) and not can_decrypt_frag)) +        return all(check_results)      def real_download(self, filename, info_dict):          man_url = info_dict['url'] @@ -57,36 +69,58 @@ class HlsFD(FragmentFD):                  fd.add_progress_hook(ph)              return fd.real_download(filename, info_dict) -        fragment_urls = [] +        total_frags = 0          for line in s.splitlines():              line = line.strip()              if line and not line.startswith('#'): -                segment_url = ( -                    line -                    if re.match(r'^https?://', line) -                    else compat_urlparse.urljoin(man_url, line)) -                fragment_urls.append(segment_url) -                # We only download the first fragment during the test -                if self.params.get('test', False): -                    break +                total_frags += 1          ctx = {              'filename': filename, -            'total_frags': len(fragment_urls), +            'total_frags': total_frags,          }          self._prepare_and_start_frag_download(ctx) +        i = 0 +        media_sequence = 0 +        decrypt_info = {'METHOD': 'NONE'}          frags_filenames = [] -        for i, frag_url in enumerate(fragment_urls): -            frag_filename = '%s-Frag%d' % (ctx['tmpfilename'], i) -            success = ctx['dl'].download(frag_filename, {'url': frag_url}) -            if not success: -                return False -            down, frag_sanitized = sanitize_open(frag_filename, 'rb') -            ctx['dest_stream'].write(down.read()) -            down.close() -            frags_filenames.append(frag_sanitized) +        for line in s.splitlines(): +            line = line.strip() +            if line: +                if not line.startswith('#'): +                    frag_url = ( +                        line +                        if re.match(r'^https?://', line) +                        else compat_urlparse.urljoin(man_url, line)) +                    frag_filename = '%s-Frag%d' % (ctx['tmpfilename'], i) +                    success = ctx['dl'].download(frag_filename, {'url': frag_url}) +                    if not success: +                        return False +                    down, frag_sanitized = sanitize_open(frag_filename, 'rb') +                    frag_content = down.read() +                    down.close() +                    if decrypt_info['METHOD'] == 'AES-128': +                        iv = decrypt_info.get('IV') or compat_struct_pack(">8xq", media_sequence) +                        frag_content = AES.new(decrypt_info['KEY'], AES.MODE_CBC, iv).decrypt(frag_content) +                    ctx['dest_stream'].write(frag_content) +                    frags_filenames.append(frag_sanitized) +                    # We only download the first fragment during the test +                    if self.params.get('test', False): +                        break +                    i += 1 +                    media_sequence += 1 +                elif line.startswith('#EXT-X-KEY'): +                    decrypt_info = parse_m3u8_attributes(line[11:]) +                    if decrypt_info['METHOD'] == 'AES-128': +                        if 'IV' in decrypt_info: +                            decrypt_info['IV'] = binascii.unhexlify(decrypt_info['IV'][2:]) +                        if not re.match(r'^https?://', decrypt_info['URI']): +                            decrypt_info['URI'] = compat_urlparse.urljoin(man_url, decrypt_info['URI']) +                        decrypt_info['KEY'] = self.ydl.urlopen(decrypt_info['URI']).read() +                elif line.startswith('#EXT-X-MEDIA-SEQUENCE'): +                    media_sequence = int(line[22:])          self._finish_frag_download(ctx)  | 
