ichinanalive.py 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. from .common import InfoExtractor
  2. from ..utils import ExtractorError, str_or_none, traverse_obj, unified_strdate
  3. class IchinanaLiveIE(InfoExtractor):
  4. IE_NAME = '17live'
  5. _VALID_URL = r'https?://(?:www\.)?17\.live/(?:[^/]+/)*(?:live|profile/r)/(?P<id>\d+)'
  6. _TESTS = [{
  7. 'url': 'https://17.live/live/3773096',
  8. 'info_dict': {
  9. 'id': '3773096',
  10. 'title': '萠珈☕🤡🍫moka',
  11. 'is_live': True,
  12. 'uploader': '萠珈☕🤡🍫moka',
  13. 'uploader_id': '3773096',
  14. 'like_count': 366,
  15. 'view_count': 18121,
  16. 'timestamp': 1630569012,
  17. },
  18. 'skip': 'running as of writing, but may be ended as of testing',
  19. }, {
  20. 'note': 'nothing except language differs',
  21. 'url': 'https://17.live/ja/live/3773096',
  22. 'only_matching': True,
  23. }]
  24. @classmethod
  25. def suitable(cls, url):
  26. return not IchinanaLiveClipIE.suitable(url) and super().suitable(url)
  27. def _real_extract(self, url):
  28. video_id = self._match_id(url)
  29. url = f'https://17.live/live/{video_id}'
  30. enter = self._download_json(
  31. f'https://api-dsa.17app.co/api/v1/lives/{video_id}/enter', video_id,
  32. headers={'Referer': url}, fatal=False, expected_status=420,
  33. data=b'\0')
  34. if enter and enter.get('message') == 'ended':
  35. raise ExtractorError('This live has ended.', expected=True)
  36. view_data = self._download_json(
  37. f'https://api-dsa.17app.co/api/v1/lives/{video_id}', video_id,
  38. headers={'Referer': url})
  39. uploader = traverse_obj(
  40. view_data, ('userInfo', 'displayName'), ('userInfo', 'openID'))
  41. video_urls = view_data.get('rtmpUrls')
  42. if not video_urls:
  43. raise ExtractorError('unable to extract live URL information')
  44. formats = []
  45. for (name, value) in video_urls[0].items():
  46. if not isinstance(value, str):
  47. continue
  48. if not value.startswith('http'):
  49. continue
  50. quality = -1
  51. if 'web' in name:
  52. quality -= 1
  53. if 'High' in name:
  54. quality += 4
  55. if 'Low' in name:
  56. quality -= 2
  57. formats.append({
  58. 'format_id': name,
  59. 'url': value,
  60. 'quality': quality,
  61. 'http_headers': {'Referer': url},
  62. 'ext': 'flv',
  63. 'vcodec': 'h264',
  64. 'acodec': 'aac',
  65. })
  66. return {
  67. 'id': video_id,
  68. 'title': uploader or video_id,
  69. 'formats': formats,
  70. 'is_live': True,
  71. 'uploader': uploader,
  72. 'uploader_id': video_id,
  73. 'like_count': view_data.get('receivedLikeCount'),
  74. 'view_count': view_data.get('viewerCount'),
  75. 'thumbnail': view_data.get('coverPhoto'),
  76. 'description': view_data.get('caption'),
  77. 'timestamp': view_data.get('beginTime'),
  78. }
  79. class IchinanaLiveClipIE(InfoExtractor):
  80. IE_NAME = '17live:clip'
  81. _VALID_URL = r'https?://(?:www\.)?17\.live/(?:[^/]+/)*profile/r/(?P<uploader_id>\d+)/clip/(?P<id>[^/]+)'
  82. _TESTS = [{
  83. 'url': 'https://17.live/profile/r/1789280/clip/1bHQSK8KUieruFXaCH4A4upCzlN',
  84. 'info_dict': {
  85. 'id': '1bHQSK8KUieruFXaCH4A4upCzlN',
  86. 'title': 'マチコ先生🦋Class💋',
  87. 'description': 'マチ戦隊 第一次 バスターコール\n総額200万coin!\n動画制作@うぉーかー🌱Walker🎫',
  88. 'uploader_id': '1789280',
  89. },
  90. }, {
  91. 'url': 'https://17.live/ja/profile/r/1789280/clip/1bHQSK8KUieruFXaCH4A4upCzlN',
  92. 'only_matching': True,
  93. }]
  94. def _real_extract(self, url):
  95. uploader_id, video_id = self._match_valid_url(url).groups()
  96. url = f'https://17.live/profile/r/{uploader_id}/clip/{video_id}'
  97. view_data = self._download_json(
  98. f'https://api-dsa.17app.co/api/v1/clips/{video_id}', video_id,
  99. headers={'Referer': url})
  100. uploader = traverse_obj(
  101. view_data, ('userInfo', 'displayName'), ('userInfo', 'name'))
  102. formats = []
  103. if view_data.get('videoURL'):
  104. formats.append({
  105. 'id': 'video',
  106. 'url': view_data['videoURL'],
  107. 'quality': -1,
  108. })
  109. if view_data.get('transcodeURL'):
  110. formats.append({
  111. 'id': 'transcode',
  112. 'url': view_data['transcodeURL'],
  113. 'quality': -1,
  114. })
  115. if view_data.get('srcVideoURL'):
  116. # highest quality
  117. formats.append({
  118. 'id': 'srcVideo',
  119. 'url': view_data['srcVideoURL'],
  120. 'quality': 1,
  121. })
  122. for fmt in formats:
  123. fmt.update({
  124. 'ext': 'mp4',
  125. 'protocol': 'https',
  126. 'vcodec': 'h264',
  127. 'acodec': 'aac',
  128. 'http_headers': {'Referer': url},
  129. })
  130. return {
  131. 'id': video_id,
  132. 'title': uploader or video_id,
  133. 'formats': formats,
  134. 'uploader': uploader,
  135. 'uploader_id': uploader_id,
  136. 'like_count': view_data.get('likeCount'),
  137. 'view_count': view_data.get('viewCount'),
  138. 'thumbnail': view_data.get('imageURL'),
  139. 'duration': view_data.get('duration'),
  140. 'description': view_data.get('caption'),
  141. 'upload_date': unified_strdate(str_or_none(view_data.get('createdAt'))),
  142. }