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

chzzk.py (7495B)


  1. from .common import InfoExtractor
  2. from ..utils import (
  3. UserNotLive,
  4. float_or_none,
  5. int_or_none,
  6. parse_iso8601,
  7. url_or_none,
  8. )
  9. from ..utils.traversal import traverse_obj
  10. class CHZZKLiveIE(InfoExtractor):
  11. IE_NAME = 'chzzk:live'
  12. _VALID_URL = r'https?://chzzk\.naver\.com/live/(?P<id>[\da-f]+)'
  13. _TESTS = [{
  14. 'url': 'https://chzzk.naver.com/live/c68b8ef525fb3d2fa146344d84991753',
  15. 'info_dict': {
  16. 'id': 'c68b8ef525fb3d2fa146344d84991753',
  17. 'ext': 'mp4',
  18. 'title': str,
  19. 'channel': '진짜도현',
  20. 'channel_id': 'c68b8ef525fb3d2fa146344d84991753',
  21. 'channel_is_verified': False,
  22. 'thumbnail': r're:^https?://.*\.jpg$',
  23. 'timestamp': 1705510344,
  24. 'upload_date': '20240117',
  25. 'live_status': 'is_live',
  26. 'view_count': int,
  27. 'concurrent_view_count': int,
  28. },
  29. 'skip': 'The channel is not currently live',
  30. }]
  31. def _real_extract(self, url):
  32. channel_id = self._match_id(url)
  33. live_detail = self._download_json(
  34. f'https://api.chzzk.naver.com/service/v3/channels/{channel_id}/live-detail', channel_id,
  35. note='Downloading channel info', errnote='Unable to download channel info')['content']
  36. if live_detail.get('status') == 'CLOSE':
  37. raise UserNotLive(video_id=channel_id)
  38. live_playback = self._parse_json(live_detail['livePlaybackJson'], channel_id)
  39. thumbnails = []
  40. thumbnail_template = traverse_obj(
  41. live_playback, ('thumbnail', 'snapshotThumbnailTemplate', {url_or_none}))
  42. if thumbnail_template and '{type}' in thumbnail_template:
  43. for width in traverse_obj(live_playback, ('thumbnail', 'types', ..., {str})):
  44. thumbnails.append({
  45. 'id': width,
  46. 'url': thumbnail_template.replace('{type}', width),
  47. 'width': int_or_none(width),
  48. })
  49. formats, subtitles = [], {}
  50. for media in traverse_obj(live_playback, ('media', lambda _, v: url_or_none(v['path']))):
  51. is_low_latency = media.get('mediaId') == 'LLHLS'
  52. fmts, subs = self._extract_m3u8_formats_and_subtitles(
  53. media['path'], channel_id, 'mp4', fatal=False, live=True,
  54. m3u8_id='hls-ll' if is_low_latency else 'hls')
  55. for f in fmts:
  56. if is_low_latency:
  57. f['source_preference'] = -2
  58. if '-afragalow.stream-audio.stream' in f['format_id']:
  59. f['quality'] = -2
  60. formats.extend(fmts)
  61. self._merge_subtitles(subs, target=subtitles)
  62. return {
  63. 'id': channel_id,
  64. 'is_live': True,
  65. 'formats': formats,
  66. 'subtitles': subtitles,
  67. 'thumbnails': thumbnails,
  68. **traverse_obj(live_detail, {
  69. 'title': ('liveTitle', {str}),
  70. 'timestamp': ('openDate', {parse_iso8601(delimiter=' ')}),
  71. 'concurrent_view_count': ('concurrentUserCount', {int_or_none}),
  72. 'view_count': ('accumulateCount', {int_or_none}),
  73. 'channel': ('channel', 'channelName', {str}),
  74. 'channel_id': ('channel', 'channelId', {str}),
  75. 'channel_is_verified': ('channel', 'verifiedMark', {bool}),
  76. }),
  77. }
  78. class CHZZKVideoIE(InfoExtractor):
  79. IE_NAME = 'chzzk:video'
  80. _VALID_URL = r'https?://chzzk\.naver\.com/video/(?P<id>\d+)'
  81. _TESTS = [{
  82. 'url': 'https://chzzk.naver.com/video/1754',
  83. 'md5': 'b0c0c1bb888d913b93d702b1512c7f06',
  84. 'info_dict': {
  85. 'id': '1754',
  86. 'ext': 'mp4',
  87. 'title': '치지직 테스트 방송',
  88. 'channel': '침착맨',
  89. 'channel_id': 'bb382c2c0cc9fa7c86ab3b037fb5799c',
  90. 'channel_is_verified': False,
  91. 'thumbnail': r're:^https?://.*\.jpg$',
  92. 'duration': 15577,
  93. 'timestamp': 1702970505.417,
  94. 'upload_date': '20231219',
  95. 'view_count': int,
  96. },
  97. 'skip': 'Replay video is expired',
  98. }, {
  99. # Manually uploaded video
  100. 'url': 'https://chzzk.naver.com/video/1980',
  101. 'info_dict': {
  102. 'id': '1980',
  103. 'ext': 'mp4',
  104. 'title': '※시청주의※한번보면 잊기 힘든 영상',
  105. 'channel': '라디유radiyu',
  106. 'channel_id': '68f895c59a1043bc5019b5e08c83a5c5',
  107. 'channel_is_verified': False,
  108. 'thumbnail': r're:^https?://.*\.jpg$',
  109. 'duration': 95,
  110. 'timestamp': 1703102631.722,
  111. 'upload_date': '20231220',
  112. 'view_count': int,
  113. },
  114. }, {
  115. # Partner channel replay video
  116. 'url': 'https://chzzk.naver.com/video/2458',
  117. 'info_dict': {
  118. 'id': '2458',
  119. 'ext': 'mp4',
  120. 'title': '첫 방송',
  121. 'channel': '강지',
  122. 'channel_id': 'b5ed5db484d04faf4d150aedd362f34b',
  123. 'channel_is_verified': True,
  124. 'thumbnail': r're:^https?://.*\.jpg$',
  125. 'duration': 4433,
  126. 'timestamp': 1703307460.214,
  127. 'upload_date': '20231223',
  128. 'view_count': int,
  129. },
  130. }]
  131. def _real_extract(self, url):
  132. video_id = self._match_id(url)
  133. video_meta = self._download_json(
  134. f'https://api.chzzk.naver.com/service/v3/videos/{video_id}', video_id,
  135. note='Downloading video info', errnote='Unable to download video info')['content']
  136. live_status = 'was_live' if video_meta.get('liveOpenDate') else 'not_live'
  137. video_status = video_meta.get('vodStatus')
  138. if video_status == 'UPLOAD':
  139. playback = self._parse_json(video_meta['liveRewindPlaybackJson'], video_id)
  140. formats, subtitles = self._extract_m3u8_formats_and_subtitles(
  141. playback['media'][0]['path'], video_id, 'mp4', m3u8_id='hls')
  142. elif video_status == 'ABR_HLS':
  143. formats, subtitles = self._extract_mpd_formats_and_subtitles(
  144. f'https://apis.naver.com/neonplayer/vodplay/v1/playback/{video_meta["videoId"]}',
  145. video_id, query={
  146. 'key': video_meta['inKey'],
  147. 'env': 'real',
  148. 'lc': 'en_US',
  149. 'cpl': 'en_US',
  150. })
  151. else:
  152. self.raise_no_formats(
  153. f'Unknown video status detected: "{video_status}"', expected=True, video_id=video_id)
  154. formats, subtitles = [], {}
  155. live_status = 'post_live' if live_status == 'was_live' else None
  156. return {
  157. 'id': video_id,
  158. 'formats': formats,
  159. 'subtitles': subtitles,
  160. 'live_status': live_status,
  161. **traverse_obj(video_meta, {
  162. 'title': ('videoTitle', {str}),
  163. 'thumbnail': ('thumbnailImageUrl', {url_or_none}),
  164. 'timestamp': ('publishDateAt', {float_or_none(scale=1000)}),
  165. 'view_count': ('readCount', {int_or_none}),
  166. 'duration': ('duration', {int_or_none}),
  167. 'channel': ('channel', 'channelName', {str}),
  168. 'channel_id': ('channel', 'channelId', {str}),
  169. 'channel_is_verified': ('channel', 'verifiedMark', {bool}),
  170. }),
  171. }