4 from .naver
import NaverBaseIE
23 class VLiveBaseIE(NaverBaseIE
):
24 _NETRC_MACHINE
= 'vlive'
27 def _perform_login(self
, username
, password
):
30 LOGIN_URL
= 'https://www.vlive.tv/auth/email/login'
31 self
._request
_webpage
(
32 LOGIN_URL
, None, note
='Downloading login cookies')
34 self
._download
_webpage
(
35 LOGIN_URL
, None, note
='Logging in',
36 data
=urlencode_postdata({'email': username, 'pwd': password}
),
39 'Content-Type': 'application/x-www-form-urlencoded'
42 login_info
= self
._download
_json
(
43 'https://www.vlive.tv/auth/loginInfo', None,
44 note
='Checking login status',
45 headers
={'Referer': 'https://www.vlive.tv/home'}
)
47 if not try_get(login_info
, lambda x
: x
['message']['login'], bool):
48 raise ExtractorError('Unable to log in', expected
=True)
49 VLiveBaseIE
._logged
_in
= True
51 def _call_api(self
, path_template
, video_id
, fields
=None, query_add
={}, note
=None):
53 note
= 'Downloading %s JSON metadata' % path_template
.split('/')[-1].split('-')[0]
54 query
= {'appId': '8c6cc7b45d2568fb668be6e05b6e5a3b', 'gcc': 'KR', 'platformType': 'PC'}
56 query
['fields'] = fields
58 query
.update(query_add
)
60 return self
._download
_json
(
61 'https://www.vlive.tv/globalv-web/vam-web/' + path_template
% video_id
, video_id
,
62 note
, headers
={'Referer': 'https://www.vlive.tv/'}
, query
=query
)
63 except ExtractorError
as e
:
64 if isinstance(e
.cause
, compat_HTTPError
) and e
.cause
.code
== 403:
65 self
.raise_login_required(json
.loads(e
.cause
.read().decode('utf-8'))['message'])
69 class VLiveIE(VLiveBaseIE
):
71 _VALID_URL
= r
'https?://(?:(?:www|m)\.)?vlive\.tv/(?:video|embed)/(?P<id>[0-9]+)'
73 'url': 'http://www.vlive.tv/video/1326',
74 'md5': 'cc7314812855ce56de70a06a27314983',
78 'title': "Girl's Day's Broadcast",
79 'creator': "Girl's Day",
81 'uploader_id': 'muploader_a',
82 'upload_date': '20150817',
83 'thumbnail': r
're:^https?://.*\.(?:jpg|png)$',
84 'timestamp': 1439816449,
86 'channel': 'Girl\'s Day',
87 'channel_id': 'FDF27',
89 'release_timestamp': 1439818140,
90 'release_date': '20150817',
94 'skip_download': True,
97 'url': 'http://www.vlive.tv/video/16937',
104 'subtitles': 'mincount:12',
105 'uploader_id': 'muploader_j',
106 'upload_date': '20161112',
107 'thumbnail': r
're:^https?://.*\.(?:jpg|png)$',
108 'timestamp': 1478923074,
111 'channel_id': 'F94BD',
112 'comment_count': int,
113 'release_timestamp': 1478924280,
114 'release_date': '20161112',
118 'skip_download': True,
121 'url': 'https://www.vlive.tv/video/129100',
122 'md5': 'ca2569453b79d66e5b919e5d308bff6b',
126 'title': '[V LIVE] [BTS+] Run BTS! 2019 - EP.71 :: Behind the scene',
129 'subtitles': 'mincount:10',
131 'skip': 'This video is only available for CH+ subscribers',
133 'url': 'https://www.vlive.tv/embed/1326',
134 'only_matching': True,
136 # works only with gcc=KR
137 'url': 'https://www.vlive.tv/video/225019',
138 'only_matching': True,
140 'url': 'https://www.vlive.tv/video/223906',
145 'playlist_mincount': 120
148 def _real_extract(self
, url
):
149 video_id
= self
._match
_id
(url
)
151 post
= self
._call
_api
(
152 'post/v1.0/officialVideoPost-%s', video_id
,
153 'author{nickname},channel{channelCode,channelName},officialVideo{commentCount,exposeStatus,likeCount,playCount,playTime,status,title,type,vodId},playlist{playlistSeq,totalCount,name}')
155 playlist_id
= str_or_none(try_get(post
, lambda x
: x
['playlist']['playlistSeq']))
156 if not self
._yes
_playlist
(playlist_id
, video_id
):
157 video
= post
['officialVideo']
158 return self
._get
_vlive
_info
(post
, video
, video_id
)
160 playlist_name
= str_or_none(try_get(post
, lambda x
: x
['playlist']['name']))
161 playlist_count
= str_or_none(try_get(post
, lambda x
: x
['playlist']['totalCount']))
163 playlist
= self
._call
_api
(
164 'playlist/v1.0/playlist-%s/posts', playlist_id
, 'data', {'limit': playlist_count}
)
167 for video_data
in playlist
['data']:
168 video
= video_data
.get('officialVideo')
169 video_id
= str_or_none(video
.get('videoSeq'))
170 entries
.append(self
._get
_vlive
_info
(video_data
, video
, video_id
))
172 return self
.playlist_result(entries
, playlist_id
, playlist_name
)
174 def _get_vlive_info(self
, post
, video
, video_id
):
175 def get_common_fields():
176 channel
= post
.get('channel') or {}
178 'title': video
.get('title'),
179 'creator': post
.get('author', {}).get('nickname'),
180 'channel': channel
.get('channelName'),
181 'channel_id': channel
.get('channelCode'),
182 'duration': int_or_none(video
.get('playTime')),
183 'view_count': int_or_none(video
.get('playCount')),
184 'like_count': int_or_none(video
.get('likeCount')),
185 'comment_count': int_or_none(video
.get('commentCount')),
186 'timestamp': int_or_none(video
.get('createdAt'), scale
=1000),
187 'release_timestamp': int_or_none(traverse_obj(video
, 'onAirStartAt', 'willStartAt'), scale
=1000),
188 'thumbnail': video
.get('thumb'),
191 video_type
= video
.get('type')
192 if video_type
== 'VOD':
193 inkey
= self
._call
_api
('video/v1.0/vod/%s/inkey', video_id
)['inkey']
194 vod_id
= video
['vodId']
195 info_dict
= merge_dicts(
197 self
._extract
_video
_info
(video_id
, vod_id
, inkey
))
198 thumbnail
= video
.get('thumb')
200 if not info_dict
.get('thumbnails') and info_dict
.get('thumbnail'):
201 info_dict
['thumbnails'] = [{'url': info_dict.pop('thumbnail')}
]
202 info_dict
.setdefault('thumbnails', []).append({'url': thumbnail, 'preference': 1}
)
204 elif video_type
== 'LIVE':
205 status
= video
.get('status')
206 if status
== 'ON_AIR':
207 stream_url
= self
._call
_api
(
208 'old/v3/live/%s/playInfo',
209 video_id
)['result']['adaptiveStreamUrl']
210 formats
= self
._extract
_m
3u8_formats
(stream_url
, video_id
, 'mp4')
211 self
._sort
_formats
(formats
)
212 info
= get_common_fields()
214 'title': video
['title'],
220 elif status
== 'ENDED':
221 raise ExtractorError(
222 'Uploading for replay. Please wait...', expected
=True)
223 elif status
== 'RESERVED':
224 raise ExtractorError('Coming soon!', expected
=True)
225 elif video
.get('exposeStatus') == 'CANCEL':
226 raise ExtractorError(
227 'We are sorry, but the live broadcast has been canceled.',
230 raise ExtractorError('Unknown status ' + status
)
233 class VLivePostIE(VLiveBaseIE
):
234 IE_NAME
= 'vlive:post'
235 _VALID_URL
= r
'https?://(?:(?:www|m)\.)?vlive\.tv/post/(?P<id>\d-\d+)'
238 'url': 'https://www.vlive.tv/post/1-20088044',
241 'title': 'Hola estrellitas la tierra les dice hola (si era así no?) Ha...',
242 'description': 'md5:fab8a1e50e6e51608907f46c7fa4b407',
247 'url': 'https://www.vlive.tv/post/1-20087926',
250 'title': 'James Corden: And so, the baby becamos the Papa💜😭💪😭',
254 _FVIDEO_TMPL
= 'fvideo/v1.0/fvideo-%%s/%s'
256 def _real_extract(self
, url
):
257 post_id
= self
._match
_id
(url
)
259 post
= self
._call
_api
(
260 'post/v1.0/post-%s', post_id
,
261 'attachments{video},officialVideo{videoSeq},plainBody,title')
263 video_seq
= str_or_none(try_get(
264 post
, lambda x
: x
['officialVideo']['videoSeq']))
266 return self
.url_result(
267 'http://www.vlive.tv/video/' + video_seq
,
268 VLiveIE
.ie_key(), video_seq
)
270 title
= post
['title']
272 for idx
, video
in enumerate(post
['attachments']['video'].values()):
273 video_id
= video
.get('videoId')
276 upload_type
= video
.get('uploadType')
277 upload_info
= video
.get('uploadInfo') or {}
279 if upload_type
== 'SOS':
280 download
= self
._call
_api
(
281 self
._FVIDEO
_TMPL
% 'sosPlayInfo', video_id
)['videoUrl']['download']
283 for f_id
, f_url
in download
.items():
287 'height': int_or_none(f_id
[:-1]),
289 self
._sort
_formats
(formats
)
293 'thumbnail': upload_info
.get('imageUrl'),
295 elif upload_type
== 'V':
296 vod_id
= upload_info
.get('videoId')
299 inkey
= self
._call
_api
(self
._FVIDEO
_TMPL
% 'inKey', video_id
)['inKey']
300 entry
= self
._extract
_video
_info
(video_id
, vod_id
, inkey
)
302 entry
['title'] = '%s_part%s' % (title
, idx
)
303 entries
.append(entry
)
304 return self
.playlist_result(
305 entries
, post_id
, title
, strip_or_none(post
.get('plainBody')))
308 class VLiveChannelIE(VLiveBaseIE
):
309 IE_NAME
= 'vlive:channel'
310 _VALID_URL
= r
'https?://(?:channels\.vlive\.tv|(?:(?:www|m)\.)?vlive\.tv/channel)/(?P<channel_id>[0-9A-Z]+)(?:/board/(?P<posts_id>\d+))?'
312 'url': 'http://channels.vlive.tv/FCD4B',
317 'playlist_mincount': 110
319 'url': 'https://www.vlive.tv/channel/FCD4B',
320 'only_matching': True,
322 'url': 'https://www.vlive.tv/channel/FCD4B/board/3546',
325 'title': 'MAMAMOO - Star Board',
327 'playlist_mincount': 880
330 def _entries(self
, posts_id
, board_name
):
332 posts_path
= 'post/v1.0/board-%s/posts'
333 query_add
= {'limit': 100, 'sortType': 'LATEST'}
335 posts_path
= 'post/v1.0/channel-%s/starPosts'
336 query_add
= {'limit': 100}
338 for page_num
in itertools
.count(1):
339 video_list
= self
._call
_api
(
340 posts_path
, posts_id
, 'channel{channelName},contentType,postId,title,url', query_add
,
341 note
=f
'Downloading playlist page {page_num}')
343 for video
in try_get(video_list
, lambda x
: x
['data'], list) or []:
344 video_id
= str(video
.get('postId'))
345 video_title
= str_or_none(video
.get('title'))
346 video_url
= url_or_none(video
.get('url'))
347 if not all((video_id
, video_title
, video_url
)) or video
.get('contentType') != 'VIDEO':
349 channel_name
= try_get(video
, lambda x
: x
['channel']['channelName'], compat_str
)
350 yield self
.url_result(video_url
, VLivePostIE
.ie_key(), video_id
, video_title
, channel
=channel_name
)
352 after
= try_get(video_list
, lambda x
: x
['paging']['nextParams']['after'], compat_str
)
355 query_add
['after'] = after
357 def _real_extract(self
, url
):
358 channel_id
, posts_id
= self
._match
_valid
_url
(url
).groups()
362 board
= self
._call
_api
(
363 'board/v1.0/board-%s', posts_id
, 'title,boardType')
364 board_name
= board
.get('title') or 'Unknown'
365 if board
.get('boardType') not in ('STAR', 'VLIVE_PLUS'):
366 raise ExtractorError(f
'Board {board_name!r} is not supported', expected
=True)
368 entries
= LazyList(self
._entries
(posts_id
or channel_id
, board_name
))
369 channel_name
= entries
[0]['channel']
371 return self
.playlist_result(
373 f
'{channel_id}-{posts_id}' if posts_id
else channel_id
,
374 f
'{channel_name} - {board_name}' if channel_name
and board_name
else channel_name
)