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.

278 lines
9.1 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://(?: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),
  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. # last update at 2015-12-06 for Zombie::bite
  188. enc_key = '3719f6a1da83ee0aee3488d8802d7696'[::-1]
  189. return enc_key
  190. def _real_extract(self, url):
  191. webpage = self._download_webpage(
  192. url, 'temp_id', note='download video page')
  193. tvid = self._search_regex(
  194. r'data-player-tvid\s*=\s*[\'"](\d+)', webpage, 'tvid')
  195. video_id = self._search_regex(
  196. r'data-player-videoid\s*=\s*[\'"]([a-f\d]+)', webpage, 'video_id')
  197. swf_url = self._search_regex(
  198. r'(http://[^\'"]+MainPlayer[^.]+\.swf)', webpage, 'swf player URL')
  199. _uuid = uuid.uuid4().hex
  200. enc_key = self.get_enc_key(swf_url, video_id)
  201. raw_data = self.get_raw_data(tvid, video_id, enc_key, _uuid)
  202. if raw_data['code'] != 'A000000':
  203. raise ExtractorError('Unable to load data. Error code: ' + raw_data['code'])
  204. if not raw_data['data']['vp']['tkl']:
  205. raise ExtractorError('No support iQiqy VIP video')
  206. data = raw_data['data']
  207. title = data['vi']['vn']
  208. # generate video_urls_dict
  209. video_urls_dict = self.construct_video_urls(
  210. data, video_id, _uuid)
  211. # construct info
  212. entries = []
  213. for format_id in video_urls_dict:
  214. video_urls = video_urls_dict[format_id]
  215. for i, video_url_info in enumerate(video_urls):
  216. if len(entries) < i + 1:
  217. entries.append({'formats': []})
  218. entries[i]['formats'].append(
  219. {
  220. 'url': video_url_info[0],
  221. 'filesize': video_url_info[-1],
  222. 'format_id': format_id,
  223. 'preference': int(self.get_bid(format_id))
  224. }
  225. )
  226. for i in range(len(entries)):
  227. self._sort_formats(entries[i]['formats'])
  228. entries[i].update(
  229. {
  230. 'id': '%s_part%d' % (video_id, i + 1),
  231. 'title': title,
  232. }
  233. )
  234. if len(entries) > 1:
  235. info = {
  236. '_type': 'multi_video',
  237. 'id': video_id,
  238. 'title': title,
  239. 'entries': entries,
  240. }
  241. else:
  242. info = entries[0]
  243. info['id'] = video_id
  244. info['title'] = title
  245. return info