logo

youtube-dl

[mirror] Download/Watch videos from video hostersgit clone https://hacktivis.me/git/mirror/youtube-dl.git

curiositystream.py (6829B)


  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, query=None):
  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, query=query)
  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. 'info_dict': {
  47. 'id': '2',
  48. 'ext': 'mp4',
  49. 'title': 'How Did You Develop The Internet?',
  50. 'description': 'Vint Cerf, Google\'s Chief Internet Evangelist, describes how he and Bob Kahn created the internet.',
  51. },
  52. 'params': {
  53. 'format': 'bestvideo',
  54. # m3u8 download
  55. 'skip_download': True,
  56. },
  57. }
  58. def _real_extract(self, url):
  59. video_id = self._match_id(url)
  60. formats = []
  61. for encoding_format in ('m3u8', 'mpd'):
  62. media = self._call_api('media/' + video_id, video_id, query={
  63. 'encodingsNew': 'true',
  64. 'encodingsFormat': encoding_format,
  65. })
  66. for encoding in media.get('encodings', []):
  67. playlist_url = encoding.get('master_playlist_url')
  68. if encoding_format == 'm3u8':
  69. # use `m3u8` entry_protocol until EXT-X-MAP is properly supported by `m3u8_native` entry_protocol
  70. formats.extend(self._extract_m3u8_formats(
  71. playlist_url, video_id, 'mp4',
  72. m3u8_id='hls', fatal=False))
  73. elif encoding_format == 'mpd':
  74. formats.extend(self._extract_mpd_formats(
  75. playlist_url, video_id, mpd_id='dash', fatal=False))
  76. encoding_url = encoding.get('url')
  77. file_url = encoding.get('file_url')
  78. if not encoding_url and not file_url:
  79. continue
  80. f = {
  81. 'width': int_or_none(encoding.get('width')),
  82. 'height': int_or_none(encoding.get('height')),
  83. 'vbr': int_or_none(encoding.get('video_bitrate')),
  84. 'abr': int_or_none(encoding.get('audio_bitrate')),
  85. 'filesize': int_or_none(encoding.get('size_in_bytes')),
  86. 'vcodec': encoding.get('video_codec'),
  87. 'acodec': encoding.get('audio_codec'),
  88. 'container': encoding.get('container_type'),
  89. }
  90. for f_url in (encoding_url, file_url):
  91. if not f_url:
  92. continue
  93. fmt = f.copy()
  94. rtmp = re.search(r'^(?P<url>rtmpe?://(?P<host>[^/]+)/(?P<app>.+))/(?P<playpath>mp[34]:.+)$', f_url)
  95. if rtmp:
  96. fmt.update({
  97. 'url': rtmp.group('url'),
  98. 'play_path': rtmp.group('playpath'),
  99. 'app': rtmp.group('app'),
  100. 'ext': 'flv',
  101. 'format_id': 'rtmp',
  102. })
  103. else:
  104. fmt.update({
  105. 'url': f_url,
  106. 'format_id': 'http',
  107. })
  108. formats.append(fmt)
  109. self._sort_formats(formats)
  110. title = media['title']
  111. subtitles = {}
  112. for closed_caption in media.get('closed_captions', []):
  113. sub_url = closed_caption.get('file')
  114. if not sub_url:
  115. continue
  116. lang = closed_caption.get('code') or closed_caption.get('language') or 'en'
  117. subtitles.setdefault(lang, []).append({
  118. 'url': sub_url,
  119. })
  120. return {
  121. 'id': video_id,
  122. 'formats': formats,
  123. 'title': title,
  124. 'description': media.get('description'),
  125. 'thumbnail': media.get('image_large') or media.get('image_medium') or media.get('image_small'),
  126. 'duration': int_or_none(media.get('duration')),
  127. 'tags': media.get('tags'),
  128. 'subtitles': subtitles,
  129. }
  130. class CuriosityStreamCollectionIE(CuriosityStreamBaseIE):
  131. IE_NAME = 'curiositystream:collection'
  132. _VALID_URL = r'https?://(?:app\.)?curiositystream\.com/(?:collections?|series)/(?P<id>\d+)'
  133. _TESTS = [{
  134. 'url': 'https://app.curiositystream.com/collection/2',
  135. 'info_dict': {
  136. 'id': '2',
  137. 'title': 'Curious Minds: The Internet',
  138. 'description': 'How is the internet shaping our lives in the 21st Century?',
  139. },
  140. 'playlist_mincount': 16,
  141. }, {
  142. 'url': 'https://curiositystream.com/series/2',
  143. 'only_matching': True,
  144. }, {
  145. 'url': 'https://curiositystream.com/collections/36',
  146. 'only_matching': True,
  147. }]
  148. def _real_extract(self, url):
  149. collection_id = self._match_id(url)
  150. collection = self._call_api(
  151. 'collections/' + collection_id, collection_id)
  152. entries = []
  153. for media in collection.get('media', []):
  154. media_id = compat_str(media.get('id'))
  155. entries.append(self.url_result(
  156. 'https://curiositystream.com/video/' + media_id,
  157. CuriosityStreamIE.ie_key(), media_id))
  158. return self.playlist_result(
  159. entries, collection_id,
  160. collection.get('title'), collection.get('description'))