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.

212 lines
7.4 KiB

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