]>
Commit | Line | Data |
---|---|---|
605ec701 | 1 | # coding: utf-8 |
605ec701 P |
2 | from __future__ import unicode_literals |
3 | ||
958d0b65 YCH |
4 | import hashlib |
5 | import math | |
6 | import random | |
605ec701 P |
7 | import re |
8 | import time | |
605ec701 | 9 | import uuid |
605ec701 | 10 | import zlib |
958d0b65 YCH |
11 | |
12 | from .common import InfoExtractor | |
13 | from ..compat import compat_urllib_parse | |
14 | from ..utils import ExtractorError | |
605ec701 | 15 | |
f1da8610 | 16 | |
605ec701 P |
17 | class IqiyiIE(InfoExtractor): |
18 | IE_NAME = 'iqiyi' | |
19 | ||
865ab62f | 20 | _VALID_URL = r'http://(?:www\.)iqiyi.com/v_.+?\.html' |
605ec701 | 21 | |
99481135 | 22 | _TESTS = [{ |
f1da8610 YCH |
23 | 'url': 'http://www.iqiyi.com/v_19rrojlavg.html', |
24 | 'md5': '2cb594dc2781e6c941a110d8f358118b', | |
25 | 'info_dict': { | |
26 | 'id': '9c1fb1b99d192b21c559e5a1a2cb3c73', | |
27 | 'title': '美国德州空中惊现奇异云团 酷似UFO', | |
28 | 'ext': 'f4v', | |
29 | } | |
99481135 YCH |
30 | }, { |
31 | 'url': 'http://www.iqiyi.com/v_19rrhnnclk.html', | |
32 | 'info_dict': { | |
33 | 'id': 'e3f585b550a280af23c98b6cb2be19fb', | |
34 | 'title': '名侦探柯南第752集', | |
35 | }, | |
36 | 'playlist': [{ | |
37 | 'md5': '7e49376fecaffa115d951634917fe105', | |
38 | 'info_dict': { | |
39 | 'id': 'e3f585b550a280af23c98b6cb2be19fb_part1', | |
40 | 'ext': 'f4v', | |
41 | 'title': '名侦探柯南第752集', | |
42 | }, | |
43 | }, { | |
44 | 'md5': '41b75ba13bb7ac0e411131f92bc4f6ca', | |
45 | 'info_dict': { | |
46 | 'id': 'e3f585b550a280af23c98b6cb2be19fb_part2', | |
47 | 'ext': 'f4v', | |
48 | 'title': '名侦探柯南第752集', | |
49 | }, | |
50 | }, { | |
51 | 'md5': '0cee1dd0a3d46a83e71e2badeae2aab0', | |
52 | 'info_dict': { | |
53 | 'id': 'e3f585b550a280af23c98b6cb2be19fb_part3', | |
54 | 'ext': 'f4v', | |
55 | 'title': '名侦探柯南第752集', | |
56 | }, | |
57 | }, { | |
58 | 'md5': '4f8ad72373b0c491b582e7c196b0b1f9', | |
59 | 'info_dict': { | |
60 | 'id': 'e3f585b550a280af23c98b6cb2be19fb_part4', | |
61 | 'ext': 'f4v', | |
62 | 'title': '名侦探柯南第752集', | |
63 | }, | |
64 | }, { | |
65 | 'md5': 'd89ad028bcfad282918e8098e811711d', | |
66 | 'info_dict': { | |
67 | 'id': 'e3f585b550a280af23c98b6cb2be19fb_part5', | |
68 | 'ext': 'f4v', | |
69 | 'title': '名侦探柯南第752集', | |
70 | }, | |
71 | }, { | |
72 | 'md5': '9cb1e5c95da25dff0660c32ae50903b7', | |
73 | 'info_dict': { | |
74 | 'id': 'e3f585b550a280af23c98b6cb2be19fb_part6', | |
75 | 'ext': 'f4v', | |
76 | 'title': '名侦探柯南第752集', | |
77 | }, | |
78 | }, { | |
79 | 'md5': '155116e0ff1867bbc9b98df294faabc9', | |
80 | 'info_dict': { | |
81 | 'id': 'e3f585b550a280af23c98b6cb2be19fb_part7', | |
82 | 'ext': 'f4v', | |
83 | 'title': '名侦探柯南第752集', | |
84 | }, | |
85 | }, { | |
86 | 'md5': '53f5db77622ae14fa493ed2a278a082b', | |
87 | 'info_dict': { | |
88 | 'id': 'e3f585b550a280af23c98b6cb2be19fb_part8', | |
89 | 'ext': 'f4v', | |
90 | 'title': '名侦探柯南第752集', | |
91 | }, | |
92 | }], | |
93 | }] | |
605ec701 | 94 | |
7012620e | 95 | def construct_video_urls(self, data, video_id, _uuid): |
605ec701 P |
96 | def do_xor(x, y): |
97 | a = y % 3 | |
98 | if a == 1: | |
99 | return x ^ 121 | |
100 | if a == 2: | |
101 | return x ^ 72 | |
102 | return x ^ 103 | |
103 | ||
104 | def get_encode_code(l): | |
105 | a = 0 | |
106 | b = l.split('-') | |
107 | c = len(b) | |
108 | s = '' | |
109 | for i in range(c - 1, -1, -1): | |
f1da8610 | 110 | a = do_xor(int(b[c - i - 1], 16), i) |
605ec701 P |
111 | s += chr(a) |
112 | return s[::-1] | |
113 | ||
ffba4edb | 114 | def get_path_key(x, format_id, segment_index): |
605ec701 P |
115 | mg = ')(*&^flash@#$%a' |
116 | tm = self._download_json( | |
ffba4edb YCH |
117 | 'http://data.video.qiyi.com/t?tn=' + str(random.random()), video_id, |
118 | note='Download path key of segment %d for format %s' % (segment_index + 1, format_id) | |
119 | )['t'] | |
f1da8610 YCH |
120 | t = str(int(math.floor(int(tm) / (600.0)))) |
121 | return hashlib.md5((t + mg + x).encode('utf8')).hexdigest() | |
605ec701 P |
122 | |
123 | video_urls_dict = {} | |
ffba4edb YCH |
124 | for format_item in data['vp']['tkl'][0]['vs']: |
125 | if 0 < int(format_item['bid']) <= 10: | |
126 | format_id = self.get_format(format_item['bid']) | |
670861bd P |
127 | else: |
128 | continue | |
129 | ||
130 | video_urls = [] | |
605ec701 | 131 | |
ffba4edb YCH |
132 | video_urls_info = format_item['fs'] |
133 | if not format_item['fs'][0]['l'].startswith('/'): | |
134 | t = get_encode_code(format_item['fs'][0]['l']) | |
605ec701 | 135 | if t.endswith('mp4'): |
ffba4edb | 136 | video_urls_info = format_item['flvs'] |
605ec701 | 137 | |
ffba4edb YCH |
138 | for segment_index, segment in enumerate(video_urls_info): |
139 | vl = segment['l'] | |
605ec701 P |
140 | if not vl.startswith('/'): |
141 | vl = get_encode_code(vl) | |
142 | key = get_path_key( | |
ffba4edb YCH |
143 | vl.split('/')[-1].split('.')[0], format_id, segment_index) |
144 | filesize = segment['b'] | |
605ec701 P |
145 | base_url = data['vp']['du'].split('/') |
146 | base_url.insert(-1, key) | |
147 | base_url = '/'.join(base_url) | |
148 | param = { | |
149 | 'su': _uuid, | |
150 | 'qyid': uuid.uuid4().hex, | |
151 | 'client': '', | |
152 | 'z': '', | |
153 | 'bt': '', | |
154 | 'ct': '', | |
155 | 'tn': str(int(time.time())) | |
156 | } | |
157 | api_video_url = base_url + vl + '?' + \ | |
158 | compat_urllib_parse.urlencode(param) | |
ffba4edb YCH |
159 | js = self._download_json( |
160 | api_video_url, video_id, | |
161 | note='Download video info of segment %d for format %s' % (segment_index + 1, format_id)) | |
605ec701 P |
162 | video_url = js['l'] |
163 | video_urls.append( | |
164 | (video_url, filesize)) | |
165 | ||
166 | video_urls_dict[format_id] = video_urls | |
167 | return video_urls_dict | |
168 | ||
169 | def get_format(self, bid): | |
670861bd | 170 | _dict = { |
f1da8610 YCH |
171 | '1': 'h6', |
172 | '2': 'h5', | |
173 | '3': 'h4', | |
174 | '4': 'h3', | |
175 | '5': 'h2', | |
176 | '10': 'h1' | |
670861bd P |
177 | } |
178 | return _dict.get(str(bid), None) | |
179 | ||
180 | def get_bid(self, format_id): | |
181 | _dict = { | |
f1da8610 YCH |
182 | 'h6': '1', |
183 | 'h5': '2', | |
184 | 'h4': '3', | |
185 | 'h3': '4', | |
186 | 'h2': '5', | |
187 | 'h1': '10', | |
188 | 'best': 'best' | |
605ec701 | 189 | } |
670861bd | 190 | return _dict.get(format_id, None) |
605ec701 P |
191 | |
192 | def get_raw_data(self, tvid, video_id, enc_key, _uuid): | |
193 | tm = str(int(time.time())) | |
194 | param = { | |
195 | 'key': 'fvip', | |
196 | 'src': hashlib.md5(b'youtube-dl').hexdigest(), | |
197 | 'tvId': tvid, | |
198 | 'vid': video_id, | |
199 | 'vinfo': 1, | |
200 | 'tm': tm, | |
201 | 'enc': hashlib.md5( | |
202 | (enc_key + tm + tvid).encode('utf8')).hexdigest(), | |
203 | 'qyid': _uuid, | |
204 | 'tn': random.random(), | |
205 | 'um': 0, | |
206 | 'authkey': hashlib.md5( | |
207 | (tm + tvid).encode('utf8')).hexdigest() | |
208 | } | |
209 | ||
210 | api_url = 'http://cache.video.qiyi.com/vms' + '?' + \ | |
211 | compat_urllib_parse.urlencode(param) | |
212 | raw_data = self._download_json(api_url, video_id) | |
213 | return raw_data | |
214 | ||
215 | def get_enc_key(self, swf_url, video_id): | |
216 | req = self._request_webpage( | |
217 | swf_url, video_id, note='download swf content') | |
218 | cn = req.read() | |
219 | cn = zlib.decompress(cn[8:]) | |
220 | pt = re.compile(b'MixerRemote\x08(?P<enc_key>.+?)\$&vv') | |
221 | enc_key = self._search_regex(pt, cn, 'enc_key').decode('utf8') | |
222 | return enc_key | |
223 | ||
224 | def _real_extract(self, url): | |
225 | webpage = self._download_webpage( | |
226 | url, 'temp_id', note='download video page') | |
227 | tvid = self._search_regex( | |
29e7e078 | 228 | r'data-player-tvid\s*=\s*[\'"](\d+)', webpage, 'tvid') |
605ec701 | 229 | video_id = self._search_regex( |
29e7e078 | 230 | r'data-player-videoid\s*=\s*[\'"]([a-f\d]+)', webpage, 'video_id') |
605ec701 | 231 | swf_url = self._search_regex( |
29e7e078 | 232 | r'(http://.+?MainPlayer.+?\.swf)', webpage, 'swf player URL') |
605ec701 P |
233 | _uuid = uuid.uuid4().hex |
234 | ||
235 | enc_key = self.get_enc_key(swf_url, video_id) | |
236 | ||
237 | raw_data = self.get_raw_data(tvid, video_id, enc_key, _uuid) | |
aacda28b YCH |
238 | |
239 | if raw_data['code'] != 'A000000': | |
240 | raise ExtractorError('Unable to load data. Error code: ' + raw_data['code']) | |
241 | ||
605ec701 P |
242 | if not raw_data['data']['vp']['tkl']: |
243 | raise ExtractorError('No support iQiqy VIP video') | |
244 | ||
245 | data = raw_data['data'] | |
246 | ||
247 | title = data['vi']['vn'] | |
248 | ||
249 | # generate video_urls_dict | |
670861bd | 250 | video_urls_dict = self.construct_video_urls( |
7012620e | 251 | data, video_id, _uuid) |
605ec701 P |
252 | |
253 | # construct info | |
254 | entries = [] | |
255 | for format_id in video_urls_dict: | |
256 | video_urls = video_urls_dict[format_id] | |
257 | for i, video_url_info in enumerate(video_urls): | |
f1da8610 | 258 | if len(entries) < i + 1: |
605ec701 P |
259 | entries.append({'formats': []}) |
260 | entries[i]['formats'].append( | |
261 | { | |
262 | 'url': video_url_info[0], | |
263 | 'filesize': video_url_info[-1], | |
264 | 'format_id': format_id, | |
670861bd | 265 | 'preference': int(self.get_bid(format_id)) |
605ec701 P |
266 | } |
267 | ) | |
268 | ||
269 | for i in range(len(entries)): | |
670861bd | 270 | self._sort_formats(entries[i]['formats']) |
605ec701 P |
271 | entries[i].update( |
272 | { | |
c4ee8702 | 273 | 'id': '%s_part%d' % (video_id, i + 1), |
605ec701 P |
274 | 'title': title, |
275 | } | |
276 | ) | |
277 | ||
278 | if len(entries) > 1: | |
279 | info = { | |
280 | '_type': 'multi_video', | |
281 | 'id': video_id, | |
282 | 'title': title, | |
283 | 'entries': entries, | |
284 | } | |
285 | else: | |
286 | info = entries[0] | |
287 | info['id'] = video_id | |
288 | info['title'] = title | |
289 | ||
290 | return info |