]>
Commit | Line | Data |
---|---|---|
ddbd9035 | 1 | # coding: utf-8 |
8a32b82e PH |
2 | from __future__ import unicode_literals |
3 | ||
f9355dc9 | 4 | import base64 |
9c286cfa PH |
5 | |
6 | from .common import InfoExtractor | |
f9355dc9 | 7 | from ..utils import ExtractorError |
9c286cfa | 8 | |
c203be3f YCH |
9 | from ..compat import ( |
10 | compat_urllib_parse, | |
11 | compat_ord, | |
5228b756 | 12 | compat_urllib_request, |
c203be3f | 13 | ) |
1498940b | 14 | |
aed473cc | 15 | |
9c286cfa | 16 | class YoukuIE(InfoExtractor): |
f9355dc9 | 17 | IE_NAME = 'youku' |
246995db | 18 | IE_DESC = '优酷' |
8a32b82e PH |
19 | _VALID_URL = r'''(?x) |
20 | (?: | |
21 | http://(?:v|player)\.youku\.com/(?:v_show/id_|player\.php/sid/)| | |
22 | youku:) | |
23 | (?P<id>[A-Za-z0-9]+)(?:\.html|/v\.swf|) | |
24 | ''' | |
f9355dc9 | 25 | |
ee697992 | 26 | _TESTS = [{ |
aed473cc YCH |
27 | 'url': 'http://v.youku.com/v_show/id_XMTc1ODE5Njcy.html', |
28 | 'md5': '5f3af4192eabacc4501508d54a8cabd7', | |
29 | 'info_dict': { | |
f1e66cb2 | 30 | 'id': 'XMTc1ODE5Njcy_part1', |
aed473cc YCH |
31 | 'title': '★Smile﹗♡ Git Fresh -Booty Music舞蹈.', |
32 | 'ext': 'flv' | |
33 | } | |
ee697992 YCH |
34 | }, { |
35 | 'url': 'http://player.youku.com/player.php/sid/XNDgyMDQ2NTQw/v.swf', | |
36 | 'only_matching': True, | |
f1e66cb2 YCH |
37 | }, { |
38 | 'url': 'http://v.youku.com/v_show/id_XODgxNjg1Mzk2_ev_1.html', | |
39 | 'info_dict': { | |
40 | 'id': 'XODgxNjg1Mzk2', | |
41 | 'title': '武媚娘传奇 85', | |
42 | }, | |
43 | 'playlist_count': 11, | |
5228b756 YCH |
44 | }, { |
45 | 'url': 'http://v.youku.com/v_show/id_XMTI1OTczNDM5Mg==.html', | |
46 | 'info_dict': { | |
47 | 'id': 'XMTI1OTczNDM5Mg', | |
48 | 'title': '花千骨 04', | |
49 | }, | |
50 | 'playlist_count': 13, | |
51 | 'skip': 'Available in China only', | |
33eae08f P |
52 | }, { |
53 | 'url': 'http://v.youku.com/v_show/id_XNjA1NzA2Njgw.html', | |
54 | 'note': 'Video protected with password', | |
55 | 'info_dict': { | |
56 | 'id': 'XNjA1NzA2Njgw', | |
5ddc127d | 57 | 'title': '邢義田复旦讲座之想象中的胡人—从“左衽孔子”说起', |
33eae08f | 58 | }, |
cd5d7542 | 59 | 'playlist_count': 19, |
33eae08f P |
60 | 'params': { |
61 | 'videopassword': '100600', | |
62 | }, | |
ee697992 | 63 | }] |
67f51b3d | 64 | |
f9355dc9 P |
65 | def construct_video_urls(self, data1, data2): |
66 | # get sid, token | |
67 | def yk_t(s1, s2): | |
68 | ls = list(range(256)) | |
69 | t = 0 | |
70 | for i in range(256): | |
c203be3f | 71 | t = (t + ls[i] + compat_ord(s1[i % len(s1)])) % 256 |
f9355dc9 | 72 | ls[i], ls[t] = ls[t], ls[i] |
c203be3f | 73 | s = bytearray() |
ca452466 | 74 | x, y = 0, 0 |
f9355dc9 P |
75 | for i in range(len(s2)): |
76 | y = (y + 1) % 256 | |
77 | x = (x + ls[y]) % 256 | |
78 | ls[x], ls[y] = ls[y], ls[x] | |
c203be3f YCH |
79 | s.append(compat_ord(s2[i]) ^ ls[(ls[x] + ls[y]) % 256]) |
80 | return bytes(s) | |
f9355dc9 P |
81 | |
82 | sid, token = yk_t( | |
c203be3f YCH |
83 | b'becaf9be', base64.b64decode(data2['ep'].encode('ascii')) |
84 | ).decode('ascii').split('_') | |
f9355dc9 P |
85 | |
86 | # get oip | |
87 | oip = data2['ip'] | |
88 | ||
89 | # get fileid | |
90 | string_ls = list( | |
91 | 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/\:._-1234567890') | |
92 | shuffled_string_ls = [] | |
93 | seed = data1['seed'] | |
94 | N = len(string_ls) | |
95 | for ii in range(N): | |
96 | seed = (seed * 0xd3 + 0x754f) % 0x10000 | |
97 | idx = seed * len(string_ls) // 0x10000 | |
98 | shuffled_string_ls.append(string_ls[idx]) | |
99 | del string_ls[idx] | |
100 | ||
101 | fileid_dict = {} | |
102 | for format in data1['streamtypes']: | |
103 | streamfileid = [ | |
104 | int(i) for i in data1['streamfileids'][format].strip('*').split('*')] | |
105 | fileid = ''.join( | |
106 | [shuffled_string_ls[i] for i in streamfileid]) | |
107 | fileid_dict[format] = fileid[:8] + '%s' + fileid[10:] | |
108 | ||
109 | def get_fileid(format, n): | |
110 | fileid = fileid_dict[format] % hex(int(n))[2:].upper().zfill(2) | |
111 | return fileid | |
112 | ||
113 | # get ep | |
114 | def generate_ep(format, n): | |
115 | fileid = get_fileid(format, n) | |
116 | ep_t = yk_t( | |
c203be3f YCH |
117 | b'bf7e5f01', |
118 | ('%s_%s_%s' % (sid, fileid, token)).encode('ascii') | |
ca452466 | 119 | ) |
c203be3f | 120 | ep = base64.b64encode(ep_t).decode('ascii') |
f9355dc9 P |
121 | return ep |
122 | ||
123 | # generate video_urls | |
124 | video_urls_dict = {} | |
125 | for format in data1['streamtypes']: | |
126 | video_urls = [] | |
127 | for dt in data1['segs'][format]: | |
128 | n = str(int(dt['no'])) | |
1498940b P |
129 | param = { |
130 | 'K': dt['k'], | |
131 | 'hd': self.get_hd(format), | |
132 | 'myp': 0, | |
133 | 'ts': dt['seconds'], | |
134 | 'ypp': 0, | |
135 | 'ctype': 12, | |
136 | 'ev': 1, | |
137 | 'token': token, | |
138 | 'oip': oip, | |
139 | 'ep': generate_ep(format, n) | |
140 | } | |
f9355dc9 P |
141 | video_url = \ |
142 | 'http://k.youku.com/player/getFlvPath/' + \ | |
143 | 'sid/' + sid + \ | |
aed473cc | 144 | '_' + str(int(n) + 1).zfill(2) + \ |
f9355dc9 | 145 | '/st/' + self.parse_ext_l(format) + \ |
aed473cc | 146 | '/fileid/' + get_fileid(format, n) + '?' + \ |
1498940b | 147 | compat_urllib_parse.urlencode(param) |
f9355dc9 P |
148 | video_urls.append(video_url) |
149 | video_urls_dict[format] = video_urls | |
150 | ||
151 | return video_urls_dict | |
152 | ||
153 | def get_hd(self, fm): | |
154 | hd_id_dict = { | |
aed473cc YCH |
155 | 'flv': '0', |
156 | 'mp4': '1', | |
157 | 'hd2': '2', | |
158 | 'hd3': '3', | |
159 | '3gp': '0', | |
160 | '3gphd': '1' | |
f9355dc9 P |
161 | } |
162 | return hd_id_dict[fm] | |
163 | ||
164 | def parse_ext_l(self, fm): | |
165 | ext_dict = { | |
aed473cc YCH |
166 | 'flv': 'flv', |
167 | 'mp4': 'mp4', | |
168 | 'hd2': 'flv', | |
169 | 'hd3': 'flv', | |
170 | '3gp': 'flv', | |
171 | '3gphd': 'mp4' | |
f9355dc9 P |
172 | } |
173 | return ext_dict[fm] | |
9c286cfa | 174 | |
08f7db20 P |
175 | def get_format_name(self, fm): |
176 | _dict = { | |
aed473cc YCH |
177 | '3gp': 'h6', |
178 | '3gphd': 'h5', | |
179 | 'flv': 'h4', | |
180 | 'mp4': 'h3', | |
181 | 'hd2': 'h2', | |
182 | 'hd3': 'h1' | |
08f7db20 P |
183 | } |
184 | return _dict[fm] | |
185 | ||
9c286cfa | 186 | def _real_extract(self, url): |
9383e66f | 187 | video_id = self._match_id(url) |
9c286cfa | 188 | |
5228b756 YCH |
189 | def retrieve_data(req_url, note): |
190 | req = compat_urllib_request.Request(req_url) | |
9c286cfa | 191 | |
5228b756 YCH |
192 | cn_verification_proxy = self._downloader.params.get('cn_verification_proxy') |
193 | if cn_verification_proxy: | |
194 | req.add_header('Ytdl-request-proxy', cn_verification_proxy) | |
195 | ||
196 | raw_data = self._download_json(req, video_id, note=note) | |
197 | return raw_data['data'][0] | |
198 | ||
33eae08f P |
199 | video_password = self._downloader.params.get('videopassword', None) |
200 | ||
5228b756 | 201 | # request basic data |
cb3d2eb9 | 202 | basic_data_url = 'http://v.youku.com/player/getPlayList/VideoIDS/%s' % video_id |
33eae08f | 203 | if video_password: |
5ddc127d | 204 | basic_data_url += '?password=%s' % video_password |
cb3d2eb9 P |
205 | |
206 | data1 = retrieve_data( | |
207 | basic_data_url, | |
208 | 'Downloading JSON metadata 1') | |
5228b756 YCH |
209 | data2 = retrieve_data( |
210 | 'http://v.youku.com/player/getPlayList/VideoIDS/%s/Pf/4/ctype/12/ev/1' % video_id, | |
211 | 'Downloading JSON metadata 2') | |
8a32b82e | 212 | |
f9355dc9 | 213 | error_code = data1.get('error_code') |
8a32b82e | 214 | if error_code: |
f9355dc9 | 215 | error = data1.get('error') |
04e75966 YCH |
216 | if error is not None and '因版权原因无法观看此视频' in error: |
217 | raise ExtractorError( | |
218 | 'Youku said: Sorry, this video is available in China only', expected=True) | |
219 | else: | |
220 | msg = 'Youku server reported error %i' % error_code | |
221 | if error is not None: | |
222 | msg += ': ' + error | |
223 | raise ExtractorError(msg) | |
f9355dc9 P |
224 | |
225 | title = data1['title'] | |
226 | ||
227 | # generate video_urls_dict | |
228 | video_urls_dict = self.construct_video_urls(data1, data2) | |
229 | ||
230 | # construct info | |
f3aecb27 JMF |
231 | entries = [{ |
232 | 'id': '%s_part%d' % (video_id, i + 1), | |
233 | 'title': title, | |
234 | 'formats': [], | |
235 | # some formats are not available for all parts, we have to detect | |
236 | # which one has all | |
237 | } for i in range(max(len(v) for v in data1['segs'].values()))] | |
f9355dc9 | 238 | for fm in data1['streamtypes']: |
f9355dc9 | 239 | video_urls = video_urls_dict[fm] |
f3aecb27 JMF |
240 | for video_url, seg, entry in zip(video_urls, data1['segs'][fm], entries): |
241 | entry['formats'].append({ | |
242 | 'url': video_url, | |
a155b7e7 YCH |
243 | 'format_id': self.get_format_name(fm), |
244 | 'ext': self.parse_ext_l(fm), | |
f3aecb27 | 245 | 'filesize': int(seg['size']), |
a155b7e7 | 246 | }) |
f9355dc9 | 247 | |
f1e66cb2 YCH |
248 | return { |
249 | '_type': 'multi_video', | |
250 | 'id': video_id, | |
251 | 'title': title, | |
252 | 'entries': entries, | |
253 | } |