7 from .common
import InfoExtractor
16 class ZingMp3BaseIE(InfoExtractor
):
17 _VALID_URL_TMPL
= r
'https?://(?:mp3\.zing|zingmp3)\.vn/(?P<type>(?:%s))/[^/?#]+/(?P<id>\w+)(?:\.html|\?)'
18 _GEO_COUNTRIES
= ['VN']
19 _DOMAIN
= 'https://zingmp3.vn'
23 'bai-hat': '/api/v2/page/get/song',
24 'embed': '/api/v2/page/get/song',
25 'video-clip': '/api/v2/page/get/video',
26 'lyric': '/api/v2/lyric/get/lyric',
27 'song-streaming': '/api/v2/song/get/streaming',
29 'playlist': '/api/v2/page/get/playlist',
30 'album': '/api/v2/page/get/playlist',
32 'zing-chart': '/api/v2/page/get/chart-home',
33 'zing-chart-tuan': '/api/v2/page/get/week-chart',
34 'moi-phat-hanh': '/api/v2/page/get/newrelease-chart',
35 'the-loai-video': '/api/v2/video/get/list',
37 'info-artist': '/api/v2/page/get/artist',
38 'user-list-song': '/api/v2/song/get/list',
39 'user-list-video': '/api/v2/video/get/list',
42 def _api_url(self
, url_type
, params
):
43 api_slug
= self
._API
_SLUGS
[url_type
]
44 params
.update({'ctime': '1'}
)
45 sha256
= hashlib
.sha256(
46 ''.join(f
'{k}={v}' for k
, v
in sorted(params
.items())).encode()).hexdigest()
49 'apiKey': '88265e23d4284f25963e6eedac8fbfa3',
51 b
'2aa2d1c561e809b267f3638c4a307aab', f
'{api_slug}{sha256}'.encode(), hashlib
.sha512
).hexdigest(),
53 return f
'{self._DOMAIN}{api_slug}?{urllib.parse.urlencode(data)}'
55 def _call_api(self
, url_type
, params
, display_id
=None, **kwargs
):
56 resp
= self
._download
_json
(
57 self
._api
_url
(url_type
, params
), display_id
or params
.get('id'),
58 note
=f
'Downloading {url_type} JSON metadata', **kwargs
)
59 return (resp
or {}).get('data') or {}
61 def _real_initialize(self
):
62 if not self
._cookies
_passed
:
63 self
._request
_webpage
(
64 self
._api
_url
('bai-hat', {'id': ''}
), None, note
='Updating cookies')
66 def _parse_items(self
, items
):
67 for url
in traverse_obj(items
, (..., 'link')) or []:
68 yield self
.url_result(urljoin(self
._DOMAIN
, url
))
71 class ZingMp3IE(ZingMp3BaseIE
):
72 _VALID_URL
= ZingMp3BaseIE
._VALID
_URL
_TMPL
% 'bai-hat|video-clip|embed'
74 IE_DESC
= 'zingmp3.vn'
76 'url': 'https://mp3.zing.vn/bai-hat/Xa-Mai-Xa-Bao-Thy/ZWZB9WAB.html',
77 'md5': 'ead7ae13693b3205cbc89536a077daed',
82 'thumbnail': r
're:^https?://.+\.jpg',
91 'album': 'Special Album',
92 'album_artist': 'Bảo Thy',
95 'url': 'https://zingmp3.vn/video-clip/Suong-Hoa-Dua-Loi-K-ICM-RYO/ZO8ZF7C7.html',
96 'md5': '3c2081e79471a2f4a3edd90b70b185ea',
99 'title': 'Sương Hoa Đưa Lối',
101 'thumbnail': r
're:^https?://.+\.jpg',
103 'track': 'Sương Hoa Đưa Lối',
104 'artist': 'K-ICM, RYO',
105 'album': 'Sương Hoa Đưa Lối (Single)',
106 'album_artist': 'K-ICM, RYO',
109 'url': 'https://zingmp3.vn/bai-hat/Nguoi-Yeu-Toi-Lanh-Lung-Sat-Da-Mr-Siro/ZZ6IW7OU.html',
110 'md5': '3e9f7a9bd0d965573dbff8d7c68b629d',
113 'title': 'Người Yêu Tôi Lạnh Lùng Sắt Đá',
115 'thumbnail': r
're:^https?://.+\.jpg',
117 'track': 'Người Yêu Tôi Lạnh Lùng Sắt Đá',
118 'artist': 'Mr. Siro',
119 'album': 'Người Yêu Tôi Lạnh Lùng Sắt Đá (Single)',
120 'album_artist': 'Mr. Siro',
123 'url': 'https://zingmp3.vn/embed/song/ZWZEI76B?start=false',
124 'only_matching': True,
126 'url': 'https://zingmp3.vn/bai-hat/Xa-Mai-Xa-Bao-Thy/ZWZB9WAB.html',
127 'only_matching': True,
130 def _real_extract(self
, url
):
131 song_id
, url_type
= self
._match
_valid
_url
(url
).group('id', 'type')
132 item
= self
._call
_api
(url_type
, {'id': song_id}
)
134 item_id
= item
.get('encodeId') or song_id
135 if url_type
== 'video-clip':
136 source
= item
.get('streaming')
137 source
['mp4'] = self
._download
_json
(
138 'http://api.mp3.zing.vn/api/mobile/video/getvideoinfo', item_id
,
139 query
={'requestdata': json.dumps({'id': item_id}
)},
140 note
='Downloading mp4 JSON metadata').get('source')
142 source
= self
._call
_api
('song-streaming', {'id': item_id}
)
145 for k
, v
in (source
or {}).items():
146 if not v
or v
== 'VIP':
148 if k
not in ('mp4', 'hls'):
152 'tbr': int_or_none(k
),
153 'url': self
._proto
_relative
_url
(v
),
157 for res
, video_url
in v
.items():
161 formats
.extend(self
._extract
_m
3u8_formats
(video_url
, item_id
, 'mp4', m3u8_id
=k
, fatal
=False))
164 'format_id': f
'mp4-{res}',
166 'height': int_or_none(res
),
169 if not formats
and item
.get('msg') == 'Sorry, this content is not available in your country.':
170 self
.raise_geo_restricted(countries
=self
._GEO
_COUNTRIES
, metadata_available
=True)
172 lyric
= item
.get('lyric') or self
._call
_api
('lyric', {'id': item_id}
, fatal
=False).get('file')
176 'title': traverse_obj(item
, 'title', 'alias'),
177 'thumbnail': traverse_obj(item
, 'thumbnail', 'thumbnailM'),
178 'duration': int_or_none(item
.get('duration')),
179 'track': traverse_obj(item
, 'title', 'alias'),
180 'artist': traverse_obj(item
, 'artistsNames', 'artists_names'),
181 'album': traverse_obj(item
, ('album', ('name', 'title')), get_all
=False),
182 'album_artist': traverse_obj(item
, ('album', ('artistsNames', 'artists_names')), get_all
=False),
184 'subtitles': {'origin': [{'url': lyric}
]} if lyric
else None,
188 class ZingMp3AlbumIE(ZingMp3BaseIE
):
189 _VALID_URL
= ZingMp3BaseIE
._VALID
_URL
_TMPL
% 'album|playlist'
191 'url': 'http://mp3.zing.vn/album/Lau-Dai-Tinh-Ai-Bang-Kieu-Minh-Tuyet/ZWZBWDAF.html',
194 'title': 'Lâu Đài Tình Ái',
196 'playlist_mincount': 9,
198 'url': 'https://zingmp3.vn/album/Nhung-Bai-Hat-Hay-Nhat-Cua-Mr-Siro-Mr-Siro/ZWZAEZZD.html',
201 'title': 'Những Bài Hát Hay Nhất Của Mr. Siro',
203 'playlist_mincount': 49,
205 'url': 'http://mp3.zing.vn/playlist/Duong-Hong-Loan-apollobee/IWCAACCB.html',
206 'only_matching': True,
208 'url': 'https://zingmp3.vn/album/Lau-Dai-Tinh-Ai-Bang-Kieu-Minh-Tuyet/ZWZBWDAF.html',
209 'only_matching': True,
211 IE_NAME
= 'zingmp3:album'
213 def _real_extract(self
, url
):
214 song_id
, url_type
= self
._match
_valid
_url
(url
).group('id', 'type')
215 data
= self
._call
_api
(url_type
, {'id': song_id}
)
216 return self
.playlist_result(
217 self
._parse
_items
(traverse_obj(data
, ('song', 'items'))),
218 traverse_obj(data
, 'id', 'encodeId'), traverse_obj(data
, 'name', 'title'))
221 class ZingMp3ChartHomeIE(ZingMp3BaseIE
):
222 _VALID_URL
= r
'https?://(?:mp3\.zing|zingmp3)\.vn/(?P<id>(?:zing-chart|moi-phat-hanh))/?(?:[#?]|$)'
224 'url': 'https://zingmp3.vn/zing-chart',
228 'playlist_mincount': 100,
230 'url': 'https://zingmp3.vn/moi-phat-hanh',
232 'id': 'moi-phat-hanh',
234 'playlist_mincount': 100,
236 IE_NAME
= 'zingmp3:chart-home'
238 def _real_extract(self
, url
):
239 url_type
= self
._match
_id
(url
)
240 data
= self
._call
_api
(url_type
, {'id': url_type}
)
241 items
= traverse_obj(data
, ('RTChart', 'items') if url_type
== 'zing-chart' else 'items')
242 return self
.playlist_result(self
._parse
_items
(items
), url_type
)
245 class ZingMp3WeekChartIE(ZingMp3BaseIE
):
246 _VALID_URL
= ZingMp3BaseIE
._VALID
_URL
_TMPL
% 'zing-chart-tuan'
247 IE_NAME
= 'zingmp3:week-chart'
249 'url': 'https://zingmp3.vn/zing-chart-tuan/Bai-hat-Viet-Nam/IWZ9Z08I.html',
252 'title': 'zing-chart-vn',
254 'playlist_mincount': 10,
256 'url': 'https://zingmp3.vn/zing-chart-tuan/Bai-hat-US-UK/IWZ9Z0BW.html',
259 'title': 'zing-chart-us',
261 'playlist_mincount': 10,
263 'url': 'https://zingmp3.vn/zing-chart-tuan/Bai-hat-KPop/IWZ9Z0BO.html',
266 'title': 'zing-chart-korea',
268 'playlist_mincount': 10,
271 def _real_extract(self
, url
):
272 song_id
, url_type
= self
._match
_valid
_url
(url
).group('id', 'type')
273 data
= self
._call
_api
(url_type
, {'id': song_id}
)
274 return self
.playlist_result(
275 self
._parse
_items
(data
['items']), song_id
, f
'zing-chart-{data.get("country", "")}')
278 class ZingMp3ChartMusicVideoIE(ZingMp3BaseIE
):
279 _VALID_URL
= r
'https?://(?:mp3\.zing|zingmp3)\.vn/(?P<type>the-loai-video)/(?P<regions>[^/]+)/(?P<id>[^\.]+)'
280 IE_NAME
= 'zingmp3:chart-music-video'
282 'url': 'https://zingmp3.vn/the-loai-video/Viet-Nam/IWZ9Z08I.html',
285 'title': 'the-loai-video_Viet-Nam',
287 'playlist_mincount': 400,
289 'url': 'https://zingmp3.vn/the-loai-video/Au-My/IWZ9Z08O.html',
292 'title': 'the-loai-video_Au-My',
294 'playlist_mincount': 40,
296 'url': 'https://zingmp3.vn/the-loai-video/Han-Quoc/IWZ9Z08W.html',
299 'title': 'the-loai-video_Han-Quoc',
301 'playlist_mincount': 30,
303 'url': 'https://zingmp3.vn/the-loai-video/Khong-Loi/IWZ9Z086.html',
306 'title': 'the-loai-video_Khong-Loi',
308 'playlist_mincount': 10,
311 def _fetch_page(self
, song_id
, url_type
, page
):
312 return self
._parse
_items
(self
._call
_api
(url_type
, {
316 'count': self
._PER
_PAGE
319 def _real_extract(self
, url
):
320 song_id
, regions
, url_type
= self
._match
_valid
_url
(url
).group('id', 'regions', 'type')
321 return self
.playlist_result(
322 OnDemandPagedList(functools
.partial(self
._fetch
_page
, song_id
, url_type
), self
._PER
_PAGE
),
323 song_id
, f
'{url_type}_{regions}')
326 class ZingMp3UserIE(ZingMp3BaseIE
):
327 _VALID_URL
= r
'https?://(?:mp3\.zing|zingmp3)\.vn/(?P<user>[^/]+)/(?P<type>bai-hat|single|album|video)/?(?:[?#]|$)'
328 IE_NAME
= 'zingmp3:user'
330 'url': 'https://zingmp3.vn/Mr-Siro/bai-hat',
333 'title': 'Mr. Siro - bai-hat',
334 'description': 'md5:85ab29bd7b21725c12bf76fd1d6922e5',
336 'playlist_mincount': 91,
338 'url': 'https://zingmp3.vn/Mr-Siro/album',
341 'title': 'Mr. Siro - album',
342 'description': 'md5:85ab29bd7b21725c12bf76fd1d6922e5',
344 'playlist_mincount': 3,
346 'url': 'https://zingmp3.vn/Mr-Siro/single',
349 'title': 'Mr. Siro - single',
350 'description': 'md5:85ab29bd7b21725c12bf76fd1d6922e5',
352 'playlist_mincount': 20,
354 'url': 'https://zingmp3.vn/Mr-Siro/video',
357 'title': 'Mr. Siro - video',
358 'description': 'md5:85ab29bd7b21725c12bf76fd1d6922e5',
360 'playlist_mincount': 15,
363 def _fetch_page(self
, user_id
, url_type
, page
):
364 url_type
= 'user-list-song' if url_type
== 'bai-hat' else 'user-list-video'
365 return self
._parse
_items
(self
._call
_api
(url_type
, {
369 'count': self
._PER
_PAGE
370 }, query
={'sort': 'new', 'sectionId': 'aSong'}
).get('items'))
372 def _real_extract(self
, url
):
373 user_alias
, url_type
= self
._match
_valid
_url
(url
).group('user', 'type')
377 user_info
= self
._call
_api
('info-artist', {}, user_alias, query={'alias': user_alias}
)
378 if url_type
in ('bai-hat', 'video'):
379 entries
= OnDemandPagedList(
380 functools
.partial(self
._fetch
_page
, user_info
['id'], url_type
), self
._PER
_PAGE
)
382 entries
= self
._parse
_items
(traverse_obj(user_info
, (
383 'sections', lambda _
, v
: v
['link'] == f
'/{user_alias}/{url_type}', 'items', ...)))
384 return self
.playlist_result(
385 entries
, user_info
['id'], f
'{user_info.get("name")} - {url_type}', user_info
.get('biography'))