huya.py 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
  1. import hashlib
  2. import random
  3. from ..compat import compat_urlparse, compat_b64decode
  4. from ..utils import (
  5. ExtractorError,
  6. int_or_none,
  7. str_or_none,
  8. try_get,
  9. unescapeHTML,
  10. update_url_query,
  11. )
  12. from .common import InfoExtractor
  13. class HuyaLiveIE(InfoExtractor):
  14. _VALID_URL = r'https?://(?:www\.|m\.)?huya\.com/(?P<id>[^/#?&]+)(?:\D|$)'
  15. IE_NAME = 'huya:live'
  16. IE_DESC = 'huya.com'
  17. TESTS = [{
  18. 'url': 'https://www.huya.com/572329',
  19. 'info_dict': {
  20. 'id': '572329',
  21. 'title': str,
  22. 'description': str,
  23. 'is_live': True,
  24. 'view_count': int,
  25. },
  26. 'params': {
  27. 'skip_download': True,
  28. },
  29. }, {
  30. 'url': 'https://www.huya.com/xiaoyugame',
  31. 'only_matching': True
  32. }]
  33. _RESOLUTION = {
  34. '蓝光4M': {
  35. 'width': 1920,
  36. 'height': 1080,
  37. },
  38. '超清': {
  39. 'width': 1280,
  40. 'height': 720,
  41. },
  42. '流畅': {
  43. 'width': 800,
  44. 'height': 480
  45. }
  46. }
  47. def _real_extract(self, url):
  48. video_id = self._match_id(url)
  49. webpage = self._download_webpage(url, video_id=video_id)
  50. stream_data = self._search_json(r'stream:\s', webpage, 'stream', video_id=video_id, default=None)
  51. room_info = try_get(stream_data, lambda x: x['data'][0]['gameLiveInfo'])
  52. if not room_info:
  53. raise ExtractorError('Can not extract the room info', expected=True)
  54. title = room_info.get('roomName') or room_info.get('introduction') or self._html_extract_title(webpage)
  55. screen_type = room_info.get('screenType')
  56. live_source_type = room_info.get('liveSourceType')
  57. stream_info_list = stream_data['data'][0]['gameStreamInfoList']
  58. if not stream_info_list:
  59. raise ExtractorError('Video is offline', expected=True)
  60. formats = []
  61. for stream_info in stream_info_list:
  62. stream_url = stream_info.get('sFlvUrl')
  63. if not stream_url:
  64. continue
  65. stream_name = stream_info.get('sStreamName')
  66. re_secret = not screen_type and live_source_type in (0, 8, 13)
  67. params = dict(compat_urlparse.parse_qsl(unescapeHTML(stream_info['sFlvAntiCode'])))
  68. fm, ss = '', ''
  69. if re_secret:
  70. fm, ss = self.encrypt(params, stream_info, stream_name)
  71. for si in stream_data.get('vMultiStreamInfo'):
  72. rate = si.get('iBitRate')
  73. if rate:
  74. params['ratio'] = rate
  75. else:
  76. params.pop('ratio', None)
  77. if re_secret:
  78. params['wsSecret'] = hashlib.md5(
  79. '_'.join([fm, params['u'], stream_name, ss, params['wsTime']]))
  80. formats.append({
  81. 'ext': stream_info.get('sFlvUrlSuffix'),
  82. 'format_id': str_or_none(stream_info.get('iLineIndex')),
  83. 'tbr': rate,
  84. 'url': update_url_query(f'{stream_url}/{stream_name}.{stream_info.get("sFlvUrlSuffix")}',
  85. query=params),
  86. **self._RESOLUTION.get(si.get('sDisplayName'), {}),
  87. })
  88. return {
  89. 'id': video_id,
  90. 'title': title,
  91. 'formats': formats,
  92. 'view_count': room_info.get('totalCount'),
  93. 'thumbnail': room_info.get('screenshot'),
  94. 'description': room_info.get('contentIntro'),
  95. 'http_headers': {
  96. 'Origin': 'https://www.huya.com',
  97. 'Referer': 'https://www.huya.com/',
  98. },
  99. }
  100. def encrypt(self, params, stream_info, stream_name):
  101. ct = int_or_none(params.get('wsTime'), 16) + random.random()
  102. presenter_uid = stream_info['lPresenterUid']
  103. if not stream_name.startswith(str(presenter_uid)):
  104. uid = presenter_uid
  105. else:
  106. uid = int_or_none(ct % 1e7 * 1e6 % 0xffffffff)
  107. u1 = uid & 0xffffffff00000000
  108. u2 = uid & 0xffffffff
  109. u3 = uid & 0xffffff
  110. u = u1 | u2 >> 24 | u3 << 8
  111. params.update({
  112. 'u': str_or_none(u),
  113. 'seqid': str_or_none(int_or_none(ct * 1000) + uid),
  114. 'ver': '1',
  115. 'uuid': int_or_none(ct % 1e7 * 1e6 % 0xffffffff),
  116. 't': '100',
  117. })
  118. fm = compat_b64decode(params['fm']).decode().split('_', 1)[0]
  119. ss = hashlib.md5('|'.join([params['seqid'], params['ctype'], params['t']]))
  120. return fm, ss