2 from __future__
import unicode_literals
7 from .common
import InfoExtractor
8 from ..utils
import ExtractorError
, try_get
, compat_str
, str_or_none
9 from ..compat
import compat_urllib_parse_unquote
12 class AudiusBaseIE(InfoExtractor
):
16 def _get_response_data(self
, response
):
17 if isinstance(response
, dict):
18 response_data
= response
.get('data')
19 if response_data
is not None:
21 if len(response
) == 1 and 'message' in response
:
22 raise ExtractorError('API error: %s' % response
['message'],
24 raise ExtractorError('Unexpected API response')
26 def _select_api_base(self
):
27 """Selecting one of the currently available API hosts"""
28 response
= super(AudiusBaseIE
, self
)._download
_json
(
29 'https://api.audius.co/', None,
30 note
='Requesting available API hosts',
31 errnote
='Unable to request available API hosts')
32 hosts
= self
._get
_response
_data
(response
)
33 if isinstance(hosts
, list):
34 self
._API
_BASE
= random
.choice(hosts
)
36 raise ExtractorError('Unable to get available API hosts')
39 def _prepare_url(url
, title
):
41 Audius removes forward slashes from the uri, but leaves backslashes.
42 The problem is that the current version of Chrome replaces backslashes
43 in the address bar with a forward slashes, so if you copy the link from
44 there and paste it into youtube-dl, you won't be able to download
45 anything from this link, since the Audius API won't be able to resolve
48 url
= compat_urllib_parse_unquote(url
)
49 title
= compat_urllib_parse_unquote(title
)
50 if '/' in title
or '%2F' in title
:
51 fixed_title
= title
.replace('/', '%5C').replace('%2F', '%5C')
52 return url
.replace(title
, fixed_title
)
55 def _api_request(self
, path
, item_id
=None, note
='Downloading JSON metadata',
56 errnote
='Unable to download JSON metadata',
57 expected_status
=None):
58 if self
._API
_BASE
is None:
59 self
._select
_api
_base
()
61 response
= super(AudiusBaseIE
, self
)._download
_json
(
62 '%s%s%s' % (self
._API
_BASE
, self
._API
_V
, path
), item_id
, note
=note
,
63 errnote
=errnote
, expected_status
=expected_status
)
64 except ExtractorError
as exc
:
65 # some of Audius API hosts may not work as expected and return HTML
66 if 'Failed to parse JSON' in compat_str(exc
):
67 raise ExtractorError('An error occurred while receiving data. Try again',
70 return self
._get
_response
_data
(response
)
72 def _resolve_url(self
, url
, item_id
):
73 return self
._api
_request
('/resolve?url=%s' % url
, item_id
,
77 class AudiusIE(AudiusBaseIE
):
78 _VALID_URL
= r
'''(?x)https?://(?:www\.)?(?:audius\.co/(?P<uploader>[\w\d-]+)(?!/album|/playlist)/(?P<title>\S+))'''
82 # URL from Chrome address bar which replace backslash to forward slash
83 'url': 'https://audius.co/test_acc/t%D0%B5%D0%B5%D0%B5est-1.%5E_%7B%7D/%22%3C%3E.%E2%84%96~%60-198631',
84 'md5': '92c35d3e754d5a0f17eef396b0d33582',
87 'title': '''Tеееest/ 1.!@#$%^&*()_+=[]{};'\\\":<>,.?/№~`''',
89 'description': 'Description',
91 'track': '''Tеееest/ 1.!@#$%^&*()_+=[]{};'\\\":<>,.?/№~`''',
93 'genre': 'Electronic',
94 'thumbnail': r
're:https?://.*\.jpg',
102 'url': 'https://audius.co/voltra/radar-103692',
103 'md5': '491898a0a8de39f20c5d6a8a80ab5132',
112 'thumbnail': r
're:https?://.*\.jpg',
126 def _real_extract(self
, url
):
127 mobj
= re
.match(self
._VALID
_URL
, url
)
128 track_id
= try_get(mobj
, lambda x
: x
.group('track_id'))
130 title
= mobj
.group('title')
131 # uploader = mobj.group('uploader')
132 url
= self
._prepare
_url
(url
, title
)
133 track_data
= self
._resolve
_url
(url
, title
)
137 track_data
= self
._api
_request
('/tracks/%s' % track_id
, track_id
)
139 if not isinstance(track_data
, dict):
140 raise ExtractorError('Unexpected API response')
142 track_id
= track_data
.get('id')
144 raise ExtractorError('Unable to get ID of the track')
146 artworks_data
= track_data
.get('artwork')
148 if isinstance(artworks_data
, dict):
149 for quality_key
, thumbnail_url
in artworks_data
.items():
153 quality_code
= self
._ARTWORK
_MAP
.get(quality_key
)
154 if quality_code
is not None:
155 thumbnail
['preference'] = quality_code
156 thumbnails
.append(thumbnail
)
160 'title': track_data
.get('title', title
),
161 'url': '%s/v1/tracks/%s/stream' % (self
._API
_BASE
, track_id
),
163 'description': track_data
.get('description'),
164 'duration': track_data
.get('duration'),
165 'track': track_data
.get('title'),
166 'artist': try_get(track_data
, lambda x
: x
['user']['name'], compat_str
),
167 'genre': track_data
.get('genre'),
168 'thumbnails': thumbnails
,
169 'view_count': track_data
.get('play_count'),
170 'like_count': track_data
.get('favorite_count'),
171 'repost_count': track_data
.get('repost_count'),
175 class AudiusTrackIE(AudiusIE
):
176 _VALID_URL
= r
'''(?x)(?:audius:)(?:https?://(?:www\.)?.+/v1/tracks/)?(?P<track_id>\w+)'''
177 IE_NAME
= 'audius:track'
178 IE_DESC
= 'Audius track ID or API link. Prepend with "audius:"'
181 'url': 'audius:9RWlo',
182 'only_matching': True
185 'url': 'audius:http://discoveryprovider.audius.prod-us-west-2.staked.cloud/v1/tracks/9RWlo',
186 'only_matching': True
191 class AudiusPlaylistIE(AudiusBaseIE
):
192 _VALID_URL
= r
'https?://(?:www\.)?audius\.co/(?P<uploader>[\w\d-]+)/(?:album|playlist)/(?P<title>\S+)'
193 IE_NAME
= 'audius:playlist'
194 IE_DESC
= 'Audius.co playlists'
196 'url': 'https://audius.co/test_acc/playlist/test-playlist-22910',
199 'title': 'test playlist',
200 'description': 'Test description\n\nlol',
202 'playlist_count': 175,
205 def _build_playlist(self
, tracks
):
208 if not isinstance(track
, dict):
209 raise ExtractorError('Unexpected API response')
210 track_id
= str_or_none(track
.get('id'))
212 raise ExtractorError('Unable to get track ID from playlist')
213 entries
.append(self
.url_result(
214 'audius:%s' % track_id
,
215 ie
=AudiusTrackIE
.ie_key(), video_id
=track_id
))
218 def _real_extract(self
, url
):
219 self
._select
_api
_base
()
220 mobj
= re
.match(self
._VALID
_URL
, url
)
221 title
= mobj
.group('title')
222 # uploader = mobj.group('uploader')
223 url
= self
._prepare
_url
(url
, title
)
224 playlist_response
= self
._resolve
_url
(url
, title
)
226 if not isinstance(playlist_response
, list) or len(playlist_response
) != 1:
227 raise ExtractorError('Unexpected API response')
229 playlist_data
= playlist_response
[0]
230 if not isinstance(playlist_data
, dict):
231 raise ExtractorError('Unexpected API response')
233 playlist_id
= playlist_data
.get('id')
234 if playlist_id
is None:
235 raise ExtractorError('Unable to get playlist ID')
237 playlist_tracks
= self
._api
_request
(
238 '/playlists/%s/tracks' % playlist_id
,
239 title
, note
='Downloading playlist tracks metadata',
240 errnote
='Unable to download playlist tracks metadata')
241 if not isinstance(playlist_tracks
, list):
242 raise ExtractorError('Unexpected API response')
244 entries
= self
._build
_playlist
(playlist_tracks
)
245 return self
.playlist_result(entries
, playlist_id
,
246 playlist_data
.get('playlist_name', title
),
247 playlist_data
.get('description'))