2 from __future__ 
import unicode_literals
 
  10 from .common 
import InfoExtractor
 
  11 from ..compat 
import ( 
  14     compat_urllib_parse_urlencode
, 
  29 class LeIE(InfoExtractor
): 
  31     _VALID_URL 
= r
'https?://(?:www\.le\.com/ptv/vplay|sports\.le\.com/video)/(?P<id>\d+)\.html' 
  33     _URL_TEMPLATE 
= 'http://www.le.com/ptv/vplay/%s.html' 
  36         'url': 'http://www.le.com/ptv/vplay/22005890.html', 
  37         'md5': 'edadcfe5406976f42f9f266057ee5e40', 
  41             'title': '第87届奥斯卡颁奖礼完美落幕 《鸟人》成最大赢家', 
  42             'description': 'md5:a9cb175fd753e2962176b7beca21a47c', 
  45             'hls_prefer_native': True, 
  48         'url': 'http://www.le.com/ptv/vplay/1415246.html', 
  53             'description': 'md5:f88573d9d7225ada1359eaf0dbf8bcda', 
  56             'hls_prefer_native': True, 
  59         'note': 'This video is available only in Mainland China, thus a proxy is needed', 
  60         'url': 'http://www.le.com/ptv/vplay/1118082.html', 
  61         'md5': '2424c74948a62e5f31988438979c5ad1', 
  66             'description': 'md5:7506a5eeb1722bb9d4068f85024e3986', 
  69             'hls_prefer_native': True, 
  71         'skip': 'Only available in China', 
  73         'url': 'http://sports.le.com/video/25737697.html', 
  74         'only_matching': True, 
  79         return val 
>> n 
if val 
>= 0 else (val 
+ 0x100000000) >> n
 
  81     # ror() and calc_time_key() are reversed from a embedded swf file in KLetvPlayer.swf 
  82     def ror(self
, param1
, param2
): 
  84         while _loc3_ 
< param2
: 
  85             param1 
= self
.urshift(param1
, 1) + ((param1 
& 1) << 31) 
  89     def calc_time_key(self
, param1
): 
  91         _loc3_ 
= self
.ror(param1
, _loc2_ 
% 13) 
  92         _loc3_ 
= _loc3_ ^ _loc2_
 
  93         _loc3_ 
= self
.ror(_loc3_
, _loc2_ 
% 17) 
  96     # see M3U8Encryption class in KLetvPlayer.swf 
  98     def decrypt_m3u8(encrypted_data
): 
  99         if encrypted_data
[:5].decode('utf-8').lower() != 'vc_01': 
 100             return encrypted_data
 
 101         encrypted_data 
= encrypted_data
[5:] 
 103         _loc4_ 
= bytearray(2 * len(encrypted_data
)) 
 104         for idx
, val 
in enumerate(encrypted_data
): 
 106             _loc4_
[2 * idx
] = b 
// 16 
 107             _loc4_
[2 * idx 
+ 1] = b 
% 16 
 108         idx 
= len(_loc4_
) - 11 
 109         _loc4_ 
= _loc4_
[idx
:] + _loc4_
[:idx
] 
 110         _loc7_ 
= bytearray(len(encrypted_data
)) 
 111         for i 
in range(len(encrypted_data
)): 
 112             _loc7_
[i
] = _loc4_
[2 * i
] * 16 + _loc4_
[2 * i 
+ 1] 
 116     def _real_extract(self
, url
): 
 117         media_id 
= self
._match
_id
(url
) 
 118         page 
= self
._download
_webpage
(url
, media_id
) 
 124             'tkey': self
.calc_time_key(int(time
.time())), 
 125             'domain': 'www.le.com' 
 127         play_json_req 
= sanitized_Request( 
 128             'http://api.le.com/mms/out/video/playJson?' + compat_urllib_parse_urlencode(params
) 
 130         cn_verification_proxy 
= self
._downloader
.params
.get('cn_verification_proxy') 
 131         if cn_verification_proxy
: 
 132             play_json_req
.add_header('Ytdl-request-proxy', cn_verification_proxy
) 
 134         play_json 
= self
._download
_json
( 
 136             media_id
, 'Downloading playJson data') 
 139         playstatus 
= play_json
['playstatus'] 
 140         if playstatus
['status'] == 0: 
 141             flag 
= playstatus
['flag'] 
 143                 msg 
= 'Country %s auth error' % playstatus
['country'] 
 145                 msg 
= 'Generic error. flag = %d' % flag
 
 146             raise ExtractorError(msg
, expected
=True) 
 148         playurl 
= play_json
['playurl'] 
 150         formats 
= ['350', '1000', '1300', '720p', '1080p'] 
 151         dispatch 
= playurl
['dispatch'] 
 154         for format_id 
in formats
: 
 155             if format_id 
in dispatch
: 
 156                 media_url 
= playurl
['domain'][0] + dispatch
[format_id
][0] 
 157                 media_url 
+= '&' + compat_urllib_parse_urlencode({ 
 164                 nodes_data 
= self
._download
_json
( 
 166                     'Download JSON metadata for format %s' % format_id
) 
 168                 req 
= self
._request
_webpage
( 
 169                     nodes_data
['nodelist'][0]['location'], media_id
, 
 170                     note
='Downloading m3u8 information for format %s' % format_id
) 
 172                 m3u8_data 
= self
.decrypt_m3u8(req
.read()) 
 175                     'url': encode_data_uri(m3u8_data
, 'application/vnd.apple.mpegurl'), 
 176                     'ext': determine_ext(dispatch
[format_id
][1]), 
 177                     'format_id': format_id
, 
 181                 if format_id
[-1:] == 'p': 
 182                     url_info_dict
['height'] = int_or_none(format_id
[:-1]) 
 184                 urls
.append(url_info_dict
) 
 186         publish_time 
= parse_iso8601(self
._html
_search
_regex
( 
 187             r
'发布时间 ([^<>]+) ', page
, 'publish time', default
=None), 
 188             delimiter
=' ', timezone
=datetime
.timedelta(hours
=8)) 
 189         description 
= self
._html
_search
_meta
('description', page
, fatal
=False) 
 194             'title': playurl
['title'], 
 195             'thumbnail': playurl
['pic'], 
 196             'description': description
, 
 197             'timestamp': publish_time
, 
 201 class LePlaylistIE(InfoExtractor
): 
 202     _VALID_URL 
= r
'https?://[a-z]+\.le\.com/(?!video)[a-z]+/(?P<id>[a-z0-9_]+)' 
 205         'url': 'http://www.le.com/tv/46177.html', 
 209             'description': 'md5:395666ff41b44080396e59570dbac01c' 
 213         'url': 'http://tv.le.com/izt/wuzetian/index.html', 
 217             'description': 'md5:e12499475ab3d50219e5bba00b3cb248' 
 219         # This playlist contains some extra videos other than the drama itself 
 220         'playlist_mincount': 96 
 222         'url': 'http://tv.le.com/pzt/lswjzzjc/index.shtml', 
 223         # This series is moved to http://www.le.com/tv/10005297.html 
 224         'only_matching': True, 
 226         'url': 'http://www.le.com/comic/92063.html', 
 227         'only_matching': True, 
 229         'url': 'http://list.le.com/listn/c1009_sc532002_d2_p1_o1.html', 
 230         'only_matching': True, 
 234     def suitable(cls
, url
): 
 235         return False if LeIE
.suitable(url
) else super(LePlaylistIE
, cls
).suitable(url
) 
 237     def _real_extract(self
, url
): 
 238         playlist_id 
= self
._match
_id
(url
) 
 239         page 
= self
._download
_webpage
(url
, playlist_id
) 
 241         # Currently old domain names are still used in playlists 
 242         media_ids 
= orderedSet(re
.findall( 
 243             r
'<a[^>]+href="http://www\.letv\.com/ptv/vplay/(\d+)\.html', page
)) 
 244         entries 
= [self
.url_result(LeIE
._URL
_TEMPLATE 
% media_id
, ie
='Le') 
 245                    for media_id 
in media_ids
] 
 247         title 
= self
._html
_search
_meta
('keywords', page
, 
 248                                        fatal
=False).split(',')[0] 
 249         description 
= self
._html
_search
_meta
('description', page
, fatal
=False) 
 251         return self
.playlist_result(entries
, playlist_id
, playlist_title
=title
, 
 252                                     playlist_description
=description
) 
 255 class LetvCloudIE(InfoExtractor
): 
 256     # Most of *.letv.com is changed to *.le.com on 2016/01/02 
 257     # but yuntv.letv.com is kept, so also keep the extractor name 
 259     _VALID_URL 
= r
'https?://yuntv\.letv\.com/bcloud.html\?.+' 
 262         'url': 'http://yuntv.letv.com/bcloud.html?uu=p7jnfw5hw9&vu=467623dedf', 
 263         'md5': '26450599afd64c513bc77030ad15db44', 
 265             'id': 'p7jnfw5hw9_467623dedf', 
 267             'title': 'Video p7jnfw5hw9_467623dedf', 
 270         'url': 'http://yuntv.letv.com/bcloud.html?uu=p7jnfw5hw9&vu=ec93197892&pu=2c7cd40209&auto_play=1&gpcflag=1&width=640&height=360', 
 271         'md5': 'e03d9cc8d9c13191e1caf277e42dbd31', 
 273             'id': 'p7jnfw5hw9_ec93197892', 
 275             'title': 'Video p7jnfw5hw9_ec93197892', 
 278         'url': 'http://yuntv.letv.com/bcloud.html?uu=p7jnfw5hw9&vu=187060b6fd', 
 279         'md5': 'cb988699a776b22d4a41b9d43acfb3ac', 
 281             'id': 'p7jnfw5hw9_187060b6fd', 
 283             'title': 'Video p7jnfw5hw9_187060b6fd', 
 289         if obj
['cf'] == 'flash': 
 290             salt 
= '2f9d6924b33a165a6d8b5d3d42f4f987' 
 291             items 
= ['cf', 'format', 'ran', 'uu', 'ver', 'vu'] 
 292         elif obj
['cf'] == 'html5': 
 293             salt 
= 'fbeh5player12c43eccf2bec3300344' 
 294             items 
= ['cf', 'ran', 'uu', 'bver', 'vu'] 
 295         input_data 
= ''.join([item 
+ obj
[item
] for item 
in items
]) + salt
 
 296         obj
['sign'] = hashlib
.md5(input_data
.encode('utf-8')).hexdigest() 
 298     def _get_formats(self
, cf
, uu
, vu
, media_id
): 
 299         def get_play_json(cf
, timestamp
): 
 303                 'bver': 'firefox44.0', 
 307                 'ran': compat_str(timestamp
), 
 310             return self
._download
_json
( 
 311                 'http://api.letvcloud.com/gpc.php?' + compat_urllib_parse_urlencode(data
), 
 312                 media_id
, 'Downloading playJson data for type %s' % cf
) 
 314         play_json 
= get_play_json(cf
, time
.time()) 
 315         # The server time may be different from local time 
 316         if play_json
.get('code') == 10071: 
 317             play_json 
= get_play_json(cf
, play_json
['timestamp']) 
 319         if not play_json
.get('data'): 
 320             if play_json
.get('message'): 
 321                 raise ExtractorError('Letv cloud said: %s' % play_json
['message'], expected
=True) 
 322             elif play_json
.get('code'): 
 323                 raise ExtractorError('Letv cloud returned error %d' % play_json
['code'], expected
=True) 
 325                 raise ExtractorError('Letv cloud returned an unknwon error') 
 328             return base64
.b64decode(s
.encode('utf-8')).decode('utf-8') 
 331         for media 
in play_json
['data']['video_info']['media'].values(): 
 332             play_url 
= media
['play_url'] 
 333             url 
= b64decode(play_url
['main_url']) 
 334             decoded_url 
= b64decode(url_basename(url
)) 
 337                 'ext': determine_ext(decoded_url
), 
 338                 'format_id': str_or_none(play_url
.get('vtype')), 
 339                 'format_note': str_or_none(play_url
.get('definition')), 
 340                 'width': int_or_none(play_url
.get('vwidth')), 
 341                 'height': int_or_none(play_url
.get('vheight')), 
 346     def _real_extract(self
, url
): 
 347         uu_mobj 
= re
.search('uu=([\w]+)', url
) 
 348         vu_mobj 
= re
.search('vu=([\w]+)', url
) 
 350         if not uu_mobj 
or not vu_mobj
: 
 351             raise ExtractorError('Invalid URL: %s' % url
, expected
=True) 
 353         uu 
= uu_mobj
.group(1) 
 354         vu 
= vu_mobj
.group(1) 
 355         media_id 
= uu 
+ '_' + vu
 
 357         formats 
= self
._get
_formats
('flash', uu
, vu
, media_id
) + self
._get
_formats
('html5', uu
, vu
, media_id
) 
 358         self
._sort
_formats
(formats
) 
 362             'title': 'Video %s' % media_id
,