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.

273 lines
9.0 KiB

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