2 from __future__
import unicode_literals
6 from .common
import InfoExtractor
8 compat_urllib_parse_urlparse
,
20 class AfreecaTVIE(InfoExtractor
):
22 IE_DESC
= 'afreecatv.com'
26 (?:(?:live|afbbs|www)\.)?afreeca(?:tv)?\.com(?::\d+)?
28 /app/(?:index|read_ucc_bbs)\.cgi|
29 /player/[Pp]layer\.(?:swf|html)
31 vod\.afreecatv\.com/PLAYER/STATION/
36 'url': 'http://live.afreecatv.com:8079/app/index.cgi?szType=read_ucc_bbs&szBjId=dailyapril&nStationNo=16711924&nBbsNo=18605867&nTitleNo=36164052&szSkin=',
37 'md5': 'f72c89fe7ecc14c1b5ce506c4996046e',
41 'title': '데일리 에이프릴 요정들의 시상식!',
42 'thumbnail': 're:^https?://(?:video|st)img.afreecatv.com/.*$',
43 'uploader': 'dailyapril',
44 'uploader_id': 'dailyapril',
45 'upload_date': '20160503',
48 'url': 'http://afbbs.afreecatv.com:8080/app/read_ucc_bbs.cgi?nStationNo=16711924&nTitleNo=36153164&szBjId=dailyapril&nBbsNo=18605867',
51 'title': "BJ유트루와 함께하는 '팅커벨 메이크업!'",
52 'thumbnail': 're:^https?://(?:video|st)img.afreecatv.com/.*$',
53 'uploader': 'dailyapril',
54 'uploader_id': 'dailyapril',
58 'md5': 'd8b7c174568da61d774ef0203159bf97',
62 'title': "BJ유트루와 함께하는 '팅커벨 메이크업!'",
63 'upload_date': '20160502',
66 'md5': '58f2ce7f6044e34439ab2d50612ab02b',
70 'title': "BJ유트루와 함께하는 '팅커벨 메이크업!'",
71 'upload_date': '20160502',
75 'url': 'http://www.afreecatv.com/player/Player.swf?szType=szBjId=djleegoon&nStationNo=11273158&nBbsNo=13161095&nTitleNo=36327652',
76 'only_matching': True,
78 'url': 'http://vod.afreecatv.com/PLAYER/STATION/15055030',
79 'only_matching': True,
83 def parse_video_key(key
):
85 m
= re
.match(r
'^(?P<upload_date>\d{8})_\w+_(?P<part>\d+)$', key
)
87 video_key
['upload_date'] = m
.group('upload_date')
88 video_key
['part'] = m
.group('part')
91 def _real_extract(self
, url
):
92 video_id
= self
._match
_id
(url
)
93 parsed_url
= compat_urllib_parse_urlparse(url
)
94 info_url
= compat_urlparse
.urlunparse(parsed_url
._replace
(
95 netloc
='afbbs.afreecatv.com:8080',
96 path
='/api/video/get_video_info.php'))
98 video_xml
= self
._download
_xml
(
99 update_url_query(info_url
, {'nTitleNo': video_id}
), video_id
)
101 if xpath_element(video_xml
, './track/video/file') is None:
102 raise ExtractorError('Specified AfreecaTV video does not exist',
105 title
= xpath_text(video_xml
, './track/title', 'title')
106 uploader
= xpath_text(video_xml
, './track/nickname', 'uploader')
107 uploader_id
= xpath_text(video_xml
, './track/bj_id', 'uploader id')
108 duration
= int_or_none(xpath_text(video_xml
, './track/duration',
110 thumbnail
= xpath_text(video_xml
, './track/titleImage', 'thumbnail')
113 for i
, video_file
in enumerate(video_xml
.findall('./track/video/file')):
114 video_key
= self
.parse_video_key(video_file
.get('key', ''))
118 'id': '%s_%s' % (video_id
, video_key
.get('part', i
+ 1)),
120 'upload_date': video_key
.get('upload_date'),
121 'duration': int_or_none(video_file
.get('duration')),
122 'url': video_file
.text
,
128 'uploader': uploader
,
129 'uploader_id': uploader_id
,
130 'duration': duration
,
131 'thumbnail': thumbnail
,
135 info
['_type'] = 'multi_video'
136 info
['entries'] = entries
137 elif len(entries
) == 1:
138 info
['url'] = entries
[0]['url']
139 info
['upload_date'] = entries
[0].get('upload_date')
141 raise ExtractorError(
142 'No files found for the specified AfreecaTV video, either'
143 ' the URL is incorrect or the video has been made private.',
149 class AfreecaTVGlobalIE(AfreecaTVIE
):
150 IE_NAME
= 'afreecatv:global'
151 _VALID_URL
= r
'https?://(?:www\.)?afreeca\.tv/(?P<channel_id>\d+)(?:/v/(?P<video_id>\d+))?'
153 'url': 'http://afreeca.tv/36853014/v/58301',
156 'title': 'tryhard top100',
157 'uploader_id': '36853014',
158 'uploader': 'makgi Hearthstone Live!',
163 def _real_extract(self
, url
):
164 channel_id
, video_id
= re
.match(self
._VALID
_URL
, url
).groups()
165 video_type
= 'video' if video_id
else 'live'
171 query
['vno'] = video_id
172 video_data
= self
._download
_json
(
173 'http://api.afreeca.tv/%s/view_%s.php' % (video_type
, video_type
),
174 video_id
or channel_id
, query
=query
)['channel']
176 if video_data
.get('result') != 1:
177 raise ExtractorError('%s said: %s' % (self
.IE_NAME
, video_data
['remsg']))
179 title
= video_data
['title']
182 'thumbnail': video_data
.get('thumb'),
183 'view_count': int_or_none(video_data
.get('vcnt')),
184 'age_limit': int_or_none(video_data
.get('grade')),
185 'uploader_id': channel_id
,
186 'uploader': video_data
.get('cname'),
191 for i
, f
in enumerate(video_data
.get('flist', [])):
192 video_key
= self
.parse_video_key(f
.get('key', ''))
193 f_url
= f
.get('file')
194 if not video_key
or not f_url
:
197 'id': '%s_%s' % (video_id
, video_key
.get('part', i
+ 1)),
199 'upload_date': video_key
.get('upload_date'),
200 'duration': int_or_none(f
.get('length')),
202 'protocol': 'm3u8_native',
209 'duration': int_or_none(video_data
.get('length')),
212 info
['_type'] = 'multi_video'
213 info
['entries'] = entries
214 elif len(entries
) == 1:
215 i
= entries
[0].copy()
220 for s
in video_data
.get('strm', []):
221 s_url
= s
.get('purl')
224 # TODO: extract rtmp formats
225 if s
.get('stype') == 'HLS':
226 formats
.extend(self
._extract
_m
3u8_formats
(
227 s_url
, channel_id
, 'mp4', fatal
=False))
228 self
._sort
_formats
(formats
)
232 'title': self
._live
_title
(title
),