iqiyi.py 8.9 KB

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