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.

287 lines
9.3 KiB

10 years ago
10 years ago
10 years ago
10 years ago
10 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://(?:[^.]+\.)?iqiyi\.com/.+\.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. 'url': 'http://www.iqiyi.com/w_19rt6o8t9p.html',
  83. 'only_matching': True,
  84. }, {
  85. 'url': 'http://www.iqiyi.com/a_19rrhbc6kt.html',
  86. 'only_matching': True,
  87. }, {
  88. 'url': 'http://yule.iqiyi.com/pcb.html',
  89. 'only_matching': True,
  90. }]
  91. _FORMATS_MAP = [
  92. ('1', 'h6'),
  93. ('2', 'h5'),
  94. ('3', 'h4'),
  95. ('4', 'h3'),
  96. ('5', 'h2'),
  97. ('10', 'h1'),
  98. ]
  99. @staticmethod
  100. def md5_text(text):
  101. return hashlib.md5(text.encode('utf-8')).hexdigest()
  102. def construct_video_urls(self, data, video_id, _uuid):
  103. def do_xor(x, y):
  104. a = y % 3
  105. if a == 1:
  106. return x ^ 121
  107. if a == 2:
  108. return x ^ 72
  109. return x ^ 103
  110. def get_encode_code(l):
  111. a = 0
  112. b = l.split('-')
  113. c = len(b)
  114. s = ''
  115. for i in range(c - 1, -1, -1):
  116. a = do_xor(int(b[c - i - 1], 16), i)
  117. s += chr(a)
  118. return s[::-1]
  119. def get_path_key(x, format_id, segment_index):
  120. mg = ')(*&^flash@#$%a'
  121. tm = self._download_json(
  122. 'http://data.video.qiyi.com/t?tn=' + str(random.random()), video_id,
  123. note='Download path key of segment %d for format %s' % (segment_index + 1, format_id)
  124. )['t']
  125. t = str(int(math.floor(int(tm) / (600.0))))
  126. return self.md5_text(t + mg + x)
  127. video_urls_dict = {}
  128. for format_item in data['vp']['tkl'][0]['vs']:
  129. if 0 < int(format_item['bid']) <= 10:
  130. format_id = self.get_format(format_item['bid'])
  131. else:
  132. continue
  133. video_urls = []
  134. video_urls_info = format_item['fs']
  135. if not format_item['fs'][0]['l'].startswith('/'):
  136. t = get_encode_code(format_item['fs'][0]['l'])
  137. if t.endswith('mp4'):
  138. video_urls_info = format_item['flvs']
  139. for segment_index, segment in enumerate(video_urls_info):
  140. vl = segment['l']
  141. if not vl.startswith('/'):
  142. vl = get_encode_code(vl)
  143. key = get_path_key(
  144. vl.split('/')[-1].split('.')[0], format_id, segment_index)
  145. filesize = segment['b']
  146. base_url = data['vp']['du'].split('/')
  147. base_url.insert(-1, key)
  148. base_url = '/'.join(base_url)
  149. param = {
  150. 'su': _uuid,
  151. 'qyid': uuid.uuid4().hex,
  152. 'client': '',
  153. 'z': '',
  154. 'bt': '',
  155. 'ct': '',
  156. 'tn': str(int(time.time()))
  157. }
  158. api_video_url = base_url + vl + '?' + \
  159. compat_urllib_parse.urlencode(param)
  160. js = self._download_json(
  161. api_video_url, video_id,
  162. note='Download video info of segment %d for format %s' % (segment_index + 1, format_id))
  163. video_url = js['l']
  164. video_urls.append(
  165. (video_url, filesize))
  166. video_urls_dict[format_id] = video_urls
  167. return video_urls_dict
  168. def get_format(self, bid):
  169. matched_format_ids = [_format_id for _bid, _format_id in self._FORMATS_MAP if _bid == str(bid)]
  170. return matched_format_ids[0] if len(matched_format_ids) else None
  171. def get_bid(self, format_id):
  172. matched_bids = [_bid for _bid, _format_id in self._FORMATS_MAP if _format_id == format_id]
  173. return matched_bids[0] if len(matched_bids) else None
  174. def get_raw_data(self, tvid, video_id, enc_key, _uuid):
  175. tm = str(int(time.time()))
  176. tail = tm + tvid
  177. param = {
  178. 'key': 'fvip',
  179. 'src': self.md5_text('youtube-dl'),
  180. 'tvId': tvid,
  181. 'vid': video_id,
  182. 'vinfo': 1,
  183. 'tm': tm,
  184. 'enc': self.md5_text(enc_key + tail),
  185. 'qyid': _uuid,
  186. 'tn': random.random(),
  187. 'um': 0,
  188. 'authkey': self.md5_text(self.md5_text('') + tail),
  189. }
  190. api_url = 'http://cache.video.qiyi.com/vms' + '?' + \
  191. compat_urllib_parse.urlencode(param)
  192. raw_data = self._download_json(api_url, video_id)
  193. return raw_data
  194. def get_enc_key(self, swf_url, video_id):
  195. # TODO: automatic key extraction
  196. # last update at 2015-12-18 for Zombie::bite
  197. enc_key = '8b6b683780897eb8d9a48a02ccc4817d'[::-1]
  198. return enc_key
  199. def _real_extract(self, url):
  200. webpage = self._download_webpage(
  201. url, 'temp_id', note='download video page')
  202. tvid = self._search_regex(
  203. r'data-player-tvid\s*=\s*[\'"](\d+)', webpage, 'tvid')
  204. video_id = self._search_regex(
  205. r'data-player-videoid\s*=\s*[\'"]([a-f\d]+)', webpage, 'video_id')
  206. swf_url = self._search_regex(
  207. r'(http://[^\'"]+MainPlayer[^.]+\.swf)', webpage, 'swf player URL')
  208. _uuid = uuid.uuid4().hex
  209. enc_key = self.get_enc_key(swf_url, video_id)
  210. raw_data = self.get_raw_data(tvid, video_id, enc_key, _uuid)
  211. if raw_data['code'] != 'A000000':
  212. raise ExtractorError('Unable to load data. Error code: ' + raw_data['code'])
  213. if not raw_data['data']['vp']['tkl']:
  214. raise ExtractorError('No support iQiqy VIP video')
  215. data = raw_data['data']
  216. title = data['vi']['vn']
  217. # generate video_urls_dict
  218. video_urls_dict = self.construct_video_urls(
  219. data, video_id, _uuid)
  220. # construct info
  221. entries = []
  222. for format_id in video_urls_dict:
  223. video_urls = video_urls_dict[format_id]
  224. for i, video_url_info in enumerate(video_urls):
  225. if len(entries) < i + 1:
  226. entries.append({'formats': []})
  227. entries[i]['formats'].append(
  228. {
  229. 'url': video_url_info[0],
  230. 'filesize': video_url_info[-1],
  231. 'format_id': format_id,
  232. 'preference': int(self.get_bid(format_id))
  233. }
  234. )
  235. for i in range(len(entries)):
  236. self._sort_formats(entries[i]['formats'])
  237. entries[i].update(
  238. {
  239. 'id': '%s_part%d' % (video_id, i + 1),
  240. 'title': title,
  241. }
  242. )
  243. if len(entries) > 1:
  244. info = {
  245. '_type': 'multi_video',
  246. 'id': video_id,
  247. 'title': title,
  248. 'entries': entries,
  249. }
  250. else:
  251. info = entries[0]
  252. info['id'] = video_id
  253. info['title'] = title
  254. return info