X-Git-Url: http://git.jankratochvil.net/?a=blobdiff_plain;f=youtube-dl;h=c2511e8644ed5c7ab65c69697deec68c9387c61d;hb=633cf7cbade120710497c7606bc5bbb0a4f50792;hp=67f8a38fcdaee99af1f29969af0108ef269dd555;hpb=aab771fbdfeb7bb57b4a7d8e22e13bf34d31bac3;p=youtube-dl.git
diff --git a/youtube-dl b/youtube-dl
index 67f8a38..c2511e8 100755
--- a/youtube-dl
+++ b/youtube-dl
@@ -14,10 +14,11 @@ __author__ = (
'Sören Schulze',
'Kevin Ngo',
'Ori Avtalion',
+ 'shizeeg',
)
__license__ = 'Public Domain'
-__version__ = '2011.10.19'
+__version__ = '2011.12.15'
UPDATE_URL = 'https://raw.github.com/rg3/youtube-dl/master/youtube-dl'
@@ -79,8 +80,6 @@ std_headers = {
'Accept-Language': 'en-us,en;q=0.5',
}
-simple_title_chars = string.ascii_letters.decode('ascii') + string.digits.decode('ascii')
-
try:
import json
except ImportError: # Python <2.6, use trivialjson (https://github.com/phihag/trivialjson):
@@ -279,6 +278,17 @@ def timeconvert(timestr):
timestamp = email.utils.mktime_tz(timetuple)
return timestamp
+def _simplify_title(title):
+ expr = re.compile(ur'[^\w\d_\-]+', flags=re.UNICODE)
+ return expr.sub(u'_', title).strip(u'_')
+
+def _orderedSet(iterable):
+ """ Remove all duplicates from the input iterable """
+ res = []
+ for el in iterable:
+ if el not in res:
+ res.append(el)
+ return res
class DownloadError(Exception):
"""Download Error exception.
@@ -307,6 +317,10 @@ class PostProcessingError(Exception):
"""
pass
+class MaxDownloadsReached(Exception):
+ """ --max-downloads limit has been reached. """
+ pass
+
class UnavailableVideoError(Exception):
"""Unavailable Format exception.
@@ -697,8 +711,31 @@ class FileDownloader(object):
self.trouble(u'ERROR: invalid system charset or erroneous output template')
return None
+ def _match_entry(self, info_dict):
+ """ Returns None iff the file should be downloaded """
+
+ title = info_dict['title']
+ matchtitle = self.params.get('matchtitle', False)
+ if matchtitle and not re.search(matchtitle, title, re.IGNORECASE):
+ return u'[download] "' + title + '" title did not match pattern "' + matchtitle + '"'
+ rejecttitle = self.params.get('rejecttitle', False)
+ if rejecttitle and re.search(rejecttitle, title, re.IGNORECASE):
+ return u'"' + title + '" title matched reject pattern "' + rejecttitle + '"'
+ return None
+
def process_info(self, info_dict):
"""Process a single dictionary returned by an InfoExtractor."""
+
+ reason = self._match_entry(info_dict)
+ if reason is not None:
+ self.to_screen(u'[download] ' + reason)
+ return
+
+ max_downloads = self.params.get('max_downloads')
+ if max_downloads is not None:
+ if self._num_downloads > int(max_downloads):
+ raise MaxDownloadsReached()
+
filename = self.prepare_filename(info_dict)
# Forced printings
@@ -722,16 +759,6 @@ class FileDownloader(object):
if filename is None:
return
- matchtitle=self.params.get('matchtitle',False)
- rejecttitle=self.params.get('rejecttitle',False)
- title=info_dict['title'].encode(preferredencoding(), 'xmlcharrefreplace')
- if matchtitle and not re.search(matchtitle, title, re.IGNORECASE):
- self.to_screen(u'[download] "%s" title did not match pattern "%s"' % (title, matchtitle))
- return
- if rejecttitle and re.search(rejecttitle, title, re.IGNORECASE):
- self.to_screen(u'[download] "%s" title matched reject pattern "%s"' % (title, rejecttitle))
- return
-
if self.params.get('nooverwrites', False) and os.path.exists(filename):
self.to_stderr(u'WARNING: file exists and will be skipped')
return
@@ -1094,6 +1121,7 @@ class YoutubeIE(InfoExtractor):
_NETRC_MACHINE = 'youtube'
# Listed in order of quality
_available_formats = ['38', '37', '22', '45', '35', '44', '34', '18', '43', '6', '5', '17', '13']
+ _available_formats_prefer_free = ['38', '37', '45', '22', '44', '35', '43', '34', '18', '6', '5', '17', '13']
_video_extensions = {
'13': '3gp',
'17': 'mp4',
@@ -1291,8 +1319,7 @@ class YoutubeIE(InfoExtractor):
video_title = sanitize_title(video_title)
# simplified title
- simple_title = re.sub(ur'(?u)([^%s]+)' % simple_title_chars, ur'_', video_title)
- simple_title = simple_title.strip(ur'_')
+ simple_title = _simplify_title(video_title)
# thumbnail image
if 'thumbnail_url' not in video_info:
@@ -1344,10 +1371,11 @@ class YoutubeIE(InfoExtractor):
url_map = dict((ud['itag'][0], ud['url'][0]) for ud in url_data)
format_limit = self._downloader.params.get('format_limit', None)
- if format_limit is not None and format_limit in self._available_formats:
- format_list = self._available_formats[self._available_formats.index(format_limit):]
+ available_formats = self._available_formats_prefer_free if self._downloader.params.get('prefer_free_formats', False) else self._available_formats
+ if format_limit is not None and format_limit in available_formats:
+ format_list = available_formats[available_formats.index(format_limit):]
else:
- format_list = self._available_formats
+ format_list = available_formats
existing_formats = [x for x in format_list if x in url_map]
if len(existing_formats) == 0:
self._downloader.trouble(u'ERROR: no known formats available for video')
@@ -1562,10 +1590,9 @@ class DailymotionIE(InfoExtractor):
"""Report information extraction."""
self._downloader.to_screen(u'[dailymotion] %s: Extracting information' % video_id)
- def _real_initialize(self):
- return
-
def _real_extract(self, url):
+ htmlParser = HTMLParser.HTMLParser()
+
# Extract id and simplified title from URL
mobj = re.match(self._VALID_URL, url)
if mobj is None:
@@ -1576,7 +1603,6 @@ class DailymotionIE(InfoExtractor):
self._downloader.increment_downloads()
video_id = mobj.group(1)
- simple_title = mobj.group(2).decode('utf-8')
video_extension = 'flv'
# Retrieve video webpage to extract further information
@@ -1606,12 +1632,13 @@ class DailymotionIE(InfoExtractor):
video_url = mediaURL
- mobj = re.search(r'(?im)
Dailymotion\s*-\s*(.+)\s*-\s*[^<]+?', webpage)
+ mobj = re.search(r'', webpage)
if mobj is None:
self._downloader.trouble(u'ERROR: unable to extract title')
return
- video_title = mobj.group(1).decode('utf-8')
+ video_title = htmlParser.unescape(mobj.group('title')).decode('utf-8')
video_title = sanitize_title(video_title)
+ simple_title = _simplify_title(video_title)
mobj = re.search(r'(?im)[^<]+?]+?>([^<]+?)', webpage)
if mobj is None:
@@ -1653,9 +1680,6 @@ class GoogleIE(InfoExtractor):
"""Report information extraction."""
self._downloader.to_screen(u'[video.google] %s: Extracting information' % video_id)
- def _real_initialize(self):
- return
-
def _real_extract(self, url):
# Extract id from URL
mobj = re.match(self._VALID_URL, url)
@@ -1699,7 +1723,7 @@ class GoogleIE(InfoExtractor):
return
video_title = mobj.group(1).decode('utf-8')
video_title = sanitize_title(video_title)
- simple_title = re.sub(ur'(?u)([^%s]+)' % simple_title_chars, ur'_', video_title)
+ simple_title = _simplify_title(video_title)
# Extract video description
mobj = re.search(r'([^<]*)', webpage)
@@ -1760,9 +1784,6 @@ class PhotobucketIE(InfoExtractor):
"""Report information extraction."""
self._downloader.to_screen(u'[photobucket] %s: Extracting information' % video_id)
- def _real_initialize(self):
- return
-
def _real_extract(self, url):
# Extract id from URL
mobj = re.match(self._VALID_URL, url)
@@ -1801,7 +1822,7 @@ class PhotobucketIE(InfoExtractor):
return
video_title = mobj.group(1).decode('utf-8')
video_title = sanitize_title(video_title)
- simple_title = re.sub(ur'(?u)([^%s]+)' % simple_title_chars, ur'_', video_title)
+ simple_title = _simplify_title(vide_title)
video_uploader = mobj.group(2).decode('utf-8')
@@ -1842,9 +1863,6 @@ class YahooIE(InfoExtractor):
"""Report information extraction."""
self._downloader.to_screen(u'[video.yahoo] %s: Extracting information' % video_id)
- def _real_initialize(self):
- return
-
def _real_extract(self, url, new_video=True):
# Extract ID from URL
mobj = re.match(self._VALID_URL, url)
@@ -1898,7 +1916,7 @@ class YahooIE(InfoExtractor):
self._downloader.trouble(u'ERROR: unable to extract video title')
return
video_title = mobj.group(1).decode('utf-8')
- simple_title = re.sub(ur'(?u)([^%s]+)' % simple_title_chars, ur'_', video_title)
+ simple_title = _simplify_title(video_title)
mobj = re.search(r'', webpage)
if mobj is None:
@@ -1995,9 +2013,6 @@ class VimeoIE(InfoExtractor):
"""Report information extraction."""
self._downloader.to_screen(u'[vimeo] %s: Extracting information' % video_id)
- def _real_initialize(self):
- return
-
def _real_extract(self, url, new_video=True):
# Extract ID from URL
mobj = re.match(self._VALID_URL, url)
@@ -2029,7 +2044,7 @@ class VimeoIE(InfoExtractor):
self._downloader.trouble(u'ERROR: unable to extract video title')
return
video_title = mobj.group(1).decode('utf-8')
- simple_title = re.sub(ur'(?u)([^%s]+)' % simple_title_chars, ur'_', video_title)
+ simple_title = _simplify_title(video_title)
# Extract uploader
mobj = re.search(r'http://vimeo.com/(.*?)', webpage)
@@ -2120,9 +2135,6 @@ class GenericIE(InfoExtractor):
"""Report information extraction."""
self._downloader.to_screen(u'[generic] %s: Extracting information' % video_id)
- def _real_initialize(self):
- return
-
def _real_extract(self, url):
# At this point we have a new video
self._downloader.increment_downloads()
@@ -2176,7 +2188,7 @@ class GenericIE(InfoExtractor):
return
video_title = mobj.group(1).decode('utf-8')
video_title = sanitize_title(video_title)
- simple_title = re.sub(ur'(?u)([^%s]+)' % simple_title_chars, ur'_', video_title)
+ simple_title = _simplify_title(video_title)
# video uploader is domain name
mobj = re.match(r'(?:https?://)?([^/]*)/.*', url)
@@ -2649,9 +2661,6 @@ class DepositFilesIE(InfoExtractor):
"""Report information extraction."""
self._downloader.to_screen(u'[DepositFiles] %s: Extracting information' % file_id)
- def _real_initialize(self):
- return
-
def _real_extract(self, url):
# At this point we have a new file
self._downloader.increment_downloads()
@@ -2849,9 +2858,7 @@ class FacebookIE(InfoExtractor):
video_title = video_title.decode('utf-8')
video_title = sanitize_title(video_title)
- # simplified title
- simple_title = re.sub(ur'(?u)([^%s]+)' % simple_title_chars, ur'_', video_title)
- simple_title = simple_title.strip(ur'_')
+ simple_title = _simplify_title(video_title)
# thumbnail image
if 'thumbnail' not in video_info:
@@ -2942,11 +2949,6 @@ class BlipTVIE(InfoExtractor):
"""Report information extraction."""
self._downloader.to_screen(u'[%s] %s: Direct download detected' % (self.IE_NAME, title))
- def _simplify_title(self, title):
- res = re.sub(ur'(?u)([^%s]+)' % simple_title_chars, ur'_', title)
- res = res.strip(ur'_')
- return res
-
def _real_extract(self, url):
mobj = re.match(self._VALID_URL, url)
if mobj is None:
@@ -2966,13 +2968,14 @@ class BlipTVIE(InfoExtractor):
if urlh.headers.get('Content-Type', '').startswith('video/'): # Direct download
basename = url.split('/')[-1]
title,ext = os.path.splitext(basename)
+ title = title.decode('UTF-8')
ext = ext.replace('.', '')
self.report_direct_download(title)
info = {
'id': title,
'url': url,
'title': title,
- 'stitle': self._simplify_title(title),
+ 'stitle': _simplify_title(title),
'ext': ext,
'urlhandle': urlh
}
@@ -3006,7 +3009,7 @@ class BlipTVIE(InfoExtractor):
'uploader': data['display_name'],
'upload_date': upload_date,
'title': data['title'],
- 'stitle': self._simplify_title(data['title']),
+ 'stitle': _simplify_title(data['title']),
'ext': ext,
'format': data['media']['mimeType'],
'thumbnail': data['thumbnailUrl'],
@@ -3042,9 +3045,6 @@ class MyVideoIE(InfoExtractor):
"""Report information extraction."""
self._downloader.to_screen(u'[myvideo] %s: Extracting information' % video_id)
- def _real_initialize(self):
- return
-
def _real_extract(self,url):
mobj = re.match(self._VALID_URL, url)
if mobj is None:
@@ -3052,10 +3052,6 @@ class MyVideoIE(InfoExtractor):
return
video_id = mobj.group(1)
- simple_title = mobj.group(2).decode('utf-8')
- # should actually not be necessary
- simple_title = sanitize_title(simple_title)
- simple_title = re.sub(ur'(?u)([^%s]+)' % simple_title_chars, ur'_', simple_title)
# Get video webpage
request = urllib2.Request('http://www.myvideo.de/watch/%s' % video_id)
@@ -3082,6 +3078,8 @@ class MyVideoIE(InfoExtractor):
video_title = mobj.group(1)
video_title = sanitize_title(video_title)
+ simple_title = _simplify_title(video_title)
+
try:
self._downloader.process_info({
'id': video_id,
@@ -3115,11 +3113,6 @@ class ComedyCentralIE(InfoExtractor):
def report_player_url(self, episode_id):
self._downloader.to_screen(u'[comedycentral] %s: Determining player URL' % episode_id)
- def _simplify_title(self, title):
- res = re.sub(ur'(?u)([^%s]+)' % simple_title_chars, ur'_', title)
- res = res.strip(ur'_')
- return res
-
def _real_extract(self, url):
mobj = re.match(self._VALID_URL, url)
if mobj is None:
@@ -3128,9 +3121,9 @@ class ComedyCentralIE(InfoExtractor):
if mobj.group('shortname'):
if mobj.group('shortname') in ('tds', 'thedailyshow'):
- url = 'http://www.thedailyshow.com/full-episodes/'
+ url = u'http://www.thedailyshow.com/full-episodes/'
else:
- url = 'http://www.colbertnation.com/full-episodes/'
+ url = u'http://www.colbertnation.com/full-episodes/'
mobj = re.match(self._VALID_URL, url)
assert mobj is not None
@@ -3216,14 +3209,14 @@ class ComedyCentralIE(InfoExtractor):
self._downloader.increment_downloads()
- effTitle = showId + '-' + epTitle
+ effTitle = showId + u'-' + epTitle
info = {
'id': shortMediaId,
'url': video_url,
'uploader': showId,
'upload_date': officialDate,
'title': effTitle,
- 'stitle': self._simplify_title(effTitle),
+ 'stitle': _simplify_title(effTitle),
'ext': 'mp4',
'format': format,
'thumbnail': None,
@@ -3250,11 +3243,6 @@ class EscapistIE(InfoExtractor):
def report_config_download(self, showName):
self._downloader.to_screen(u'[escapist] %s: Downloading configuration' % showName)
- def _simplify_title(self, title):
- res = re.sub(ur'(?u)([^%s]+)' % simple_title_chars, ur'_', title)
- res = res.strip(ur'_')
- return res
-
def _real_extract(self, url):
htmlParser = HTMLParser.HTMLParser()
@@ -3307,7 +3295,7 @@ class EscapistIE(InfoExtractor):
'uploader': showName,
'upload_date': None,
'title': showName,
- 'stitle': self._simplify_title(showName),
+ 'stitle': _simplify_title(showName),
'ext': 'flv',
'format': 'flv',
'thumbnail': imgUrl,
@@ -3335,11 +3323,6 @@ class CollegeHumorIE(InfoExtractor):
"""Report information extraction."""
self._downloader.to_screen(u'[%s] %s: Extracting information' % (self.IE_NAME, video_id))
- def _simplify_title(self, title):
- res = re.sub(ur'(?u)([^%s]+)' % simple_title_chars, ur'_', title)
- res = res.strip(ur'_')
- return res
-
def _real_extract(self, url):
htmlParser = HTMLParser.HTMLParser()
@@ -3381,7 +3364,7 @@ class CollegeHumorIE(InfoExtractor):
videoNode = mdoc.findall('./video')[0]
info['description'] = videoNode.findall('./description')[0].text
info['title'] = videoNode.findall('./caption')[0].text
- info['stitle'] = self._simplify_title(info['title'])
+ info['stitle'] = _simplify_title(info['title'])
info['url'] = videoNode.findall('./file')[0].text
info['thumbnail'] = videoNode.findall('./thumbnail')[0].text
info['ext'] = info['url'].rpartition('.')[2]
@@ -3412,11 +3395,6 @@ class XVideosIE(InfoExtractor):
"""Report information extraction."""
self._downloader.to_screen(u'[%s] %s: Extracting information' % (self.IE_NAME, video_id))
- def _simplify_title(self, title):
- res = re.sub(ur'(?u)([^%s]+)' % simple_title_chars, ur'_', title)
- res = res.strip(ur'_')
- return res
-
def _real_extract(self, url):
htmlParser = HTMLParser.HTMLParser()
@@ -3470,7 +3448,7 @@ class XVideosIE(InfoExtractor):
'uploader': None,
'upload_date': None,
'title': video_title,
- 'stitle': self._simplify_title(video_title),
+ 'stitle': _simplify_title(video_title),
'ext': 'flv',
'format': 'flv',
'thumbnail': video_thumbnail,
@@ -3507,9 +3485,6 @@ class SoundcloudIE(InfoExtractor):
"""Report information extraction."""
self._downloader.to_screen(u'[%s] %s: Extracting information' % (self.IE_NAME, video_id))
- def _real_initialize(self):
- return
-
def _real_extract(self, url):
htmlParser = HTMLParser.HTMLParser()
@@ -3536,7 +3511,7 @@ class SoundcloudIE(InfoExtractor):
self.report_extraction('%s/%s' % (uploader, slug_title))
# extract uid and stream token that soundcloud hands out for access
- mobj = re.search('"uid":"([\w\d]+?)".*?stream_token=([\w\d]+)', webpage)
+ mobj = re.search('"uid":"([\w\d]+?)".*?stream_token=([\w\d]+)', webpage)
if mobj:
video_id = mobj.group(1)
stream_token = mobj.group(2)
@@ -3562,7 +3537,7 @@ class SoundcloudIE(InfoExtractor):
if mobj:
try:
upload_date = datetime.datetime.strptime(mobj.group(1), '%B %d, %Y %H:%M').strftime('%Y%m%d')
- except Exception as e:
+ except Exception, e:
print str(e)
# for soundcloud, a request to a cross domain is required for cookies
@@ -3599,11 +3574,6 @@ class InfoQIE(InfoExtractor):
"""Report information extraction."""
self._downloader.to_screen(u'[%s] %s: Extracting information' % (self.IE_NAME, video_id))
- def _simplify_title(self, title):
- res = re.sub(ur'(?u)([^%s]+)' % simple_title_chars, ur'_', title)
- res = res.strip(ur'_')
- return res
-
def _real_extract(self, url):
htmlParser = HTMLParser.HTMLParser()
@@ -3639,7 +3609,6 @@ class InfoQIE(InfoExtractor):
return
video_title = mobj.group(1).decode('utf-8')
-
# Extract description
video_description = u'No description available.'
mobj = re.search(r'', webpage)
@@ -3656,7 +3625,7 @@ class InfoQIE(InfoExtractor):
'uploader': None,
'upload_date': None,
'title': video_title,
- 'stitle': self._simplify_title(video_title),
+ 'stitle': _simplify_title(video_title),
'ext': extension,
'format': extension, # Extension is always(?) mp4, but seems to be flv
'thumbnail': None,
@@ -3669,6 +3638,245 @@ class InfoQIE(InfoExtractor):
except UnavailableVideoError, err:
self._downloader.trouble(u'\nERROR: unable to download ' + video_url)
+class MixcloudIE(InfoExtractor):
+ """Information extractor for www.mixcloud.com"""
+ _VALID_URL = r'^(?:https?://)?(?:www\.)?mixcloud\.com/([\w\d-]+)/([\w\d-]+)'
+ IE_NAME = u'mixcloud'
+
+ def __init__(self, downloader=None):
+ InfoExtractor.__init__(self, downloader)
+
+ def report_download_json(self, file_id):
+ """Report JSON download."""
+ self._downloader.to_screen(u'[%s] Downloading json' % self.IE_NAME)
+
+ def report_extraction(self, file_id):
+ """Report information extraction."""
+ self._downloader.to_screen(u'[%s] %s: Extracting information' % (self.IE_NAME, file_id))
+
+ def get_urls(self, jsonData, fmt, bitrate='best'):
+ """Get urls from 'audio_formats' section in json"""
+ file_url = None
+ try:
+ bitrate_list = jsonData[fmt]
+ if bitrate is None or bitrate == 'best' or bitrate not in bitrate_list:
+ bitrate = max(bitrate_list) # select highest
+
+ url_list = jsonData[fmt][bitrate]
+ except TypeError: # we have no bitrate info.
+ url_list = jsonData[fmt]
+
+ return url_list
+
+ def check_urls(self, url_list):
+ """Returns 1st active url from list"""
+ for url in url_list:
+ try:
+ urllib2.urlopen(url)
+ return url
+ except (urllib2.URLError, httplib.HTTPException, socket.error), err:
+ url = None
+
+ return None
+
+ def _print_formats(self, formats):
+ print 'Available formats:'
+ for fmt in formats.keys():
+ for b in formats[fmt]:
+ try:
+ ext = formats[fmt][b][0]
+ print '%s\t%s\t[%s]' % (fmt, b, ext.split('.')[-1])
+ except TypeError: # we have no bitrate info
+ ext = formats[fmt][0]
+ print '%s\t%s\t[%s]' % (fmt, '??', ext.split('.')[-1])
+ break
+
+ 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 & filename from url
+ uploader = mobj.group(1).decode('utf-8')
+ file_id = uploader + "-" + mobj.group(2).decode('utf-8')
+
+ # construct API request
+ file_url = 'http://www.mixcloud.com/api/1/cloudcast/' + '/'.join(url.split('/')[-3:-1]) + '.json'
+ # retrieve .json file with links to files
+ request = urllib2.Request(file_url)
+ try:
+ self.report_download_json(file_url)
+ jsonData = urllib2.urlopen(request).read()
+ except (urllib2.URLError, httplib.HTTPException, socket.error), err:
+ self._downloader.trouble(u'ERROR: Unable to retrieve file: %s' % str(err))
+ return
+
+ # parse JSON
+ json_data = json.loads(jsonData)
+ player_url = json_data['player_swf_url']
+ formats = dict(json_data['audio_formats'])
+
+ req_format = self._downloader.params.get('format', None)
+ bitrate = None
+
+ if self._downloader.params.get('listformats', None):
+ self._print_formats(formats)
+ return
+
+ if req_format is None or req_format == 'best':
+ for format_param in formats.keys():
+ url_list = self.get_urls(formats, format_param)
+ # check urls
+ file_url = self.check_urls(url_list)
+ if file_url is not None:
+ break # got it!
+ else:
+ if req_format not in formats.keys():
+ self._downloader.trouble(u'ERROR: format is not available')
+ return
+
+ url_list = self.get_urls(formats, req_format)
+ file_url = self.check_urls(url_list)
+ format_param = req_format
+
+ # We have audio
+ self._downloader.increment_downloads()
+ try:
+ # Process file information
+ self._downloader.process_info({
+ 'id': file_id.decode('utf-8'),
+ 'url': file_url.decode('utf-8'),
+ 'uploader': uploader.decode('utf-8'),
+ 'upload_date': u'NA',
+ 'title': json_data['name'],
+ 'stitle': _simplify_title(json_data['name']),
+ 'ext': file_url.split('.')[-1].decode('utf-8'),
+ 'format': (format_param is None and u'NA' or format_param.decode('utf-8')),
+ 'thumbnail': json_data['thumbnail_url'],
+ 'description': json_data['description'],
+ 'player_url': player_url.decode('utf-8'),
+ })
+ except UnavailableVideoError, err:
+ self._downloader.trouble(u'ERROR: unable to download file')
+
+class StanfordOpenClassroomIE(InfoExtractor):
+ """Information extractor for Stanford's Open ClassRoom"""
+
+ _VALID_URL = r'^(?:https?://)?openclassroom.stanford.edu(?P/?|(/MainFolder/(?:HomePage|CoursePage|VideoPage)\.php([?]course=(?P[^&]+)(&video=(?P