+ def _real_extract(self, url):
+ song_id, url_type = self._match_valid_url(url).group('id', 'type')
+ data = self._call_api(url_type, {'id': song_id})
+ return self.playlist_result(
+ self._parse_items(traverse_obj(data, ('song', 'items'))),
+ traverse_obj(data, 'id', 'encodeId'), traverse_obj(data, 'name', 'title'))
+
+
+class ZingMp3ChartHomeIE(ZingMp3BaseIE):
+ _VALID_URL = r'https?://(?:mp3\.zing|zingmp3)\.vn/(?P<id>(?:zing-chart|moi-phat-hanh|top100|podcast-discover))/?(?:[#?]|$)'
+ _TESTS = [{
+ 'url': 'https://zingmp3.vn/zing-chart',
+ 'info_dict': {
+ 'id': 'zing-chart',
+ },
+ 'playlist_mincount': 100,
+ }, {
+ 'url': 'https://zingmp3.vn/moi-phat-hanh',
+ 'info_dict': {
+ 'id': 'moi-phat-hanh',
+ },
+ 'playlist_mincount': 100,
+ }, {
+ 'url': 'https://zingmp3.vn/top100',
+ 'info_dict': {
+ 'id': 'top100',
+ },
+ 'playlist_mincount': 50,
+ }, {
+ 'url': 'https://zingmp3.vn/podcast-discover',
+ 'info_dict': {
+ 'id': 'podcast-discover',
+ },
+ 'playlist_mincount': 4,
+ }]
+ IE_NAME = 'zingmp3:chart-home'
+
+ def _real_extract(self, url):
+ url_type = self._match_id(url)
+ params = {'id': url_type}
+ if url_type == 'podcast-discover':
+ params['type'] = 'discover'
+ data = self._call_api(url_type, params)
+ items = []
+ if url_type == 'top100':
+ items.extend(traverse_obj(data, (..., 'items', ..., {dict})))
+ elif url_type == 'zing-chart':
+ items.extend(traverse_obj(data, ('RTChart', 'items', ..., {dict})))
+ else:
+ items.extend(traverse_obj(data, ('items', ..., {dict})))
+ return self.playlist_result(self._parse_items(items), url_type)
+
+
+class ZingMp3WeekChartIE(ZingMp3BaseIE):
+ _VALID_URL = ZingMp3BaseIE._VALID_URL_TMPL % 'zing-chart-tuan'
+ IE_NAME = 'zingmp3:week-chart'
+ _TESTS = [{
+ 'url': 'https://zingmp3.vn/zing-chart-tuan/Bai-hat-Viet-Nam/IWZ9Z08I.html',
+ 'info_dict': {
+ 'id': 'IWZ9Z08I',
+ 'title': 'zing-chart-vn',
+ },
+ 'playlist_mincount': 10,
+ }, {
+ 'url': 'https://zingmp3.vn/zing-chart-tuan/Bai-hat-US-UK/IWZ9Z0BW.html',
+ 'info_dict': {
+ 'id': 'IWZ9Z0BW',
+ 'title': 'zing-chart-us',
+ },
+ 'playlist_mincount': 10,
+ }, {
+ 'url': 'https://zingmp3.vn/zing-chart-tuan/Bai-hat-KPop/IWZ9Z0BO.html',
+ 'info_dict': {
+ 'id': 'IWZ9Z0BO',
+ 'title': 'zing-chart-korea',
+ },
+ 'playlist_mincount': 10,
+ }]
+
+ def _real_extract(self, url):
+ song_id, url_type = self._match_valid_url(url).group('id', 'type')
+ data = self._call_api(url_type, {'id': song_id})
+ return self.playlist_result(
+ self._parse_items(data['items']), song_id, f'zing-chart-{data.get("country", "")}')
+
+
+class ZingMp3ChartMusicVideoIE(ZingMp3BaseIE):
+ _VALID_URL = r'https?://(?:mp3\.zing|zingmp3)\.vn/(?P<type>the-loai-video)/(?P<regions>[^/]+)/(?P<id>[^\.]+)'
+ IE_NAME = 'zingmp3:chart-music-video'
+ _TESTS = [{
+ 'url': 'https://zingmp3.vn/the-loai-video/Viet-Nam/IWZ9Z08I.html',
+ 'info_dict': {
+ 'id': 'IWZ9Z08I',
+ 'title': 'the-loai-video_Viet-Nam',
+ },
+ 'playlist_mincount': 400,
+ }, {
+ 'url': 'https://zingmp3.vn/the-loai-video/Au-My/IWZ9Z08O.html',
+ 'info_dict': {
+ 'id': 'IWZ9Z08O',
+ 'title': 'the-loai-video_Au-My',
+ },
+ 'playlist_mincount': 40,
+ }, {
+ 'url': 'https://zingmp3.vn/the-loai-video/Han-Quoc/IWZ9Z08W.html',
+ 'info_dict': {
+ 'id': 'IWZ9Z08W',
+ 'title': 'the-loai-video_Han-Quoc',
+ },
+ 'playlist_mincount': 30,
+ }, {
+ 'url': 'https://zingmp3.vn/the-loai-video/Khong-Loi/IWZ9Z086.html',
+ 'info_dict': {
+ 'id': 'IWZ9Z086',
+ 'title': 'the-loai-video_Khong-Loi',
+ },
+ 'playlist_mincount': 1,
+ }]
+
+ def _fetch_page(self, song_id, url_type, page):
+ return self._call_api(url_type, {
+ 'id': song_id,
+ 'type': 'genre',
+ 'page': page,
+ 'count': self._PER_PAGE,
+ })
+
+ def _real_extract(self, url):
+ song_id, regions, url_type = self._match_valid_url(url).group('id', 'regions', 'type')
+ return self.playlist_result(self._paged_list(song_id, url_type), song_id, f'{url_type}_{regions}')
+
+
+class ZingMp3UserIE(ZingMp3BaseIE):
+ _VALID_URL = r'https?://(?:mp3\.zing|zingmp3)\.vn/(?P<user>[^/]+)/(?P<type>bai-hat|single|album|video|song)/?(?:[?#]|$)'
+ IE_NAME = 'zingmp3:user'
+ _TESTS = [{
+ 'url': 'https://zingmp3.vn/Mr-Siro/bai-hat',
+ 'info_dict': {
+ 'id': 'IWZ98609',
+ 'title': 'Mr. Siro - bai-hat',
+ 'description': 'md5:5bdcf45e955dc1b8d7f518f322ffef36',
+ },
+ 'playlist_mincount': 91,
+ }, {
+ 'url': 'https://zingmp3.vn/Mr-Siro/album',
+ 'info_dict': {
+ 'id': 'IWZ98609',
+ 'title': 'Mr. Siro - album',
+ 'description': 'md5:5bdcf45e955dc1b8d7f518f322ffef36',
+ },
+ 'playlist_mincount': 3,
+ }, {
+ 'url': 'https://zingmp3.vn/Mr-Siro/single',
+ 'info_dict': {
+ 'id': 'IWZ98609',
+ 'title': 'Mr. Siro - single',
+ 'description': 'md5:5bdcf45e955dc1b8d7f518f322ffef36',
+ },
+ 'playlist_mincount': 20,
+ }, {
+ 'url': 'https://zingmp3.vn/Mr-Siro/video',
+ 'info_dict': {
+ 'id': 'IWZ98609',
+ 'title': 'Mr. Siro - video',
+ 'description': 'md5:5bdcf45e955dc1b8d7f518f322ffef36',
+ },
+ 'playlist_mincount': 15,
+ }, {
+ 'url': 'https://zingmp3.vn/new-release/song',
+ 'info_dict': {
+ 'id': 'new-release-song',
+ },
+ 'playlist_mincount': 50,
+ }, {
+ 'url': 'https://zingmp3.vn/new-release/album',
+ 'info_dict': {
+ 'id': 'new-release-album',
+ },
+ 'playlist_mincount': 20,
+ }]
+
+ def _fetch_page(self, user_id, url_type, page):
+ url_type = 'user-list-song' if url_type == 'bai-hat' else 'user-list-video'
+ return self._call_api(url_type, {
+ 'id': user_id,
+ 'type': 'artist',
+ 'page': page,
+ 'count': self._PER_PAGE,
+ })
+
+ def _real_extract(self, url):
+ alias, url_type = self._match_valid_url(url).group('user', 'type')
+ if not url_type:
+ url_type = 'bai-hat'
+
+ user_info = self._call_api('info-artist', {}, alias, query={'alias': alias})
+
+ # Handle for new-release
+ if alias == 'new-release' and url_type in ('song', 'album'):
+ _id = f'{alias}-{url_type}'
+ return self.playlist_result(self._parse_items(
+ self._call_api('new-release', params={'type': url_type}, display_id=_id)), _id)
+ else:
+ # Handle for user/artist
+ if url_type in ('bai-hat', 'video'):
+ entries = self._paged_list(user_info['id'], url_type)
+ else:
+ section_id = 'aAlbum' if url_type == 'album' else 'aSingle'
+ entries = self._parse_items(traverse_obj(user_info, (
+ 'sections', lambda _, v: v['sectionId'] == section_id, 'items', ...)))
+ return self.playlist_result(
+ entries, user_info['id'], join_nonempty(user_info.get('name'), url_type, delim=' - '),
+ user_info.get('biography'))
+