2 from __future__ 
import unicode_literals
 
  11 from ..compat 
import ( 
  14     compat_urllib_parse_urlencode
, 
  31 class SoundcloudIE(InfoExtractor
): 
  32     """Information extractor for soundcloud.com 
  33        To access the media, the uid of the song and a stream token 
  34        must be extracted from the page source and the script must make 
  35        a request to media.soundcloud.com/crossdomain.xml. Then 
  36        the media can be grabbed by requesting from an url composed 
  37        of the stream token and uid 
  40     _VALID_URL 
= r
'''(?x)^(?:https?://)? 
  41                     (?:(?:(?:www\.|m\.)?soundcloud\.com/ 
  43                             (?P<uploader>[\w\d-]+)/ 
  44                             (?!(?:tracks|albums|sets(?:/.+?)?|reposts|likes|spotlight)/?(?:$|[?#])) 
  46                             (?P<token>[^?]+?)?(?:[?].*)?$) 
  47                        |(?:api\.soundcloud\.com/tracks/(?P<track_id>\d+) 
  48                           (?:/?\?secret_token=(?P<secret_token>[^&]+))?) 
  49                        |(?P<player>(?:w|player|p.)\.soundcloud\.com/player/?.*?url=.*) 
  52     IE_NAME 
= 'soundcloud' 
  55             'url': 'http://soundcloud.com/ethmusic/lostin-powers-she-so-heavy', 
  56             'md5': 'ebef0a451b909710ed1d7787dddbf0d7', 
  60                 'title': 'Lostin Powers - She so Heavy (SneakPreview) Adrian Ackers Blueprint 1', 
  61                 'description': 'No Downloads untill we record the finished version this weekend, i was too pumped n i had to post it , earl is prolly gonna b hella p.o\'d', 
  62                 'uploader': 'E.T. ExTerrestrial Music', 
  63                 'timestamp': 1349920598, 
  64                 'upload_date': '20121011', 
  66                 'license': 'all-rights-reserved', 
  75             'url': 'https://soundcloud.com/the-concept-band/goldrushed-mastered?in=the-concept-band/sets/the-royal-concept-ep', 
  79                 'title': 'Goldrushed', 
  80                 'description': 'From Stockholm Sweden\r\nPovel / Magnus / Filip / David\r\nwww.theroyalconcept.com', 
  81                 'uploader': 'The Royal Concept', 
  82                 'timestamp': 1337635207, 
  83                 'upload_date': '20120521', 
  85                 'license': 'all-rights-reserved', 
  93                 'skip_download': True, 
  98             'url': 'https://soundcloud.com/jaimemf/youtube-dl-test-video-a-y-baw/s-8Pjrp', 
  99             'md5': 'aa0dd32bfea9b0c5ef4f02aacd080604', 
 103                 'title': 'Youtube - Dl Test Video \'\' Ä↭', 
 104                 'description': 'test chars:  \"\'/\\ä↭', 
 105                 'uploader': 'jaimeMF', 
 106                 'timestamp': 1386604920, 
 107                 'upload_date': '20131209', 
 109                 'license': 'all-rights-reserved', 
 112                 'comment_count': int, 
 116         # private link (alt format) 
 118             'url': 'https://api.soundcloud.com/tracks/123998367?secret_token=s-8Pjrp', 
 119             'md5': 'aa0dd32bfea9b0c5ef4f02aacd080604', 
 123                 'title': 'Youtube - Dl Test Video \'\' Ä↭', 
 124                 'description': 'test chars:  \"\'/\\ä↭', 
 125                 'uploader': 'jaimeMF', 
 126                 'timestamp': 1386604920, 
 127                 'upload_date': '20131209', 
 129                 'license': 'all-rights-reserved', 
 132                 'comment_count': int, 
 138             'url': 'https://soundcloud.com/oddsamples/bus-brakes', 
 139             'md5': '7624f2351f8a3b2e7cd51522496e7631', 
 143                 'title': 'Bus Brakes', 
 144                 'description': 'md5:0053ca6396e8d2fd7b7e1595ef12ab66', 
 145                 'uploader': 'oddsamples', 
 146                 'timestamp': 1389232924, 
 147                 'upload_date': '20140109', 
 149                 'license': 'cc-by-sa', 
 152                 'comment_count': int, 
 156         # private link, downloadable format 
 158             'url': 'https://soundcloud.com/oriuplift/uponly-238-no-talking-wav/s-AyZUd', 
 159             'md5': '64a60b16e617d41d0bef032b7f55441e', 
 163                 'title': 'Uplifting Only 238 [No Talking] (incl. Alex Feed Guestmix) (Aug 31, 2017) [wav]', 
 164                 'description': 'md5:fa20ee0fca76a3d6df8c7e57f3715366', 
 165                 'uploader': 'Ori Uplift Music', 
 166                 'timestamp': 1504206263, 
 167                 'upload_date': '20170831', 
 168                 'duration': 7449.096, 
 169                 'license': 'all-rights-reserved', 
 172                 'comment_count': int, 
 176         # no album art, use avatar pic for thumbnail 
 178             'url': 'https://soundcloud.com/garyvee/sideways-prod-mad-real', 
 179             'md5': '59c7872bc44e5d99b7211891664760c2', 
 183                 'title': 'Sideways (Prod. Mad Real)', 
 184                 'description': 'md5:d41d8cd98f00b204e9800998ecf8427e', 
 185                 'uploader': 'garyvee', 
 186                 'timestamp': 1488152409, 
 187                 'upload_date': '20170226', 
 189                 'thumbnail': r
're:https?://.*\.jpg', 
 190                 'license': 'all-rights-reserved', 
 193                 'comment_count': int, 
 197                 'skip_download': True, 
 200         # not avaialble via api.soundcloud.com/i1/tracks/id/streams 
 202             'url': 'https://soundcloud.com/giovannisarani/mezzo-valzer', 
 203             'md5': 'e22aecd2bc88e0e4e432d7dcc0a1abf7', 
 207                 'title': 'Mezzo Valzer', 
 208                 'description': 'md5:4138d582f81866a530317bae316e8b61', 
 209                 'uploader': 'Giovanni Sarani', 
 210                 'timestamp': 1551394171, 
 211                 'upload_date': '20190228', 
 213                 'thumbnail': r
're:https?://.*\.jpg', 
 214                 'license': 'all-rights-reserved', 
 217                 'comment_count': int, 
 220             'expected_warnings': ['Unable to download JSON metadata'], 
 224     _CLIENT_ID 
= 'BeGVhOrGmfboy1LtiHTQF6Ejpt9ULJCI' 
 227     def _extract_urls(webpage
): 
 228         return [m
.group('url') for m 
in re
.finditer( 
 229             r
'<iframe[^>]+src=(["\'])(?P
<url
>(?
:https?
://)?
(?
:w\
.)?soundcloud\
.com
/player
.+?
)\
1', 
 233     def _resolv_url(cls, url): 
 234         return 'https
://api
.soundcloud
.com
/resolve
.json?url
=' + url + '&client_id
=' + cls._CLIENT_ID 
 236     def _extract_info_dict(self, info, full_title=None, quiet=False, secret_token=None): 
 237         track_id = compat_str(info['id']) 
 238         title = info['title
'] 
 239         name = full_title or track_id 
 241             self.report_extraction(name) 
 242         thumbnail = info.get('artwork_url
') or info.get('user
', {}).get('avatar_url
') 
 243         if isinstance(thumbnail, compat_str): 
 244             thumbnail = thumbnail.replace('-large
', '-t500x500
') 
 245         username = try_get(info, lambda x: x['user
']['username
'], compat_str) 
 247         def extract_count(key): 
 248             return int_or_none(info.get('%s_count
' % key)) 
 250         like_count = extract_count('favoritings
') 
 251         if like_count is None: 
 252             like_count = extract_count('likes
') 
 256             'uploader
': username, 
 257             'timestamp
': unified_timestamp(info.get('created_at
')), 
 259             'description
': info.get('description
'), 
 260             'thumbnail
': thumbnail, 
 261             'duration
': float_or_none(info.get('duration
'), 1000), 
 262             'webpage_url
': info.get('permalink_url
'), 
 263             'license
': info.get('license
'), 
 264             'view_count
': extract_count('playback
'), 
 265             'like_count
': like_count, 
 266             'comment_count
': extract_count('comment
'), 
 267             'repost_count
': extract_count('reposts
'), 
 268             'genre
': info.get('genre
'), 
 273         query = {'client_id
': self._CLIENT_ID} 
 274         if secret_token is not None: 
 275             query['secret_token
'] = secret_token 
 276         if info.get('downloadable
', False): 
 277             # We can build a direct link to the song 
 278             format_url = update_url_query( 
 279                 'https
://api
.soundcloud
.com
/tracks
/%s/download
' % track_id, query) 
 280             format_urls.add(format_url) 
 282                 'format_id
': 'download
', 
 283                 'ext
': info.get('original_format
', 'mp3
'), 
 289         # Old API, does not work for some tracks (e.g. 
 290         # https://soundcloud.com/giovannisarani/mezzo-valzer) 
 291         format_dict = self._download_json( 
 292             'https
://api
.soundcloud
.com
/i1
/tracks
/%s/streams
' % track_id, 
 293             track_id, 'Downloading track url
', query=query, fatal=False) 
 296             for key, stream_url in format_dict.items(): 
 297                 if stream_url in format_urls: 
 299                 format_urls.add(stream_url) 
 300                 ext, abr = 'mp3
', None 
 301                 mobj = re.search(r'_([^_
]+)_(\d
+)_url
', key) 
 303                     ext, abr = mobj.groups() 
 305                 if key.startswith('http
'): 
 311                 elif key.startswith('rtmp
'): 
 312                     # The url doesn't have an rtmp app
, we have to extract the playpath
 
 313                     url
, path 
= stream_url
.split('mp3:', 1) 
 317                         'play_path': 'mp3:' + path
, 
 320                 elif key
.startswith('hls'): 
 321                     stream_formats 
= self
._extract
_m
3u8_formats
( 
 322                         stream_url
, track_id
, ext
, entry_protocol
='m3u8_native', 
 323                         m3u8_id
=key
, fatal
=False) 
 328                     for f 
in stream_formats
: 
 331                 formats
.extend(stream_formats
) 
 334         transcodings 
= try_get( 
 335             info
, lambda x
: x
['media']['transcodings'], list) or [] 
 336         for t 
in transcodings
: 
 337             if not isinstance(t
, dict): 
 339             format_url 
= url_or_none(t
.get('url')) 
 342             stream 
= self
._download
_json
( 
 343                 update_url_query(format_url
, query
), track_id
, fatal
=False) 
 344             if not isinstance(stream
, dict): 
 346             stream_url 
= url_or_none(stream
.get('url')) 
 349             if stream_url 
in format_urls
: 
 351             format_urls
.add(stream_url
) 
 352             protocol 
= try_get(t
, lambda x
: x
['format']['protocol'], compat_str
) 
 353             if protocol 
!= 'hls' and '/hls' in format_url
: 
 356             preset 
= str_or_none(t
.get('preset')) 
 358                 ext 
= preset
.split('_')[0] 
 359                 if ext 
not in KNOWN_EXTENSIONS
: 
 361                         t
, lambda x
: x
['format']['mime_type'], compat_str
) 
 362                     ext 
= mimetype2ext(mimetype
) or 'mp3' 
 365                 format_id_list
.append(protocol
) 
 366             format_id_list
.append(ext
) 
 367             format_id 
= '_'.join(format_id_list
) 
 370                 'format_id': format_id
, 
 372                 'protocol': 'm3u8_native' if protocol 
== 'hls' else 'http', 
 376             # We fallback to the stream_url in the original info, this 
 377             # cannot be always used, sometimes it can give an HTTP 404 error 
 379                 'format_id': 'fallback', 
 380                 'url': update_url_query(info
['stream_url'], query
), 
 383             self
._check
_formats
(formats
, track_id
) 
 388         self
._sort
_formats
(formats
) 
 389         result
['formats'] = formats
 
 393     def _real_extract(self
, url
): 
 394         mobj 
= re
.match(self
._VALID
_URL
, url
, flags
=re
.VERBOSE
) 
 396             raise ExtractorError('Invalid URL: %s' % url
) 
 398         track_id 
= mobj
.group('track_id') 
 401         if track_id 
is not None: 
 402             info_json_url 
= 'https://api.soundcloud.com/tracks/' + track_id 
+ '.json?client_id=' + self
._CLIENT
_ID
 
 403             full_title 
= track_id
 
 404             token 
= mobj
.group('secret_token') 
 406                 info_json_url 
+= '&secret_token=' + token
 
 407         elif mobj
.group('player'): 
 408             query 
= compat_urlparse
.parse_qs(compat_urlparse
.urlparse(url
).query
) 
 409             real_url 
= query
['url'][0] 
 410             # If the token is in the query of the original url we have to 
 412             if 'secret_token' in query
: 
 413                 real_url 
+= '?secret_token=' + query
['secret_token'][0] 
 414             return self
.url_result(real_url
) 
 416             # extract uploader (which is in the url) 
 417             uploader 
= mobj
.group('uploader') 
 418             # extract simple title (uploader + slug of song title) 
 419             slug_title 
= mobj
.group('title') 
 420             token 
= mobj
.group('token') 
 421             full_title 
= resolve_title 
= '%s/%s' % (uploader
, slug_title
) 
 423                 resolve_title 
+= '/%s' % token
 
 425             webpage 
= self
._download
_webpage
(url
, full_title
, fatal
=False) 
 427                 entries 
= self
._parse
_json
( 
 429                         r
'var\s+c\s*=\s*(\[.+?\])\s*,\s*o\s*=Date\b', webpage
, 
 430                         'data', default
='[]'), full_title
, fatal
=False) 
 433                         if not isinstance(e
, dict): 
 435                         if e
.get('id') != 67: 
 437                         data 
= try_get(e
, lambda x
: x
['data'][0], dict) 
 441                 info_json_url 
= self
._resolv
_url
( 
 442                     'https://soundcloud.com/%s' % resolve_title
) 
 444         # Contains some additional info missing from new_info 
 445         info 
= self
._download
_json
( 
 446             info_json_url
, full_title
, 'Downloading info JSON') 
 448         return self
._extract
_info
_dict
( 
 449             merge_dicts(info
, new_info
), full_title
, secret_token
=token
) 
 452 class SoundcloudPlaylistBaseIE(SoundcloudIE
): 
 455         return compat_str(e
['id']) if e
.get('id') else None 
 457     def _extract_track_entries(self
, tracks
): 
 460                 track
['permalink_url'], SoundcloudIE
.ie_key(), 
 461                 video_id
=self
._extract
_id
(track
)) 
 462             for track 
in tracks 
if track
.get('permalink_url')] 
 465 class SoundcloudSetIE(SoundcloudPlaylistBaseIE
): 
 466     _VALID_URL 
= r
'https?://(?:(?:www|m)\.)?soundcloud\.com/(?P<uploader>[\w\d-]+)/sets/(?P<slug_title>[\w\d-]+)(?:/(?P<token>[^?/]+))?' 
 467     IE_NAME 
= 'soundcloud:set' 
 469         'url': 'https://soundcloud.com/the-concept-band/sets/the-royal-concept-ep', 
 472             'title': 'The Royal Concept EP', 
 474         'playlist_mincount': 5, 
 476         'url': 'https://soundcloud.com/the-concept-band/sets/the-royal-concept-ep/token', 
 477         'only_matching': True, 
 480     def _real_extract(self
, url
): 
 481         mobj 
= re
.match(self
._VALID
_URL
, url
) 
 483         # extract uploader (which is in the url) 
 484         uploader 
= mobj
.group('uploader') 
 485         # extract simple title (uploader + slug of song title) 
 486         slug_title 
= mobj
.group('slug_title') 
 487         full_title 
= '%s/sets/%s' % (uploader
, slug_title
) 
 488         url 
= 'https://soundcloud.com/%s/sets/%s' % (uploader
, slug_title
) 
 490         token 
= mobj
.group('token') 
 492             full_title 
+= '/' + token
 
 495         resolv_url 
= self
._resolv
_url
(url
) 
 496         info 
= self
._download
_json
(resolv_url
, full_title
) 
 499             msgs 
= (compat_str(err
['error_message']) for err 
in info
['errors']) 
 500             raise ExtractorError('unable to download video webpage: %s' % ','.join(msgs
)) 
 502         entries 
= self
._extract
_track
_entries
(info
['tracks']) 
 507             'id': '%s' % info
['id'], 
 508             'title': info
['title'], 
 512 class SoundcloudPagedPlaylistBaseIE(SoundcloudPlaylistBaseIE
): 
 513     _API_V2_BASE 
= 'https://api-v2.soundcloud.com' 
 515     def _extract_playlist(self
, base_url
, playlist_id
, playlist_title
): 
 518             'client_id': self
._CLIENT
_ID
, 
 519             'linked_partitioning': '1', 
 522         query 
= COMMON_QUERY
.copy() 
 525         next_href 
= base_url 
+ '?' + compat_urllib_parse_urlencode(query
) 
 528         for i 
in itertools
.count(): 
 529             response 
= self
._download
_json
( 
 530                 next_href
, playlist_id
, 'Downloading track page %s' % (i 
+ 1)) 
 532             collection 
= response
['collection'] 
 534             if not isinstance(collection
, list): 
 537             # Empty collection may be returned, in this case we proceed 
 538             # straight to next_href 
 540             def resolve_entry(candidates
): 
 541                 for cand 
in candidates
: 
 542                     if not isinstance(cand
, dict): 
 544                     permalink_url 
= url_or_none(cand
.get('permalink_url')) 
 545                     if not permalink_url
: 
 547                     return self
.url_result( 
 549                         ie
=SoundcloudIE
.ie_key() if SoundcloudIE
.suitable(permalink_url
) else None, 
 550                         video_id
=self
._extract
_id
(cand
), 
 551                         video_title
=cand
.get('title')) 
 554                 entry 
= resolve_entry((e
, e
.get('track'), e
.get('playlist'))) 
 556                     entries
.append(entry
) 
 558             next_href 
= response
.get('next_href') 
 562             parsed_next_href 
= compat_urlparse
.urlparse(response
['next_href']) 
 563             qs 
= compat_urlparse
.parse_qs(parsed_next_href
.query
) 
 564             qs
.update(COMMON_QUERY
) 
 565             next_href 
= compat_urlparse
.urlunparse( 
 566                 parsed_next_href
._replace
(query
=compat_urllib_parse_urlencode(qs
, True))) 
 571             'title': playlist_title
, 
 576 class SoundcloudUserIE(SoundcloudPagedPlaylistBaseIE
): 
 577     _VALID_URL 
= r
'''(?x) 
 579                             (?:(?:www|m)\.)?soundcloud\.com/ 
 582                                 (?P<rsrc>tracks|albums|sets|reposts|likes|spotlight) 
 586     IE_NAME 
= 'soundcloud:user' 
 588         'url': 'https://soundcloud.com/soft-cell-official', 
 591             'title': 'Soft Cell (All)', 
 593         'playlist_mincount': 28, 
 595         'url': 'https://soundcloud.com/soft-cell-official/tracks', 
 598             'title': 'Soft Cell (Tracks)', 
 600         'playlist_mincount': 27, 
 602         'url': 'https://soundcloud.com/soft-cell-official/albums', 
 605             'title': 'Soft Cell (Albums)', 
 607         'playlist_mincount': 1, 
 609         'url': 'https://soundcloud.com/jcv246/sets', 
 612             'title': 'Jordi / cv (Playlists)', 
 614         'playlist_mincount': 2, 
 616         'url': 'https://soundcloud.com/jcv246/reposts', 
 619             'title': 'Jordi / cv (Reposts)', 
 621         'playlist_mincount': 6, 
 623         'url': 'https://soundcloud.com/clalberg/likes', 
 626             'title': 'clalberg (Likes)', 
 628         'playlist_mincount': 5, 
 630         'url': 'https://soundcloud.com/grynpyret/spotlight', 
 633             'title': 'Grynpyret (Spotlight)', 
 635         'playlist_mincount': 1, 
 639         'all': '%s/stream/users/%%s' % SoundcloudPagedPlaylistBaseIE
._API
_V
2_BASE
, 
 640         'tracks': '%s/users/%%s/tracks' % SoundcloudPagedPlaylistBaseIE
._API
_V
2_BASE
, 
 641         'albums': '%s/users/%%s/albums' % SoundcloudPagedPlaylistBaseIE
._API
_V
2_BASE
, 
 642         'sets': '%s/users/%%s/playlists' % SoundcloudPagedPlaylistBaseIE
._API
_V
2_BASE
, 
 643         'reposts': '%s/stream/users/%%s/reposts' % SoundcloudPagedPlaylistBaseIE
._API
_V
2_BASE
, 
 644         'likes': '%s/users/%%s/likes' % SoundcloudPagedPlaylistBaseIE
._API
_V
2_BASE
, 
 645         'spotlight': '%s/users/%%s/spotlight' % SoundcloudPagedPlaylistBaseIE
._API
_V
2_BASE
, 
 653         'reposts': 'Reposts', 
 655         'spotlight': 'Spotlight', 
 658     def _real_extract(self
, url
): 
 659         mobj 
= re
.match(self
._VALID
_URL
, url
) 
 660         uploader 
= mobj
.group('user') 
 662         url 
= 'https://soundcloud.com/%s/' % uploader
 
 663         resolv_url 
= self
._resolv
_url
(url
) 
 664         user 
= self
._download
_json
( 
 665             resolv_url
, uploader
, 'Downloading user info') 
 667         resource 
= mobj
.group('rsrc') or 'all' 
 669         return self
._extract
_playlist
( 
 670             self
._BASE
_URL
_MAP
[resource
] % user
['id'], compat_str(user
['id']), 
 671             '%s (%s)' % (user
['username'], self
._TITLE
_MAP
[resource
])) 
 674 class SoundcloudTrackStationIE(SoundcloudPagedPlaylistBaseIE
): 
 675     _VALID_URL 
= r
'https?://(?:(?:www|m)\.)?soundcloud\.com/stations/track/[^/]+/(?P<id>[^/?#&]+)' 
 676     IE_NAME 
= 'soundcloud:trackstation' 
 678         'url': 'https://soundcloud.com/stations/track/officialsundial/your-text', 
 681             'title': 'Track station: your-text', 
 683         'playlist_mincount': 47, 
 686     def _real_extract(self
, url
): 
 687         track_name 
= self
._match
_id
(url
) 
 689         webpage 
= self
._download
_webpage
(url
, track_name
) 
 691         track_id 
= self
._search
_regex
( 
 692             r
'soundcloud:track-stations:(\d+)', webpage
, 'track id') 
 694         return self
._extract
_playlist
( 
 695             '%s/stations/soundcloud:track-stations:%s/tracks' 
 696             % (self
._API
_V
2_BASE
, track_id
), 
 697             track_id
, 'Track station: %s' % track_name
) 
 700 class SoundcloudPlaylistIE(SoundcloudPlaylistBaseIE
): 
 701     _VALID_URL 
= r
'https?://api\.soundcloud\.com/playlists/(?P<id>[0-9]+)(?:/?\?secret_token=(?P<token>[^&]+?))?$' 
 702     IE_NAME 
= 'soundcloud:playlist' 
 704         'url': 'https://api.soundcloud.com/playlists/4110309', 
 707             'title': 'TILT Brass - Bowery Poetry Club, August \'03 [Non-Site SCR 02]', 
 708             'description': 're:.*?TILT Brass - Bowery Poetry Club', 
 713     def _real_extract(self
, url
): 
 714         mobj 
= re
.match(self
._VALID
_URL
, url
) 
 715         playlist_id 
= mobj
.group('id') 
 716         base_url 
= '%s//api.soundcloud.com/playlists/%s.json?' % (self
.http_scheme(), playlist_id
) 
 719             'client_id': self
._CLIENT
_ID
, 
 721         token 
= mobj
.group('token') 
 724             data_dict
['secret_token'] = token
 
 726         data 
= compat_urllib_parse_urlencode(data_dict
) 
 727         data 
= self
._download
_json
( 
 728             base_url 
+ data
, playlist_id
, 'Downloading playlist') 
 730         entries 
= self
._extract
_track
_entries
(data
['tracks']) 
 735             'title': data
.get('title'), 
 736             'description': data
.get('description'), 
 741 class SoundcloudSearchIE(SearchInfoExtractor
, SoundcloudIE
): 
 742     IE_NAME 
= 'soundcloud:search' 
 743     IE_DESC 
= 'Soundcloud search' 
 744     _MAX_RESULTS 
= float('inf') 
 746         'url': 'scsearch15:post-avant jazzcore', 
 748             'title': 'post-avant jazzcore', 
 750         'playlist_count': 15, 
 753     _SEARCH_KEY 
= 'scsearch' 
 754     _MAX_RESULTS_PER_PAGE 
= 200 
 755     _DEFAULT_RESULTS_PER_PAGE 
= 50 
 756     _API_V2_BASE 
= 'https://api-v2.soundcloud.com' 
 758     def _get_collection(self
, endpoint
, collection_id
, **query
): 
 760             query
.get('limit', self
._DEFAULT
_RESULTS
_PER
_PAGE
), 
 761             self
._MAX
_RESULTS
_PER
_PAGE
) 
 762         query
['limit'] = limit
 
 763         query
['client_id'] = self
._CLIENT
_ID
 
 764         query
['linked_partitioning'] = '1' 
 766         data 
= compat_urllib_parse_urlencode(query
) 
 767         next_url 
= '{0}{1}?{2}'.format(self
._API
_V
2_BASE
, endpoint
, data
) 
 769         collected_results 
= 0 
 771         for i 
in itertools
.count(1): 
 772             response 
= self
._download
_json
( 
 773                 next_url
, collection_id
, 'Downloading page {0}'.format(i
), 
 774                 'Unable to download API page') 
 776             collection 
= response
.get('collection', []) 
 780             collection 
= list(filter(bool, collection
)) 
 781             collected_results 
+= len(collection
) 
 783             for item 
in collection
: 
 784                 yield self
.url_result(item
['uri'], SoundcloudIE
.ie_key()) 
 786             if not collection 
or collected_results 
>= limit
: 
 789             next_url 
= response
.get('next_href') 
 793     def _get_n_results(self
, query
, n
): 
 794         tracks 
= self
._get
_collection
('/search/tracks', query
, limit
=n
, q
=query
) 
 795         return self
.playlist_result(tracks
, playlist_title
=query
)