diff options
| -rw-r--r-- | youtube_dl/extractor/hotstar.py | 163 | 
1 files changed, 75 insertions, 88 deletions
| diff --git a/youtube_dl/extractor/hotstar.py b/youtube_dl/extractor/hotstar.py index d28af36ec..354ac00dc 100644 --- a/youtube_dl/extractor/hotstar.py +++ b/youtube_dl/extractor/hotstar.py @@ -1,10 +1,12 @@  # coding: utf-8  from __future__ import unicode_literals -import re +import hashlib +import hmac +import time  from .common import InfoExtractor -from ..compat import compat_str +from ..compat import compat_HTTPError  from ..utils import (      determine_ext,      ExtractorError, @@ -13,37 +15,40 @@ from ..utils import (  class HotStarBaseIE(InfoExtractor): -    _GEO_COUNTRIES = ['IN'] - -    def _download_json(self, *args, **kwargs): -        response = super(HotStarBaseIE, self)._download_json(*args, **kwargs) -        if response['resultCode'] != 'OK': -            if kwargs.get('fatal'): -                raise ExtractorError( -                    response['errorDescription'], expected=True) -            return None -        return response['resultObj'] - -    def _download_content_info(self, content_id): -        return self._download_json( -            'https://account.hotstar.com/AVS/besc', content_id, query={ -                'action': 'GetAggregatedContentDetails', -                'appVersion': '5.0.40', -                'channel': 'PCTV', -                'contentId': content_id, -            })['contentInfo'][0] +    _AKAMAI_ENCRYPTION_KEY = b'\x05\xfc\x1a\x01\xca\xc9\x4b\xc4\x12\xfc\x53\x12\x07\x75\xf9\xee' + +    def _call_api(self, path, video_id, query_name='contentId'): +        st = int(time.time()) +        exp = st + 6000 +        auth = 'st=%d~exp=%d~acl=/*' % (st, exp) +        auth += '~hmac=' + hmac.new(self._AKAMAI_ENCRYPTION_KEY, auth.encode(), hashlib.sha256).hexdigest() +        response = self._download_json( +            'https://api.hotstar.com/' + path, +            video_id, headers={ +                'hotstarauth': auth, +                'x-country-code': 'IN', +                'x-platform-code': 'JIO', +            }, query={ +                query_name: video_id, +                'tas': 10000, +            }) +        if response['statusCode'] != 'OK': +            raise ExtractorError( +                response['body']['message'], expected=True) +        return response['body']['results']  class HotStarIE(HotStarBaseIE): +    IE_NAME = 'hotstar'      _VALID_URL = r'https?://(?:www\.)?hotstar\.com/(?:.+?[/-])?(?P<id>\d{10})'      _TESTS = [{ -        'url': 'http://www.hotstar.com/on-air-with-aib--english-1000076273', +        'url': 'https://www.hotstar.com/can-you-not-spread-rumours/1000076273',          'info_dict': {              'id': '1000076273',              'ext': 'mp4', -            'title': 'On Air With AIB', +            'title': 'Can You Not Spread Rumours?',              'description': 'md5:c957d8868e9bc793ccb813691cc4c434', -            'timestamp': 1447227000, +            'timestamp': 1447248600,              'upload_date': '20151111',              'duration': 381,          }, @@ -58,47 +63,43 @@ class HotStarIE(HotStarBaseIE):          'url': 'http://www.hotstar.com/1000000515',          'only_matching': True,      }] +    _GEO_BYPASS = False      def _real_extract(self, url):          video_id = self._match_id(url) -        video_data = self._download_content_info(video_id) +        webpage = self._download_webpage(url, video_id) +        app_state = self._parse_json(self._search_regex( +            r'<script>window\.APP_STATE\s*=\s*({.+?})</script>', +            webpage, 'app state'), video_id) +        video_data = list(app_state.values())[0]['initialState']['contentData']['content'] -        title = video_data['episodeTitle'] +        title = video_data['title'] -        if video_data.get('encrypted') == 'Y': +        if video_data.get('drmProtected'):              raise ExtractorError('This video is DRM protected.', expected=True)          formats = [] -        for f in ('JIO',): -            format_data = self._download_json( -                'http://getcdn.hotstar.com/AVS/besc', -                video_id, 'Downloading %s JSON metadata' % f, -                fatal=False, query={ -                    'action': 'GetCDN', -                    'asJson': 'Y', -                    'channel': f, -                    'id': video_id, -                    'type': 'VOD', -                }) -            if format_data: -                format_url = format_data.get('src') -                if not format_url: -                    continue -                ext = determine_ext(format_url) -                if ext == 'm3u8': -                    formats.extend(self._extract_m3u8_formats( -                        format_url, video_id, 'mp4', -                        m3u8_id='hls', fatal=False)) -                elif ext == 'f4m': -                    # produce broken files -                    continue -                else: -                    formats.append({ -                        'url': format_url, -                        'width': int_or_none(format_data.get('width')), -                        'height': int_or_none(format_data.get('height')), -                    }) +        format_data = self._call_api('h/v1/play', video_id)['item'] +        format_url = format_data['playbackUrl'] +        ext = determine_ext(format_url) +        if ext == 'm3u8': +            try: +                formats.extend(self._extract_m3u8_formats( +                    format_url, video_id, 'mp4', m3u8_id='hls')) +            except ExtractorError as e: +                if isinstance(e.cause, compat_HTTPError) and e.cause.code == 403: +                    self.raise_geo_restricted(countries=['IN']) +                raise +        elif ext == 'f4m': +            # produce broken files +            pass +        else: +            formats.append({ +                'url': format_url, +                'width': int_or_none(format_data.get('width')), +                'height': int_or_none(format_data.get('height')), +            })          self._sort_formats(formats)          return { @@ -106,57 +107,43 @@ class HotStarIE(HotStarBaseIE):              'title': title,              'description': video_data.get('description'),              'duration': int_or_none(video_data.get('duration')), -            'timestamp': int_or_none(video_data.get('broadcastDate')), +            'timestamp': int_or_none(video_data.get('broadcastDate') or video_data.get('startDate')),              'formats': formats, +            'channel': video_data.get('channelName'), +            'channel_id': video_data.get('channelId'), +            'series': video_data.get('showName'), +            'season': video_data.get('seasonName'), +            'season_number': int_or_none(video_data.get('seasonNo')), +            'season_id': video_data.get('seasonId'),              'episode': title, -            'episode_number': int_or_none(video_data.get('episodeNumber')), -            'series': video_data.get('contentTitle'), +            'episode_number': int_or_none(video_data.get('episodeNo')),          }  class HotStarPlaylistIE(HotStarBaseIE):      IE_NAME = 'hotstar:playlist' -    _VALID_URL = r'(?P<url>https?://(?:www\.)?hotstar\.com/tv/[^/]+/(?P<content_id>\d+))/(?P<type>[^/]+)/(?P<id>\d+)' +    _VALID_URL = r'https?://(?:www\.)?hotstar\.com/tv/[^/]+/s-\w+/list/[^/]+/t-(?P<id>\w+)'      _TESTS = [{ -        'url': 'http://www.hotstar.com/tv/pratidaan/14982/episodes/14812/9993', +        'url': 'https://www.hotstar.com/tv/savdhaan-india/s-26/list/popular-clips/t-3_2_26',          'info_dict': { -            'id': '14812', +            'id': '3_2_26',          }, -        'playlist_mincount': 75, +        'playlist_mincount': 20,      }, { -        'url': 'http://www.hotstar.com/tv/pratidaan/14982/popular-clips/9998/9998', +        'url': 'https://www.hotstar.com/tv/savdhaan-india/s-26/list/extras/t-2480',          'only_matching': True,      }] -    _ITEM_TYPES = { -        'episodes': 'EPISODE', -        'popular-clips': 'CLIPS', -    }      def _real_extract(self, url): -        mobj = re.match(self._VALID_URL, url) -        base_url = mobj.group('url') -        content_id = mobj.group('content_id') -        playlist_type = mobj.group('type') - -        content_info = self._download_content_info(content_id) -        playlist_id = compat_str(content_info['categoryId']) - -        collection = self._download_json( -            'https://search.hotstar.com/AVS/besc', playlist_id, query={ -                'action': 'SearchContents', -                'appVersion': '5.0.40', -                'channel': 'PCTV', -                'moreFilters': 'series:%s;' % playlist_id, -                'query': '*', -                'searchOrder': 'last_broadcast_date desc,year desc,title asc', -                'type': self._ITEM_TYPES.get(playlist_type, 'EPISODE'), -            }) +        playlist_id = self._match_id(url) + +        collection = self._call_api('o/v1/tray/find', playlist_id, 'uqId')          entries = [              self.url_result( -                '%s/_/%s' % (base_url, video['contentId']), +                'https://www.hotstar.com/%s' % video['contentId'],                  ie=HotStarIE.ie_key(), video_id=video['contentId']) -            for video in collection['response']['docs'] +            for video in collection['assets']['items']              if video.get('contentId')]          return self.playlist_result(entries, playlist_id) | 
