You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

115 lines
4.2 KiB

  1. import json
  2. import re
  3. import socket
  4. from .common import InfoExtractor
  5. from ..utils import (
  6. compat_http_client,
  7. compat_str,
  8. compat_urllib_error,
  9. compat_urllib_request,
  10. ExtractorError,
  11. )
  12. class MixcloudIE(InfoExtractor):
  13. _WORKING = False # New API, but it seems good http://www.mixcloud.com/developers/documentation/
  14. _VALID_URL = r'^(?:https?://)?(?:www\.)?mixcloud\.com/([\w\d-]+)/([\w\d-]+)'
  15. IE_NAME = u'mixcloud'
  16. def report_download_json(self, file_id):
  17. """Report JSON download."""
  18. self.to_screen(u'Downloading json')
  19. def get_urls(self, jsonData, fmt, bitrate='best'):
  20. """Get urls from 'audio_formats' section in json"""
  21. try:
  22. bitrate_list = jsonData[fmt]
  23. if bitrate is None or bitrate == 'best' or bitrate not in bitrate_list:
  24. bitrate = max(bitrate_list) # select highest
  25. url_list = jsonData[fmt][bitrate]
  26. except TypeError: # we have no bitrate info.
  27. url_list = jsonData[fmt]
  28. return url_list
  29. def check_urls(self, url_list):
  30. """Returns 1st active url from list"""
  31. for url in url_list:
  32. try:
  33. compat_urllib_request.urlopen(url)
  34. return url
  35. except (compat_urllib_error.URLError, compat_http_client.HTTPException, socket.error):
  36. url = None
  37. return None
  38. def _print_formats(self, formats):
  39. print('Available formats:')
  40. for fmt in formats.keys():
  41. for b in formats[fmt]:
  42. try:
  43. ext = formats[fmt][b][0]
  44. print('%s\t%s\t[%s]' % (fmt, b, ext.split('.')[-1]))
  45. except TypeError: # we have no bitrate info
  46. ext = formats[fmt][0]
  47. print('%s\t%s\t[%s]' % (fmt, '??', ext.split('.')[-1]))
  48. break
  49. def _real_extract(self, url):
  50. mobj = re.match(self._VALID_URL, url)
  51. if mobj is None:
  52. raise ExtractorError(u'Invalid URL: %s' % url)
  53. # extract uploader & filename from url
  54. uploader = mobj.group(1).decode('utf-8')
  55. file_id = uploader + "-" + mobj.group(2).decode('utf-8')
  56. # construct API request
  57. file_url = 'http://www.mixcloud.com/api/1/cloudcast/' + '/'.join(url.split('/')[-3:-1]) + '.json'
  58. # retrieve .json file with links to files
  59. request = compat_urllib_request.Request(file_url)
  60. try:
  61. self.report_download_json(file_url)
  62. jsonData = compat_urllib_request.urlopen(request).read()
  63. except (compat_urllib_error.URLError, compat_http_client.HTTPException, socket.error) as err:
  64. raise ExtractorError(u'Unable to retrieve file: %s' % compat_str(err))
  65. # parse JSON
  66. json_data = json.loads(jsonData)
  67. player_url = json_data['player_swf_url']
  68. formats = dict(json_data['audio_formats'])
  69. req_format = self._downloader.params.get('format', None)
  70. if self._downloader.params.get('listformats', None):
  71. self._print_formats(formats)
  72. return
  73. if req_format is None or req_format == 'best':
  74. for format_param in formats.keys():
  75. url_list = self.get_urls(formats, format_param)
  76. # check urls
  77. file_url = self.check_urls(url_list)
  78. if file_url is not None:
  79. break # got it!
  80. else:
  81. if req_format not in formats:
  82. raise ExtractorError(u'Format is not available')
  83. url_list = self.get_urls(formats, req_format)
  84. file_url = self.check_urls(url_list)
  85. format_param = req_format
  86. return [{
  87. 'id': file_id.decode('utf-8'),
  88. 'url': file_url.decode('utf-8'),
  89. 'uploader': uploader.decode('utf-8'),
  90. 'upload_date': None,
  91. 'title': json_data['name'],
  92. 'ext': file_url.split('.')[-1].decode('utf-8'),
  93. 'format': (format_param is None and u'NA' or format_param.decode('utf-8')),
  94. 'thumbnail': json_data['thumbnail_url'],
  95. 'description': json_data['description'],
  96. 'player_url': player_url.decode('utf-8'),
  97. }]