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.

277 lines
9.1 KiB

9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
  1. # coding: utf-8
  2. from __future__ import unicode_literals
  3. import hashlib
  4. import math
  5. import random
  6. import time
  7. import uuid
  8. from .common import InfoExtractor
  9. from ..compat import compat_urllib_parse
  10. from ..utils import ExtractorError
  11. class IqiyiIE(InfoExtractor):
  12. IE_NAME = 'iqiyi'
  13. IE_DESC = '爱奇艺'
  14. _VALID_URL = r'http://(?:www\.)iqiyi.com/v_.+?\.html'
  15. _TESTS = [{
  16. 'url': 'http://www.iqiyi.com/v_19rrojlavg.html',
  17. 'md5': '2cb594dc2781e6c941a110d8f358118b',
  18. 'info_dict': {
  19. 'id': '9c1fb1b99d192b21c559e5a1a2cb3c73',
  20. 'title': '美国德州空中惊现奇异云团 酷似UFO',
  21. 'ext': 'f4v',
  22. }
  23. }, {
  24. 'url': 'http://www.iqiyi.com/v_19rrhnnclk.html',
  25. 'info_dict': {
  26. 'id': 'e3f585b550a280af23c98b6cb2be19fb',
  27. 'title': '名侦探柯南第752集',
  28. },
  29. 'playlist': [{
  30. 'info_dict': {
  31. 'id': 'e3f585b550a280af23c98b6cb2be19fb_part1',
  32. 'ext': 'f4v',
  33. 'title': '名侦探柯南第752集',
  34. },
  35. }, {
  36. 'info_dict': {
  37. 'id': 'e3f585b550a280af23c98b6cb2be19fb_part2',
  38. 'ext': 'f4v',
  39. 'title': '名侦探柯南第752集',
  40. },
  41. }, {
  42. 'info_dict': {
  43. 'id': 'e3f585b550a280af23c98b6cb2be19fb_part3',
  44. 'ext': 'f4v',
  45. 'title': '名侦探柯南第752集',
  46. },
  47. }, {
  48. 'info_dict': {
  49. 'id': 'e3f585b550a280af23c98b6cb2be19fb_part4',
  50. 'ext': 'f4v',
  51. 'title': '名侦探柯南第752集',
  52. },
  53. }, {
  54. 'info_dict': {
  55. 'id': 'e3f585b550a280af23c98b6cb2be19fb_part5',
  56. 'ext': 'f4v',
  57. 'title': '名侦探柯南第752集',
  58. },
  59. }, {
  60. 'info_dict': {
  61. 'id': 'e3f585b550a280af23c98b6cb2be19fb_part6',
  62. 'ext': 'f4v',
  63. 'title': '名侦探柯南第752集',
  64. },
  65. }, {
  66. 'info_dict': {
  67. 'id': 'e3f585b550a280af23c98b6cb2be19fb_part7',
  68. 'ext': 'f4v',
  69. 'title': '名侦探柯南第752集',
  70. },
  71. }, {
  72. 'info_dict': {
  73. 'id': 'e3f585b550a280af23c98b6cb2be19fb_part8',
  74. 'ext': 'f4v',
  75. 'title': '名侦探柯南第752集',
  76. },
  77. }],
  78. 'params': {
  79. 'skip_download': True,
  80. },
  81. }]
  82. _FORMATS_MAP = [
  83. ('1', 'h6'),
  84. ('2', 'h5'),
  85. ('3', 'h4'),
  86. ('4', 'h3'),
  87. ('5', 'h2'),
  88. ('10', 'h1'),
  89. ]
  90. @staticmethod
  91. def md5_text(text):
  92. return hashlib.md5(text.encode('utf-8')).hexdigest()
  93. def construct_video_urls(self, data, video_id, _uuid):
  94. def do_xor(x, y):
  95. a = y % 3
  96. if a == 1:
  97. return x ^ 121
  98. if a == 2:
  99. return x ^ 72
  100. return x ^ 103
  101. def get_encode_code(l):
  102. a = 0
  103. b = l.split('-')
  104. c = len(b)
  105. s = ''
  106. for i in range(c - 1, -1, -1):
  107. a = do_xor(int(b[c - i - 1], 16), i)
  108. s += chr(a)
  109. return s[::-1]
  110. def get_path_key(x, format_id, segment_index):
  111. mg = ')(*&^flash@#$%a'
  112. tm = self._download_json(
  113. 'http://data.video.qiyi.com/t?tn=' + str(random.random()), video_id,
  114. note='Download path key of segment %d for format %s' % (segment_index + 1, format_id)
  115. )['t']
  116. t = str(int(math.floor(int(tm) / (600.0))))
  117. return self.md5_text(t + mg + x)
  118. video_urls_dict = {}
  119. for format_item in data['vp']['tkl'][0]['vs']:
  120. if 0 < int(format_item['bid']) <= 10:
  121. format_id = self.get_format(format_item['bid'])
  122. else:
  123. continue
  124. video_urls = []
  125. video_urls_info = format_item['fs']
  126. if not format_item['fs'][0]['l'].startswith('/'):
  127. t = get_encode_code(format_item['fs'][0]['l'])
  128. if t.endswith('mp4'):
  129. video_urls_info = format_item['flvs']
  130. for segment_index, segment in enumerate(video_urls_info):
  131. vl = segment['l']
  132. if not vl.startswith('/'):
  133. vl = get_encode_code(vl)
  134. key = get_path_key(
  135. vl.split('/')[-1].split('.')[0], format_id, segment_index)
  136. filesize = segment['b']
  137. base_url = data['vp']['du'].split('/')
  138. base_url.insert(-1, key)
  139. base_url = '/'.join(base_url)
  140. param = {
  141. 'su': _uuid,
  142. 'qyid': uuid.uuid4().hex,
  143. 'client': '',
  144. 'z': '',
  145. 'bt': '',
  146. 'ct': '',
  147. 'tn': str(int(time.time()))
  148. }
  149. api_video_url = base_url + vl + '?' + \
  150. compat_urllib_parse.urlencode(param)
  151. js = self._download_json(
  152. api_video_url, video_id,
  153. note='Download video info of segment %d for format %s' % (segment_index + 1, format_id))
  154. video_url = js['l']
  155. video_urls.append(
  156. (video_url, filesize))
  157. video_urls_dict[format_id] = video_urls
  158. return video_urls_dict
  159. def get_format(self, bid):
  160. matched_format_ids = [_format_id for _bid, _format_id in self._FORMATS_MAP if _bid == str(bid)]
  161. return matched_format_ids[0] if len(matched_format_ids) else None
  162. def get_bid(self, format_id):
  163. matched_bids = [_bid for _bid, _format_id in self._FORMATS_MAP if _format_id == format_id]
  164. return matched_bids[0] if len(matched_bids) else None
  165. def get_raw_data(self, tvid, video_id, enc_key, _uuid):
  166. tm = str(int(time.time()))
  167. tail = tm + tvid
  168. param = {
  169. 'key': 'fvip',
  170. 'src': self.md5_text('youtube-dl'),
  171. 'tvId': tvid,
  172. 'vid': video_id,
  173. 'vinfo': 1,
  174. 'tm': tm,
  175. 'enc': self.md5_text((enc_key + tail)[1:64:2] + tail),
  176. 'qyid': _uuid,
  177. 'tn': random.random(),
  178. 'um': 0,
  179. 'authkey': self.md5_text(self.md5_text('') + tail),
  180. }
  181. api_url = 'http://cache.video.qiyi.com/vms' + '?' + \
  182. compat_urllib_parse.urlencode(param)
  183. raw_data = self._download_json(api_url, video_id)
  184. return raw_data
  185. def get_enc_key(self, swf_url, video_id):
  186. # TODO: automatic key extraction
  187. enc_key = 'eac64f22daf001da6ba9aa8da4d501508bbe90a4d4091fea3b0582a85b38c2cc' # last update at 2015-09-23-23 for Zombie::bite
  188. return enc_key
  189. def _real_extract(self, url):
  190. webpage = self._download_webpage(
  191. url, 'temp_id', note='download video page')
  192. tvid = self._search_regex(
  193. r'data-player-tvid\s*=\s*[\'"](\d+)', webpage, 'tvid')
  194. video_id = self._search_regex(
  195. r'data-player-videoid\s*=\s*[\'"]([a-f\d]+)', webpage, 'video_id')
  196. swf_url = self._search_regex(
  197. r'(http://[^\'"]+MainPlayer[^.]+\.swf)', webpage, 'swf player URL')
  198. _uuid = uuid.uuid4().hex
  199. enc_key = self.get_enc_key(swf_url, video_id)
  200. raw_data = self.get_raw_data(tvid, video_id, enc_key, _uuid)
  201. if raw_data['code'] != 'A000000':
  202. raise ExtractorError('Unable to load data. Error code: ' + raw_data['code'])
  203. if not raw_data['data']['vp']['tkl']:
  204. raise ExtractorError('No support iQiqy VIP video')
  205. data = raw_data['data']
  206. title = data['vi']['vn']
  207. # generate video_urls_dict
  208. video_urls_dict = self.construct_video_urls(
  209. data, video_id, _uuid)
  210. # construct info
  211. entries = []
  212. for format_id in video_urls_dict:
  213. video_urls = video_urls_dict[format_id]
  214. for i, video_url_info in enumerate(video_urls):
  215. if len(entries) < i + 1:
  216. entries.append({'formats': []})
  217. entries[i]['formats'].append(
  218. {
  219. 'url': video_url_info[0],
  220. 'filesize': video_url_info[-1],
  221. 'format_id': format_id,
  222. 'preference': int(self.get_bid(format_id))
  223. }
  224. )
  225. for i in range(len(entries)):
  226. self._sort_formats(entries[i]['formats'])
  227. entries[i].update(
  228. {
  229. 'id': '%s_part%d' % (video_id, i + 1),
  230. 'title': title,
  231. }
  232. )
  233. if len(entries) > 1:
  234. info = {
  235. '_type': 'multi_video',
  236. 'id': video_id,
  237. 'title': title,
  238. 'entries': entries,
  239. }
  240. else:
  241. info = entries[0]
  242. info['id'] = video_id
  243. info['title'] = title
  244. return info