logo

oasis-root

Compiled tree of Oasis Linux based on own branch at <https://hacktivis.me/git/oasis/> git clone https://anongit.hacktivis.me/git/oasis-root.git

huya.py (7643B)


  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. parse_duration,
  11. str_or_none,
  12. try_get,
  13. unescapeHTML,
  14. unified_strdate,
  15. update_url_query,
  16. url_or_none,
  17. )
  18. from ..utils.traversal import traverse_obj
  19. class HuyaLiveIE(InfoExtractor):
  20. _VALID_URL = r'https?://(?:www\.|m\.)?huya\.com/(?!(?:video/play/))(?P<id>[^/#?&]+)(?:\D|$)'
  21. IE_NAME = 'huya:live'
  22. IE_DESC = 'huya.com'
  23. TESTS = [{
  24. 'url': 'https://www.huya.com/572329',
  25. 'info_dict': {
  26. 'id': '572329',
  27. 'title': str,
  28. 'ext': 'flv',
  29. 'description': str,
  30. 'is_live': True,
  31. 'view_count': int,
  32. },
  33. 'params': {
  34. 'skip_download': True,
  35. },
  36. }, {
  37. 'url': 'https://www.huya.com/xiaoyugame',
  38. 'only_matching': True,
  39. }]
  40. _RESOLUTION = {
  41. '蓝光': {
  42. 'width': 1920,
  43. 'height': 1080,
  44. },
  45. '超清': {
  46. 'width': 1280,
  47. 'height': 720,
  48. },
  49. '流畅': {
  50. 'width': 800,
  51. 'height': 480,
  52. },
  53. }
  54. def _real_extract(self, url):
  55. video_id = self._match_id(url)
  56. webpage = self._download_webpage(url, video_id=video_id)
  57. stream_data = self._search_json(r'stream:\s', webpage, 'stream', video_id=video_id, default=None)
  58. room_info = try_get(stream_data, lambda x: x['data'][0]['gameLiveInfo'])
  59. if not room_info:
  60. raise ExtractorError('Can not extract the room info', expected=True)
  61. title = room_info.get('roomName') or room_info.get('introduction') or self._html_extract_title(webpage)
  62. screen_type = room_info.get('screenType')
  63. live_source_type = room_info.get('liveSourceType')
  64. stream_info_list = stream_data['data'][0]['gameStreamInfoList']
  65. if not stream_info_list:
  66. raise ExtractorError('Video is offline', expected=True)
  67. formats = []
  68. for stream_info in stream_info_list:
  69. stream_url = stream_info.get('sFlvUrl')
  70. if not stream_url:
  71. continue
  72. stream_name = stream_info.get('sStreamName')
  73. re_secret = not screen_type and live_source_type in (0, 8, 13)
  74. params = dict(urllib.parse.parse_qsl(unescapeHTML(stream_info['sFlvAntiCode'])))
  75. fm, ss = '', ''
  76. if re_secret:
  77. fm, ss = self.encrypt(params, stream_info, stream_name)
  78. for si in stream_data.get('vMultiStreamInfo'):
  79. display_name, bitrate = re.fullmatch(
  80. r'(.+?)(?:(\d+)M)?', si.get('sDisplayName')).groups()
  81. rate = si.get('iBitRate')
  82. if rate:
  83. params['ratio'] = rate
  84. else:
  85. params.pop('ratio', None)
  86. if bitrate:
  87. rate = int(bitrate) * 1000
  88. if re_secret:
  89. params['wsSecret'] = hashlib.md5(
  90. '_'.join([fm, params['u'], stream_name, ss, params['wsTime']]))
  91. formats.append({
  92. 'ext': stream_info.get('sFlvUrlSuffix'),
  93. 'format_id': str_or_none(stream_info.get('iLineIndex')),
  94. 'tbr': rate,
  95. 'url': update_url_query(f'{stream_url}/{stream_name}.{stream_info.get("sFlvUrlSuffix")}',
  96. query=params),
  97. **self._RESOLUTION.get(display_name, {}),
  98. })
  99. return {
  100. 'id': video_id,
  101. 'title': title,
  102. 'formats': formats,
  103. 'view_count': room_info.get('totalCount'),
  104. 'thumbnail': room_info.get('screenshot'),
  105. 'description': room_info.get('contentIntro'),
  106. 'http_headers': {
  107. 'Origin': 'https://www.huya.com',
  108. 'Referer': 'https://www.huya.com/',
  109. },
  110. }
  111. def encrypt(self, params, stream_info, stream_name):
  112. ct = int_or_none(params.get('wsTime'), 16) + random.random()
  113. presenter_uid = stream_info['lPresenterUid']
  114. if not stream_name.startswith(str(presenter_uid)):
  115. uid = presenter_uid
  116. else:
  117. uid = int_or_none(ct % 1e7 * 1e6 % 0xffffffff)
  118. u1 = uid & 0xffffffff00000000
  119. u2 = uid & 0xffffffff
  120. u3 = uid & 0xffffff
  121. u = u1 | u2 >> 24 | u3 << 8
  122. params.update({
  123. 'u': str_or_none(u),
  124. 'seqid': str_or_none(int_or_none(ct * 1000) + uid),
  125. 'ver': '1',
  126. 'uuid': int_or_none(ct % 1e7 * 1e6 % 0xffffffff),
  127. 't': '100',
  128. })
  129. fm = base64.b64decode(params['fm']).decode().split('_', 1)[0]
  130. ss = hashlib.md5('|'.join([params['seqid'], params['ctype'], params['t']]))
  131. return fm, ss
  132. class HuyaVideoIE(InfoExtractor):
  133. _VALID_URL = r'https?://(?:www\.)?huya\.com/video/play/(?P<id>\d+)\.html'
  134. IE_NAME = 'huya:video'
  135. IE_DESC = '虎牙视频'
  136. _TESTS = [{
  137. 'url': 'https://www.huya.com/video/play/1002412640.html',
  138. 'info_dict': {
  139. 'id': '1002412640',
  140. 'ext': 'mp4',
  141. 'title': '8月3日',
  142. 'thumbnail': r're:https?://.*\.jpg',
  143. 'duration': 14,
  144. 'uploader': '虎牙-ATS欧卡车队青木',
  145. 'uploader_id': '1564376151',
  146. 'upload_date': '20240803',
  147. 'view_count': int,
  148. 'comment_count': int,
  149. 'like_count': int,
  150. },
  151. },
  152. {
  153. 'url': 'https://www.huya.com/video/play/556054543.html',
  154. 'info_dict': {
  155. 'id': '556054543',
  156. 'ext': 'mp4',
  157. 'title': '我不挑事 也不怕事',
  158. 'thumbnail': r're:https?://.*\.jpg',
  159. 'duration': 1864,
  160. 'uploader': '卡尔',
  161. 'uploader_id': '367138632',
  162. 'upload_date': '20210811',
  163. 'view_count': int,
  164. 'comment_count': int,
  165. 'like_count': int,
  166. },
  167. }]
  168. def _real_extract(self, url: str):
  169. video_id = self._match_id(url)
  170. video_data = self._download_json(
  171. 'https://liveapi.huya.com/moment/getMomentContent', video_id,
  172. query={'videoId': video_id})['data']['moment']['videoInfo']
  173. formats = []
  174. for definition in traverse_obj(video_data, ('definitions', lambda _, v: url_or_none(v['url']))):
  175. formats.append({
  176. 'url': definition['url'],
  177. **traverse_obj(definition, {
  178. 'format_id': ('defName', {str}),
  179. 'width': ('width', {int_or_none}),
  180. 'height': ('height', {int_or_none}),
  181. 'filesize': ('size', {int_or_none}),
  182. }),
  183. })
  184. return {
  185. 'id': video_id,
  186. 'formats': formats,
  187. **traverse_obj(video_data, {
  188. 'title': ('videoTitle', {str}),
  189. 'thumbnail': ('videoCover', {url_or_none}),
  190. 'duration': ('videoDuration', {parse_duration}),
  191. 'uploader': ('nickName', {str}),
  192. 'uploader_id': ('uid', {str_or_none}),
  193. 'upload_date': ('videoUploadTime', {unified_strdate}),
  194. 'view_count': ('videoPlayNum', {int_or_none}),
  195. 'comment_count': ('videoCommentNum', {int_or_none}),
  196. 'like_count': ('favorCount', {int_or_none}),
  197. }),
  198. }