2 from __future__
import unicode_literals
7 from .naver
import NaverBaseIE
23 class VLiveBaseIE(NaverBaseIE
):
24 _APP_ID
= '8c6cc7b45d2568fb668be6e05b6e5a3b'
27 class VLiveIE(VLiveBaseIE
):
29 _VALID_URL
= r
'https?://(?:(?:www|m)\.)?vlive\.tv/(?:video|embed)/(?P<id>[0-9]+)'
30 _NETRC_MACHINE
= 'vlive'
32 'url': 'http://www.vlive.tv/video/1326',
33 'md5': 'cc7314812855ce56de70a06a27314983',
37 'title': "Girl's Day's Broadcast",
38 'creator': "Girl's Day",
40 'uploader_id': 'muploader_a',
43 'url': 'http://www.vlive.tv/video/16937',
50 'subtitles': 'mincount:12',
51 'uploader_id': 'muploader_j',
54 'skip_download': True,
57 'url': 'https://www.vlive.tv/video/129100',
58 'md5': 'ca2569453b79d66e5b919e5d308bff6b',
62 'title': '[V LIVE] [BTS+] Run BTS! 2019 - EP.71 :: Behind the scene',
65 'subtitles': 'mincount:10',
67 'skip': 'This video is only available for CH+ subscribers',
69 'url': 'https://www.vlive.tv/embed/1326',
70 'only_matching': True,
72 # works only with gcc=KR
73 'url': 'https://www.vlive.tv/video/225019',
74 'only_matching': True,
76 'url': 'https://www.vlive.tv/video/223906',
81 'playlist_mincount': 120
84 def _real_initialize(self
):
88 email
, password
= self
._get
_login
_info
()
89 if None in (email
, password
):
93 login_info
= self
._download
_json
(
94 'https://www.vlive.tv/auth/loginInfo', None,
95 note
='Downloading login info',
96 headers
={'Referer': 'https://www.vlive.tv/home'}
)
98 login_info
, lambda x
: x
['message']['login'], bool) or False
100 LOGIN_URL
= 'https://www.vlive.tv/auth/email/login'
101 self
._request
_webpage
(
102 LOGIN_URL
, None, note
='Downloading login cookies')
104 self
._download
_webpage
(
105 LOGIN_URL
, None, note
='Logging in',
106 data
=urlencode_postdata({'email': email, 'pwd': password}
),
108 'Referer': LOGIN_URL
,
109 'Content-Type': 'application/x-www-form-urlencoded'
112 if not is_logged_in():
113 raise ExtractorError('Unable to log in', expected
=True)
115 def _call_api(self
, path_template
, video_id
, fields
=None, limit
=None):
116 query
= {'appId': self._APP_ID, 'gcc': 'KR', 'platformType': 'PC'}
118 query
['fields'] = fields
120 query
['limit'] = limit
122 return self
._download
_json
(
123 'https://www.vlive.tv/globalv-web/vam-web/' + path_template
% video_id
, video_id
,
124 'Downloading %s JSON metadata' % path_template
.split('/')[-1].split('-')[0],
125 headers
={'Referer': 'https://www.vlive.tv/'}
, query
=query
)
126 except ExtractorError
as e
:
127 if isinstance(e
.cause
, compat_HTTPError
) and e
.cause
.code
== 403:
128 self
.raise_login_required(json
.loads(e
.cause
.read().decode('utf-8'))['message'])
131 def _real_extract(self
, url
):
132 video_id
= self
._match
_id
(url
)
134 post
= self
._call
_api
(
135 'post/v1.0/officialVideoPost-%s', video_id
,
136 'author{nickname},channel{channelCode,channelName},officialVideo{commentCount,exposeStatus,likeCount,playCount,playTime,status,title,type,vodId},playlist{playlistSeq,totalCount,name}')
138 playlist
= post
.get('playlist')
139 if not playlist
or self
._downloader
.params
.get('noplaylist'):
142 'Downloading just video %s because of --no-playlist'
145 video
= post
['officialVideo']
146 return self
._get
_vlive
_info
(post
, video
, video_id
)
148 playlist_name
= playlist
.get('name')
149 playlist_id
= str_or_none(playlist
.get('playlistSeq'))
150 playlist_count
= str_or_none(playlist
.get('totalCount'))
152 playlist
= self
._call
_api
(
153 'playlist/v1.0/playlist-%s/posts', playlist_id
, 'data', limit
=playlist_count
)
156 for video_data
in playlist
['data']:
157 video
= video_data
.get('officialVideo')
158 video_id
= str_or_none(video
.get('videoSeq'))
159 entries
.append(self
._get
_vlive
_info
(video_data
, video
, video_id
))
161 return self
.playlist_result(entries
, playlist_id
, playlist_name
)
163 def _get_vlive_info(self
, post
, video
, video_id
):
164 def get_common_fields():
165 channel
= post
.get('channel') or {}
167 'title': video
.get('title'),
168 'creator': post
.get('author', {}).get('nickname'),
169 'channel': channel
.get('channelName'),
170 'channel_id': channel
.get('channelCode'),
171 'duration': int_or_none(video
.get('playTime')),
172 'view_count': int_or_none(video
.get('playCount')),
173 'like_count': int_or_none(video
.get('likeCount')),
174 'comment_count': int_or_none(video
.get('commentCount')),
177 video_type
= video
.get('type')
178 if video_type
== 'VOD':
179 inkey
= self
._call
_api
('video/v1.0/vod/%s/inkey', video_id
)['inkey']
180 vod_id
= video
['vodId']
183 self
._extract
_video
_info
(video_id
, vod_id
, inkey
))
184 elif video_type
== 'LIVE':
185 status
= video
.get('status')
186 if status
== 'ON_AIR':
187 stream_url
= self
._call
_api
(
188 'old/v3/live/%s/playInfo',
189 video_id
)['result']['adaptiveStreamUrl']
190 formats
= self
._extract
_m
3u8_formats
(stream_url
, video_id
, 'mp4')
191 self
._sort
_formats
(formats
)
192 info
= get_common_fields()
194 'title': self
._live
_title
(video
['title']),
200 elif status
== 'ENDED':
201 raise ExtractorError(
202 'Uploading for replay. Please wait...', expected
=True)
203 elif status
== 'RESERVED':
204 raise ExtractorError('Coming soon!', expected
=True)
205 elif video
.get('exposeStatus') == 'CANCEL':
206 raise ExtractorError(
207 'We are sorry, but the live broadcast has been canceled.',
210 raise ExtractorError('Unknown status ' + status
)
213 class VLivePostIE(VLiveIE
):
214 IE_NAME
= 'vlive:post'
215 _VALID_URL
= r
'https?://(?:(?:www|m)\.)?vlive\.tv/post/(?P<id>\d-\d+)'
218 'url': 'https://www.vlive.tv/post/1-20088044',
221 'title': 'Hola estrellitas la tierra les dice hola (si era así no?) Ha...',
222 'description': 'md5:fab8a1e50e6e51608907f46c7fa4b407',
227 'url': 'https://www.vlive.tv/post/1-20087926',
230 'title': 'James Corden: And so, the baby becamos the Papa💜😭💪😭',
234 _FVIDEO_TMPL
= 'fvideo/v1.0/fvideo-%%s/%s'
235 _SOS_TMPL
= _FVIDEO_TMPL
% 'sosPlayInfo'
236 _INKEY_TMPL
= _FVIDEO_TMPL
% 'inKey'
238 def _real_extract(self
, url
):
239 post_id
= self
._match
_id
(url
)
241 post
= self
._call
_api
(
242 'post/v1.0/post-%s', post_id
,
243 'attachments{video},officialVideo{videoSeq},plainBody,title')
245 video_seq
= str_or_none(try_get(
246 post
, lambda x
: x
['officialVideo']['videoSeq']))
248 return self
.url_result(
249 'http://www.vlive.tv/video/' + video_seq
,
250 VLiveIE
.ie_key(), video_seq
)
252 title
= post
['title']
254 for idx
, video
in enumerate(post
['attachments']['video'].values()):
255 video_id
= video
.get('videoId')
258 upload_type
= video
.get('uploadType')
259 upload_info
= video
.get('uploadInfo') or {}
261 if upload_type
== 'SOS':
262 download
= self
._call
_api
(
263 self
._SOS
_TMPL
, video_id
)['videoUrl']['download']
265 for f_id
, f_url
in download
.items():
269 'height': int_or_none(f_id
[:-1]),
271 self
._sort
_formats
(formats
)
275 'thumbnail': upload_info
.get('imageUrl'),
277 elif upload_type
== 'V':
278 vod_id
= upload_info
.get('videoId')
281 inkey
= self
._call
_api
(self
._INKEY
_TMPL
, video_id
)['inKey']
282 entry
= self
._extract
_video
_info
(video_id
, vod_id
, inkey
)
284 entry
['title'] = '%s_part%s' % (title
, idx
)
285 entries
.append(entry
)
286 return self
.playlist_result(
287 entries
, post_id
, title
, strip_or_none(post
.get('plainBody')))
290 class VLiveChannelIE(VLiveBaseIE
):
291 IE_NAME
= 'vlive:channel'
292 _VALID_URL
= r
'https?://(?:channels\.vlive\.tv|(?:(?:www|m)\.)?vlive\.tv/channel)/(?P<id>[0-9A-Z]+)'
294 'url': 'http://channels.vlive.tv/FCD4B',
299 'playlist_mincount': 110
301 'url': 'https://www.vlive.tv/channel/FCD4B',
302 'only_matching': True,
305 def _call_api(self
, path
, channel_key_suffix
, channel_value
, note
, query
):
307 'app_id': self
._APP
_ID
,
308 'channel' + channel_key_suffix
: channel_value
,
311 return self
._download
_json
(
312 'http://api.vfan.vlive.tv/vproxy/channelplus/' + path
,
313 channel_value
, note
='Downloading ' + note
, query
=q
)['result']
315 def _real_extract(self
, url
):
316 channel_code
= self
._match
_id
(url
)
318 channel_seq
= self
._call
_api
(
319 'decodeChannelCode', 'Code', channel_code
,
320 'decode channel code', {})['channelSeq']
325 for page_num
in itertools
.count(1):
326 video_list
= self
._call
_api
(
327 'getChannelVideoList', 'Seq', channel_seq
,
328 'channel list page #%d' % page_num
, {
329 # Large values of maxNumOfRows (~300 or above) may cause
330 # empty responses (see [1]), e.g. this happens for [2] that
331 # has more than 300 videos.
332 # 1. https://github.com/ytdl-org/youtube-dl/issues/13830
333 # 2. http://channels.vlive.tv/EDBF.
340 channel_name
= try_get(
342 lambda x
: x
['channelInfo']['channelName'],
346 video_list
, lambda x
: x
['videoList'], list)
351 video_id
= video
.get('videoSeq')
352 video_type
= video
.get('videoType')
354 if not video_id
or not video_type
:
356 video_id
= compat_str(video_id
)
358 if video_type
in ('PLAYLIST'):
359 first_video_id
= try_get(
361 lambda x
: x
['videoPlaylist']['videoList'][0]['videoSeq'], int)
363 if not first_video_id
:
368 'http://www.vlive.tv/video/%s' % first_video_id
,
369 ie
=VLiveIE
.ie_key(), video_id
=first_video_id
))
373 'http://www.vlive.tv/video/%s' % video_id
,
374 ie
=VLiveIE
.ie_key(), video_id
=video_id
))
376 return self
.playlist_result(
377 entries
, channel_code
, channel_name
)