|
|
@ -6,11 +6,18 @@ import json
|
|
|
|
import sys
|
|
|
|
import sys
|
|
|
|
|
|
|
|
|
|
|
|
from .common import InfoExtractor
|
|
|
|
from .common import InfoExtractor
|
|
|
|
from ..compat import compat_str
|
|
|
|
from ..compat import (
|
|
|
|
|
|
|
|
compat_str,
|
|
|
|
|
|
|
|
compat_urlparse,
|
|
|
|
|
|
|
|
)
|
|
|
|
from ..utils import (
|
|
|
|
from ..utils import (
|
|
|
|
|
|
|
|
clean_html,
|
|
|
|
ExtractorError,
|
|
|
|
ExtractorError,
|
|
|
|
|
|
|
|
get_element_by_class,
|
|
|
|
int_or_none,
|
|
|
|
int_or_none,
|
|
|
|
orderedSet,
|
|
|
|
orderedSet,
|
|
|
|
|
|
|
|
parse_duration,
|
|
|
|
|
|
|
|
remove_start,
|
|
|
|
str_to_int,
|
|
|
|
str_to_int,
|
|
|
|
unescapeHTML,
|
|
|
|
unescapeHTML,
|
|
|
|
unified_strdate,
|
|
|
|
unified_strdate,
|
|
|
@ -20,7 +27,54 @@ from .vimeo import VimeoIE
|
|
|
|
from .pladform import PladformIE
|
|
|
|
from .pladform import PladformIE
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class VKIE(InfoExtractor):
|
|
|
|
class VKBaseIE(InfoExtractor):
|
|
|
|
|
|
|
|
_NETRC_MACHINE = 'vk'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _login(self):
|
|
|
|
|
|
|
|
(username, password) = self._get_login_info()
|
|
|
|
|
|
|
|
if username is None:
|
|
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
login_page, url_handle = self._download_webpage_handle(
|
|
|
|
|
|
|
|
'https://vk.com', None, 'Downloading login page')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
login_form = self._hidden_inputs(login_page)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
login_form.update({
|
|
|
|
|
|
|
|
'email': username.encode('cp1251'),
|
|
|
|
|
|
|
|
'pass': password.encode('cp1251'),
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# https://new.vk.com/ serves two same remixlhk cookies in Set-Cookie header
|
|
|
|
|
|
|
|
# and expects the first one to be set rather than second (see
|
|
|
|
|
|
|
|
# https://github.com/rg3/youtube-dl/issues/9841#issuecomment-227871201).
|
|
|
|
|
|
|
|
# As of RFC6265 the newer one cookie should be set into cookie store
|
|
|
|
|
|
|
|
# what actually happens.
|
|
|
|
|
|
|
|
# We will workaround this VK issue by resetting the remixlhk cookie to
|
|
|
|
|
|
|
|
# the first one manually.
|
|
|
|
|
|
|
|
cookies = url_handle.headers.get('Set-Cookie')
|
|
|
|
|
|
|
|
if sys.version_info[0] >= 3:
|
|
|
|
|
|
|
|
cookies = cookies.encode('iso-8859-1')
|
|
|
|
|
|
|
|
cookies = cookies.decode('utf-8')
|
|
|
|
|
|
|
|
remixlhk = re.search(r'remixlhk=(.+?);.*?\bdomain=(.+?)(?:[,;]|$)', cookies)
|
|
|
|
|
|
|
|
if remixlhk:
|
|
|
|
|
|
|
|
value, domain = remixlhk.groups()
|
|
|
|
|
|
|
|
self._set_cookie(domain, 'remixlhk', value)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
login_page = self._download_webpage(
|
|
|
|
|
|
|
|
'https://login.vk.com/?act=login', None,
|
|
|
|
|
|
|
|
note='Logging in as %s' % username,
|
|
|
|
|
|
|
|
data=urlencode_postdata(login_form))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if re.search(r'onLoginFailed', login_page):
|
|
|
|
|
|
|
|
raise ExtractorError(
|
|
|
|
|
|
|
|
'Unable to login, incorrect username and/or password', expected=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _real_initialize(self):
|
|
|
|
|
|
|
|
self._login()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class VKIE(VKBaseIE):
|
|
|
|
IE_NAME = 'vk'
|
|
|
|
IE_NAME = 'vk'
|
|
|
|
IE_DESC = 'VK'
|
|
|
|
IE_DESC = 'VK'
|
|
|
|
_VALID_URL = r'''(?x)
|
|
|
|
_VALID_URL = r'''(?x)
|
|
|
@ -38,8 +92,6 @@ class VKIE(InfoExtractor):
|
|
|
|
(?P<videoid>-?\d+_\d+)(?:.*\blist=(?P<list_id>[\da-f]+))?
|
|
|
|
(?P<videoid>-?\d+_\d+)(?:.*\blist=(?P<list_id>[\da-f]+))?
|
|
|
|
)
|
|
|
|
)
|
|
|
|
'''
|
|
|
|
'''
|
|
|
|
_NETRC_MACHINE = 'vk'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
_TESTS = [
|
|
|
|
_TESTS = [
|
|
|
|
{
|
|
|
|
{
|
|
|
|
'url': 'http://vk.com/videos-77521?z=video-77521_162222515%2Fclub77521',
|
|
|
|
'url': 'http://vk.com/videos-77521?z=video-77521_162222515%2Fclub77521',
|
|
|
@ -189,49 +241,6 @@ class VKIE(InfoExtractor):
|
|
|
|
}
|
|
|
|
}
|
|
|
|
]
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
def _login(self):
|
|
|
|
|
|
|
|
(username, password) = self._get_login_info()
|
|
|
|
|
|
|
|
if username is None:
|
|
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
login_page, url_handle = self._download_webpage_handle(
|
|
|
|
|
|
|
|
'https://vk.com', None, 'Downloading login page')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
login_form = self._hidden_inputs(login_page)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
login_form.update({
|
|
|
|
|
|
|
|
'email': username.encode('cp1251'),
|
|
|
|
|
|
|
|
'pass': password.encode('cp1251'),
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# https://new.vk.com/ serves two same remixlhk cookies in Set-Cookie header
|
|
|
|
|
|
|
|
# and expects the first one to be set rather than second (see
|
|
|
|
|
|
|
|
# https://github.com/rg3/youtube-dl/issues/9841#issuecomment-227871201).
|
|
|
|
|
|
|
|
# As of RFC6265 the newer one cookie should be set into cookie store
|
|
|
|
|
|
|
|
# what actually happens.
|
|
|
|
|
|
|
|
# We will workaround this VK issue by resetting the remixlhk cookie to
|
|
|
|
|
|
|
|
# the first one manually.
|
|
|
|
|
|
|
|
cookies = url_handle.headers.get('Set-Cookie')
|
|
|
|
|
|
|
|
if sys.version_info[0] >= 3:
|
|
|
|
|
|
|
|
cookies = cookies.encode('iso-8859-1')
|
|
|
|
|
|
|
|
cookies = cookies.decode('utf-8')
|
|
|
|
|
|
|
|
remixlhk = re.search(r'remixlhk=(.+?);.*?\bdomain=(.+?)(?:[,;]|$)', cookies)
|
|
|
|
|
|
|
|
if remixlhk:
|
|
|
|
|
|
|
|
value, domain = remixlhk.groups()
|
|
|
|
|
|
|
|
self._set_cookie(domain, 'remixlhk', value)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
login_page = self._download_webpage(
|
|
|
|
|
|
|
|
'https://login.vk.com/?act=login', None,
|
|
|
|
|
|
|
|
note='Logging in as %s' % username,
|
|
|
|
|
|
|
|
data=urlencode_postdata(login_form))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if re.search(r'onLoginFailed', login_page):
|
|
|
|
|
|
|
|
raise ExtractorError(
|
|
|
|
|
|
|
|
'Unable to login, incorrect username and/or password', expected=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _real_initialize(self):
|
|
|
|
|
|
|
|
self._login()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _real_extract(self, url):
|
|
|
|
def _real_extract(self, url):
|
|
|
|
mobj = re.match(self._VALID_URL, url)
|
|
|
|
mobj = re.match(self._VALID_URL, url)
|
|
|
|
video_id = mobj.group('videoid')
|
|
|
|
video_id = mobj.group('videoid')
|
|
|
@ -355,7 +364,7 @@ class VKIE(InfoExtractor):
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class VKUserVideosIE(InfoExtractor):
|
|
|
|
class VKUserVideosIE(VKBaseIE):
|
|
|
|
IE_NAME = 'vk:uservideos'
|
|
|
|
IE_NAME = 'vk:uservideos'
|
|
|
|
IE_DESC = "VK - User's Videos"
|
|
|
|
IE_DESC = "VK - User's Videos"
|
|
|
|
_VALID_URL = r'https?://(?:(?:m|new)\.)?vk\.com/videos(?P<id>-?[0-9]+)(?!\?.*\bz=video)(?:[/?#&]|$)'
|
|
|
|
_VALID_URL = r'https?://(?:(?:m|new)\.)?vk\.com/videos(?P<id>-?[0-9]+)(?!\?.*\bz=video)(?:[/?#&]|$)'
|
|
|
@ -396,3 +405,121 @@ class VKUserVideosIE(InfoExtractor):
|
|
|
|
webpage, 'title', default=page_id))
|
|
|
|
webpage, 'title', default=page_id))
|
|
|
|
|
|
|
|
|
|
|
|
return self.playlist_result(entries, page_id, title)
|
|
|
|
return self.playlist_result(entries, page_id, title)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class VKWallPostIE(VKBaseIE):
|
|
|
|
|
|
|
|
IE_NAME = 'vk:wallpost'
|
|
|
|
|
|
|
|
_VALID_URL = r'https?://(?:(?:(?:(?:m|new)\.)?vk\.com/(?:[^?]+\?.*\bw=)?wall(?P<id>-?\d+_\d+)))'
|
|
|
|
|
|
|
|
_TESTS = [{
|
|
|
|
|
|
|
|
# public page URL, audio playlist
|
|
|
|
|
|
|
|
'url': 'https://vk.com/bs.official?w=wall-23538238_35',
|
|
|
|
|
|
|
|
'info_dict': {
|
|
|
|
|
|
|
|
'id': '23538238_35',
|
|
|
|
|
|
|
|
'title': 'Black Shadow - Wall post 23538238_35',
|
|
|
|
|
|
|
|
'description': 'md5:3f84b9c4f9ef499731cf1ced9998cc0c',
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
'playlist': [{
|
|
|
|
|
|
|
|
'md5': '5ba93864ec5b85f7ce19a9af4af080f6',
|
|
|
|
|
|
|
|
'info_dict': {
|
|
|
|
|
|
|
|
'id': '135220665_111806521',
|
|
|
|
|
|
|
|
'ext': 'mp3',
|
|
|
|
|
|
|
|
'title': 'Black Shadow - Слепое Верование',
|
|
|
|
|
|
|
|
'duration': 370,
|
|
|
|
|
|
|
|
'uploader': 'Black Shadow',
|
|
|
|
|
|
|
|
'artist': 'Black Shadow',
|
|
|
|
|
|
|
|
'track': 'Слепое Верование',
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
}, {
|
|
|
|
|
|
|
|
'md5': '4cc7e804579122b17ea95af7834c9233',
|
|
|
|
|
|
|
|
'info_dict': {
|
|
|
|
|
|
|
|
'id': '135220665_111802303',
|
|
|
|
|
|
|
|
'ext': 'mp3',
|
|
|
|
|
|
|
|
'title': 'Black Shadow - Война - Негасимое Бездны Пламя!',
|
|
|
|
|
|
|
|
'duration': 423,
|
|
|
|
|
|
|
|
'uploader': 'Black Shadow',
|
|
|
|
|
|
|
|
'artist': 'Black Shadow',
|
|
|
|
|
|
|
|
'track': 'Война - Негасимое Бездны Пламя!',
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
'params': {
|
|
|
|
|
|
|
|
'skip_download': True,
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
}],
|
|
|
|
|
|
|
|
'skip': 'Requires vk account credentials',
|
|
|
|
|
|
|
|
}, {
|
|
|
|
|
|
|
|
# single YouTube embed, no leading -
|
|
|
|
|
|
|
|
'url': 'https://vk.com/wall85155021_6319',
|
|
|
|
|
|
|
|
'info_dict': {
|
|
|
|
|
|
|
|
'id': '85155021_6319',
|
|
|
|
|
|
|
|
'title': 'Sergey Gorbunov - Wall post 85155021_6319',
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
'playlist_count': 1,
|
|
|
|
|
|
|
|
'skip': 'Requires vk account credentials',
|
|
|
|
|
|
|
|
}, {
|
|
|
|
|
|
|
|
# wall page URL
|
|
|
|
|
|
|
|
'url': 'https://vk.com/wall-23538238_35',
|
|
|
|
|
|
|
|
'only_matching': True,
|
|
|
|
|
|
|
|
}, {
|
|
|
|
|
|
|
|
# mobile wall page URL
|
|
|
|
|
|
|
|
'url': 'https://m.vk.com/wall-23538238_35',
|
|
|
|
|
|
|
|
'only_matching': True,
|
|
|
|
|
|
|
|
}]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _real_extract(self, url):
|
|
|
|
|
|
|
|
post_id = self._match_id(url)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
wall_url = 'https://vk.com/wall%s' % post_id
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
post_id = remove_start(post_id, '-')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
webpage = self._download_webpage(wall_url, post_id)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
error = self._html_search_regex(
|
|
|
|
|
|
|
|
r'>Error</div>\s*<div[^>]+class=["\']body["\'][^>]*>([^<]+)',
|
|
|
|
|
|
|
|
webpage, 'error', default=None)
|
|
|
|
|
|
|
|
if error:
|
|
|
|
|
|
|
|
raise ExtractorError('VK said: %s' % error, expected=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
description = clean_html(get_element_by_class('wall_post_text', webpage))
|
|
|
|
|
|
|
|
uploader = clean_html(get_element_by_class(
|
|
|
|
|
|
|
|
'fw_post_author', webpage)) or self._og_search_description(webpage)
|
|
|
|
|
|
|
|
thumbnail = self._og_search_thumbnail(webpage)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
entries = []
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for audio in re.finditer(r'''(?sx)
|
|
|
|
|
|
|
|
<input[^>]+
|
|
|
|
|
|
|
|
id=(?P<q1>["\'])audio_info(?P<id>\d+_\d+).*?(?P=q1)[^>]+
|
|
|
|
|
|
|
|
value=(?P<q2>["\'])(?P<url>http.+?)(?P=q2)
|
|
|
|
|
|
|
|
.+?
|
|
|
|
|
|
|
|
</table>''', webpage):
|
|
|
|
|
|
|
|
audio_html = audio.group(0)
|
|
|
|
|
|
|
|
audio_id = audio.group('id')
|
|
|
|
|
|
|
|
duration = parse_duration(get_element_by_class('duration', audio_html))
|
|
|
|
|
|
|
|
track = self._html_search_regex(
|
|
|
|
|
|
|
|
r'<span[^>]+id=["\']title%s[^>]*>([^<]+)' % audio_id,
|
|
|
|
|
|
|
|
audio_html, 'title', default=None)
|
|
|
|
|
|
|
|
artist = self._html_search_regex(
|
|
|
|
|
|
|
|
r'>([^<]+)</a></b>\s*&ndash', audio_html,
|
|
|
|
|
|
|
|
'artist', default=None)
|
|
|
|
|
|
|
|
entries.append({
|
|
|
|
|
|
|
|
'id': audio_id,
|
|
|
|
|
|
|
|
'url': audio.group('url'),
|
|
|
|
|
|
|
|
'title': '%s - %s' % (artist, track) if artist and track else audio_id,
|
|
|
|
|
|
|
|
'thumbnail': thumbnail,
|
|
|
|
|
|
|
|
'duration': duration,
|
|
|
|
|
|
|
|
'uploader': uploader,
|
|
|
|
|
|
|
|
'artist': artist,
|
|
|
|
|
|
|
|
'track': track,
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for video in re.finditer(
|
|
|
|
|
|
|
|
r'<a[^>]+href=(["\'])(?P<url>/video(?:-?[\d_]+).*?)\1', webpage):
|
|
|
|
|
|
|
|
entries.append(self.url_result(
|
|
|
|
|
|
|
|
compat_urlparse.urljoin(url, video.group('url')), VKIE.ie_key()))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
title = 'Wall post %s' % post_id
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return self.playlist_result(
|
|
|
|
|
|
|
|
orderedSet(entries), post_id,
|
|
|
|
|
|
|
|
'%s - %s' % (uploader, title) if uploader else title,
|
|
|
|
|
|
|
|
description)
|
|
|
|