[youku] update youku
[youtube-dl] / youtube_dl / extractor / youku.py
1 # coding: utf-8
2 from __future__ import unicode_literals
3
4 import re
5 import base64
6
7 from .common import InfoExtractor
8 from ..utils import ExtractorError
9
10 class YoukuIE(InfoExtractor):
11     IE_NAME = 'youku'
12     _VALID_URL = r'''(?x)
13         (?:
14             http://(?:v|player)\.youku\.com/(?:v_show/id_|player\.php/sid/)|
15             youku:)
16         (?P<id>[A-Za-z0-9]+)(?:\.html|/v\.swf|)
17     '''
18
19     _TEST = {
20             'url': 'http://v.youku.com/v_show/id_XMTc1ODE5Njcy.html',
21             'md5': '5f3af4192eabacc4501508d54a8cabd7',
22             'info_dict': {
23                 'id': 'XMTc1ODE5Njcy',
24                 'title': '★Smile﹗♡ Git Fresh -Booty Music舞蹈.',
25                 'ext': 'flv'
26             }
27     }
28
29     def construct_video_urls(self, data1, data2):
30         # get sid, token
31         def yk_t(s1, s2):
32             ls = list(range(256))
33             t = 0
34             for i in range(256):
35                 t = (t + ls[i] + ord(s1[i%len(s1)])) % 256
36                 ls[i], ls[t] = ls[t], ls[i]
37             s, x, y = '', 0, 0
38             for i in range(len(s2)):
39                 y = (y + 1) % 256
40                 x = (x + ls[y]) % 256
41                 ls[x], ls[y] = ls[y], ls[x]
42                 s += chr((s2[i] ^ ls[(ls[x]+ls[y]) % 256]))
43             return s
44
45         sid, token = yk_t(
46             'becaf9be', base64.b64decode(bytes(data2['ep'], 'ascii'))
47         ).split('_')
48
49         # get oip
50         oip = data2['ip']
51
52         # get fileid
53         string_ls = list(
54             'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/\:._-1234567890')
55         shuffled_string_ls = []
56         seed = data1['seed']
57         N = len(string_ls)
58         for ii in range(N):
59             seed = (seed * 0xd3 + 0x754f) % 0x10000
60             idx = seed * len(string_ls) // 0x10000
61             shuffled_string_ls.append(string_ls[idx])
62             del string_ls[idx]
63
64         fileid_dict = {}
65         for format in data1['streamtypes']:
66             streamfileid = [
67                 int(i) for i in data1['streamfileids'][format].strip('*').split('*')]
68             fileid = ''.join(
69                 [shuffled_string_ls[i] for i in streamfileid])
70             fileid_dict[format] = fileid[:8] + '%s' + fileid[10:]
71
72         def get_fileid(format, n):
73             fileid = fileid_dict[format] % hex(int(n))[2:].upper().zfill(2)
74             return fileid
75
76         # get ep
77         def generate_ep(format, n):
78             fileid = get_fileid(format, n)
79             ep_t = yk_t(
80                 'bf7e5f01',
81                 bytes('%s_%s_%s' % (sid, fileid, token), 'ascii'))
82             ep = base64.b64encode(bytes(ep_t, 'latin')).decode()
83             ep = ep.replace('+', '%2B')
84             ep = ep.replace('/', '%2F')
85             ep = ep.replace('=', '%2D')
86             return ep
87
88         # generate video_urls
89         video_urls_dict = {}
90         for format in data1['streamtypes']:
91             video_urls = []
92             for dt in data1['segs'][format]:
93                 n = str(int(dt['no']))
94                 video_url = \
95                     'http://k.youku.com/player/getFlvPath/' + \
96                     'sid/' + sid + \
97                     '_' + str(int(n)+1).zfill(2) + \
98                     '/st/' + self.parse_ext_l(format) + \
99                     '/fileid/' + get_fileid(format, n)  + '?' + \
100                     'K=' + str(dt['k']) + \
101                     '&hd=' + self.get_hd(format) + \
102                     '&myp=0' + \
103                     '&ts=' + str(dt['seconds']) + \
104                     '&ypp=0&ctype=12&ev=1' + \
105                     '&token=' + str(token) + \
106                     '&oip=' + str(oip) + \
107                     '&ep=' + generate_ep(format, n)
108                 video_urls.append(video_url)
109             video_urls_dict[format] = video_urls
110
111         return video_urls_dict
112
113     def get_hd(self, fm):
114         hd_id_dict = {
115             'flv': '0',
116             'mp4': '1',
117             'hd2': '2',
118             'hd3': '3',
119             '3gp': '0',
120             '3gphd': '1'
121         }
122         return hd_id_dict[fm]
123
124     def parse_ext_l(self, fm):
125         ext_dict = {
126             'flv': 'flv',
127             'mp4': 'mp4',
128             'hd2': 'flv',
129             'hd3': 'flv',
130             '3gp': 'flv',
131             '3gphd': 'mp4',
132         }
133         return ext_dict[fm]
134
135     def _real_extract(self, url):
136         mobj = re.match(self._VALID_URL, url)
137         video_id = mobj.group('id')
138
139         # request basic data
140         data1_url = 'http://v.youku.com/player/getPlayList/VideoIDS/%s' % video_id
141         data2_url = 'http://v.youku.com/player/getPlayList/VideoIDS/%s/Pf/4/ctype/12/ev/1' % video_id
142
143         raw_data1 = self._download_json(data1_url, video_id)
144         raw_data2 = self._download_json(data2_url, video_id)
145         data1 = raw_data1['data'][0]
146         data2 = raw_data2['data'][0]
147
148         error_code = data1.get('error_code')
149         if error_code:
150             # -8 means blocked outside China.
151             # Chinese and English, separated by newline.
152             error = data1.get('error')
153             raise ExtractorError(
154                 error or 'Server reported error %i' %
155                 error_code,
156                 expected=True)
157
158         title = data1['title']
159
160         # generate video_urls_dict
161         video_urls_dict = self.construct_video_urls(data1, data2)
162
163         # construct info
164         entries = []
165         for fm in data1['streamtypes']:
166             #formats = []
167             video_urls = video_urls_dict[fm]
168             for i in range(len(video_urls)):
169                 if len(entries) < i+1:
170                     entries.append({'formats': []})
171                 entries[i]['formats'].append(
172                     {
173                         'url': video_urls[i],
174                         'format_id': fm,
175                         'ext': self.parse_ext_l(fm),
176                         'filesize': int(data1['segs'][fm][i]['size'])
177                     }
178                 )
179
180         for i in range(len(entries)):
181             entries[i].update(
182                 {
183                     'id': '_part%d' % (i+1),
184                     'title': title,
185                 }
186             )
187
188         if len(entries) > 1:
189             info = {
190                 '_type': 'multi_video',
191                 'id': video_id,
192                 'title': title,
193                 'entries': entries,
194             }
195         else:
196             info = entries[0]
197             info['id'] = video_id
198
199         return info