huya.py 4.7 KB

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