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.

161 lines
5.8 KiB

  1. # coding: utf-8
  2. from __future__ import unicode_literals
  3. import re
  4. from .common import InfoExtractor
  5. from ..utils import (
  6. int_or_none,
  7. urlencode_postdata,
  8. compat_str,
  9. ExtractorError,
  10. )
  11. class CuriosityStreamBaseIE(InfoExtractor):
  12. _NETRC_MACHINE = 'curiositystream'
  13. _auth_token = None
  14. _API_BASE_URL = 'https://api.curiositystream.com/v1/'
  15. def _handle_errors(self, result):
  16. error = result.get('error', {}).get('message')
  17. if error:
  18. if isinstance(error, dict):
  19. error = ', '.join(error.values())
  20. raise ExtractorError(
  21. '%s said: %s' % (self.IE_NAME, error), expected=True)
  22. def _call_api(self, path, video_id):
  23. headers = {}
  24. if self._auth_token:
  25. headers['X-Auth-Token'] = self._auth_token
  26. result = self._download_json(
  27. self._API_BASE_URL + path, video_id, headers=headers)
  28. self._handle_errors(result)
  29. return result['data']
  30. def _real_initialize(self):
  31. email, password = self._get_login_info()
  32. if email is None:
  33. return
  34. result = self._download_json(
  35. self._API_BASE_URL + 'login', None, data=urlencode_postdata({
  36. 'email': email,
  37. 'password': password,
  38. }))
  39. self._handle_errors(result)
  40. self._auth_token = result['message']['auth_token']
  41. class CuriosityStreamIE(CuriosityStreamBaseIE):
  42. IE_NAME = 'curiositystream'
  43. _VALID_URL = r'https?://(?:app\.)?curiositystream\.com/video/(?P<id>\d+)'
  44. _TEST = {
  45. 'url': 'https://app.curiositystream.com/video/2',
  46. 'md5': '262bb2f257ff301115f1973540de8983',
  47. 'info_dict': {
  48. 'id': '2',
  49. 'ext': 'mp4',
  50. 'title': 'How Did You Develop The Internet?',
  51. 'description': 'Vint Cerf, Google\'s Chief Internet Evangelist, describes how he and Bob Kahn created the internet.',
  52. }
  53. }
  54. def _real_extract(self, url):
  55. video_id = self._match_id(url)
  56. media = self._call_api('media/' + video_id, video_id)
  57. title = media['title']
  58. formats = []
  59. for encoding in media.get('encodings', []):
  60. m3u8_url = encoding.get('master_playlist_url')
  61. if m3u8_url:
  62. formats.extend(self._extract_m3u8_formats(
  63. m3u8_url, video_id, 'mp4', 'm3u8_native',
  64. m3u8_id='hls', fatal=False))
  65. encoding_url = encoding.get('url')
  66. file_url = encoding.get('file_url')
  67. if not encoding_url and not file_url:
  68. continue
  69. f = {
  70. 'width': int_or_none(encoding.get('width')),
  71. 'height': int_or_none(encoding.get('height')),
  72. 'vbr': int_or_none(encoding.get('video_bitrate')),
  73. 'abr': int_or_none(encoding.get('audio_bitrate')),
  74. 'filesize': int_or_none(encoding.get('size_in_bytes')),
  75. 'vcodec': encoding.get('video_codec'),
  76. 'acodec': encoding.get('audio_codec'),
  77. 'container': encoding.get('container_type'),
  78. }
  79. for f_url in (encoding_url, file_url):
  80. if not f_url:
  81. continue
  82. fmt = f.copy()
  83. rtmp = re.search(r'^(?P<url>rtmpe?://(?P<host>[^/]+)/(?P<app>.+))/(?P<playpath>mp[34]:.+)$', f_url)
  84. if rtmp:
  85. fmt.update({
  86. 'url': rtmp.group('url'),
  87. 'play_path': rtmp.group('playpath'),
  88. 'app': rtmp.group('app'),
  89. 'ext': 'flv',
  90. 'format_id': 'rtmp',
  91. })
  92. else:
  93. fmt.update({
  94. 'url': f_url,
  95. 'format_id': 'http',
  96. })
  97. formats.append(fmt)
  98. self._sort_formats(formats)
  99. subtitles = {}
  100. for closed_caption in media.get('closed_captions', []):
  101. sub_url = closed_caption.get('file')
  102. if not sub_url:
  103. continue
  104. lang = closed_caption.get('code') or closed_caption.get('language') or 'en'
  105. subtitles.setdefault(lang, []).append({
  106. 'url': sub_url,
  107. })
  108. return {
  109. 'id': video_id,
  110. 'formats': formats,
  111. 'title': title,
  112. 'description': media.get('description'),
  113. 'thumbnail': media.get('image_large') or media.get('image_medium') or media.get('image_small'),
  114. 'duration': int_or_none(media.get('duration')),
  115. 'tags': media.get('tags'),
  116. 'subtitles': subtitles,
  117. }
  118. class CuriosityStreamCollectionIE(CuriosityStreamBaseIE):
  119. IE_NAME = 'curiositystream:collection'
  120. _VALID_URL = r'https?://(?:app\.)?curiositystream\.com/(?:collection|series)/(?P<id>\d+)'
  121. _TESTS = [{
  122. 'url': 'https://app.curiositystream.com/collection/2',
  123. 'info_dict': {
  124. 'id': '2',
  125. 'title': 'Curious Minds: The Internet',
  126. 'description': 'How is the internet shaping our lives in the 21st Century?',
  127. },
  128. 'playlist_mincount': 17,
  129. }, {
  130. 'url': 'https://curiositystream.com/series/2',
  131. 'only_matching': True,
  132. }]
  133. def _real_extract(self, url):
  134. collection_id = self._match_id(url)
  135. collection = self._call_api(
  136. 'collections/' + collection_id, collection_id)
  137. entries = []
  138. for media in collection.get('media', []):
  139. media_id = compat_str(media.get('id'))
  140. entries.append(self.url_result(
  141. 'https://curiositystream.com/video/' + media_id,
  142. CuriosityStreamIE.ie_key(), media_id))
  143. return self.playlist_result(
  144. entries, collection_id,
  145. collection.get('title'), collection.get('description'))