[vk] Handle access denied error
[youtube-dl] / youtube_dl / extractor / vk.py
1 # encoding: utf-8
2 from __future__ import unicode_literals
3
4 import re
5 import json
6
7 from .common import InfoExtractor
8 from ..compat import (
9     compat_str,
10     compat_urllib_parse,
11     compat_urllib_request,
12 )
13 from ..utils import (
14     ExtractorError,
15     orderedSet,
16     str_to_int,
17     unescapeHTML,
18     unified_strdate,
19 )
20
21
22 class VKIE(InfoExtractor):
23     IE_NAME = 'vk.com'
24     _VALID_URL = r'''(?x)
25                     https?://
26                         (?:
27                             (?:m\.)?vk\.com/video_ext\.php\?.*?\boid=(?P<oid>-?\d+).*?\bid=(?P<id>\d+)|
28                             (?:
29                                 (?:m\.)?vk\.com/(?:.+?\?.*?z=)?video|
30                                 (?:www\.)?biqle\.ru/watch/
31                             )
32                             (?P<videoid>[^s].*?)(?:\?(?:.*\blist=(?P<list_id>[\da-f]+))?|%2F|$)
33                         )
34                     '''
35     _NETRC_MACHINE = 'vk'
36
37     _TESTS = [
38         {
39             'url': 'http://vk.com/videos-77521?z=video-77521_162222515%2Fclub77521',
40             'md5': '0deae91935c54e00003c2a00646315f0',
41             'info_dict': {
42                 'id': '162222515',
43                 'ext': 'flv',
44                 'title': 'ProtivoGunz - Хуёвая песня',
45                 'uploader': 're:(?:Noize MC|Alexander Ilyashenko).*',
46                 'duration': 195,
47                 'upload_date': '20120212',
48                 'view_count': int,
49             },
50         },
51         {
52             'url': 'http://vk.com/video205387401_165548505',
53             'md5': '6c0aeb2e90396ba97035b9cbde548700',
54             'info_dict': {
55                 'id': '165548505',
56                 'ext': 'mp4',
57                 'uploader': 'Tom Cruise',
58                 'title': 'No name',
59                 'duration': 9,
60                 'upload_date': '20130721',
61                 'view_count': int,
62             }
63         },
64         {
65             'note': 'Embedded video',
66             'url': 'http://vk.com/video_ext.php?oid=32194266&id=162925554&hash=7d8c2e0d5e05aeaa&hd=1',
67             'md5': 'c7ce8f1f87bec05b3de07fdeafe21a0a',
68             'info_dict': {
69                 'id': '162925554',
70                 'ext': 'mp4',
71                 'uploader': 'Vladimir Gavrin',
72                 'title': 'Lin Dan',
73                 'duration': 101,
74                 'upload_date': '20120730',
75                 'view_count': int,
76             }
77         },
78         {
79             # VIDEO NOW REMOVED
80             # please update if you find a video whose URL follows the same pattern
81             'url': 'http://vk.com/video-8871596_164049491',
82             'md5': 'a590bcaf3d543576c9bd162812387666',
83             'note': 'Only available for registered users',
84             'info_dict': {
85                 'id': '164049491',
86                 'ext': 'mp4',
87                 'uploader': 'Триллеры',
88                 'title': '► Бойцовский клуб / Fight Club 1999 [HD 720]',
89                 'duration': 8352,
90                 'upload_date': '20121218',
91                 'view_count': int,
92             },
93             'skip': 'Requires vk account credentials',
94         },
95         {
96             'url': 'http://vk.com/hd_kino_mania?z=video-43215063_168067957%2F15c66b9b533119788d',
97             'md5': '4d7a5ef8cf114dfa09577e57b2993202',
98             'info_dict': {
99                 'id': '168067957',
100                 'ext': 'mp4',
101                 'uploader': 'Киномания - лучшее из мира кино',
102                 'title': ' ',
103                 'duration': 7291,
104                 'upload_date': '20140328',
105             },
106             'skip': 'Requires vk account credentials',
107         },
108         {
109             'url': 'http://m.vk.com/video-43215063_169084319?list=125c627d1aa1cebb83&from=wall-43215063_2566540',
110             'md5': '0c45586baa71b7cb1d0784ee3f4e00a6',
111             'note': 'ivi.ru embed',
112             'info_dict': {
113                 'id': '60690',
114                 'ext': 'mp4',
115                 'title': 'Книга Илая',
116                 'duration': 6771,
117                 'upload_date': '20140626',
118                 'view_count': int,
119             },
120             'skip': 'Only works from Russia',
121         },
122         {
123             # video (removed?) only available with list id
124             'url': 'https://vk.com/video30481095_171201961?list=8764ae2d21f14088d4',
125             'md5': '091287af5402239a1051c37ec7b92913',
126             'info_dict': {
127                 'id': '171201961',
128                 'ext': 'mp4',
129                 'title': 'ТюменцевВВ_09.07.2015',
130                 'uploader': 'Anton Ivanov',
131                 'duration': 109,
132                 'upload_date': '20150709',
133                 'view_count': int,
134             },
135         },
136         {
137             # youtube embed
138             'url': 'https://vk.com/video276849682_170681728',
139             'info_dict': {
140                 'id': 'V3K4mi0SYkc',
141                 'ext': 'mp4',
142                 'title': "DSWD Awards 'Children's Joy Foundation, Inc.' Certificate of Registration and License to Operate",
143                 'description': 'md5:bf9c26cfa4acdfb146362682edd3827a',
144                 'duration': 179,
145                 'upload_date': '20130116',
146                 'uploader': "Children's Joy Foundation",
147                 'uploader_id': 'thecjf',
148                 'view_count': int,
149             },
150         },
151         {
152             # removed video, just testing that we match the pattern
153             'url': 'http://vk.com/feed?z=video-43215063_166094326%2Fbb50cacd3177146d7a',
154             'only_matching': True,
155         },
156         {
157             # vk wrapper
158             'url': 'http://www.biqle.ru/watch/847655_160197695',
159             'only_matching': True,
160         }
161     ]
162
163     def _login(self):
164         (username, password) = self._get_login_info()
165         if username is None:
166             return
167
168         login_page = self._download_webpage(
169             'https://vk.com', None, 'Downloading login page')
170
171         login_form = self._form_hidden_inputs(login_page)
172
173         login_form.update({
174             'email': username.encode('cp1251'),
175             'pass': password.encode('cp1251'),
176         })
177
178         request = compat_urllib_request.Request(
179             'https://login.vk.com/?act=login',
180             compat_urllib_parse.urlencode(login_form).encode('utf-8'))
181         login_page = self._download_webpage(
182             request, None, note='Logging in as %s' % username)
183
184         if re.search(r'onLoginFailed', login_page):
185             raise ExtractorError(
186                 'Unable to login, incorrect username and/or password', expected=True)
187
188     def _real_initialize(self):
189         self._login()
190
191     def _real_extract(self, url):
192         mobj = re.match(self._VALID_URL, url)
193         video_id = mobj.group('videoid')
194
195         if not video_id:
196             video_id = '%s_%s' % (mobj.group('oid'), mobj.group('id'))
197
198         info_url = 'https://vk.com/al_video.php?act=show&al=1&module=video&video=%s' % video_id
199
200         # Some videos (removed?) can only be downloaded with list id specified
201         list_id = mobj.group('list_id')
202         if list_id:
203             info_url += '&list=%s' % list_id
204
205         info_page = self._download_webpage(info_url, video_id)
206
207         if re.search(r'<!>/login\.php\?.*\bact=security_check', info_page):
208             raise ExtractorError(
209                 'You are trying to log in from an unusual location. You should confirm ownership at vk.com to log in with this IP.',
210                 expected=True)
211
212         ERRORS = {
213             r'>Видеозапись .*? была изъята из публичного доступа в связи с обращением правообладателя.<':
214             'Video %s has been removed from public access due to rightholder complaint.',
215
216             r'<!>Please log in or <':
217             'Video %s is only available for registered users, '
218             'use --username and --password options to provide account credentials.',
219
220             r'<!>Unknown error':
221             'Video %s does not exist.',
222
223             r'<!>Видео временно недоступно':
224             'Video %s is temporarily unavailable.',
225
226             r'<!>Access denied':
227             'Access denied to video %s.',
228         }
229
230         for error_re, error_msg in ERRORS.items():
231             if re.search(error_re, info_page):
232                 raise ExtractorError(error_msg % video_id, expected=True)
233
234         youtube_url = self._search_regex(
235             r'<iframe[^>]+src="((?:https?:)?//www.youtube.com/embed/[^"]+)"',
236             info_page, 'youtube iframe', default=None)
237         if youtube_url:
238             return self.url_result(youtube_url, 'Youtube')
239
240         m_rutube = re.search(
241             r'\ssrc="((?:https?:)?//rutube\.ru\\?/video\\?/embed(?:.*?))\\?"', info_page)
242         if m_rutube is not None:
243             self.to_screen('rutube video detected')
244             rutube_url = self._proto_relative_url(
245                 m_rutube.group(1).replace('\\', ''))
246             return self.url_result(rutube_url)
247
248         m_opts = re.search(r'(?s)var\s+opts\s*=\s*({.+?});', info_page)
249         if m_opts:
250             m_opts_url = re.search(r"url\s*:\s*'((?!/\b)[^']+)", m_opts.group(1))
251             if m_opts_url:
252                 opts_url = m_opts_url.group(1)
253                 if opts_url.startswith('//'):
254                     opts_url = 'http:' + opts_url
255                 return self.url_result(opts_url)
256
257         data_json = self._search_regex(r'var\s+vars\s*=\s*({.+?});', info_page, 'vars')
258         data = json.loads(data_json)
259
260         # Extract upload date
261         upload_date = None
262         mobj = re.search(r'id="mv_date(?:_views)?_wrap"[^>]*>([a-zA-Z]+ [0-9]+), ([0-9]+) at', info_page)
263         if mobj is not None:
264             mobj.group(1) + ' ' + mobj.group(2)
265             upload_date = unified_strdate(mobj.group(1) + ' ' + mobj.group(2))
266
267         view_count = str_to_int(self._search_regex(
268             r'"mv_views_count_number"[^>]*>([\d,.]+) views<',
269             info_page, 'view count', fatal=False))
270
271         formats = [{
272             'format_id': k,
273             'url': v,
274             'width': int(k[len('url'):]),
275         } for k, v in data.items()
276             if k.startswith('url')]
277         self._sort_formats(formats)
278
279         return {
280             'id': compat_str(data['vid']),
281             'formats': formats,
282             'title': unescapeHTML(data['md_title']),
283             'thumbnail': data.get('jpg'),
284             'uploader': data.get('md_author'),
285             'duration': data.get('duration'),
286             'upload_date': upload_date,
287             'view_count': view_count,
288         }
289
290
291 class VKUserVideosIE(InfoExtractor):
292     IE_NAME = 'vk.com:user-videos'
293     IE_DESC = 'vk.com:All of a user\'s videos'
294     _VALID_URL = r'https?://vk\.com/videos(?P<id>[0-9]+)(?:m\?.*)?'
295     _TEMPLATE_URL = 'https://vk.com/videos'
296     _TEST = {
297         'url': 'http://vk.com/videos205387401',
298         'info_dict': {
299             'id': '205387401',
300         },
301         'playlist_mincount': 4,
302     }
303
304     def _real_extract(self, url):
305         page_id = self._match_id(url)
306         page = self._download_webpage(url, page_id)
307         video_ids = orderedSet(
308             m.group(1) for m in re.finditer(r'href="/video([0-9_]+)"', page))
309         url_entries = [
310             self.url_result(
311                 'http://vk.com/video' + video_id, 'VK', video_id=video_id)
312             for video_id in video_ids]
313         return self.playlist_result(url_entries, page_id)