""" Returns the response handle """
if note is None:
note = u'Downloading video webpage'
- self._downloader.to_screen(u'[%s] %s: %s' % (self.IE_NAME, video_id, note))
+ if note is not False:
+ self._downloader.to_screen(u'[%s] %s: %s' % (self.IE_NAME, video_id, note))
try:
return compat_urllib_request.urlopen(url_or_request)
except (compat_urllib_error.URLError, compat_http_client.HTTPException, socket.error) as err:
else:
encoding = 'utf-8'
webpage_bytes = urlh.read()
+ if self._downloader.params.get('dump_intermediate_pages', False):
+ try:
+ url = url_or_request.get_full_url()
+ except AttributeError:
+ url = url_or_request
+ self._downloader.to_screen(u'Dumping request to ' + url)
+ dump = base64.b64encode(webpage_bytes).decode('ascii')
+ self._downloader.to_screen(dump)
return webpage_bytes.decode(encoding, 'replace')
+
+ #Methods for following #608
+ #They set the correct value of the '_type' key
+ def video_result(self, video_info):
+ """Returns a video"""
+ video_info['_type'] = 'video'
+ return video_info
+ def url_result(self, url, ie=None):
+ """Returns a url that points to a page that should be processed"""
+ #TODO: ie should be the class used for getting the info
+ video_info = {'_type': 'url',
+ 'url': url}
+ return video_info
+ def playlist_result(self, entries, playlist_id=None, playlist_title=None):
+ """Returns a playlist"""
+ video_info = {'_type': 'playlist',
+ 'entries': entries}
+ if playlist_id:
+ video_info['id'] = playlist_id
+ if playlist_title:
+ video_info['title'] = playlist_title
+ return video_info
class YoutubeIE(InfoExtractor):
try:
sub_list = compat_urllib_request.urlopen(request).read().decode('utf-8')
except (compat_urllib_error.URLError, compat_http_client.HTTPException, socket.error) as err:
- return (u'WARNING: unable to download video subtitles: %s' % compat_str(err), None)
+ return (u'unable to download video subtitles: %s' % compat_str(err), None)
sub_lang_list = re.findall(r'name="([^"]*)"[^>]+lang_code="([\w\-]+)"', sub_list)
sub_lang_list = dict((l[1], l[0]) for l in sub_lang_list)
if not sub_lang_list:
- return (u'WARNING: video doesn\'t have subtitles', None)
+ return (u'video doesn\'t have subtitles', None)
return sub_lang_list
def _list_available_subtitles(self, video_id):
self.report_video_subtitles_available(video_id, sub_lang_list)
def _request_subtitle(self, sub_lang, sub_name, video_id, format):
+ """
+ Return tuple:
+ (error_message, sub_lang, sub)
+ """
self.report_video_subtitles_request(video_id, sub_lang, format)
params = compat_urllib_parse.urlencode({
'lang': sub_lang,
try:
sub = compat_urllib_request.urlopen(url).read().decode('utf-8')
except (compat_urllib_error.URLError, compat_http_client.HTTPException, socket.error) as err:
- return (u'WARNING: unable to download video subtitles: %s' % compat_str(err), None)
+ return (u'unable to download video subtitles: %s' % compat_str(err), None, None)
if not sub:
- return (u'WARNING: Did not fetch video subtitles', None)
+ return (u'Did not fetch video subtitles', None, None)
return (None, sub_lang, sub)
def _extract_subtitle(self, video_id):
+ """
+ Return a list with a tuple:
+ [(error_message, sub_lang, sub)]
+ """
sub_lang_list = self._get_available_subtitles(video_id)
sub_format = self._downloader.params.get('subtitlesformat')
+ if isinstance(sub_lang_list,tuple): #There was some error, it didn't get the available subtitles
+ return [(sub_lang_list[0], None, None)]
if self._downloader.params.get('subtitleslang', False):
sub_lang = self._downloader.params.get('subtitleslang')
elif 'en' in sub_lang_list:
else:
sub_lang = list(sub_lang_list.keys())[0]
if not sub_lang in sub_lang_list:
- return (u'WARNING: no closed captions found in the specified language "%s"' % sub_lang, None)
+ return [(u'no closed captions found in the specified language "%s"' % sub_lang, None, None)]
subtitle = self._request_subtitle(sub_lang, sub_lang_list[sub_lang].encode('utf-8'), video_id, sub_format)
return [subtitle]
def _extract_all_subtitles(self, video_id):
sub_lang_list = self._get_available_subtitles(video_id)
sub_format = self._downloader.params.get('subtitlesformat')
+ if isinstance(sub_lang_list,tuple): #There was some error, it didn't get the available subtitles
+ return [(sub_lang_list[0], None, None)]
subtitles = []
for sub_lang in sub_lang_list:
subtitle = self._request_subtitle(sub_lang, sub_lang_list[sub_lang].encode('utf-8'), video_id, sub_format)
# Get video info
self.report_video_info_webpage_download(video_id)
for el_type in ['&el=embedded', '&el=detailpage', '&el=vevo', '']:
- video_info_url = ('http://www.youtube.com/get_video_info?&video_id=%s%s&ps=default&eurl=&gl=US&hl=en'
+ video_info_url = ('https://www.youtube.com/get_video_info?&video_id=%s%s&ps=default&eurl=&gl=US&hl=en'
% (video_id, el_type))
- request = compat_urllib_request.Request(video_info_url)
- try:
- video_info_webpage_bytes = compat_urllib_request.urlopen(request).read()
- video_info_webpage = video_info_webpage_bytes.decode('utf-8', 'ignore')
- video_info = compat_parse_qs(video_info_webpage)
- if 'token' in video_info:
- break
- except (compat_urllib_error.URLError, compat_http_client.HTTPException, socket.error) as err:
- self._downloader.report_error(u'unable to download video info webpage: %s' % compat_str(err))
- return
+ video_info_webpage = self._download_webpage(video_info_url, video_id,
+ note=False,
+ errnote='unable to download video info webpage')
+ video_info = compat_parse_qs(video_info_webpage)
+ if 'token' in video_info:
+ break
if 'token' not in video_info:
if 'reason' in video_info:
self._downloader.report_error(u'YouTube said: %s' % video_info['reason'][0])
if video_subtitles:
(sub_error, sub_lang, sub) = video_subtitles[0]
if sub_error:
- self._downloader.trouble(sub_error)
+ self._downloader.report_error(sub_error)
if self._downloader.params.get('allsubtitles', False):
video_subtitles = self._extract_all_subtitles(video_id)
for video_subtitle in video_subtitles:
(sub_error, sub_lang, sub) = video_subtitle
if sub_error:
- self._downloader.trouble(sub_error)
+ self._downloader.report_error(sub_error)
if self._downloader.params.get('listsubtitles', False):
sub_lang_list = self._list_available_subtitles(video_id)
# Check if video comes from YouTube
mobj2 = re.match(r'^yt-(.*)$', video_id)
if mobj2 is not None:
- self._downloader.download(['http://www.youtube.com/watch?v=%s' % mobj2.group(1)])
- return
+ return [self.url_result('http://www.youtube.com/watch?v=%s' % mobj2.group(1))]
# Retrieve video webpage to extract further information
request = compat_urllib_request.Request('http://www.metacafe.com/watch/%s/' % video_id)
# Extract video description
video_description = get_element_by_attribute("itemprop", "description", webpage)
if video_description: video_description = clean_html(video_description)
- else: video_description = ''
+ else: video_description = u''
# Extract upload date
video_upload_date = None
self._downloader.to_screen(u'[redirect] Following redirect to %s' % new_url)
def _test_redirect(self, url):
- """Check if it is a redirect, like url shorteners, in case restart chain."""
+ """Check if it is a redirect, like url shorteners, in case return the new url."""
class HeadRequest(compat_urllib_request.Request):
def get_method(self):
return "HEAD"
return False
self.report_following_redirect(new_url)
- self._downloader.download([new_url])
- return True
+ return new_url
def _real_extract(self, url):
- if self._test_redirect(url): return
+ new_url = self._test_redirect(url)
+ if new_url: return [self.url_result(new_url)]
video_id = url.split('/')[-1]
try:
(?:
(?:course|view_play_list|my_playlists|artist|playlist|watch)
\? (?:.*?&)*? (?:p|a|list)=
- | user/.*?/user/
| p/
- | user/.*?#[pg]/c/
)
((?:PL|EC|UU)?[0-9A-Za-z-_]{10,})
.*
self._downloader.report_error(u'Invalid JSON in API response: ' + compat_str(err))
return
- if not 'feed' in response or not 'entry' in response['feed']:
+ if 'feed' not in response:
self._downloader.report_error(u'Got a malformed response from YouTube API')
return
+ if 'entry' not in response['feed']:
+ # Number of videos is a multiple of self._MAX_RESULTS
+ break
+
videos += [ (entry['yt$position']['$t'], entry['content']['src'])
for entry in response['feed']['entry']
if 'content' in entry ]
page_num += 1
videos = [v[1] for v in sorted(videos)]
- total = len(videos)
-
- playliststart = self._downloader.params.get('playliststart', 1) - 1
- playlistend = self._downloader.params.get('playlistend', -1)
- if playlistend == -1:
- videos = videos[playliststart:]
- else:
- videos = videos[playliststart:playlistend]
- if len(videos) == total:
- self._downloader.to_screen(u'[youtube] PL %s: Found %i videos' % (playlist_id, total))
- else:
- self._downloader.to_screen(u'[youtube] PL %s: Found %i videos, downloading %i' % (playlist_id, total, len(videos)))
-
- for video in videos:
- self._downloader.download([video])
- return
+ url_results = [self.url_result(url) for url in videos]
+ return [self.playlist_result(url_results, playlist_id)]
class YoutubeChannelIE(InfoExtractor):
self._downloader.to_screen(u'[youtube] Channel %s: Found %i videos' % (channel_id, len(video_ids)))
- for id in video_ids:
- self._downloader.download(['http://www.youtube.com/watch?v=%s' % id])
- return
+ urls = ['http://www.youtube.com/watch?v=%s' % id for id in video_ids]
+ url_entries = [self.url_result(url) for url in urls]
+ return [self.playlist_result(url_entries, channel_id)]
class YoutubeUserIE(InfoExtractor):
pagenum += 1
- all_ids_count = len(video_ids)
- playliststart = self._downloader.params.get('playliststart', 1) - 1
- playlistend = self._downloader.params.get('playlistend', -1)
-
- if playlistend == -1:
- video_ids = video_ids[playliststart:]
- else:
- video_ids = video_ids[playliststart:playlistend]
-
- self._downloader.to_screen(u"[youtube] user %s: Collected %d video ids (downloading %d of them)" %
- (username, all_ids_count, len(video_ids)))
-
- for video_id in video_ids:
- self._downloader.download(['http://www.youtube.com/watch?v=%s' % video_id])
+ urls = ['http://www.youtube.com/watch?v=%s' % video_id for video_id in video_ids]
+ url_results = [self.url_result(url) for url in urls]
+ return [self.playlist_result(url_results, playlist_title = username)]
class BlipTVUserIE(InfoExtractor):
pagenum += 1
- all_ids_count = len(video_ids)
- playliststart = self._downloader.params.get('playliststart', 1) - 1
- playlistend = self._downloader.params.get('playlistend', -1)
-
- if playlistend == -1:
- video_ids = video_ids[playliststart:]
- else:
- video_ids = video_ids[playliststart:playlistend]
-
self._downloader.to_screen(u"[%s] user %s: Collected %d video ids (downloading %d of them)" %
(self.IE_NAME, username, all_ids_count, len(video_ids)))
- for video_id in video_ids:
- self._downloader.download([u'http://blip.tv/'+video_id])
+ urls = [u'http://blip.tv/%s' % video_id for video_id in video_ids]
+ url_entries = [self.url_result(url) for url in urls]
+ return [self.playlist_result(url_entries, playlist_title = username)]
class DepositFilesIE(InfoExtractor):
url = 'https://www.facebook.com/video/video.php?v=%s' % video_id
webpage = self._download_webpage(url, video_id)
- BEFORE = '[["allowFullScreen","true"],["allowScriptAccess","always"],["salign","tl"],["scale","noscale"],["wmode","opaque"]].forEach(function(param) {swf.addParam(param[0], param[1]);});\n'
+ BEFORE = '{swf.addParam(param[0], param[1]);});\n'
AFTER = '.forEach(function(variable) {swf.addVariable(variable[0], variable[1]);});'
m = re.search(re.escape(BEFORE) + '(.*?)' + re.escape(AFTER), webpage)
if not m:
data = dict(json.loads(m.group(1)))
params_raw = compat_urllib_parse.unquote(data['params'])
params = json.loads(params_raw)
- video_url = params['hd_src']
+ video_data = params['video_data'][0]
+ video_url = video_data.get('hd_src')
if not video_url:
- video_url = params['sd_src']
+ video_url = video_data['sd_src']
if not video_url:
raise ExtractorError(u'Cannot find video URL')
- video_duration = int(params['video_duration'])
+ video_duration = int(video_data['video_duration'])
+ thumbnail = video_data['thumbnail_src']
m = re.search('<h2 class="uiHeaderTitle">([^<]+)</h2>', webpage)
if not m:
'url': video_url,
'ext': 'mp4',
'duration': video_duration,
- 'thumbnail': params['thumbnail_src'],
+ 'thumbnail': thumbnail,
}
return [info]
'description': info['description'],
}]
+class SoundcloudSetIE(InfoExtractor):
+ """Information extractor for soundcloud.com sets
+ To access the media, the uid of the song and a stream token
+ must be extracted from the page source and the script must make
+ a request to media.soundcloud.com/crossdomain.xml. Then
+ the media can be grabbed by requesting from an url composed
+ of the stream token and uid
+ """
+
+ _VALID_URL = r'^(?:https?://)?(?:www\.)?soundcloud\.com/([\w\d-]+)/sets/([\w\d-]+)'
+ IE_NAME = u'soundcloud'
+
+ def __init__(self, downloader=None):
+ InfoExtractor.__init__(self, downloader)
+
+ def report_resolve(self, video_id):
+ """Report information extraction."""
+ self._downloader.to_screen(u'[%s] %s: Resolving id' % (self.IE_NAME, video_id))
+
+ def report_extraction(self, video_id):
+ """Report information extraction."""
+ self._downloader.to_screen(u'[%s] %s: Retrieving stream' % (self.IE_NAME, video_id))
+
+ def _real_extract(self, url):
+ mobj = re.match(self._VALID_URL, url)
+ if mobj is None:
+ self._downloader.trouble(u'ERROR: invalid URL: %s' % url)
+ return
+
+ # extract uploader (which is in the url)
+ uploader = mobj.group(1)
+ # extract simple title (uploader + slug of song title)
+ slug_title = mobj.group(2)
+ simple_title = uploader + u'-' + slug_title
+
+ self.report_resolve('%s/sets/%s' % (uploader, slug_title))
+
+ url = 'http://soundcloud.com/%s/sets/%s' % (uploader, slug_title)
+ resolv_url = 'http://api.soundcloud.com/resolve.json?url=' + url + '&client_id=b45b1aa10f1ac2941910a7f0d10f8e28'
+ request = compat_urllib_request.Request(resolv_url)
+ try:
+ info_json_bytes = compat_urllib_request.urlopen(request).read()
+ info_json = info_json_bytes.decode('utf-8')
+ except (compat_urllib_error.URLError, compat_http_client.HTTPException, socket.error) as err:
+ self._downloader.trouble(u'ERROR: unable to download video webpage: %s' % compat_str(err))
+ return
+
+ videos = []
+ info = json.loads(info_json)
+ if 'errors' in info:
+ for err in info['errors']:
+ self._downloader.trouble(u'ERROR: unable to download video webpage: %s' % compat_str(err['error_message']))
+ return
+
+ for track in info['tracks']:
+ video_id = track['id']
+ self.report_extraction('%s/sets/%s' % (uploader, slug_title))
+
+ streams_url = 'https://api.sndcdn.com/i1/tracks/' + str(video_id) + '/streams?client_id=b45b1aa10f1ac2941910a7f0d10f8e28'
+ request = compat_urllib_request.Request(streams_url)
+ try:
+ stream_json_bytes = compat_urllib_request.urlopen(request).read()
+ stream_json = stream_json_bytes.decode('utf-8')
+ except (compat_urllib_error.URLError, compat_http_client.HTTPException, socket.error) as err:
+ self._downloader.trouble(u'ERROR: unable to download stream definitions: %s' % compat_str(err))
+ return
+
+ streams = json.loads(stream_json)
+ mediaURL = streams['http_mp3_128_url']
+
+ videos.append({
+ 'id': video_id,
+ 'url': mediaURL,
+ 'uploader': track['user']['username'],
+ 'upload_date': track['created_at'],
+ 'title': track['title'],
+ 'ext': u'mp3',
+ 'description': track['description'],
+ })
+ return videos
+
class InfoQIE(InfoExtractor):
"""Information extractor for infoq.com"""
m = re.search(r"<h1 class='player_page_h1'.*?>(?P<title>.*?)</h1>", webpage, flags=re.DOTALL)
if not m:
- self._downloader.trouble(u'Cannot find video title')
+ m = re.search(r'<title>(?P<title>[^<]+?)</title>', webpage)
+ if not m:
+ self._downloader.trouble(u'Cannot find video title')
title = clean_html(m.group('title'))
m = re.search(r'<meta property="og:description" content="(?P<desc>.*?)"', webpage)
_title = r"""<title>(.*)</title>"""
mobj = re.search(_title, webpage_src)
-
+
if mobj is not None:
title = mobj.group(1)
else:
if mobj is not None:
title = mobj.group(1)
thumbnail = None
-
+
results = [{
'id': video_id,
'url' : video_url,
video_url = u'http://cdn.keek.com/keek/video/%s' % video_id
thumbnail = u'http://cdn.keek.com/keek/thumbnail/%s/w100/h75' % video_id
webpage = self._download_webpage(url, video_id)
- m = re.search(r'<meta property="og:title" content="(?P<title>.+)"', webpage)
+ m = re.search(r'<meta property="og:title" content="(?P<title>.*?)"', webpage)
title = unescapeHTML(m.group('title'))
m = re.search(r'<div class="user-name-and-bio">[\S\s]+?<h2>(?P<uploader>.+?)</h2>', webpage)
uploader = clean_html(m.group('uploader'))
return [info]
+class ARDIE(InfoExtractor):
+ _VALID_URL = r'^(?:https?://)?(?:(?:www\.)?ardmediathek\.de|mediathek\.daserste\.de)/(?:.*/)(?P<video_id>[^/\?]+)(?:\?.*)?'
+ _TITLE = r'<h1(?: class="boxTopHeadline")?>(?P<title>.*)</h1>'
+ _MEDIA_STREAM = r'mediaCollection\.addMediaStream\((?P<media_type>\d+), (?P<quality>\d+), "(?P<rtmp_url>[^"]*)", "(?P<video_url>[^"]*)", "[^"]*"\)'
+
+ def _real_extract(self, url):
+ # determine video id from url
+ m = re.match(self._VALID_URL, url)
+
+ numid = re.search(r'documentId=([0-9]+)', url)
+ if numid:
+ video_id = numid.group(1)
+ else:
+ video_id = m.group('video_id')
+
+ # determine title and media streams from webpage
+ html = self._download_webpage(url, video_id)
+ title = re.search(self._TITLE, html).group('title')
+ streams = [m.groupdict() for m in re.finditer(self._MEDIA_STREAM, html)]
+ if not streams:
+ assert '"fsk"' in html
+ self._downloader.report_error(u'this video is only available after 8:00 pm')
+ return
+
+ # choose default media type and highest quality for now
+ stream = max([s for s in streams if int(s["media_type"]) == 0],
+ key=lambda s: int(s["quality"]))
+
+ # there's two possibilities: RTMP stream or HTTP download
+ info = {'id': video_id, 'title': title, 'ext': 'mp4'}
+ if stream['rtmp_url']:
+ self._downloader.to_screen(u'[%s] RTMP download detected' % self.IE_NAME)
+ assert stream['video_url'].startswith('mp4:')
+ info["url"] = stream["rtmp_url"]
+ info["play_path"] = stream['video_url']
+ else:
+ assert stream["video_url"].endswith('.mp4')
+ info["url"] = stream["video_url"]
+ return [info]
+
def gen_extractors():
""" Return a list of an instance of every supported extractor.
EscapistIE(),
CollegeHumorIE(),
XVideosIE(),
+ SoundcloudSetIE(),
SoundcloudIE(),
InfoQIE(),
MixcloudIE(),
MySpassIE(),
SpiegelIE(),
LiveLeakIE(),
+ ARDIE(),
GenericIE()
]