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.

290 lines
10 KiB

11 years ago
11 years ago
11 years ago
11 years ago
10 years ago
10 years ago
  1. from __future__ import unicode_literals
  2. import time
  3. import hmac
  4. import hashlib
  5. import itertools
  6. from ..utils import (
  7. ExtractorError,
  8. int_or_none,
  9. parse_age_limit,
  10. parse_iso8601,
  11. )
  12. from .common import InfoExtractor
  13. class VikiBaseIE(InfoExtractor):
  14. _VALID_URL_BASE = r'https?://(?:www\.)?viki\.(?:com|net|mx|jp|fr)/'
  15. _API_QUERY_TEMPLATE = '/v4/%sapp=%s&t=%s&site=www.viki.com'
  16. _API_URL_TEMPLATE = 'http://api.viki.io%s&sig=%s'
  17. _APP = '65535a'
  18. _APP_VERSION = '2.2.5.1428709186'
  19. _APP_SECRET = '-$iJ}@p7!G@SyU/je1bEyWg}upLu-6V6-Lg9VD(]siH,r.,m-r|ulZ,U4LC/SeR)'
  20. def _prepare_call(self, path, timestamp=None):
  21. path += '?' if '?' not in path else '&'
  22. if not timestamp:
  23. timestamp = int(time.time())
  24. query = self._API_QUERY_TEMPLATE % (path, self._APP, timestamp)
  25. sig = hmac.new(
  26. self._APP_SECRET.encode('ascii'),
  27. query.encode('ascii'),
  28. hashlib.sha1
  29. ).hexdigest()
  30. return self._API_URL_TEMPLATE % (query, sig)
  31. def _call_api(self, path, video_id, note, timestamp=None):
  32. resp = self._download_json(
  33. self._prepare_call(path, timestamp), video_id, note)
  34. error = resp.get('error')
  35. if error:
  36. if error == 'invalid timestamp':
  37. resp = self._download_json(
  38. self._prepare_call(path, int(resp['current_timestamp'])),
  39. video_id, '%s (retry)' % note)
  40. error = resp.get('error')
  41. if error:
  42. self._raise_error(resp['error'])
  43. return resp
  44. def _raise_error(self, error):
  45. raise ExtractorError(
  46. '%s returned error: %s' % (self.IE_NAME, error),
  47. expected=True)
  48. class VikiIE(VikiBaseIE):
  49. IE_NAME = 'viki'
  50. _VALID_URL = r'%s(?:videos|player)/(?P<id>[0-9]+v)' % VikiBaseIE._VALID_URL_BASE
  51. _TESTS = [{
  52. 'url': 'http://www.viki.com/videos/1023585v-heirs-episode-14',
  53. 'info_dict': {
  54. 'id': '1023585v',
  55. 'ext': 'mp4',
  56. 'title': 'Heirs Episode 14',
  57. 'uploader': 'SBS',
  58. 'description': 'md5:c4b17b9626dd4b143dcc4d855ba3474e',
  59. 'upload_date': '20131121',
  60. 'age_limit': 13,
  61. },
  62. 'skip': 'Blocked in the US',
  63. }, {
  64. # clip
  65. 'url': 'http://www.viki.com/videos/1067139v-the-avengers-age-of-ultron-press-conference',
  66. 'md5': '86c0b5dbd4d83a6611a79987cc7a1989',
  67. 'info_dict': {
  68. 'id': '1067139v',
  69. 'ext': 'mp4',
  70. 'title': "'The Avengers: Age of Ultron' Press Conference",
  71. 'description': 'md5:d70b2f9428f5488321bfe1db10d612ea',
  72. 'duration': 352,
  73. 'timestamp': 1430380829,
  74. 'upload_date': '20150430',
  75. 'uploader': 'Arirang TV',
  76. 'like_count': int,
  77. 'age_limit': 0,
  78. }
  79. }, {
  80. 'url': 'http://www.viki.com/videos/1048879v-ankhon-dekhi',
  81. 'info_dict': {
  82. 'id': '1048879v',
  83. 'ext': 'mp4',
  84. 'title': 'Ankhon Dekhi',
  85. 'duration': 6512,
  86. 'timestamp': 1408532356,
  87. 'upload_date': '20140820',
  88. 'uploader': 'Spuul',
  89. 'like_count': int,
  90. 'age_limit': 13,
  91. },
  92. 'params': {
  93. # m3u8 download
  94. 'skip_download': True,
  95. }
  96. }, {
  97. # episode
  98. 'url': 'http://www.viki.com/videos/44699v-boys-over-flowers-episode-1',
  99. 'md5': '190f3ef426005ba3a080a63325955bc3',
  100. 'info_dict': {
  101. 'id': '44699v',
  102. 'ext': 'mp4',
  103. 'title': 'Boys Over Flowers - Episode 1',
  104. 'description': 'md5:52617e4f729c7d03bfd4bcbbb6e946f2',
  105. 'duration': 4155,
  106. 'timestamp': 1270496524,
  107. 'upload_date': '20100405',
  108. 'uploader': 'group8',
  109. 'like_count': int,
  110. 'age_limit': 13,
  111. }
  112. }, {
  113. # youtube external
  114. 'url': 'http://www.viki.com/videos/50562v-poor-nastya-complete-episode-1',
  115. 'md5': '216d1afdc0c64d1febc1e9f2bd4b864b',
  116. 'info_dict': {
  117. 'id': '50562v',
  118. 'ext': 'mp4',
  119. 'title': 'Poor Nastya [COMPLETE] - Episode 1',
  120. 'description': '',
  121. 'duration': 607,
  122. 'timestamp': 1274949505,
  123. 'upload_date': '20101213',
  124. 'uploader': 'ad14065n',
  125. 'uploader_id': 'ad14065n',
  126. 'like_count': int,
  127. 'age_limit': 13,
  128. }
  129. }, {
  130. 'url': 'http://www.viki.com/player/44699v',
  131. 'only_matching': True,
  132. }]
  133. def _real_extract(self, url):
  134. video_id = self._match_id(url)
  135. video = self._call_api(
  136. 'videos/%s.json' % video_id, video_id, 'Downloading video JSON')
  137. title = None
  138. titles = video.get('titles')
  139. if titles:
  140. title = titles.get('en') or titles[titles.keys()[0]]
  141. if not title:
  142. title = 'Episode %d' % video.get('number') if video.get('type') == 'episode' else video.get('id') or video_id
  143. container_titles = video.get('container', {}).get('titles')
  144. if container_titles:
  145. container_title = container_titles.get('en') or container_titles[container_titles.keys()[0]]
  146. title = '%s - %s' % (container_title, title)
  147. descriptions = video.get('descriptions')
  148. description = descriptions.get('en') or descriptions[titles.keys()[0]] if descriptions else None
  149. duration = int_or_none(video.get('duration'))
  150. timestamp = parse_iso8601(video.get('created_at'))
  151. uploader = video.get('author')
  152. like_count = int_or_none(video.get('likes', {}).get('count'))
  153. age_limit = parse_age_limit(video.get('rating'))
  154. thumbnails = []
  155. for thumbnail_id, thumbnail in video.get('images', {}).items():
  156. thumbnails.append({
  157. 'id': thumbnail_id,
  158. 'url': thumbnail.get('url'),
  159. })
  160. subtitles = {}
  161. for subtitle_lang, _ in video.get('subtitle_completions', {}).items():
  162. subtitles[subtitle_lang] = [{
  163. 'ext': subtitles_format,
  164. 'url': self._prepare_call(
  165. 'videos/%s/subtitles/%s.%s' % (video_id, subtitle_lang, subtitles_format)),
  166. } for subtitles_format in ('srt', 'vtt')]
  167. result = {
  168. 'id': video_id,
  169. 'title': title,
  170. 'description': description,
  171. 'duration': duration,
  172. 'timestamp': timestamp,
  173. 'uploader': uploader,
  174. 'like_count': like_count,
  175. 'age_limit': age_limit,
  176. 'thumbnails': thumbnails,
  177. 'subtitles': subtitles,
  178. }
  179. streams = self._call_api(
  180. 'videos/%s/streams.json' % video_id, video_id,
  181. 'Downloading video streams JSON')
  182. if 'external' in streams:
  183. result.update({
  184. '_type': 'url_transparent',
  185. 'url': streams['external']['url'],
  186. })
  187. return result
  188. formats = []
  189. for format_id, stream_dict in streams.items():
  190. height = self._search_regex(
  191. r'^(\d+)[pP]$', format_id, 'height', default=None)
  192. for protocol, format_dict in stream_dict.items():
  193. if format_id == 'm3u8':
  194. formats = self._extract_m3u8_formats(
  195. format_dict['url'], video_id, 'mp4', m3u8_id='m3u8-%s' % protocol)
  196. else:
  197. formats.append({
  198. 'url': format_dict['url'],
  199. 'format_id': '%s-%s' % (format_id, protocol),
  200. 'height': height,
  201. })
  202. self._sort_formats(formats)
  203. result['formats'] = formats
  204. return result
  205. class VikiChannelIE(VikiBaseIE):
  206. IE_NAME = 'viki:channel'
  207. _VALID_URL = r'%s(?:tv|news|movies|artists)/(?P<id>[0-9]+c)' % VikiBaseIE._VALID_URL_BASE
  208. _TESTS = [{
  209. 'url': 'http://www.viki.com/tv/50c-boys-over-flowers',
  210. 'info_dict': {
  211. 'id': '50c',
  212. 'title': 'Boys Over Flowers',
  213. 'description': 'md5:ecd3cff47967fe193cff37c0bec52790',
  214. },
  215. 'playlist_count': 70,
  216. }, {
  217. 'url': 'http://www.viki.com/tv/1354c-poor-nastya-complete',
  218. 'info_dict': {
  219. 'id': '1354c',
  220. 'title': 'Poor Nastya [COMPLETE]',
  221. 'description': 'md5:05bf5471385aa8b21c18ad450e350525',
  222. },
  223. 'playlist_count': 127,
  224. }, {
  225. 'url': 'http://www.viki.com/news/24569c-showbiz-korea',
  226. 'only_matching': True,
  227. }, {
  228. 'url': 'http://www.viki.com/movies/22047c-pride-and-prejudice-2005',
  229. 'only_matching': True,
  230. }, {
  231. 'url': 'http://www.viki.com/artists/2141c-shinee',
  232. 'only_matching': True,
  233. }]
  234. _PER_PAGE = 25
  235. def _real_extract(self, url):
  236. channel_id = self._match_id(url)
  237. channel = self._call_api(
  238. 'containers/%s.json' % channel_id, channel_id,
  239. 'Downloading channel JSON')
  240. titles = channel['titles']
  241. title = titles.get('en') or titles[titles.keys()[0]]
  242. descriptions = channel['descriptions']
  243. description = descriptions.get('en') or descriptions[descriptions.keys()[0]]
  244. entries = []
  245. for video_type in ('episodes', 'clips', 'movies'):
  246. for page_num in itertools.count(1):
  247. page = self._call_api(
  248. 'containers/%s/%s.json?per_page=%d&sort=number&direction=asc&with_paging=true&page=%d'
  249. % (channel_id, video_type, self._PER_PAGE, page_num), channel_id,
  250. 'Downloading %s JSON page #%d' % (video_type, page_num))
  251. for video in page['response']:
  252. video_id = video['id']
  253. entries.append(self.url_result(
  254. 'http://www.viki.com/videos/%s' % video_id, 'Viki'))
  255. if not page['pagination']['next']:
  256. break
  257. return self.playlist_result(entries, channel_id, title, description)