logo

youtube-dl

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

noco.py (8443B)


  1. # coding: utf-8
  2. from __future__ import unicode_literals
  3. import re
  4. import time
  5. import hashlib
  6. from .common import InfoExtractor
  7. from ..compat import (
  8. compat_str,
  9. compat_urlparse,
  10. )
  11. from ..utils import (
  12. clean_html,
  13. ExtractorError,
  14. int_or_none,
  15. float_or_none,
  16. parse_iso8601,
  17. sanitized_Request,
  18. urlencode_postdata,
  19. )
  20. class NocoIE(InfoExtractor):
  21. _VALID_URL = r'https?://(?:(?:www\.)?noco\.tv/emission/|player\.noco\.tv/\?idvideo=)(?P<id>\d+)'
  22. _LOGIN_URL = 'https://noco.tv/do.php'
  23. _API_URL_TEMPLATE = 'https://api.noco.tv/1.1/%s?ts=%s&tk=%s'
  24. _SUB_LANG_TEMPLATE = '&sub_lang=%s'
  25. _NETRC_MACHINE = 'noco'
  26. _TESTS = [
  27. {
  28. 'url': 'http://noco.tv/emission/11538/nolife/ami-ami-idol-hello-france/',
  29. 'md5': '0a993f0058ddbcd902630b2047ef710e',
  30. 'info_dict': {
  31. 'id': '11538',
  32. 'ext': 'mp4',
  33. 'title': 'Ami Ami Idol - Hello! France',
  34. 'description': 'md5:4eaab46ab68fa4197a317a88a53d3b86',
  35. 'upload_date': '20140412',
  36. 'uploader': 'Nolife',
  37. 'uploader_id': 'NOL',
  38. 'duration': 2851.2,
  39. },
  40. 'skip': 'Requires noco account',
  41. },
  42. {
  43. 'url': 'http://noco.tv/emission/12610/lbl42/the-guild/s01e01-wake-up-call',
  44. 'md5': 'c190f1f48e313c55838f1f412225934d',
  45. 'info_dict': {
  46. 'id': '12610',
  47. 'ext': 'mp4',
  48. 'title': 'The Guild #1 - Wake-Up Call',
  49. 'timestamp': 1403863200,
  50. 'upload_date': '20140627',
  51. 'uploader': 'LBL42',
  52. 'uploader_id': 'LBL',
  53. 'duration': 233.023,
  54. },
  55. 'skip': 'Requires noco account',
  56. }
  57. ]
  58. def _real_initialize(self):
  59. self._login()
  60. def _login(self):
  61. username, password = self._get_login_info()
  62. if username is None:
  63. return
  64. login = self._download_json(
  65. self._LOGIN_URL, None, 'Logging in',
  66. data=urlencode_postdata({
  67. 'a': 'login',
  68. 'cookie': '1',
  69. 'username': username,
  70. 'password': password,
  71. }),
  72. headers={
  73. 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
  74. })
  75. if 'erreur' in login:
  76. raise ExtractorError('Unable to login: %s' % clean_html(login['erreur']), expected=True)
  77. @staticmethod
  78. def _ts():
  79. return int(time.time() * 1000)
  80. def _call_api(self, path, video_id, note, sub_lang=None):
  81. ts = compat_str(self._ts() + self._ts_offset)
  82. tk = hashlib.md5((hashlib.md5(ts.encode('ascii')).hexdigest() + '#8S?uCraTedap6a').encode('ascii')).hexdigest()
  83. url = self._API_URL_TEMPLATE % (path, ts, tk)
  84. if sub_lang:
  85. url += self._SUB_LANG_TEMPLATE % sub_lang
  86. request = sanitized_Request(url)
  87. request.add_header('Referer', self._referer)
  88. resp = self._download_json(request, video_id, note)
  89. if isinstance(resp, dict) and resp.get('error'):
  90. self._raise_error(resp['error'], resp['description'])
  91. return resp
  92. def _raise_error(self, error, description):
  93. raise ExtractorError(
  94. '%s returned error: %s - %s' % (self.IE_NAME, error, description),
  95. expected=True)
  96. def _real_extract(self, url):
  97. video_id = self._match_id(url)
  98. # Timestamp adjustment offset between server time and local time
  99. # must be calculated in order to use timestamps closest to server's
  100. # in all API requests (see https://github.com/ytdl-org/youtube-dl/issues/7864)
  101. webpage = self._download_webpage(url, video_id)
  102. player_url = self._search_regex(
  103. r'(["\'])(?P<player>https?://noco\.tv/(?:[^/]+/)+NocoPlayer.+?\.swf.*?)\1',
  104. webpage, 'noco player', group='player',
  105. default='http://noco.tv/cdata/js/player/NocoPlayer-v1.2.40.swf')
  106. qs = compat_urlparse.parse_qs(compat_urlparse.urlparse(player_url).query)
  107. ts = int_or_none(qs.get('ts', [None])[0])
  108. self._ts_offset = ts - self._ts() if ts else 0
  109. self._referer = player_url
  110. medias = self._call_api(
  111. 'shows/%s/medias' % video_id,
  112. video_id, 'Downloading video JSON')
  113. show = self._call_api(
  114. 'shows/by_id/%s' % video_id,
  115. video_id, 'Downloading show JSON')[0]
  116. options = self._call_api(
  117. 'users/init', video_id,
  118. 'Downloading user options JSON')['options']
  119. audio_lang_pref = options.get('audio_language') or options.get('language', 'fr')
  120. if audio_lang_pref == 'original':
  121. audio_lang_pref = show['original_lang']
  122. if len(medias) == 1:
  123. audio_lang_pref = list(medias.keys())[0]
  124. elif audio_lang_pref not in medias:
  125. audio_lang_pref = 'fr'
  126. qualities = self._call_api(
  127. 'qualities',
  128. video_id, 'Downloading qualities JSON')
  129. formats = []
  130. for audio_lang, audio_lang_dict in medias.items():
  131. preference = 1 if audio_lang == audio_lang_pref else 0
  132. for sub_lang, lang_dict in audio_lang_dict['video_list'].items():
  133. for format_id, fmt in lang_dict['quality_list'].items():
  134. format_id_extended = 'audio-%s_sub-%s_%s' % (audio_lang, sub_lang, format_id)
  135. video = self._call_api(
  136. 'shows/%s/video/%s/%s' % (video_id, format_id.lower(), audio_lang),
  137. video_id, 'Downloading %s video JSON' % format_id_extended,
  138. sub_lang if sub_lang != 'none' else None)
  139. file_url = video['file']
  140. if not file_url:
  141. continue
  142. if file_url in ['forbidden', 'not found']:
  143. popmessage = video['popmessage']
  144. self._raise_error(popmessage['title'], popmessage['message'])
  145. formats.append({
  146. 'url': file_url,
  147. 'format_id': format_id_extended,
  148. 'width': int_or_none(fmt.get('res_width')),
  149. 'height': int_or_none(fmt.get('res_lines')),
  150. 'abr': int_or_none(fmt.get('audiobitrate'), 1000),
  151. 'vbr': int_or_none(fmt.get('videobitrate'), 1000),
  152. 'filesize': int_or_none(fmt.get('filesize')),
  153. 'format_note': qualities[format_id].get('quality_name'),
  154. 'quality': qualities[format_id].get('priority'),
  155. 'preference': preference,
  156. })
  157. self._sort_formats(formats)
  158. timestamp = parse_iso8601(show.get('online_date_start_utc'), ' ')
  159. if timestamp is not None and timestamp < 0:
  160. timestamp = None
  161. uploader = show.get('partner_name')
  162. uploader_id = show.get('partner_key')
  163. duration = float_or_none(show.get('duration_ms'), 1000)
  164. thumbnails = []
  165. for thumbnail_key, thumbnail_url in show.items():
  166. m = re.search(r'^screenshot_(?P<width>\d+)x(?P<height>\d+)$', thumbnail_key)
  167. if not m:
  168. continue
  169. thumbnails.append({
  170. 'url': thumbnail_url,
  171. 'width': int(m.group('width')),
  172. 'height': int(m.group('height')),
  173. })
  174. episode = show.get('show_TT') or show.get('show_OT')
  175. family = show.get('family_TT') or show.get('family_OT')
  176. episode_number = show.get('episode_number')
  177. title = ''
  178. if family:
  179. title += family
  180. if episode_number:
  181. title += ' #' + compat_str(episode_number)
  182. if episode:
  183. title += ' - ' + compat_str(episode)
  184. description = show.get('show_resume') or show.get('family_resume')
  185. return {
  186. 'id': video_id,
  187. 'title': title,
  188. 'description': description,
  189. 'thumbnails': thumbnails,
  190. 'timestamp': timestamp,
  191. 'uploader': uploader,
  192. 'uploader_id': uploader_id,
  193. 'duration': duration,
  194. 'formats': formats,
  195. }