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,
77 def _real_initialize(self
):
81 email
, password
= self
._get
_login
_info
()
82 if None in (email
, password
):
86 login_info
= self
._download
_json
(
87 'https://www.vlive.tv/auth/loginInfo', None,
88 note
='Downloading login info',
89 headers
={'Referer': 'https://www.vlive.tv/home'}
)
91 login_info
, lambda x
: x
['message']['login'], bool) or False
93 LOGIN_URL
= 'https://www.vlive.tv/auth/email/login'
94 self
._request
_webpage
(
95 LOGIN_URL
, None, note
='Downloading login cookies')
97 self
._download
_webpage
(
98 LOGIN_URL
, None, note
='Logging in',
99 data
=urlencode_postdata({'email': email, 'pwd': password}
),
101 'Referer': LOGIN_URL
,
102 'Content-Type': 'application/x-www-form-urlencoded'
105 if not is_logged_in():
106 raise ExtractorError('Unable to log in', expected
=True)
108 def _call_api(self
, path_template
, video_id
, fields
=None):
109 query
= {'appId': self._APP_ID, 'gcc': 'KR'}
111 query
['fields'] = fields
113 return self
._download
_json
(
114 'https://www.vlive.tv/globalv-web/vam-web/' + path_template
% video_id
, video_id
,
115 'Downloading %s JSON metadata' % path_template
.split('/')[-1].split('-')[0],
116 headers
={'Referer': 'https://www.vlive.tv/'}
, query
=query
)
117 except ExtractorError
as e
:
118 if isinstance(e
.cause
, compat_HTTPError
) and e
.cause
.code
== 403:
119 self
.raise_login_required(json
.loads(e
.cause
.read().decode())['message'])
122 def _real_extract(self
, url
):
123 video_id
= self
._match
_id
(url
)
125 post
= self
._call
_api
(
126 'post/v1.0/officialVideoPost-%s', video_id
,
127 'author{nickname},channel{channelCode,channelName},officialVideo{commentCount,exposeStatus,likeCount,playCount,playTime,status,title,type,vodId}')
129 video
= post
['officialVideo']
131 def get_common_fields():
132 channel
= post
.get('channel') or {}
134 'title': video
.get('title'),
135 'creator': post
.get('author', {}).get('nickname'),
136 'channel': channel
.get('channelName'),
137 'channel_id': channel
.get('channelCode'),
138 'duration': int_or_none(video
.get('playTime')),
139 'view_count': int_or_none(video
.get('playCount')),
140 'like_count': int_or_none(video
.get('likeCount')),
141 'comment_count': int_or_none(video
.get('commentCount')),
144 video_type
= video
.get('type')
145 if video_type
== 'VOD':
146 inkey
= self
._call
_api
('video/v1.0/vod/%s/inkey', video_id
)['inkey']
147 vod_id
= video
['vodId']
150 self
._extract
_video
_info
(video_id
, vod_id
, inkey
))
151 elif video_type
== 'LIVE':
152 status
= video
.get('status')
153 if status
== 'ON_AIR':
154 stream_url
= self
._call
_api
(
155 'old/v3/live/%s/playInfo',
156 video_id
)['result']['adaptiveStreamUrl']
157 formats
= self
._extract
_m
3u8_formats
(stream_url
, video_id
, 'mp4')
158 info
= get_common_fields()
160 'title': self
._live
_title
(video
['title']),
166 elif status
== 'ENDED':
167 raise ExtractorError(
168 'Uploading for replay. Please wait...', expected
=True)
169 elif status
== 'RESERVED':
170 raise ExtractorError('Coming soon!', expected
=True)
171 elif video
.get('exposeStatus') == 'CANCEL':
172 raise ExtractorError(
173 'We are sorry, but the live broadcast has been canceled.',
176 raise ExtractorError('Unknown status ' + status
)
179 class VLivePostIE(VLiveIE
):
180 IE_NAME
= 'vlive:post'
181 _VALID_URL
= r
'https?://(?:(?:www|m)\.)?vlive\.tv/post/(?P<id>\d-\d+)'
184 'url': 'https://www.vlive.tv/post/1-20088044',
187 'title': 'Hola estrellitas la tierra les dice hola (si era así no?) Ha...',
188 'description': 'md5:fab8a1e50e6e51608907f46c7fa4b407',
193 'url': 'https://www.vlive.tv/post/1-20087926',
196 'title': 'James Corden: And so, the baby becamos the Papa💜😭💪😭',
200 _FVIDEO_TMPL
= 'fvideo/v1.0/fvideo-%%s/%s'
201 _SOS_TMPL
= _FVIDEO_TMPL
% 'sosPlayInfo'
202 _INKEY_TMPL
= _FVIDEO_TMPL
% 'inKey'
204 def _real_extract(self
, url
):
205 post_id
= self
._match
_id
(url
)
207 post
= self
._call
_api
(
208 'post/v1.0/post-%s', post_id
,
209 'attachments{video},officialVideo{videoSeq},plainBody,title')
211 video_seq
= str_or_none(try_get(
212 post
, lambda x
: x
['officialVideo']['videoSeq']))
214 return self
.url_result(
215 'http://www.vlive.tv/video/' + video_seq
,
216 VLiveIE
.ie_key(), video_seq
)
218 title
= post
['title']
220 for idx
, video
in enumerate(post
['attachments']['video'].values()):
221 video_id
= video
.get('videoId')
224 upload_type
= video
.get('uploadType')
225 upload_info
= video
.get('uploadInfo') or {}
227 if upload_type
== 'SOS':
228 download
= self
._call
_api
(
229 self
._SOS
_TMPL
, video_id
)['videoUrl']['download']
231 for f_id
, f_url
in download
.items():
235 'height': int_or_none(f_id
[:-1]),
237 self
._sort
_formats
(formats
)
241 'thumbnail': upload_info
.get('imageUrl'),
243 elif upload_type
== 'V':
244 vod_id
= upload_info
.get('videoId')
247 inkey
= self
._call
_api
(self
._INKEY
_TMPL
, video_id
)['inKey']
248 entry
= self
._extract
_video
_info
(video_id
, vod_id
, inkey
)
250 entry
['title'] = '%s_part%s' % (title
, idx
)
251 entries
.append(entry
)
252 return self
.playlist_result(
253 entries
, post_id
, title
, strip_or_none(post
.get('plainBody')))
256 class VLiveChannelIE(VLiveBaseIE
):
257 IE_NAME
= 'vlive:channel'
258 _VALID_URL
= r
'https?://(?:channels\.vlive\.tv|(?:(?:www|m)\.)?vlive\.tv/channel)/(?P<id>[0-9A-Z]+)'
260 'url': 'http://channels.vlive.tv/FCD4B',
265 'playlist_mincount': 110
267 'url': 'https://www.vlive.tv/channel/FCD4B',
268 'only_matching': True,
271 def _call_api(self
, path
, channel_key_suffix
, channel_value
, note
, query
):
273 'app_id': self
._APP
_ID
,
274 'channel' + channel_key_suffix
: channel_value
,
277 return self
._download
_json
(
278 'http://api.vfan.vlive.tv/vproxy/channelplus/' + path
,
279 channel_value
, note
='Downloading ' + note
, query
=q
)['result']
281 def _real_extract(self
, url
):
282 channel_code
= self
._match
_id
(url
)
284 channel_seq
= self
._call
_api
(
285 'decodeChannelCode', 'Code', channel_code
,
286 'decode channel code', {})['channelSeq']
291 for page_num
in itertools
.count(1):
292 video_list
= self
._call
_api
(
293 'getChannelVideoList', 'Seq', channel_seq
,
294 'channel list page #%d' % page_num
, {
295 # Large values of maxNumOfRows (~300 or above) may cause
296 # empty responses (see [1]), e.g. this happens for [2] that
297 # has more than 300 videos.
298 # 1. https://github.com/ytdl-org/youtube-dl/issues/13830
299 # 2. http://channels.vlive.tv/EDBF.
306 channel_name
= try_get(
308 lambda x
: x
['channelInfo']['channelName'],
312 video_list
, lambda x
: x
['videoList'], list)
317 video_id
= video
.get('videoSeq')
318 video_type
= video
.get('videoType')
320 if not video_id
or not video_type
:
322 video_id
= compat_str(video_id
)
324 if video_type
in ('PLAYLIST'):
325 playlist_videos
= try_get(
327 lambda x
: x
['videoPlaylist']['videoList'], list)
328 if not playlist_videos
:
331 for playlist_video
in playlist_videos
:
332 playlist_video_id
= playlist_video
.get('videoSeq')
333 if not playlist_video_id
:
335 playlist_video_id
= compat_str(playlist_video_id
)
339 'http://www.vlive.tv/video/%s' % playlist_video_id
,
340 ie
=VLiveIE
.ie_key(), video_id
=playlist_video_id
))
344 'http://www.vlive.tv/video/%s' % video_id
,
345 ie
=VLiveIE
.ie_key(), video_id
=video_id
))
347 return self
.playlist_result(
348 entries
, channel_code
, channel_name
)