curiositystream.py 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  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. def _extract_media_info(self, media):
  42. video_id = compat_str(media['id'])
  43. limelight_media_id = media['limelight_media_id']
  44. title = media['title']
  45. formats = []
  46. for encoding in media.get('encodings', []):
  47. m3u8_url = encoding.get('master_playlist_url')
  48. if m3u8_url:
  49. formats.extend(self._extract_m3u8_formats(
  50. m3u8_url, video_id, 'mp4', 'm3u8_native',
  51. m3u8_id='hls', fatal=False))
  52. encoding_url = encoding.get('url')
  53. file_url = encoding.get('file_url')
  54. if not encoding_url and not file_url:
  55. continue
  56. f = {
  57. 'width': int_or_none(encoding.get('width')),
  58. 'height': int_or_none(encoding.get('height')),
  59. 'vbr': int_or_none(encoding.get('video_bitrate')),
  60. 'abr': int_or_none(encoding.get('audio_bitrate')),
  61. 'filesize': int_or_none(encoding.get('size_in_bytes')),
  62. 'vcodec': encoding.get('video_codec'),
  63. 'acodec': encoding.get('audio_codec'),
  64. 'container': encoding.get('container_type'),
  65. }
  66. for f_url in (encoding_url, file_url):
  67. if not f_url:
  68. continue
  69. fmt = f.copy()
  70. rtmp = re.search(r'^(?P<url>rtmpe?://(?P<host>[^/]+)/(?P<app>.+))/(?P<playpath>mp[34]:.+)$', f_url)
  71. if rtmp:
  72. fmt.update({
  73. 'url': rtmp.group('url'),
  74. 'play_path': rtmp.group('playpath'),
  75. 'app': rtmp.group('app'),
  76. 'ext': 'flv',
  77. 'format_id': 'rtmp',
  78. })
  79. else:
  80. fmt.update({
  81. 'url': f_url,
  82. 'format_id': 'http',
  83. })
  84. formats.append(fmt)
  85. self._sort_formats(formats)
  86. subtitles = {}
  87. for closed_caption in media.get('closed_captions', []):
  88. sub_url = closed_caption.get('file')
  89. if not sub_url:
  90. continue
  91. lang = closed_caption.get('code') or closed_caption.get('language') or 'en'
  92. subtitles.setdefault(lang, []).append({
  93. 'url': sub_url,
  94. })
  95. return {
  96. 'id': video_id,
  97. 'formats': formats,
  98. 'title': title,
  99. 'description': media.get('description'),
  100. 'thumbnail': media.get('image_large') or media.get('image_medium') or media.get('image_small'),
  101. 'duration': int_or_none(media.get('duration')),
  102. 'tags': media.get('tags'),
  103. 'subtitles': subtitles,
  104. }
  105. class CuriosityStreamIE(CuriosityStreamBaseIE):
  106. IE_NAME = 'curiositystream'
  107. _VALID_URL = r'https?://app\.curiositystream\.com/video/(?P<id>\d+)'
  108. _TEST = {
  109. 'url': 'https://app.curiositystream.com/video/2',
  110. 'md5': '262bb2f257ff301115f1973540de8983',
  111. 'info_dict': {
  112. 'id': '2',
  113. 'ext': 'mp4',
  114. 'title': 'How Did You Develop The Internet?',
  115. 'description': 'Vint Cerf, Google\'s Chief Internet Evangelist, describes how he and Bob Kahn created the internet.',
  116. }
  117. }
  118. def _real_extract(self, url):
  119. video_id = self._match_id(url)
  120. media = self._call_api('media/' + video_id, video_id)
  121. return self._extract_media_info(media)
  122. class CuriosityStreamCollectionIE(CuriosityStreamBaseIE):
  123. IE_NAME = 'curiositystream:collection'
  124. _VALID_URL = r'https?://app\.curiositystream\.com/collection/(?P<id>\d+)'
  125. _TEST = {
  126. 'url': 'https://app.curiositystream.com/collection/2',
  127. 'info_dict': {
  128. 'id': '2',
  129. 'title': 'Curious Minds: The Internet',
  130. 'description': 'How is the internet shaping our lives in the 21st Century?',
  131. },
  132. 'playlist_mincount': 12,
  133. }
  134. def _real_extract(self, url):
  135. collection_id = self._match_id(url)
  136. collection = self._call_api(
  137. 'collections/' + collection_id, collection_id)
  138. entries = []
  139. for media in collection.get('media', []):
  140. entries.append(self._extract_media_info(media))
  141. return self.playlist_result(
  142. entries, collection_id,
  143. collection.get('title'), collection.get('description'))