2 from __future__
import unicode_literals
7 from .common
import InfoExtractor
8 from ..compat
import compat_str
17 class YandexMusicBaseIE(InfoExtractor
):
19 def _handle_error(response
):
20 if isinstance(response
, dict):
21 error
= response
.get('error')
23 raise ExtractorError(error
, expected
=True)
24 if response
.get('type') == 'captcha' or 'captcha' in response
:
25 YandexMusicBaseIE
._raise
_captcha
()
30 'YandexMusic has considered youtube-dl requests automated and '
31 'asks you to solve a CAPTCHA. You can either wait for some '
32 'time until unblocked and optionally use --sleep-interval '
33 'in future or alternatively you can go to https://music.yandex.ru/ '
34 'solve CAPTCHA, then export cookies and pass cookie file to '
35 'youtube-dl with --cookies',
38 def _download_webpage_handle(self
, *args
, **kwargs
):
39 webpage
= super(YandexMusicBaseIE
, self
)._download
_webpage
_handle
(*args
, **kwargs
)
40 if 'Нам очень жаль, но запросы, поступившие с вашего IP-адреса, похожи на автоматические.' in webpage
:
44 def _download_json(self
, *args
, **kwargs
):
45 response
= super(YandexMusicBaseIE
, self
)._download
_json
(*args
, **kwargs
)
46 self
._handle
_error
(response
)
50 class YandexMusicTrackIE(YandexMusicBaseIE
):
51 IE_NAME
= 'yandexmusic:track'
52 IE_DESC
= 'Яндекс.Музыка - Трек'
53 _VALID_URL
= r
'https?://music\.yandex\.(?:ru|kz|ua|by)/album/(?P<album_id>\d+)/track/(?P<id>\d+)'
56 'url': 'http://music.yandex.ru/album/540508/track/4878838',
57 'md5': 'f496818aa2f60b6c0062980d2e00dc20',
61 'title': 'Carlo Ambrosio & Fabio Di Bari - Gypsy Eyes 1',
64 'track': 'Gypsy Eyes 1',
65 'album': 'Gypsy Soul',
66 'album_artist': 'Carlo Ambrosio',
67 'artist': 'Carlo Ambrosio & Fabio Di Bari',
70 'skip': 'Travis CI servers blocked by YandexMusic',
73 'url': 'http://music.yandex.ru/album/3840501/track/705105',
74 'md5': 'ebe7b4e2ac7ac03fe11c19727ca6153e',
78 'title': 'Hooverphonic - Sometimes',
82 'album': 'The Best of Hooverphonic',
83 'album_artist': 'Hooverphonic',
84 'artist': 'Hooverphonic',
90 'skip': 'Travis CI servers blocked by YandexMusic',
93 def _real_extract(self
, url
):
94 mobj
= re
.match(self
._VALID
_URL
, url
)
95 album_id
, track_id
= mobj
.group('album_id'), mobj
.group('id')
97 track
= self
._download
_json
(
98 'http://music.yandex.ru/handlers/track.jsx?track=%s:%s' % (track_id
, album_id
),
99 track_id
, 'Downloading track JSON')['track']
100 track_title
= track
['title']
102 download_data
= self
._download
_json
(
103 'https://music.yandex.ru/api/v2.1/handlers/track/%s:%s/web-album_track-track-track-main/download/m' % (track_id
, album_id
),
104 track_id
, 'Downloading track location url JSON',
105 headers
={'X-Retpath-Y': url}
)
107 fd_data
= self
._download
_json
(
108 download_data
['src'], track_id
,
109 'Downloading track location JSON',
110 query
={'format': 'json'}
)
111 key
= hashlib
.md5(('XGRlBW9FXlekgbPrRHuSiA' + fd_data
['path'][1:] + fd_data
['s']).encode('utf-8')).hexdigest()
112 storage
= track
['storageDir'].split('.')
113 f_url
= 'http://%s/get-mp3/%s/%s?track-id=%s ' % (fd_data
['host'], key
, fd_data
['ts'] + fd_data
['path'], storage
[1])
116 cover_uri
= track
.get('albums', [{}])[0].get('coverUri')
118 thumbnail
= cover_uri
.replace('%%', 'orig')
119 if not thumbnail
.startswith('http'):
120 thumbnail
= 'http://' + thumbnail
126 'filesize': int_or_none(track
.get('fileSize')),
127 'duration': float_or_none(track
.get('durationMs'), 1000),
128 'thumbnail': thumbnail
,
129 'track': track_title
,
130 'acodec': download_data
.get('codec'),
131 'abr': int_or_none(download_data
.get('bitrate')),
134 def extract_artist_name(artist
):
135 decomposed
= artist
.get('decomposed')
136 if not isinstance(decomposed
, list):
137 return artist
['name']
138 parts
= [artist
['name']]
139 for element
in decomposed
:
140 if isinstance(element
, dict) and element
.get('name'):
141 parts
.append(element
['name'])
142 elif isinstance(element
, compat_str
):
143 parts
.append(element
)
144 return ''.join(parts
)
146 def extract_artist(artist_list
):
147 if artist_list
and isinstance(artist_list
, list):
148 artists_names
= [extract_artist_name(a
) for a
in artist_list
if a
.get('name')]
150 return ', '.join(artists_names
)
152 albums
= track
.get('albums')
153 if albums
and isinstance(albums
, list):
155 if isinstance(album
, dict):
156 year
= album
.get('year')
157 disc_number
= int_or_none(try_get(
158 album
, lambda x
: x
['trackPosition']['volume']))
159 track_number
= int_or_none(try_get(
160 album
, lambda x
: x
['trackPosition']['index']))
162 'album': album
.get('title'),
163 'album_artist': extract_artist(album
.get('artists')),
164 'release_year': int_or_none(year
),
165 'genre': album
.get('genre'),
166 'disc_number': disc_number
,
167 'track_number': track_number
,
170 track_artist
= extract_artist(track
.get('artists'))
173 'artist': track_artist
,
174 'title': '%s - %s' % (track_artist
, track_title
),
177 track_info
['title'] = track_title
182 class YandexMusicPlaylistBaseIE(YandexMusicBaseIE
):
183 def _build_playlist(self
, tracks
):
186 'http://music.yandex.ru/album/%s/track/%s' % (track
['albums'][0]['id'], track
['id']))
187 for track
in tracks
if track
.get('albums') and isinstance(track
.get('albums'), list)]
190 class YandexMusicAlbumIE(YandexMusicPlaylistBaseIE
):
191 IE_NAME
= 'yandexmusic:album'
192 IE_DESC
= 'Яндекс.Музыка - Альбом'
193 _VALID_URL
= r
'https?://music\.yandex\.(?:ru|kz|ua|by)/album/(?P<id>\d+)/?(\?|$)'
196 'url': 'http://music.yandex.ru/album/540508',
199 'title': 'Carlo Ambrosio - Gypsy Soul (2009)',
201 'playlist_count': 50,
202 'skip': 'Travis CI servers blocked by YandexMusic',
204 'url': 'https://music.yandex.ru/album/3840501',
207 'title': 'Hooverphonic - The Best of Hooverphonic (2016)',
209 'playlist_count': 33,
210 'skip': 'Travis CI servers blocked by YandexMusic',
213 def _real_extract(self
, url
):
214 album_id
= self
._match
_id
(url
)
216 album
= self
._download
_json
(
217 'http://music.yandex.ru/handlers/album.jsx?album=%s' % album_id
,
218 album_id
, 'Downloading album JSON')
220 entries
= self
._build
_playlist
([track
for volume
in album
['volumes'] for track
in volume
])
222 title
= '%s - %s' % (album
['artists'][0]['name'], album
['title'])
223 year
= album
.get('year')
225 title
+= ' (%s)' % year
227 return self
.playlist_result(entries
, compat_str(album
['id']), title
)
230 class YandexMusicPlaylistIE(YandexMusicPlaylistBaseIE
):
231 IE_NAME
= 'yandexmusic:playlist'
232 IE_DESC
= 'Яндекс.Музыка - Плейлист'
233 _VALID_URL
= r
'https?://music\.yandex\.(?P<tld>ru|kz|ua|by)/users/(?P<user>[^/]+)/playlists/(?P<id>\d+)'
236 'url': 'http://music.yandex.ru/users/music.partners/playlists/1245',
239 'title': 'Что слушают Enter Shikari',
240 'description': 'md5:3b9f27b0efbe53f2ee1e844d07155cc9',
243 'skip': 'Travis CI servers blocked by YandexMusic',
245 # playlist exceeding the limit of 150 tracks shipped with webpage (see
246 # https://github.com/ytdl-org/youtube-dl/issues/6666)
247 'url': 'https://music.yandex.ru/users/ya.playlist/playlists/1036',
250 'title': 'Музыка 90-х',
252 'playlist_mincount': 300,
253 'skip': 'Travis CI servers blocked by YandexMusic',
256 def _real_extract(self
, url
):
257 mobj
= re
.match(self
._VALID
_URL
, url
)
258 tld
= mobj
.group('tld')
259 user
= mobj
.group('user')
260 playlist_id
= mobj
.group('id')
262 playlist
= self
._download
_json
(
263 'https://music.yandex.%s/handlers/playlist.jsx' % tld
,
264 playlist_id
, 'Downloading missing tracks JSON',
268 'X-Requested-With': 'XMLHttpRequest',
273 'kinds': playlist_id
,
276 'external-domain': 'music.yandex.%s' % tld
,
277 'overembed': 'false',
280 tracks
= playlist
['tracks']
281 track_ids
= [compat_str(track_id
) for track_id
in playlist
['trackIds']]
283 # tracks dictionary shipped with playlist.jsx API is limited to 150 tracks,
284 # missing tracks should be retrieved manually.
285 if len(tracks
) < len(track_ids
):
286 present_track_ids
= set([
287 compat_str(track
['id'])
288 for track
in tracks
if track
.get('id')])
289 missing_track_ids
= [
290 track_id
for track_id
in track_ids
291 if track_id
not in present_track_ids
]
292 missing_tracks
= self
._download
_json
(
293 'https://music.yandex.%s/handlers/track-entries.jsx' % tld
,
294 playlist_id
, 'Downloading missing tracks JSON',
298 'X-Requested-With': 'XMLHttpRequest',
301 'entries': ','.join(missing_track_ids
),
303 'external-domain': 'music.yandex.%s' % tld
,
304 'overembed': 'false',
308 tracks
.extend(missing_tracks
)
310 return self
.playlist_result(
311 self
._build
_playlist
(tracks
),
312 compat_str(playlist_id
),
313 playlist
.get('title'), playlist
.get('description'))