mixch.py 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118
  1. from .common import InfoExtractor
  2. from ..networking.exceptions import HTTPError
  3. from ..utils import (
  4. ExtractorError,
  5. UserNotLive,
  6. int_or_none,
  7. str_or_none,
  8. url_or_none,
  9. )
  10. from ..utils.traversal import traverse_obj
  11. class MixchIE(InfoExtractor):
  12. IE_NAME = 'mixch'
  13. _VALID_URL = r'https?://(?:www\.)?mixch\.tv/u/(?P<id>\d+)'
  14. _TESTS = [{
  15. 'url': 'https://mixch.tv/u/16943797/live',
  16. 'skip': 'don\'t know if this live persists',
  17. 'info_dict': {
  18. 'id': '16943797',
  19. 'ext': 'mp4',
  20. 'title': '#EntView #カリナ #セブチ 2024-05-05 06:58',
  21. 'comment_count': int,
  22. 'view_count': int,
  23. 'timestamp': 1714726805,
  24. 'uploader': 'Ent.View K-news🎶💕',
  25. 'uploader_id': '16943797',
  26. 'live_status': 'is_live',
  27. 'upload_date': '20240503',
  28. },
  29. }, {
  30. 'url': 'https://mixch.tv/u/16137876/live',
  31. 'only_matching': True,
  32. }]
  33. def _real_extract(self, url):
  34. video_id = self._match_id(url)
  35. data = self._download_json(f'https://mixch.tv/api-web/users/{video_id}/live', video_id)
  36. if not traverse_obj(data, ('liveInfo', {dict})):
  37. raise UserNotLive(video_id=video_id)
  38. return {
  39. 'id': video_id,
  40. 'uploader_id': video_id,
  41. **traverse_obj(data, {
  42. 'title': ('liveInfo', 'title', {str}),
  43. 'comment_count': ('liveInfo', 'comments', {int_or_none}),
  44. 'view_count': ('liveInfo', 'visitor', {int_or_none}),
  45. 'timestamp': ('liveInfo', 'created', {int_or_none}),
  46. 'uploader': ('broadcasterInfo', 'name', {str}),
  47. }),
  48. 'formats': [{
  49. 'format_id': 'hls',
  50. 'url': data['liveInfo']['hls'],
  51. 'ext': 'mp4',
  52. 'protocol': 'm3u8',
  53. }],
  54. 'is_live': True,
  55. '__post_extractor': self.extract_comments(video_id),
  56. }
  57. def _get_comments(self, video_id):
  58. yield from traverse_obj(self._download_json(
  59. f'https://mixch.tv/api-web/lives/{video_id}/messages', video_id,
  60. note='Downloading comments', errnote='Failed to download comments'), (..., {
  61. 'author': ('name', {str}),
  62. 'author_id': ('user_id', {str_or_none}),
  63. 'id': ('message_id', {str}, {lambda x: x or None}),
  64. 'text': ('body', {str}),
  65. 'timestamp': ('created', {int}),
  66. }))
  67. class MixchArchiveIE(InfoExtractor):
  68. IE_NAME = 'mixch:archive'
  69. _VALID_URL = r'https?://(?:www\.)?mixch\.tv/archive/(?P<id>\d+)'
  70. _TESTS = [{
  71. 'url': 'https://mixch.tv/archive/421',
  72. 'skip': 'paid video, no DRM. expires at Jan 23',
  73. 'info_dict': {
  74. 'id': '421',
  75. 'ext': 'mp4',
  76. 'title': '96NEKO SHOW TIME',
  77. },
  78. }, {
  79. 'url': 'https://mixch.tv/archive/1213',
  80. 'skip': 'paid video, no DRM. expires at Dec 31, 2023',
  81. 'info_dict': {
  82. 'id': '1213',
  83. 'ext': 'mp4',
  84. 'title': '【特別トーク番組アーカイブス】Merm4id×燐舞曲 2nd LIVE「VERSUS」',
  85. 'release_date': '20231201',
  86. 'thumbnail': str,
  87. },
  88. }, {
  89. 'url': 'https://mixch.tv/archive/1214',
  90. 'only_matching': True,
  91. }]
  92. def _real_extract(self, url):
  93. video_id = self._match_id(url)
  94. try:
  95. info_json = self._download_json(
  96. f'https://mixch.tv/api-web/archive/{video_id}', video_id)['archive']
  97. except ExtractorError as e:
  98. if isinstance(e.cause, HTTPError) and e.cause.status == 401:
  99. self.raise_login_required()
  100. raise
  101. return {
  102. 'id': video_id,
  103. 'title': traverse_obj(info_json, ('title', {str})),
  104. 'formats': self._extract_m3u8_formats(info_json['archiveURL'], video_id),
  105. 'thumbnail': traverse_obj(info_json, ('thumbnailURL', {url_or_none})),
  106. }