|
@@ -3608,6 +3608,127 @@ 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 PostProcessor(object):
|
|
@@ -4008,6 +4129,7 @@ def gen_extractors():
|
|
|
XVideosIE(),
|
|
|
SoundcloudIE(),
|
|
|
InfoQIE(),
|
|
|
+ MixcloudIE(),
|
|
|
|
|
|
GenericIE()
|
|
|
]
|