youtube music album extractor
This commit is contained in:
parent
b044df8d86
commit
d51d7e3865
|
@ -1526,6 +1526,7 @@ from .youtube import (
|
||||||
YoutubeChannelIE,
|
YoutubeChannelIE,
|
||||||
YoutubeHistoryIE,
|
YoutubeHistoryIE,
|
||||||
YoutubeLikedIE,
|
YoutubeLikedIE,
|
||||||
|
YoutubeMusicAlbumIE,
|
||||||
YoutubePlaylistIE,
|
YoutubePlaylistIE,
|
||||||
YoutubeSearchIE,
|
YoutubeSearchIE,
|
||||||
YoutubeSubscriptionsIE,
|
YoutubeSubscriptionsIE,
|
||||||
|
|
|
@ -2355,7 +2355,7 @@ class YoutubeYti1ListInfoExtractor(YoutubeBaseListInfoExtractor):
|
||||||
|
|
||||||
class YoutubeChannelIE(YoutubeAjaxListInfoExtractor):
|
class YoutubeChannelIE(YoutubeAjaxListInfoExtractor):
|
||||||
IE_NAME = 'youtube:channel'
|
IE_NAME = 'youtube:channel'
|
||||||
_VALID_URL = r'https?://(?:\w+\.)?youtube\.com/(?!watch|playlist|v|e|embed|shared)(?:(?P<type>user|channel|c)/)?(?P<id>[\w-]+)(?!/live)'
|
_VALID_URL = r'https?://(?:\w+\.)?youtube\.com/(?!watch|playlist|v|e|embed|shared|browse)(?:(?P<type>user|channel|c)/)?(?P<id>[\w-]+)(?!/live)'
|
||||||
_LIST_NAME = 'channel'
|
_LIST_NAME = 'channel'
|
||||||
|
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
|
@ -2404,7 +2404,7 @@ class YoutubeChannelIE(YoutubeAjaxListInfoExtractor):
|
||||||
|
|
||||||
class YoutubePlaylistIE(YoutubeYti1ListInfoExtractor):
|
class YoutubePlaylistIE(YoutubeYti1ListInfoExtractor):
|
||||||
IE_NAME = 'youtube:playlist'
|
IE_NAME = 'youtube:playlist'
|
||||||
_VALID_URL = r'(?:https?://(?:\w+\.)?youtube\.com/(?:playlist\?(?:[^&;]+[&;])*|watch\?(?:[^&;]+[&;])*playnext=1&(?:[^&;]+[&;])*)list=|ytplaylist:)?(?P<id>%(playlist_id)s)' % {'playlist_id': YoutubeBaseInfoExtractor._PLAYLIST_ID_RE}
|
_VALID_URL = r'(?:https?://(?:www\.)?youtube\.com/(?:playlist\?(?:[^&;]+[&;])*|watch\?(?:[^&;]+[&;])*playnext=1&(?:[^&;]+[&;])*)list=|ytplaylist:)?(?P<id>%(playlist_id)s)' % {'playlist_id': YoutubeBaseInfoExtractor._PLAYLIST_ID_RE}
|
||||||
_LIST_NAME = 'playlist'
|
_LIST_NAME = 'playlist'
|
||||||
|
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
|
@ -2584,7 +2584,7 @@ class YoutubeBaseShelfInfoExtractor(YoutubeYti1ListInfoExtractor):
|
||||||
|
|
||||||
|
|
||||||
class YoutubeSubscriptionsIE(YoutubeBaseShelfInfoExtractor):
|
class YoutubeSubscriptionsIE(YoutubeBaseShelfInfoExtractor):
|
||||||
_VALID_URL = r'(?:https?://(?:www\.)youtube\.com/feed/|:yt)(?P<id>subs(?:criptions)?)'
|
_VALID_URL = r'(?:https?://(?:www\.)?youtube\.com/feed/|:yt)(?P<id>subs(?:criptions)?)'
|
||||||
IE_NAME = 'youtube:subscriptions'
|
IE_NAME = 'youtube:subscriptions'
|
||||||
_LIST_NAME = 'subscriptions'
|
_LIST_NAME = 'subscriptions'
|
||||||
_LOGIN_REQUIRED = True
|
_LOGIN_REQUIRED = True
|
||||||
|
@ -2594,7 +2594,7 @@ class YoutubeSubscriptionsIE(YoutubeBaseShelfInfoExtractor):
|
||||||
|
|
||||||
|
|
||||||
class YoutubeHistoryIE(YoutubeYti1ListInfoExtractor):
|
class YoutubeHistoryIE(YoutubeYti1ListInfoExtractor):
|
||||||
_VALID_URL = r'(?:https?://(?:www\.)youtube\.com/feed/|:yt)(?P<id>history)'
|
_VALID_URL = r'(?:https?://(?:www\.)?youtube\.com/feed/|:yt)(?P<id>history)'
|
||||||
IE_NAME = 'youtube:history'
|
IE_NAME = 'youtube:history'
|
||||||
_LIST_NAME = 'history'
|
_LIST_NAME = 'history'
|
||||||
_LOGIN_REQUIRED = True
|
_LOGIN_REQUIRED = True
|
||||||
|
@ -2628,6 +2628,82 @@ class YoutubeHistoryIE(YoutubeYti1ListInfoExtractor):
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class YoutubeMusicAlbumIE(YoutubeBaseListInfoExtractor):
|
||||||
|
_VALID_URL = r'https://music\.youtube\.com/browse/(?P<id>MPREb_\w{11})'
|
||||||
|
IE_NAME = 'youtube:music:album'
|
||||||
|
_LIST_NAME = 'album'
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'https://music.youtube.com/browse/MPREb_R4FiVJOusZp',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'MPREb_R4FiVJOusZp',
|
||||||
|
'title': '1000 gecs',
|
||||||
|
'description': 'md5:107a8e7bbef00df750350775ad6a2601',
|
||||||
|
'age_limit': 18,
|
||||||
|
},
|
||||||
|
'playlist_count': 10,
|
||||||
|
}]
|
||||||
|
|
||||||
|
def _parse_video(self, video, full_data=None, entry_key=None):
|
||||||
|
return {
|
||||||
|
'id': video['videoId'],
|
||||||
|
'url': 'https://www.youtube.com/watch?v=%s' % video['videoId'],
|
||||||
|
'title': video['title'],
|
||||||
|
'thumbnails': try_get(video, lambda x: x['thumbnailDetails']['thumbnails']),
|
||||||
|
'age_limit': 18 if try_get(video, lambda x: x['contentRating']['explicitType']) == 'MUSIC_ENTITY_EXPLICIT_TYPE_EXPLICIT' else 0,
|
||||||
|
'track': video['title'],
|
||||||
|
'track_number': int_or_none(video.get('albumTrackIndex')),
|
||||||
|
'track_id': video.get('albumTrackIndex'),
|
||||||
|
'artist': video.get('artistNames'),
|
||||||
|
}
|
||||||
|
|
||||||
|
def _download_first_data(self, url, list_id, query=None):
|
||||||
|
webpage = self._download_webpage(url, list_id)
|
||||||
|
|
||||||
|
data = self._search_regex(
|
||||||
|
r"initialData\.push\({path: '\\\/browse',.+, data: '([^']+)'}\);",
|
||||||
|
webpage, 'album data')
|
||||||
|
# prevent DeprecationWarning on the next step
|
||||||
|
data = data.replace(r'\/', '/')
|
||||||
|
# https://stackoverflow.com/a/16493336/8222484
|
||||||
|
data = bytes(data.encode('utf-8')).decode('unicode_escape')
|
||||||
|
data = self._parse_json(data, list_id)
|
||||||
|
return data, webpage
|
||||||
|
|
||||||
|
def _parse_init_video_list(self, data):
|
||||||
|
item_list = try_get(data, [
|
||||||
|
lambda x: x['frameworkUpdates']['entityBatchUpdate']['mutations'],
|
||||||
|
])
|
||||||
|
if not item_list:
|
||||||
|
raise ExtractorError('Could not extract album item list')
|
||||||
|
entries = []
|
||||||
|
info_dict = {}
|
||||||
|
for item in item_list:
|
||||||
|
if item.get('type') == 'ENTITY_MUTATION_TYPE_REPLACE':
|
||||||
|
payload = item.get('payload', {})
|
||||||
|
if 'musicTrack' in payload:
|
||||||
|
entries.append(self._parse_video(payload['musicTrack']))
|
||||||
|
elif 'musicAlbumRelease' in payload:
|
||||||
|
release = payload['musicAlbumRelease']
|
||||||
|
info_dict.update({
|
||||||
|
'title': release['title'],
|
||||||
|
'thumbnails': try_get(release, lambda x: x['thumbnailDetails']['thumbnails']),
|
||||||
|
'age_limit': 18 if try_get(release, lambda x: x['contentRating']['explicitType']) == 'MUSIC_ENTITY_EXPLICIT_TYPE_EXPLICIT' else 0,
|
||||||
|
'album': release['title'],
|
||||||
|
'album_artist': release.get('artistDisplayName'),
|
||||||
|
'release_year': try_get(release, lambda x: x['releaseDate']['year'], int),
|
||||||
|
})
|
||||||
|
elif 'musicAlbumReleaseDetail' in payload:
|
||||||
|
release = payload['musicAlbumReleaseDetail']
|
||||||
|
info_dict.update({
|
||||||
|
'description': release.get('description'),
|
||||||
|
})
|
||||||
|
return {
|
||||||
|
'entries': entries,
|
||||||
|
'continuation': None, # everything is returned on the webpage
|
||||||
|
'info_dict': info_dict,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class YoutubeTruncatedURLIE(InfoExtractor):
|
class YoutubeTruncatedURLIE(InfoExtractor):
|
||||||
IE_NAME = 'youtube:truncated_url'
|
IE_NAME = 'youtube:truncated_url'
|
||||||
IE_DESC = False # Do not list
|
IE_DESC = False # Do not list
|
||||||
|
|
Loading…
Reference in a new issue