]>
jfr.im git - yt-dlp.git/blob - yt_dlp/extractor/nuum.py
3db663ded0dda8ac82a9b4f2610fb14b94ecbc8b
3 from .common
import InfoExtractor
14 from ..utils
.traversal
import traverse_obj
17 class NuumBaseIE(InfoExtractor
):
18 def _call_api(self
, path
, video_id
, description
, query
={}):
19 response
= self
._download
_json
(
20 f
'https://nuum.ru/api/v2/{path}', video_id
, query
=query
,
21 note
=f
'Downloading {description} metadata',
22 errnote
=f
'Unable to download {description} metadata')
23 if error
:= response
.get('error'):
24 raise ExtractorError(f
'API returned error: {error!r}')
25 return response
['result']
27 def _get_channel_info(self
, channel_name
):
28 return self
._call
_api
(
29 'broadcasts/public', video_id
=channel_name
, description
='channel',
32 'channel_name': channel_name
,
33 'with_deleted': 'true',
36 def _parse_video_data(self
, container
, extract_formats
=True):
37 stream
= traverse_obj(container
, ('media_container_streams', 0, {dict}
)) or {}
38 media
= traverse_obj(stream
, ('stream_media', 0, {dict}
)) or {}
39 media_url
= traverse_obj(media
, (
40 'media_meta', ('media_archive_url', 'media_url'), {url_or_none}
), get_all
=False)
42 video_id
= str(container
['media_container_id'])
43 is_live
= media
.get('media_status') == 'RUNNING'
45 formats
, subtitles
= None, None
47 formats
, subtitles
= self
._extract
_m
3u8_formats
_and
_subtitles
(
48 media_url
, video_id
, 'mp4', live
=is_live
)
54 'subtitles': subtitles
,
55 **traverse_obj(container
, {
56 'title': ('media_container_name', {str}
),
57 'description': ('media_container_description', {str}
),
58 'timestamp': ('created_at', {parse_iso8601}
),
59 'channel': ('media_container_channel', 'channel_name', {str}
),
60 'channel_id': ('media_container_channel', 'channel_id', {str_or_none}
),
62 **traverse_obj(stream
, {
63 'view_count': ('stream_total_viewers', {int_or_none}
),
64 'concurrent_view_count': ('stream_current_viewers', {int_or_none}
),
66 **traverse_obj(media
, {
67 'duration': ('media_duration', {int_or_none}
),
68 'thumbnail': ('media_meta', ('media_preview_archive_url', 'media_preview_url'), {url_or_none}
),
73 class NuumMediaIE(NuumBaseIE
):
74 IE_NAME
= 'nuum:media'
75 _VALID_URL
= r
'https?://nuum\.ru/(?:streams|videos|clips)/(?P<id>[\d]+)'
77 'url': 'https://nuum.ru/streams/1592713-7-days-to-die',
78 'only_matching': True,
80 'url': 'https://nuum.ru/videos/1567547-toxi-hurtz',
81 'md5': 'f1d9118a30403e32b702a204eb03aca3',
85 'title': 'Toxi$ - Hurtz',
87 'timestamp': 1702631651,
88 'upload_date': '20231215',
89 'thumbnail': r
're:^https?://.+\.jpg',
91 'concurrent_view_count': int,
97 'url': 'https://nuum.ru/clips/1552564-pro-misu',
98 'md5': 'b248ae1565b1e55433188f11beeb0ca1',
102 'title': 'Про Мису 🙃',
103 'timestamp': 1701971828,
104 'upload_date': '20231207',
105 'thumbnail': r
're:^https?://.+\.jpg',
107 'concurrent_view_count': int,
108 'channel_id': '3320',
109 'channel': 'Misalelik',
114 def _real_extract(self
, url
):
115 video_id
= self
._match
_id
(url
)
116 video_data
= self
._call
_api
(f
'media-containers/{video_id}', video_id
, 'media')
118 return self
._parse
_video
_data
(video_data
)
121 class NuumLiveIE(NuumBaseIE
):
122 IE_NAME
= 'nuum:live'
123 _VALID_URL
= r
'https?://nuum\.ru/channel/(?P<id>[^/#?]+)/?(?:$|[#?])'
125 'url': 'https://nuum.ru/channel/mts_live',
126 'only_matching': True,
129 def _real_extract(self
, url
):
130 channel
= self
._match
_id
(url
)
131 channel_info
= self
._get
_channel
_info
(channel
)
132 if traverse_obj(channel_info
, ('channel', 'channel_is_live')) is False:
133 raise UserNotLive(video_id
=channel
)
135 info
= self
._parse
_video
_data
(channel_info
['media_container'])
137 'webpage_url': f
'https://nuum.ru/streams/{info["id"]}',
138 'extractor_key': NuumMediaIE
.ie_key(),
139 'extractor': NuumMediaIE
.IE_NAME
,
144 class NuumTabIE(NuumBaseIE
):
146 _VALID_URL
= r
'https?://nuum\.ru/channel/(?P<id>[^/#?]+)/(?P<type>streams|videos|clips)'
148 'url': 'https://nuum.ru/channel/dankon_/clips',
150 'id': 'dankon__clips',
153 'playlist_mincount': 29,
155 'url': 'https://nuum.ru/channel/dankon_/videos',
157 'id': 'dankon__videos',
160 'playlist_mincount': 2,
162 'url': 'https://nuum.ru/channel/dankon_/streams',
164 'id': 'dankon__streams',
167 'playlist_mincount': 1,
172 def _fetch_page(self
, channel_id
, tab_type
, tab_id
, page
):
174 'clips': ['SHORT_VIDEO', 'REVIEW_VIDEO'],
175 'videos': ['LONG_VIDEO'],
176 'streams': ['SINGLE'],
179 media_containers
= self
._call
_api
(
180 'media-containers', video_id
=tab_id
, description
=f
'{tab_type} tab page {page + 1}',
182 'limit': self
._PAGE
_SIZE
,
183 'offset': page
* self
._PAGE
_SIZE
,
184 'channel_id': channel_id
,
185 'media_container_status': 'STOPPED',
186 'media_container_type': CONTAINER_TYPES
[tab_type
],
188 for container
in traverse_obj(media_containers
, (..., {dict}
)):
189 metadata
= self
._parse
_video
_data
(container
, extract_formats
=False)
190 yield self
.url_result(f
'https://nuum.ru/videos/{metadata["id"]}', NuumMediaIE
, **metadata
)
192 def _real_extract(self
, url
):
193 channel_name
, tab_type
= self
._match
_valid
_url
(url
).group('id', 'type')
194 tab_id
= f
'{channel_name}_{tab_type}'
195 channel_data
= self
._get
_channel
_info
(channel_name
)['channel']
197 return self
.playlist_result(OnDemandPagedList(functools
.partial(
198 self
._fetch
_page
, channel_data
['channel_id'], tab_type
, tab_id
), self
._PAGE
_SIZE
),
199 playlist_id
=tab_id
, playlist_title
=channel_data
.get('channel_name'))