[hotstar] Move to API v2 (closes #20931)
This commit is contained in:
		@@ -4,40 +4,59 @@ from __future__ import unicode_literals
 | 
				
			|||||||
import hashlib
 | 
					import hashlib
 | 
				
			||||||
import hmac
 | 
					import hmac
 | 
				
			||||||
import time
 | 
					import time
 | 
				
			||||||
 | 
					import uuid
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from .common import InfoExtractor
 | 
					from .common import InfoExtractor
 | 
				
			||||||
from ..compat import compat_HTTPError
 | 
					from ..compat import (
 | 
				
			||||||
 | 
					    compat_HTTPError,
 | 
				
			||||||
 | 
					    compat_str,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
from ..utils import (
 | 
					from ..utils import (
 | 
				
			||||||
    determine_ext,
 | 
					    determine_ext,
 | 
				
			||||||
    ExtractorError,
 | 
					    ExtractorError,
 | 
				
			||||||
    int_or_none,
 | 
					    int_or_none,
 | 
				
			||||||
 | 
					    str_or_none,
 | 
				
			||||||
    try_get,
 | 
					    try_get,
 | 
				
			||||||
 | 
					    url_or_none,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class HotStarBaseIE(InfoExtractor):
 | 
					class HotStarBaseIE(InfoExtractor):
 | 
				
			||||||
    _AKAMAI_ENCRYPTION_KEY = b'\x05\xfc\x1a\x01\xca\xc9\x4b\xc4\x12\xfc\x53\x12\x07\x75\xf9\xee'
 | 
					    _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'):
 | 
					    def _call_api_impl(self, path, video_id, query):
 | 
				
			||||||
        st = int(time.time())
 | 
					        st = int(time.time())
 | 
				
			||||||
        exp = st + 6000
 | 
					        exp = st + 6000
 | 
				
			||||||
        auth = 'st=%d~exp=%d~acl=/*' % (st, exp)
 | 
					        auth = 'st=%d~exp=%d~acl=/*' % (st, exp)
 | 
				
			||||||
        auth += '~hmac=' + hmac.new(self._AKAMAI_ENCRYPTION_KEY, auth.encode(), hashlib.sha256).hexdigest()
 | 
					        auth += '~hmac=' + hmac.new(self._AKAMAI_ENCRYPTION_KEY, auth.encode(), hashlib.sha256).hexdigest()
 | 
				
			||||||
        response = self._download_json(
 | 
					        response = self._download_json(
 | 
				
			||||||
            'https://api.hotstar.com/' + path,
 | 
					            'https://api.hotstar.com/' + path, video_id, headers={
 | 
				
			||||||
            video_id, headers={
 | 
					 | 
				
			||||||
                'hotstarauth': auth,
 | 
					                'hotstarauth': auth,
 | 
				
			||||||
                'x-country-code': 'IN',
 | 
					                'x-country-code': 'IN',
 | 
				
			||||||
                'x-platform-code': 'JIO',
 | 
					                'x-platform-code': 'JIO',
 | 
				
			||||||
            }, query={
 | 
					            }, query=query)
 | 
				
			||||||
                query_name: video_id,
 | 
					 | 
				
			||||||
                'tas': 10000,
 | 
					 | 
				
			||||||
            })
 | 
					 | 
				
			||||||
        if response['statusCode'] != 'OK':
 | 
					        if response['statusCode'] != 'OK':
 | 
				
			||||||
            raise ExtractorError(
 | 
					            raise ExtractorError(
 | 
				
			||||||
                response['body']['message'], expected=True)
 | 
					                response['body']['message'], expected=True)
 | 
				
			||||||
        return response['body']['results']
 | 
					        return response['body']['results']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _call_api(self, path, video_id, query_name='contentId'):
 | 
				
			||||||
 | 
					        return self._call_api_impl(path, video_id, {
 | 
				
			||||||
 | 
					            query_name: video_id,
 | 
				
			||||||
 | 
					            'tas': 10000,
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _call_api_v2(self, path, video_id):
 | 
				
			||||||
 | 
					        return self._call_api_impl(
 | 
				
			||||||
 | 
					            '%s/in/contents/%s' % (path, video_id), video_id, {
 | 
				
			||||||
 | 
					                'desiredConfig': 'encryption:plain;ladder:phone,tv;package:hls,dash',
 | 
				
			||||||
 | 
					                'client': 'mweb',
 | 
				
			||||||
 | 
					                'clientVersion': '6.18.0',
 | 
				
			||||||
 | 
					                'deviceId': compat_str(uuid.uuid4()),
 | 
				
			||||||
 | 
					                'osName': 'Windows',
 | 
				
			||||||
 | 
					                'osVersion': '10',
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class HotStarIE(HotStarBaseIE):
 | 
					class HotStarIE(HotStarBaseIE):
 | 
				
			||||||
    IE_NAME = 'hotstar'
 | 
					    IE_NAME = 'hotstar'
 | 
				
			||||||
@@ -68,6 +87,10 @@ class HotStarIE(HotStarBaseIE):
 | 
				
			|||||||
    }, {
 | 
					    }, {
 | 
				
			||||||
        'url': 'http://www.hotstar.com/1000000515',
 | 
					        'url': 'http://www.hotstar.com/1000000515',
 | 
				
			||||||
        'only_matching': True,
 | 
					        'only_matching': True,
 | 
				
			||||||
 | 
					    }, {
 | 
				
			||||||
 | 
					        # only available via api v2
 | 
				
			||||||
 | 
					        'url': 'https://www.hotstar.com/tv/ek-bhram-sarvagun-sampanna/s-2116/janhvi-targets-suman/1000234847',
 | 
				
			||||||
 | 
					        'only_matching': True,
 | 
				
			||||||
    }]
 | 
					    }]
 | 
				
			||||||
    _GEO_BYPASS = False
 | 
					    _GEO_BYPASS = False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -95,26 +118,40 @@ class HotStarIE(HotStarBaseIE):
 | 
				
			|||||||
            raise ExtractorError('This video is DRM protected.', expected=True)
 | 
					            raise ExtractorError('This video is DRM protected.', expected=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        formats = []
 | 
					        formats = []
 | 
				
			||||||
        format_data = self._call_api('h/v1/play', video_id)['item']
 | 
					        geo_restricted = False
 | 
				
			||||||
        format_url = format_data['playbackUrl']
 | 
					        playback_sets = self._call_api_v2('h/v2/play', video_id)['playBackSets']
 | 
				
			||||||
        ext = determine_ext(format_url)
 | 
					        for playback_set in playback_sets:
 | 
				
			||||||
        if ext == 'm3u8':
 | 
					            if not isinstance(playback_set, dict):
 | 
				
			||||||
 | 
					                continue
 | 
				
			||||||
 | 
					            format_url = url_or_none(playback_set.get('playbackUrl'))
 | 
				
			||||||
 | 
					            if not format_url:
 | 
				
			||||||
 | 
					                continue
 | 
				
			||||||
 | 
					            tags = str_or_none(playback_set.get('tagsCombination')) or ''
 | 
				
			||||||
 | 
					            if tags and 'encryption:plain' not in tags:
 | 
				
			||||||
 | 
					                continue
 | 
				
			||||||
 | 
					            ext = determine_ext(format_url)
 | 
				
			||||||
            try:
 | 
					            try:
 | 
				
			||||||
                formats.extend(self._extract_m3u8_formats(
 | 
					                if 'package:hls' in tags or ext == 'm3u8':
 | 
				
			||||||
                    format_url, video_id, 'mp4', m3u8_id='hls'))
 | 
					                    formats.extend(self._extract_m3u8_formats(
 | 
				
			||||||
 | 
					                        format_url, video_id, 'mp4', m3u8_id='hls'))
 | 
				
			||||||
 | 
					                elif 'package:dash' in tags or ext == 'mpd':
 | 
				
			||||||
 | 
					                    formats.extend(self._extract_mpd_formats(
 | 
				
			||||||
 | 
					                        format_url, video_id, mpd_id='dash'))
 | 
				
			||||||
 | 
					                elif ext == 'f4m':
 | 
				
			||||||
 | 
					                    # produce broken files
 | 
				
			||||||
 | 
					                    pass
 | 
				
			||||||
 | 
					                else:
 | 
				
			||||||
 | 
					                    formats.append({
 | 
				
			||||||
 | 
					                        'url': format_url,
 | 
				
			||||||
 | 
					                        'width': int_or_none(playback_set.get('width')),
 | 
				
			||||||
 | 
					                        'height': int_or_none(playback_set.get('height')),
 | 
				
			||||||
 | 
					                    })
 | 
				
			||||||
            except ExtractorError as e:
 | 
					            except ExtractorError as e:
 | 
				
			||||||
                if isinstance(e.cause, compat_HTTPError) and e.cause.code == 403:
 | 
					                if isinstance(e.cause, compat_HTTPError) and e.cause.code == 403:
 | 
				
			||||||
                    self.raise_geo_restricted(countries=['IN'])
 | 
					                    geo_restricted = True
 | 
				
			||||||
                raise
 | 
					                continue
 | 
				
			||||||
        elif ext == 'f4m':
 | 
					        if not formats and geo_restricted:
 | 
				
			||||||
            # produce broken files
 | 
					            self.raise_geo_restricted(countries=['IN'])
 | 
				
			||||||
            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)
 | 
					        self._sort_formats(formats)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return {
 | 
					        return {
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user