Mopidy-SoundCloud-2.0.2/ 0000775 0001750 0001750 00000000000 12642323173 015242 5 ustar jodal jodal 0000000 0000000 Mopidy-SoundCloud-2.0.2/PKG-INFO 0000664 0001750 0001750 00000015303 12642323173 016341 0 ustar jodal jodal 0000000 0000000 Metadata-Version: 1.1
Name: Mopidy-SoundCloud
Version: 2.0.2
Summary: SoundCloud extension for Mopidy
Home-page: https://github.com/mopidy/mopidy-soundcloud
Author: dz0ny
Author-email: dz0ny@ubuntu.si
License: MIT
Description: *****************
Mopidy-SoundCloud
*****************
.. image:: https://img.shields.io/pypi/v/Mopidy-SoundCloud.svg?style=flat
:target: https://pypi.python.org/pypi/Mopidy-SoundCloud/
:alt: Latest PyPI version
.. image:: https://img.shields.io/pypi/dm/Mopidy-SoundCloud.svg?style=flat
:target: https://pypi.python.org/pypi/Mopidy-SoundCloud/
:alt: Number of PyPI downloads
.. image:: https://img.shields.io/travis/mopidy/mopidy-soundcloud/master.svg?style=flat
:target: https://travis-ci.org/mopidy/mopidy-soundcloud
:alt: Travis CI build status
.. image:: https://img.shields.io/coveralls/mopidy/mopidy-soundcloud/master.svg?style=flat
:target: https://coveralls.io/r/mopidy/mopidy-soundcloud?branch=master
:alt: Test coverage
`Mopidy `_ extension for playing music from
`SoundCloud `_.
Installation
============
Debian/Ubuntu/Raspbian: Install the ``mopidy-soundcloud`` package from
`apt.mopidy.com `_::
sudo apt-get install mopidy-soundcloud
Arch Linux: Install the ``mopidy-soundcloud`` package from
`AUR `_::
sudo yaourt -S mopidy-soundcloud
OS X: Install the ``mopidy-soundcloud`` package from the
`mopidy/mopidy `_ Homebrew tap::
brew install mopidy-soundcloud
Else: Install the dependencies listed above yourself, and then install the
package from PyPI::
pip install Mopidy-SoundCloud
If you're having trouble with audio playback from SoundCloud, make sure you
have the "ugly" plugin set from GStreamer installed for MP3 support. The
package is typically named ``gstreamer0.10-plugins-ugly`` or similar, depending
on OS and distribution. The package isn't a strict requirement for Mopidy's
core, so you may be missing it.
Configuration
=============
#. You must register for a user account at http://www.soundcloud.com/
#. You need a SoundCloud authentication token for Mopidy from
http://www.mopidy.com/authenticate
#. Add the authentication token to the ``mopidy.conf`` config file::
[soundcloud]
auth_token = 1-1111-1111111
explore_songs = 25
Project resources
=================
- `Source code `_
- `Issue tracker `_
Changelog
=========
v2.0.2 (2016-01-03)
-------------------
- Handle HTTP connection errors without a response. (PR #61)
- Ignore tracks without an URI. (Related to mopidy#1340, PR #62)
v2.0.1 (2015-10-06)
-------------------
- Fix Unicode escape sequences in SoundCloud search queries by encoding as
UTF-8. (Fixes #42, PR #55)
v2.0.0 (2015-03-25)
-------------------
- Require Mopidy >= 1.0.
- Update to work with new playback API in Mopidy 1.0.
- Update to work with new backend search API in Mopidy 1.0.
v1.2.5 (2014-06-24)
-------------------
- Add support for new explore api
v1.2.4 (2014-05-15)
-------------------
- Add support for adding track by url
- Fix search parsing
- Support for adding playlists from liked section
- Fix for track parsing and empty artists field
v1.2.3 (2014-04-02)
-------------------
- Add support for playing music from groups
v1.2.2 (2014-03-26)
-------------------
- Update Soundcloud API endpoint
v1.2.1 (2014-02-21)
-------------------
- Properly escape unsafe chars in URIs.
v1.2.0 (2014-02-16)
-------------------
- Deprecated ``explore`` and ``explore_pages`` config values.
- Extension is now using Mopidy's virtual filesystem to expose music from your
SoundCloud account instead of fake playlists. See the "Browse" or "Files"
option in your MPD client.
In the virtual file system you can browse:
- The "Stream" with tracks from the users you follow.
- All "Explore" sections.
- Your followers and their shared tracks.
- Your liked tracks.
- Your sets.
- Add search support.
- Add support for looking up music by SoundCloud URLs through searching for the
URL as a file name.
v1.1.0 (2014-01-20)
-------------------
- Updated extension and backend APIs to match Mopidy 0.18.
v1.0.18 (2014-01-11)
--------------------
- Use proper logger namespaced to ``mopidy_soundcloud`` instead of ``mopidy``.
- Fix wrong use of ``raise`` when the SoundCloud API doesn't respond as
expected.
v1.0.17 (2013-12-21)
--------------------
- Don't cache the user request.
- Require Requests >= 2.0. (Fixes #3)
v1.0.16 (2013-10-22)
--------------------
- Require Mopidy >= 0.14.
- Fix crash when SoundCloud returns 404 on track lookup. (Fixes #7)
- Add some tests.
v1.0.15 (2013-07-31)
--------------------
- Import code from old repo.
- Handle authentication errors without crashing. (Fixes #3 and #4)
Platform: UNKNOWN
Classifier: Environment :: No Input/Output (Daemon)
Classifier: Intended Audience :: End Users/Desktop
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 2
Classifier: Topic :: Multimedia :: Sound/Audio :: Players
Mopidy-SoundCloud-2.0.2/setup.py 0000664 0001750 0001750 00000002406 12505250302 016745 0 ustar jodal jodal 0000000 0000000 from __future__ import unicode_literals
import re
from setuptools import find_packages, setup
def get_version(filename):
with open(filename) as fh:
metadata = dict(re.findall("__([a-z]+)__ = '([^']+)'", fh.read()))
return metadata['version']
setup(
name='Mopidy-SoundCloud',
version=get_version('mopidy_soundcloud/__init__.py'),
url='https://github.com/mopidy/mopidy-soundcloud',
license='MIT',
author='dz0ny',
author_email='dz0ny@ubuntu.si',
description='SoundCloud extension for Mopidy',
long_description=open('README.rst').read(),
packages=find_packages(exclude=['tests', 'tests.*']),
zip_safe=False,
include_package_data=True,
install_requires=[
'setuptools',
'Mopidy >= 1.0',
'Pykka >= 1.1',
'requests >= 2.0.0',
],
entry_points={
'mopidy.ext': [
'soundcloud = mopidy_soundcloud:SoundCloudExtension',
],
},
classifiers=[
'Environment :: No Input/Output (Daemon)',
'Intended Audience :: End Users/Desktop',
'License :: OSI Approved :: MIT License',
'Operating System :: OS Independent',
'Programming Language :: Python :: 2',
'Topic :: Multimedia :: Sound/Audio :: Players',
],
)
Mopidy-SoundCloud-2.0.2/.travis.yml 0000664 0001750 0001750 00000000717 12623103152 017350 0 ustar jodal jodal 0000000 0000000 sudo: false
language: python
python:
- "2.7_with_system_site_packages"
env:
- TOX_ENV=py27
- TOX_ENV=flake8
install:
- "pip install tox"
script:
- "tox -e $TOX_ENV"
after_success:
- "if [ $TOX_ENV == 'py27' ]; then pip install coveralls; coveralls; fi"
branches:
except:
- debian
notifications:
irc:
channels:
- "irc.freenode.org#mopidy"
on_success: change
on_failure: change
use_notice: true
skip_join: true
Mopidy-SoundCloud-2.0.2/Mopidy_SoundCloud.egg-info/ 0000775 0001750 0001750 00000000000 12642323173 022334 5 ustar jodal jodal 0000000 0000000 Mopidy-SoundCloud-2.0.2/Mopidy_SoundCloud.egg-info/PKG-INFO 0000664 0001750 0001750 00000015303 12642323173 023433 0 ustar jodal jodal 0000000 0000000 Metadata-Version: 1.1
Name: Mopidy-SoundCloud
Version: 2.0.2
Summary: SoundCloud extension for Mopidy
Home-page: https://github.com/mopidy/mopidy-soundcloud
Author: dz0ny
Author-email: dz0ny@ubuntu.si
License: MIT
Description: *****************
Mopidy-SoundCloud
*****************
.. image:: https://img.shields.io/pypi/v/Mopidy-SoundCloud.svg?style=flat
:target: https://pypi.python.org/pypi/Mopidy-SoundCloud/
:alt: Latest PyPI version
.. image:: https://img.shields.io/pypi/dm/Mopidy-SoundCloud.svg?style=flat
:target: https://pypi.python.org/pypi/Mopidy-SoundCloud/
:alt: Number of PyPI downloads
.. image:: https://img.shields.io/travis/mopidy/mopidy-soundcloud/master.svg?style=flat
:target: https://travis-ci.org/mopidy/mopidy-soundcloud
:alt: Travis CI build status
.. image:: https://img.shields.io/coveralls/mopidy/mopidy-soundcloud/master.svg?style=flat
:target: https://coveralls.io/r/mopidy/mopidy-soundcloud?branch=master
:alt: Test coverage
`Mopidy `_ extension for playing music from
`SoundCloud `_.
Installation
============
Debian/Ubuntu/Raspbian: Install the ``mopidy-soundcloud`` package from
`apt.mopidy.com `_::
sudo apt-get install mopidy-soundcloud
Arch Linux: Install the ``mopidy-soundcloud`` package from
`AUR `_::
sudo yaourt -S mopidy-soundcloud
OS X: Install the ``mopidy-soundcloud`` package from the
`mopidy/mopidy `_ Homebrew tap::
brew install mopidy-soundcloud
Else: Install the dependencies listed above yourself, and then install the
package from PyPI::
pip install Mopidy-SoundCloud
If you're having trouble with audio playback from SoundCloud, make sure you
have the "ugly" plugin set from GStreamer installed for MP3 support. The
package is typically named ``gstreamer0.10-plugins-ugly`` or similar, depending
on OS and distribution. The package isn't a strict requirement for Mopidy's
core, so you may be missing it.
Configuration
=============
#. You must register for a user account at http://www.soundcloud.com/
#. You need a SoundCloud authentication token for Mopidy from
http://www.mopidy.com/authenticate
#. Add the authentication token to the ``mopidy.conf`` config file::
[soundcloud]
auth_token = 1-1111-1111111
explore_songs = 25
Project resources
=================
- `Source code `_
- `Issue tracker `_
Changelog
=========
v2.0.2 (2016-01-03)
-------------------
- Handle HTTP connection errors without a response. (PR #61)
- Ignore tracks without an URI. (Related to mopidy#1340, PR #62)
v2.0.1 (2015-10-06)
-------------------
- Fix Unicode escape sequences in SoundCloud search queries by encoding as
UTF-8. (Fixes #42, PR #55)
v2.0.0 (2015-03-25)
-------------------
- Require Mopidy >= 1.0.
- Update to work with new playback API in Mopidy 1.0.
- Update to work with new backend search API in Mopidy 1.0.
v1.2.5 (2014-06-24)
-------------------
- Add support for new explore api
v1.2.4 (2014-05-15)
-------------------
- Add support for adding track by url
- Fix search parsing
- Support for adding playlists from liked section
- Fix for track parsing and empty artists field
v1.2.3 (2014-04-02)
-------------------
- Add support for playing music from groups
v1.2.2 (2014-03-26)
-------------------
- Update Soundcloud API endpoint
v1.2.1 (2014-02-21)
-------------------
- Properly escape unsafe chars in URIs.
v1.2.0 (2014-02-16)
-------------------
- Deprecated ``explore`` and ``explore_pages`` config values.
- Extension is now using Mopidy's virtual filesystem to expose music from your
SoundCloud account instead of fake playlists. See the "Browse" or "Files"
option in your MPD client.
In the virtual file system you can browse:
- The "Stream" with tracks from the users you follow.
- All "Explore" sections.
- Your followers and their shared tracks.
- Your liked tracks.
- Your sets.
- Add search support.
- Add support for looking up music by SoundCloud URLs through searching for the
URL as a file name.
v1.1.0 (2014-01-20)
-------------------
- Updated extension and backend APIs to match Mopidy 0.18.
v1.0.18 (2014-01-11)
--------------------
- Use proper logger namespaced to ``mopidy_soundcloud`` instead of ``mopidy``.
- Fix wrong use of ``raise`` when the SoundCloud API doesn't respond as
expected.
v1.0.17 (2013-12-21)
--------------------
- Don't cache the user request.
- Require Requests >= 2.0. (Fixes #3)
v1.0.16 (2013-10-22)
--------------------
- Require Mopidy >= 0.14.
- Fix crash when SoundCloud returns 404 on track lookup. (Fixes #7)
- Add some tests.
v1.0.15 (2013-07-31)
--------------------
- Import code from old repo.
- Handle authentication errors without crashing. (Fixes #3 and #4)
Platform: UNKNOWN
Classifier: Environment :: No Input/Output (Daemon)
Classifier: Intended Audience :: End Users/Desktop
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 2
Classifier: Topic :: Multimedia :: Sound/Audio :: Players
Mopidy-SoundCloud-2.0.2/Mopidy_SoundCloud.egg-info/entry_points.txt 0000664 0001750 0001750 00000000101 12642323173 025622 0 ustar jodal jodal 0000000 0000000 [mopidy.ext]
soundcloud = mopidy_soundcloud:SoundCloudExtension
Mopidy-SoundCloud-2.0.2/Mopidy_SoundCloud.egg-info/not-zip-safe 0000664 0001750 0001750 00000000001 12624040417 024557 0 ustar jodal jodal 0000000 0000000
Mopidy-SoundCloud-2.0.2/Mopidy_SoundCloud.egg-info/dependency_links.txt 0000664 0001750 0001750 00000000001 12642323173 026402 0 ustar jodal jodal 0000000 0000000
Mopidy-SoundCloud-2.0.2/Mopidy_SoundCloud.egg-info/top_level.txt 0000664 0001750 0001750 00000000022 12642323173 025060 0 ustar jodal jodal 0000000 0000000 mopidy_soundcloud
Mopidy-SoundCloud-2.0.2/Mopidy_SoundCloud.egg-info/requires.txt 0000664 0001750 0001750 00000000070 12642323173 024731 0 ustar jodal jodal 0000000 0000000 setuptools
Mopidy >= 1.0
Pykka >= 1.1
requests >= 2.0.0
Mopidy-SoundCloud-2.0.2/Mopidy_SoundCloud.egg-info/SOURCES.txt 0000664 0001750 0001750 00000002120 12642323173 024213 0 ustar jodal jodal 0000000 0000000 .coveragerc
.mailmap
.travis.yml
LICENSE
MANIFEST.in
README.rst
setup.cfg
setup.py
tox.ini
Mopidy_SoundCloud.egg-info/PKG-INFO
Mopidy_SoundCloud.egg-info/SOURCES.txt
Mopidy_SoundCloud.egg-info/dependency_links.txt
Mopidy_SoundCloud.egg-info/entry_points.txt
Mopidy_SoundCloud.egg-info/not-zip-safe
Mopidy_SoundCloud.egg-info/requires.txt
Mopidy_SoundCloud.egg-info/top_level.txt
mopidy_soundcloud/__init__.py
mopidy_soundcloud/actor.py
mopidy_soundcloud/ext.conf
mopidy_soundcloud/library.py
mopidy_soundcloud/soundcloud.py
tests/__init__.py
tests/__main__.py
tests/test_api.py
tests/test_cache.py
tests/test_extension.py
tests/test_library.py
tests/fixtures/sc-explore.yaml
tests/fixtures/sc-following.yaml
tests/fixtures/sc-groups.yaml
tests/fixtures/sc-liked.yaml
tests/fixtures/sc-login-error.yaml
tests/fixtures/sc-login.yaml
tests/fixtures/sc-popular.yaml
tests/fixtures/sc-resolve-http.yaml
tests/fixtures/sc-resolve-track-id.yaml
tests/fixtures/sc-resolve-track-none.yaml
tests/fixtures/sc-resolve-track.yaml
tests/fixtures/sc-sets.yaml
tests/fixtures/sc-stream.yaml
tests/fixtures/sc-tracks.yaml Mopidy-SoundCloud-2.0.2/README.rst 0000664 0001750 0001750 00000011252 12642322330 016724 0 ustar jodal jodal 0000000 0000000 *****************
Mopidy-SoundCloud
*****************
.. image:: https://img.shields.io/pypi/v/Mopidy-SoundCloud.svg?style=flat
:target: https://pypi.python.org/pypi/Mopidy-SoundCloud/
:alt: Latest PyPI version
.. image:: https://img.shields.io/pypi/dm/Mopidy-SoundCloud.svg?style=flat
:target: https://pypi.python.org/pypi/Mopidy-SoundCloud/
:alt: Number of PyPI downloads
.. image:: https://img.shields.io/travis/mopidy/mopidy-soundcloud/master.svg?style=flat
:target: https://travis-ci.org/mopidy/mopidy-soundcloud
:alt: Travis CI build status
.. image:: https://img.shields.io/coveralls/mopidy/mopidy-soundcloud/master.svg?style=flat
:target: https://coveralls.io/r/mopidy/mopidy-soundcloud?branch=master
:alt: Test coverage
`Mopidy `_ extension for playing music from
`SoundCloud `_.
Installation
============
Debian/Ubuntu/Raspbian: Install the ``mopidy-soundcloud`` package from
`apt.mopidy.com `_::
sudo apt-get install mopidy-soundcloud
Arch Linux: Install the ``mopidy-soundcloud`` package from
`AUR `_::
sudo yaourt -S mopidy-soundcloud
OS X: Install the ``mopidy-soundcloud`` package from the
`mopidy/mopidy `_ Homebrew tap::
brew install mopidy-soundcloud
Else: Install the dependencies listed above yourself, and then install the
package from PyPI::
pip install Mopidy-SoundCloud
If you're having trouble with audio playback from SoundCloud, make sure you
have the "ugly" plugin set from GStreamer installed for MP3 support. The
package is typically named ``gstreamer0.10-plugins-ugly`` or similar, depending
on OS and distribution. The package isn't a strict requirement for Mopidy's
core, so you may be missing it.
Configuration
=============
#. You must register for a user account at http://www.soundcloud.com/
#. You need a SoundCloud authentication token for Mopidy from
http://www.mopidy.com/authenticate
#. Add the authentication token to the ``mopidy.conf`` config file::
[soundcloud]
auth_token = 1-1111-1111111
explore_songs = 25
Project resources
=================
- `Source code `_
- `Issue tracker `_
Changelog
=========
v2.0.2 (2016-01-03)
-------------------
- Handle HTTP connection errors without a response. (PR #61)
- Ignore tracks without an URI. (Related to mopidy#1340, PR #62)
v2.0.1 (2015-10-06)
-------------------
- Fix Unicode escape sequences in SoundCloud search queries by encoding as
UTF-8. (Fixes #42, PR #55)
v2.0.0 (2015-03-25)
-------------------
- Require Mopidy >= 1.0.
- Update to work with new playback API in Mopidy 1.0.
- Update to work with new backend search API in Mopidy 1.0.
v1.2.5 (2014-06-24)
-------------------
- Add support for new explore api
v1.2.4 (2014-05-15)
-------------------
- Add support for adding track by url
- Fix search parsing
- Support for adding playlists from liked section
- Fix for track parsing and empty artists field
v1.2.3 (2014-04-02)
-------------------
- Add support for playing music from groups
v1.2.2 (2014-03-26)
-------------------
- Update Soundcloud API endpoint
v1.2.1 (2014-02-21)
-------------------
- Properly escape unsafe chars in URIs.
v1.2.0 (2014-02-16)
-------------------
- Deprecated ``explore`` and ``explore_pages`` config values.
- Extension is now using Mopidy's virtual filesystem to expose music from your
SoundCloud account instead of fake playlists. See the "Browse" or "Files"
option in your MPD client.
In the virtual file system you can browse:
- The "Stream" with tracks from the users you follow.
- All "Explore" sections.
- Your followers and their shared tracks.
- Your liked tracks.
- Your sets.
- Add search support.
- Add support for looking up music by SoundCloud URLs through searching for the
URL as a file name.
v1.1.0 (2014-01-20)
-------------------
- Updated extension and backend APIs to match Mopidy 0.18.
v1.0.18 (2014-01-11)
--------------------
- Use proper logger namespaced to ``mopidy_soundcloud`` instead of ``mopidy``.
- Fix wrong use of ``raise`` when the SoundCloud API doesn't respond as
expected.
v1.0.17 (2013-12-21)
--------------------
- Don't cache the user request.
- Require Requests >= 2.0. (Fixes #3)
v1.0.16 (2013-10-22)
--------------------
- Require Mopidy >= 0.14.
- Fix crash when SoundCloud returns 404 on track lookup. (Fixes #7)
- Add some tests.
v1.0.15 (2013-07-31)
--------------------
- Import code from old repo.
- Handle authentication errors without crashing. (Fixes #3 and #4)
Mopidy-SoundCloud-2.0.2/setup.cfg 0000664 0001750 0001750 00000000243 12642323173 017062 0 ustar jodal jodal 0000000 0000000 [flake8]
application-import-names = mopidy_soundcloud,tests
exclude = .git,.tox
[wheel]
universal = 1
[egg_info]
tag_build =
tag_date = 0
tag_svn_revision = 0
Mopidy-SoundCloud-2.0.2/.coveragerc 0000664 0001750 0001750 00000000152 12505250302 017350 0 ustar jodal jodal 0000000 0000000 [report]
omit =
*/pyshared/*
*/python?.?/*
*/site-packages/nose/*
*/site-packages/mopidy/* Mopidy-SoundCloud-2.0.2/.mailmap 0000664 0001750 0001750 00000000354 12623103152 016655 0 ustar jodal jodal 0000000 0000000 Janez Troha
Janez Troha
Janez Troha
Morten Minde Neergaard
Tom Roth
Mopidy-SoundCloud-2.0.2/tox.ini 0000664 0001750 0001750 00000000741 12623103152 016547 0 ustar jodal jodal 0000000 0000000 [tox]
envlist = py27, flake8
[testenv]
deps =
mock
mopidy==dev
pytest
pytest-cov
pytest-xdist
vcrpy
install_command = pip install --allow-unverified=mopidy --pre {opts} {packages}
commands =
py.test \
--basetemp={envtmpdir} \
--junit-xml=xunit-{envname}.xml \
--cov=mopidy_soundcloud --cov-report=term-missing \
{posargs}
[testenv:flake8]
deps =
flake8
flake8-import-order
skip_install = true
commands = flake8
Mopidy-SoundCloud-2.0.2/MANIFEST.in 0000664 0001750 0001750 00000000340 12505250302 016764 0 ustar jodal jodal 0000000 0000000 include .coveragerc
include .mailmap
include .travis.yml
include LICENSE
include MANIFEST.in
include README.rst
include mopidy_soundcloud/ext.conf
include tox.ini
recursive-include tests *.py
recursive-include tests *.yaml
Mopidy-SoundCloud-2.0.2/LICENSE 0000664 0001750 0001750 00000002057 12224244453 016252 0 ustar jodal jodal 0000000 0000000 Copyright (C) 2013
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Mopidy-SoundCloud-2.0.2/tests/ 0000775 0001750 0001750 00000000000 12642323173 016404 5 ustar jodal jodal 0000000 0000000 Mopidy-SoundCloud-2.0.2/tests/test_cache.py 0000664 0001750 0001750 00000001470 12505250302 021051 0 ustar jodal jodal 0000000 0000000 from __future__ import unicode_literals
import unittest
from mock import Mock
from mopidy_soundcloud.soundcloud import cache
class CacheTest(unittest.TestCase):
def test_decorator(self):
func = Mock()
decorated_func = cache()
decorated_func(func)
func()
self.assertEquals(func.called, True)
self.assertEquals(decorated_func._call_count, 1)
def test_set_default_cache(self):
@cache()
def returnstring():
return 'ok'
self.assertEquals(returnstring(), 'ok')
def test_set_ttl_cache(self):
func = Mock()
decorated_func = cache(func, ttl=5)
func()
self.assertEquals(func.called, True)
self.assertEquals(decorated_func._call_count, 1)
self.assertEquals(decorated_func.ttl, 5)
Mopidy-SoundCloud-2.0.2/tests/fixtures/ 0000775 0001750 0001750 00000000000 12642323173 020255 5 ustar jodal jodal 0000000 0000000 Mopidy-SoundCloud-2.0.2/tests/fixtures/sc-liked.yaml 0000664 0001750 0001750 00000002125 12505250302 022623 0 ustar jodal jodal 0000000 0000000 interactions:
- request:
body: null
headers:
Accept: ['*/*']
Accept-Encoding: ['gzip, deflate']
Authorization: [!!python/unicode 'OAuth 1-35204-61921957-55796ebef403996']
Connection: [keep-alive]
User-Agent: [python-requests/2.4.3 CPython/2.7.8 Linux/3.16.0-28-generic]
method: GET
uri: https://api.soundcloud.com:443/e1/me/likes.json?limit=1000&client_id=93e33e327fd8a9b77becd179652272e2
response:
body:
string: !!binary |
H4sIAAAAAAAEA4uOBQApu0wNAgAAAA==
headers:
access-control-allow-headers: ['Accept, Authorization, Content-Type, Origin']
access-control-allow-methods: ['GET, PUT, POST, DELETE']
access-control-allow-origin: ['*']
access-control-expose-headers: [Date]
cache-control: ['private, max-age=0, must-revalidate']
content-encoding: [gzip]
content-length: ['22']
content-type: [application/json; charset=utf-8]
date: ['Fri, 02 Jan 2015 17:03:54 GMT']
etag: ['"d751713988987e9331980363e24189ce"']
server: [am/2]
status: {code: 200, message: OK}
version: 1
Mopidy-SoundCloud-2.0.2/tests/fixtures/sc-resolve-track-none.yaml 0000664 0001750 0001750 00000001763 12505250302 025260 0 ustar jodal jodal 0000000 0000000 interactions:
- request:
body: null
headers:
Accept: ['*/*']
Accept-Encoding: ['gzip, deflate']
Authorization: [!!python/unicode 'OAuth 1-35204-61921957-55796ebef403996']
Connection: [keep-alive]
User-Agent: [python-requests/2.4.3 CPython/2.7.8 Linux/3.16.0-28-generic]
method: GET
uri: https://api.soundcloud.com:443/tracks/s38720262.json?client_id=93e33e327fd8a9b77becd179652272e2
response:
body: {string: !!python/unicode '{"errors":[{"error_message":"404 - Not Found"}]}'}
headers:
access-control-allow-headers: ['Accept, Authorization, Content-Type, Origin']
access-control-allow-methods: ['GET, PUT, POST, DELETE']
access-control-allow-origin: ['*']
access-control-expose-headers: [Date]
cache-control: [no-cache]
content-length: ['48']
content-type: [application/json; charset=utf-8]
date: ['Fri, 02 Jan 2015 17:00:39 GMT']
server: [am/2]
status: {code: 404, message: Not Found}
version: 1
Mopidy-SoundCloud-2.0.2/tests/fixtures/sc-stream.yaml 0000664 0001750 0001750 00000005104 12505250302 023026 0 ustar jodal jodal 0000000 0000000 interactions:
- request:
body: null
headers:
Accept: ['*/*']
Accept-Encoding: ['gzip, deflate']
Authorization: [!!python/unicode 'OAuth 1-35204-61921957-55796ebef403996']
Connection: [keep-alive]
User-Agent: [python-requests/2.4.3 CPython/2.7.8 Linux/3.16.0-28-generic]
method: GET
uri: https://api.soundcloud.com:443/e1/me/stream.json?offset=0e1/me/stream.json?offset=0e1/me/stream.json?offset=0e1/me/stream.json?offset=0e1/me/stream.json?offset=0&client_id=93e33e327fd8a9b77becd179652272e2
response:
body:
string: !!binary |
H4sIAAAAAAAAAATBwQqAIAwA0H/ZOVxehehDIkJsoqFOdDtF/957L0QVHXSlQREcJJE+HaLv2UzW
dofCepvAFcliJZwyyFfzTG57yTXLZldYIHApFCRzA3ec3w8AAP//AwCP9XBDVwAAAA==
headers:
access-control-allow-headers: ['Accept, Authorization, Content-Type, Origin']
access-control-allow-methods: ['GET, PUT, POST, DELETE']
access-control-allow-origin: ['*']
access-control-expose-headers: [Date]
cache-control: ['private, max-age=0']
content-encoding: [gzip]
content-length: ['106']
content-type: [application/json]
date: ['Fri, 02 Jan 2015 17:03:54 GMT']
server: [am/2]
set-cookie: [_session_auth_key="iuo9QNN6oaPVa3gj9XyKTHZtsxE="]
status: {code: 200, message: OK}
- request:
body: null
headers:
Accept: ['*/*']
Accept-Encoding: ['gzip, deflate']
Authorization: [!!python/unicode 'OAuth 1-35204-61921957-55796ebef403996']
Connection: [keep-alive]
Cookie: [_session_auth_key="iuo9QNN6oaPVa3gj9XyKTHZtsxE="]
User-Agent: [python-requests/2.4.3 CPython/2.7.8 Linux/3.16.0-28-generic]
method: GET
uri: https://api.soundcloud.com:443/e1/me/stream.json?offset=1e1/me/stream.json?offset=1e1/me/stream.json?offset=1e1/me/stream.json?offset=1e1/me/stream.json?offset=1&client_id=93e33e327fd8a9b77becd179652272e2
response:
body:
string: !!binary |
H4sIAAAAAAAAAATBwQqAIAwA0H/ZOVxehehDIkJsoqFOdDtF/957L0QVHXSlQREcJJE+HaLv2UzW
dofCepvAFcliJZwyyFfzTG57yTXLZldYIHApFCRzA3ec3w8AAP//AwCP9XBDVwAAAA==
headers:
access-control-allow-headers: ['Accept, Authorization, Content-Type, Origin']
access-control-allow-methods: ['GET, PUT, POST, DELETE']
access-control-allow-origin: ['*']
access-control-expose-headers: [Date]
cache-control: ['private, max-age=0, must-revalidate']
content-encoding: [gzip]
content-length: ['106']
content-type: [application/json]
date: ['Fri, 02 Jan 2015 17:03:54 GMT']
server: [am/2]
status: {code: 200, message: OK}
version: 1
Mopidy-SoundCloud-2.0.2/tests/fixtures/sc-resolve-track.yaml 0000664 0001750 0001750 00000004224 12505250302 024316 0 ustar jodal jodal 0000000 0000000 interactions:
- request:
body: null
headers:
Accept: ['*/*']
Accept-Encoding: ['gzip, deflate']
Authorization: [!!python/unicode 'OAuth 1-35204-61921957-55796ebef403996']
Connection: [keep-alive]
User-Agent: [python-requests/2.4.3 CPython/2.7.8 Linux/3.16.0-28-generic]
method: GET
uri: https://api.soundcloud.com:443/tracks/38720262.json?client_id=93e33e327fd8a9b77becd179652272e2
response:
body:
string: !!binary |
H4sIAAAAAAAEA5VUTY+bMBD9K8jXhYCBkMBtK7WnVXup1CMawCHeGJvaJlF2tf+9Yz5SNtVq1SgX
DzPjN2/e8ys5cdmQglgN9Yn4hOMh2e/iKM5in9SagWVNCRZT4ojGYZSE0daLoiKlRZx6DxH+sGww
TJeuNk2i7Xbvk2bQYLmS2G2bZinFXqrrmLRQCUYKqwfmE2OxO3Y+cMnNkTXYSGnecgmirJW0mF4a
/oIpNKVZmma5TwQYW3aq4QeOBSOqbRhR/DtUSVbQ/IbKHEFz2WJWP1SC19jfQlsKbtw8eOqZ7kBw
ecJjNWgOIjioQQeW2UCqM2CKschBt0bNuoo1jYuU1RULQQjMa9RFCgVjnBQHEAYH7AddH8GwctCC
FHIQwg1QMTFyNZ1vOZZbR80UbZnUjhoHeYqTLyNA78H7hhC9n8x6gfd9AtkwU2veT4S7mukSCd3c
QzPBEMfc0O26tNd+Pp/YFWluJdhhuZMbXU/JZ94wtcZf9d0Ccu5aXhno+1iH+zveBxtAwqYBb4s+
KNyB20fXJw45r5kckSKvAarhaE2gGerrPAoEScDco7W9KcIQer4xapBNLdTQbFBj4ahkEy4ixpZO
nKR4HbW96HOW/fjpvQ4WAcyFM4cL5yO3/ygwDSkqMPNoVMR5QaObAj9F6wCYcEKFvW+CnBgf58Qx
70ZcQYQzWNCrbMcKpxuDlDRyJGRKMYGzKk33eZJnSZDTtt/GgQDdss1z35K32cS9gGvl9FEjrbgW
dO5o7gOccWXOrou2F++sLv8YaviBvUDbi9KntcAucGZOFKu+bqjLu6F+v5ziYff8+DXh57Lb9Ojy
xat3dZ9LJJw87ui/Hz7fZrtd9tfdCysJTX0yU4IvjFniaZTn+GV+65YozZLIJ2At1Ef3BhqE+H8q
DlfFDqdCm7i35/Hp6ccv8vYHpzK+98cFAAA=
headers:
access-control-allow-headers: ['Accept, Authorization, Content-Type, Origin']
access-control-allow-methods: ['GET, PUT, POST, DELETE']
access-control-allow-origin: ['*']
access-control-expose-headers: [Date]
cache-control: ['private, max-age=0, must-revalidate']
content-encoding: [gzip]
content-length: ['710']
content-type: [application/json; charset=utf-8]
date: ['Fri, 02 Jan 2015 17:00:39 GMT']
etag: ['"c886ffe06e6e5b3f18e77d4ea5a539dc"']
server: [am/2]
set-cookie: [_session_auth_key="iuo9QNN6oaPVa3gj9XyKTHZtsxE="]
status: {code: 200, message: OK}
version: 1
Mopidy-SoundCloud-2.0.2/tests/fixtures/sc-following.yaml 0000664 0001750 0001750 00000002125 12505250302 023533 0 ustar jodal jodal 0000000 0000000 interactions:
- request:
body: null
headers:
Accept: ['*/*']
Accept-Encoding: ['gzip, deflate']
Authorization: [!!python/unicode 'OAuth 1-35204-61921957-55796ebef403996']
Connection: [keep-alive]
User-Agent: [python-requests/2.4.3 CPython/2.7.8 Linux/3.16.0-28-generic]
method: GET
uri: https://api.soundcloud.com:443/me/followings.json?limit=60&client_id=93e33e327fd8a9b77becd179652272e2
response:
body:
string: !!binary |
H4sIAAAAAAAEA4uOBQApu0wNAgAAAA==
headers:
access-control-allow-headers: ['Accept, Authorization, Content-Type, Origin']
access-control-allow-methods: ['GET, PUT, POST, DELETE']
access-control-allow-origin: ['*']
access-control-expose-headers: [Date]
cache-control: ['private, max-age=0, must-revalidate']
content-encoding: [gzip]
content-length: ['22']
content-type: [application/json; charset=utf-8]
date: ['Fri, 02 Jan 2015 17:03:52 GMT']
etag: ['"d751713988987e9331980363e24189ce"']
server: [am/2]
status: {code: 200, message: OK}
version: 1
Mopidy-SoundCloud-2.0.2/tests/fixtures/sc-explore.yaml 0000664 0001750 0001750 00000002541 12505250302 023213 0 ustar jodal jodal 0000000 0000000 interactions:
- request:
body: null
headers:
Accept: ['*/*']
Accept-Encoding: ['gzip, deflate']
Authorization: [!!python/unicode 'OAuth 1-35204-61921957-55796ebef403996']
Connection: [keep-alive]
User-Agent: [python-requests/2.4.3 CPython/2.7.8 Linux/3.16.0-28-generic]
method: GET
uri: https://api-v2.soundcloud.com:443/explore/categories?client_id=93e33e327fd8a9b77becd179652272e2
response:
body:
string: !!binary |
H4sIAAAAAAAAAExRwU7DMAz9l0qcOi4cOHBjK2gghqp1Ege0Q5ZaxVpqV07CtCH+nTjdOk5+fkme
n19+ChNb5OLhs6h5iM5I+ZiJWZHrjnnvUzOPHgm8wgX30B4TeKIAEgxSDxRS/wZGCKlL8B0Ovry5
uy9rdhjQ6rs1OOyQKfPNgIIhmnSqUo1FIAuKBpag15vAcgzg3Ki4AftF7Lg7FttZ0Sc79r/pVSaS
aZc8kQn4DeWa7V6pfoejwYUzPl0zLm8RKYjOrsw4uQIYyiVHnxv0VkOoJPbZ8Nzk5au48wEG3d6B
DcJXRNnAMzsdujTSWhYoR9/KoKrry8uMF2pxcvlqTifNMFmnVFcQss0VEvbGXWVqNGPNUrVwJ+lb
dN2LbB1JBdfJ9VyrGXL4XWf09DyuSamC3DZM3SF9BIhyHN056UlsmruRc0wJDLlM+3ywuLbY/v4B
AAD//wMAP61+EEwCAAA=
headers:
cache-control: ['private, max-age=0']
content-encoding: [gzip]
content-length: ['356']
content-type: [application/json]
date: ['Fri, 02 Jan 2015 17:03:44 GMT']
server: [am/2]
set-cookie: [_session_auth_key="iuo9QNN6oaPVa3gj9XyKTHZtsxE="]
vary: [Origin]
status: {code: 200, message: OK}
version: 1
Mopidy-SoundCloud-2.0.2/tests/fixtures/sc-resolve-http.yaml 0000664 0001750 0001750 00000007211 12505250302 024170 0 ustar jodal jodal 0000000 0000000 interactions:
- request:
body: null
headers:
Accept: ['*/*']
Accept-Encoding: ['gzip, deflate']
Authorization: [!!python/unicode 'OAuth 1-35204-61921957-55796ebef403996']
Connection: [keep-alive]
User-Agent: [python-requests/2.4.3 CPython/2.7.8 Linux/3.16.0-28-generic]
method: GET
uri: https://api.soundcloud.com:443/resolve.json?url=https://soundcloud.com/bbc-radio-4/m-w-cloud&client_id=93e33e327fd8a9b77becd179652272e2
response:
body: {string: !!python/unicode '{"status":"302 - Found","location":"https://api.soundcloud.com/tracks/122889665.json?client_id=93e33e327fd8a9b77becd179652272e2"}'}
headers:
access-control-allow-headers: ['Accept, Authorization, Content-Type, Origin']
access-control-allow-methods: ['GET, PUT, POST, DELETE']
access-control-allow-origin: ['*']
access-control-expose-headers: [Date]
cache-control: [no-cache]
content-length: ['129']
content-type: [application/json; charset=utf-8]
date: ['Fri, 02 Jan 2015 17:02:10 GMT']
location: ['https://api.soundcloud.com/tracks/122889665.json?client_id=93e33e327fd8a9b77becd179652272e2']
server: [am/2]
status: {code: 302, message: Found}
- request:
body: null
headers:
Accept: ['*/*']
Accept-Encoding: ['gzip, deflate']
Authorization: [!!python/unicode 'OAuth 1-35204-61921957-55796ebef403996']
Connection: [keep-alive]
User-Agent: [python-requests/2.4.3 CPython/2.7.8 Linux/3.16.0-28-generic]
method: GET
uri: https://api.soundcloud.com:443/tracks/122889665.json?client_id=93e33e327fd8a9b77becd179652272e2
response:
body:
string: !!binary |
H4sIAAAAAAAEA5VVyY7bRhD9lQYD+GJx1cbhzR44yWGCII4NH4kmWSLbanYzvYjmGAb8Dznl9/wl
qeYicxTDSXQSS6+Kr17VK330zkxUXuYZRcuzt/EYPsRJkqZ3h8N+45UKqIEqpwYxSRRvwzgJo4TE
h2yXZPuIPI/wg3lWg8pdcro/pNtjsvEqq6hhUnjZfn+4S7GWbFsQhhYcvMwoCxtPG6yOlU9MMN1A
hYWkYjUTlOelFAbhuWaPCLlL410cY1lOtclbWbETQ/y3SMVXUrqhiokaUZ0tOCuxvKF1zpl27TBJ
FK0I0oJqIC0zZQOcEyoq0kNREC0tfkUIkzvM7EC1lDNxxtTW7/2SS+sIa4MateuuoC2gqlwkLwZE
U84RV8lecEnHuJedKNcoQGdV2VANuVXcy4Tl3HVYAB+1nJ6vGMOMk26K1iCUk871NMW9Nw015Jd1
H+9cH7+PfXz5/Cd50wC5n2lXoEvFumlCmMo0KZgB38l9FcUFlbR1Y4iRZJCWFAN5+fKevHaqkF1A
fpSKtFIB4RRhmnB2BmIawOYIEyW3FZAf7q1i0urd/SQ1EyiyRJAipgcws+RfPv+F8mtA0SkR0JOS
s869VJGeDsEDTg2E4zG+76Rkiw19p2OsDxlpjOmyMOz7PiiKMihlYM9hp2StKK6jDosoOvI/VIM6
TsIL2s66KuCAs5lFdgbJzdDNz2cYcDVrQY1d5sC0KifwhVUg1zMtunYZ3Fw1H4Cq21iLO9/cBiuK
SzQN/WqOk8RldEvc04tjzkoQI1PcNR8d1BjtKxyCuoymQv0R66TQqAXtWDAu97jCKEkbjvbXaO7Z
+VjTOdrLPo4X4Wrq+VqMvz21BIrrj17xnVkcYBZytS7k2bI45NUHMyr+DzPvwugYRjsS77Jkm+0P
VzP/aw/unTqcqWLxq2GnQYztY/c3nT/lTS/UULVKcHqxONCiKisxSjVBtO8O32EbR9tjlPo2bUE9
+pyqGoL3Xe19mk9ix+lQuM0p0YU4sHiOn+gFh+mO33IJlvOyevl32YbrI0SV6aU6r3K/QXzCzMx3
yf4Q7VP/WG63A1sx37idArdgN9X6JzJ0v721H35++OntJX3M26DDM7vcwpu8/7Bu4XRE3dBu9ErQ
nl9v56JitPFmAfG86yWaYHj+k1lCGKHG0LJx/zwaif1PH4SrbMdOotPcSX/x8PDrO+/T35IvxQU/
BwAA
headers:
access-control-allow-headers: ['Accept, Authorization, Content-Type, Origin']
access-control-allow-methods: ['GET, PUT, POST, DELETE']
access-control-allow-origin: ['*']
access-control-expose-headers: [Date]
cache-control: ['private, max-age=0, must-revalidate']
content-encoding: [gzip]
content-length: ['915']
content-type: [application/json; charset=utf-8]
date: ['Fri, 02 Jan 2015 17:02:10 GMT']
etag: ['"68d7df5086119d220bcd3a5e10b2d195"']
server: [am/2]
set-cookie: [_session_auth_key="iuo9QNN6oaPVa3gj9XyKTHZtsxE="]
status: {code: 200, message: OK}
version: 1
Mopidy-SoundCloud-2.0.2/tests/fixtures/sc-groups.yaml 0000664 0001750 0001750 00000002110 12505250302 023044 0 ustar jodal jodal 0000000 0000000 interactions:
- request:
body: null
headers:
Accept: ['*/*']
Accept-Encoding: ['gzip, deflate']
Authorization: [!!python/unicode 'OAuth 1-35204-61921957-55796ebef403996']
Connection: [keep-alive]
User-Agent: [python-requests/2.4.3 CPython/2.7.8 Linux/3.16.0-28-generic]
method: GET
uri: https://api.soundcloud.com:443/me/groups.json?client_id=93e33e327fd8a9b77becd179652272e2
response:
body:
string: !!binary |
H4sIAAAAAAAEA4uOBQApu0wNAgAAAA==
headers:
access-control-allow-headers: ['Accept, Authorization, Content-Type, Origin']
access-control-allow-methods: ['GET, PUT, POST, DELETE']
access-control-allow-origin: ['*']
access-control-expose-headers: [Date]
cache-control: ['private, max-age=0, must-revalidate']
content-encoding: [gzip]
content-length: ['22']
content-type: [application/json; charset=utf-8]
date: ['Fri, 02 Jan 2015 17:03:54 GMT']
etag: ['"d751713988987e9331980363e24189ce"']
server: [am/2]
status: {code: 200, message: OK}
version: 1
Mopidy-SoundCloud-2.0.2/tests/fixtures/sc-tracks.yaml 0000664 0001750 0001750 00000077672 12505250302 023045 0 ustar jodal jodal 0000000 0000000 interactions:
- request:
body: null
headers:
Accept: ['*/*']
Accept-Encoding: ['gzip, deflate']
Authorization: [!!python/unicode 'OAuth 1-35204-61921957-55796ebef403996']
Connection: [keep-alive]
User-Agent: [python-requests/2.4.3 CPython/2.7.8 Linux/3.16.0-28-generic]
method: GET
uri: https://api.soundcloud.com:443/groups/136/tracks.json?client_id=93e33e327fd8a9b77becd179652272e2
response:
body:
string: !!binary |
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
YgGWU+bADWUBAA==
headers:
access-control-allow-headers: ['Accept, Authorization, Content-Type, Origin']
access-control-allow-methods: ['GET, PUT, POST, DELETE']
access-control-allow-origin: ['*']
access-control-expose-headers: [Date]
cache-control: ['private, max-age=0, must-revalidate']
content-encoding: [gzip]
content-type: [application/json; charset=utf-8]
date: ['Fri, 02 Jan 2015 17:03:53 GMT']
etag: ['"83d7b92b9c74c43dde8ff4178609b297"']
server: [am/2]
set-cookie: [_session_auth_key="iuo9QNN6oaPVa3gj9XyKTHZtsxE="]
transfer-encoding: [chunked]
status: {code: 200, message: OK}
version: 1
Mopidy-SoundCloud-2.0.2/tests/fixtures/sc-sets.yaml 0000664 0001750 0001750 00000002126 12505250302 022512 0 ustar jodal jodal 0000000 0000000 interactions:
- request:
body: null
headers:
Accept: ['*/*']
Accept-Encoding: ['gzip, deflate']
Authorization: [!!python/unicode 'OAuth 1-35204-61921957-55796ebef403996']
Connection: [keep-alive]
User-Agent: [python-requests/2.4.3 CPython/2.7.8 Linux/3.16.0-28-generic]
method: GET
uri: https://api.soundcloud.com:443/me/playlists.json?limit=1000&client_id=93e33e327fd8a9b77becd179652272e2
response:
body:
string: !!binary |
H4sIAAAAAAAEA4uOBQApu0wNAgAAAA==
headers:
access-control-allow-headers: ['Accept, Authorization, Content-Type, Origin']
access-control-allow-methods: ['GET, PUT, POST, DELETE']
access-control-allow-origin: ['*']
access-control-expose-headers: [Date]
cache-control: ['private, max-age=0, must-revalidate']
content-encoding: [gzip]
content-length: ['22']
content-type: [application/json; charset=utf-8]
date: ['Fri, 02 Jan 2015 17:03:54 GMT']
etag: ['"d751713988987e9331980363e24189ce"']
server: [am/2]
status: {code: 200, message: OK}
version: 1
Mopidy-SoundCloud-2.0.2/tests/fixtures/sc-login.yaml 0000664 0001750 0001750 00000003221 12505250302 022641 0 ustar jodal jodal 0000000 0000000 interactions:
- request:
body: null
headers:
Accept: ['*/*']
Accept-Encoding: ['gzip, deflate']
Authorization: [!!python/unicode 'OAuth 1-35204-61921957-55796ebef403996']
Connection: [keep-alive]
User-Agent: [python-requests/2.4.3 CPython/2.7.8 Linux/3.16.0-28-generic]
method: GET
uri: https://api.soundcloud.com:443/me.json?client_id=93e33e327fd8a9b77becd179652272e2
response:
body:
string: !!binary |
H4sIAAAAAAAEA21SW27jMAy8SqHfDWKr2W63PkAvUSwERqKzRPRwRamFUfTuSztO1knrH0NDcmb4
+FDkVPdLP93rp4fHjTpSlLeqjFlt1IA5gKd4FCikgdxYkMsSnH4RAn4X88DFhOSoJ5z47lu9a3Tb
6N2d1t3PXbd7uPvRyiciNZNk/C1l4K5pYKAtpxqd9am6rU2hmYS4OXtc2zI1+6VWSm/KvhiGNyiQ
VzWznt6yiLk4S1GAA3LjsIfqi1kqPOQDbod4EG0r3koeVRer9xvVU5ZOT2M4IXPra6CXxCVDCYFD
tpmGQimeSSyVC6EjtunAV5Rh5AEsXmHvuGcqMvyT6vI0hYq/gCnK7uTVg2fcqJLBHs3cgOpa2a6H
0ZPYv4LElXrOiNOc696TNT28pSxavErsk/fpXfbyBaMo7v8zct1f+mXVvfyRhQ8+gTOMNkXHxmNf
VKfb3614eq2pgOo+VBXvQUSdWdKXyNLLDYeciJyZ1N/gK+5P6SeTnACaeRBrk+fAeSI3sQB5NBiA
vDQWZeVhUiu54uc/Eg4T/0ADAAA=
headers:
access-control-allow-headers: ['Accept, Authorization, Content-Type, Origin']
access-control-allow-methods: ['GET, PUT, POST, DELETE']
access-control-allow-origin: ['*']
access-control-expose-headers: [Date]
cache-control: ['private, max-age=0, must-revalidate']
content-encoding: [gzip]
content-length: ['419']
content-type: [application/json; charset=utf-8]
date: ['Fri, 02 Jan 2015 16:59:22 GMT']
etag: ['"60fdab535961251305a18c8122b7f406"']
server: [am/2]
status: {code: 200, message: OK}
version: 1
Mopidy-SoundCloud-2.0.2/tests/fixtures/sc-resolve-track-id.yaml 0000664 0001750 0001750 00000007401 12505250302 024710 0 ustar jodal jodal 0000000 0000000 interactions:
- request:
body: null
headers:
Accept: ['*/*']
Accept-Encoding: ['gzip, deflate']
Authorization: [!!python/unicode 'OAuth 1-35204-61921957-55796ebef403996']
Connection: [keep-alive]
User-Agent: [python-requests/2.4.3 CPython/2.7.8 Linux/3.16.0-28-generic]
method: GET
uri: https://api.soundcloud.com:443/tracks/38720262.json?client_id=93e33e327fd8a9b77becd179652272e2
response:
body:
string: !!binary |
H4sIAAAAAAAEA5VUTW+cMBD9K8jXLAsGlgRuqdSeovZSqUc0gBecNTa1DatNlP/eMR9bslUUdbUX
DzPPb9688Ss5cVmTnFgN1YnsCMdD/HAfhVEa7UilGVhWF2AxJQppFIRxEB68MMwTmkeJdxfiD8sG
w3ThapM4PBwedqQeNFiuJKIdkjShiKW6jkkLpWAkt3pgO2IsoiPykUtuWlYjkNK84RJEUSlpMb0w
/AVTaELTJEmzHRFgbNGpmh85FkysDkFI8e9YxWlOsysr04LmssGsfigFrxDfQlMIblw/eOqZ7kBw
ecJjOWgOwj+qQfuWWV+qETDFWNSg27JmXcnq2kWK8oKFIATm1eoshYIpTvIjCIMN9oOuWjCsGLQg
uRyEcA2UTExazedrjuXWSTNHGya1k8ZRnuPky0TQu/O+IUXvJ7Oe732fSdbMVJr3s+CuZr5EQrdg
aCYY8lgA3awLe+mX84ldUOZGgh3WO7nR1Zw88pqpLf+y71aSC2pxYaBvYx3Or70N1oCCzQ1eB31U
OAM3j66PHXNeMTkxRV19dENrja8Z+mucDIIiYG5rbW/yIICe740aZF0JNdR79FgwOdkEq4kR0pmT
5K+Tt1d/LrafPr33wWqApXDRcNV80vYfByYBRQemHg3zKMtpeHXgp2wdARPMrBD7ashZ8alPbPOm
xQ1FGMGC3mQ7VTjdG5SklpMgc4rx3arS5CGLszT2M9r0h8gXoBu2f+4b8rYscS/gUjp/VCgrjgU3
d1ruI4w4Mreuq7fX3dlc/jHV4IP1Am3PSp+2BjvDyJwpNriuqfO7pn6/nKLh/vnxa8zHotv3uOXr
rt7UfW6RYN5xJ/9t89khvc/Cv9u9qhLTZEcWSfCFMWs8CbMMvyxv3RqlaYwYYC1UrXsDDVL8PxcH
m2LHU+GauLfn8enpxy/y9gcvlQQkxwUAAA==
headers:
access-control-allow-headers: ['Accept, Authorization, Content-Type, Origin']
access-control-allow-methods: ['GET, PUT, POST, DELETE']
access-control-allow-origin: ['*']
access-control-expose-headers: [Date]
cache-control: ['private, max-age=0, must-revalidate']
content-encoding: [gzip]
content-length: ['709']
content-type: [application/json; charset=utf-8]
date: ['Fri, 02 Jan 2015 17:03:55 GMT']
etag: ['"6152060d44f2e1ca16175b22acea932d"']
server: [am/2]
set-cookie: [_session_auth_key="iuo9QNN6oaPVa3gj9XyKTHZtsxE="]
status: {code: 200, message: OK}
- request:
body: null
headers:
Accept: ['*/*']
Accept-Encoding: ['gzip, deflate']
Authorization: [!!python/unicode 'OAuth 1-35204-61921957-55796ebef403996']
Connection: [keep-alive]
Cookie: [_session_auth_key="iuo9QNN6oaPVa3gj9XyKTHZtsxE="]
User-Agent: [python-requests/2.4.3 CPython/2.7.8 Linux/3.16.0-28-generic]
method: HEAD
uri: https://api.soundcloud.com:443/tracks/38720262/stream?client_id=93e33e327fd8a9b77becd179652272e2
response:
body: {string: !!python/unicode ''}
headers:
access-control-allow-headers: ['Accept, Authorization, Content-Type, Origin']
access-control-allow-methods: ['GET, PUT, POST, DELETE']
access-control-allow-origin: ['*']
access-control-expose-headers: [Date]
cache-control: [no-cache]
content-length: ['0']
content-type: [application/json; charset=utf-8]
date: ['Fri, 02 Jan 2015 17:03:55 GMT']
location: ['https://cf-media.sndcdn.com/qzk2u7jAE3iv.128.mp3?Policy=eyJTdGF0ZW1lbnQiOlt7IlJlc291cmNlIjoiKjovL2NmLW1lZGlhLnNuZGNkbi5jb20vcXprMnU3akFFM2l2LjEyOC5tcDMiLCJDb25kaXRpb24iOnsiRGF0ZUxlc3NUaGFuIjp7IkFXUzpFcG9jaFRpbWUiOjE0MjAyMTg3MTB9fX1dfQ__&Signature=EKZT2x0hOy6g3Nn~fWvm-WOK0YJ27lFpWeU0PLvd5NEmEb8WbbSd5wyNbDN6XTYGN8TRdHUwj3jU8eQaZesNnrqE6f1HWNBA-e9-tM5bx9lrHTmJ3dQEHHRLulHt0emspkGQBRm25IhH7RMMLBM3UhB-McS~oH9rEizlpHDDAXIrNcwxGY0KG4R35auaJD-trqA3TKK3pm9ayQ-lSdgXrVjmum5TxL-15F72njAZ5WcmHKv7zSrM8-yD5HowS3n3nAQgc89mUSB834GxcIAciXg415ZF9gaHw8vgqmLvZHQVtIwVJRO73hvlh0hd4zrwh10INYnTROi3jbSkYjHw3A__&Key-Pair-Id=APKAJAGZ7VMH2PFPW6UQ']
server: [am/2]
status: {code: 302, message: Found}
version: 1
Mopidy-SoundCloud-2.0.2/tests/fixtures/sc-login-error.yaml 0000664 0001750 0001750 00000002060 12505250302 023770 0 ustar jodal jodal 0000000 0000000 interactions:
- request:
body: null
headers:
Accept: ['*/*']
Accept-Encoding: ['gzip, deflate']
Authorization: [!!python/unicode 'OAuth 1-fake-token']
Connection: [keep-alive]
User-Agent: [python-requests/2.4.3 CPython/2.7.8 Linux/3.16.0-28-generic]
method: GET
uri: https://api.soundcloud.com:443/me.json?client_id=93e33e327fd8a9b77becd179652272e2
response:
body:
string: !!binary |
H4sIAAAAAAAAAFIAAAAA//8DAEXPbOkBAAAA
headers:
access-control-allow-headers: ['Accept, Authorization, Content-Type, Origin']
access-control-allow-methods: ['GET, PUT, POST, DELETE']
access-control-allow-origin: ['*']
access-control-expose-headers: [Date]
cache-control: [no-cache]
content-encoding: [gzip]
content-length: ['27']
content-type: [text/html; charset=utf-8]
date: ['Fri, 02 Jan 2015 16:59:28 GMT']
server: [am/2]
www-authenticate: ['OAuth realm="SoundCloud", error="invalid_token"']
status: {code: 401, message: Unauthorized}
version: 1
Mopidy-SoundCloud-2.0.2/tests/fixtures/sc-popular.yaml 0000664 0001750 0001750 00000103361 12505250302 023221 0 ustar jodal jodal 0000000 0000000 interactions:
- request:
body: null
headers:
Accept: ['*/*']
Accept-Encoding: ['gzip, deflate']
Authorization: [!!python/unicode 'OAuth 1-35204-61921957-55796ebef403996']
Connection: [keep-alive]
User-Agent: [python-requests/2.4.3 CPython/2.7.8 Linux/3.16.0-28-generic]
method: GET
uri: https://api-v2.soundcloud.com:443/explore/categories?client_id=93e33e327fd8a9b77becd179652272e2
response:
body:
string: !!binary |
H4sIAAAAAAAAAExRwU7DMAz9l0qcOi4cOHBjK2gghqp1Ege0Q5ZaxVpqV07CtCH+nTjdOk5+fkme
n19+ChNb5OLhs6h5iM5I+ZiJWZHrjnnvUzOPHgm8wgX30B4TeKIAEgxSDxRS/wZGCKlL8B0Ovry5
uy9rdhjQ6rs1OOyQKfPNgIIhmnSqUo1FIAuKBpag15vAcgzg3Ki4AftF7Lg7FttZ0Sc79r/pVSaS
aZc8kQn4DeWa7V6pfoejwYUzPl0zLm8RKYjOrsw4uQIYyiVHnxv0VkOoJPbZ8Nzk5au48wEG3d6B
DcJXRNnAMzsdujTSWhYoR9/KoKrry8uMF2pxcvlqTifNMFmnVFcQss0VEvbGXWVqNGPNUrVwJ+lb
dN2LbB1JBdfJ9VyrGXL4XWf09DyuSamC3DZM3SF9BIhyHN056UlsmruRc0wJDLlM+3ywuLbY/v4B
AAD//wMAP61+EEwCAAA=
headers:
cache-control: ['private, max-age=0']
content-encoding: [gzip]
content-length: ['356']
content-type: [application/json]
date: ['Fri, 02 Jan 2015 17:19:37 GMT']
server: [am/2]
set-cookie: [_session_auth_key="iuo9QNN6oaPVa3gj9XyKTHZtsxE="]
vary: [Origin]
status: {code: 200, message: OK}
- request:
body: null
headers:
Accept: ['*/*']
Accept-Encoding: ['gzip, deflate']
Authorization: [!!python/unicode 'OAuth 1-35204-61921957-55796ebef403996']
Connection: [keep-alive]
Cookie: [_session_auth_key="iuo9QNN6oaPVa3gj9XyKTHZtsxE="]
User-Agent: [python-requests/2.4.3 CPython/2.7.8 Linux/3.16.0-28-generic]
method: GET
uri: https://api-v2.soundcloud.com:443/explore/Alternative+Rock?limit=10&offset=0&linked_partitioning=1&client_id=93e33e327fd8a9b77becd179652272e2
response:
body:
string: !!binary |
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
AP//AwC7AEBWjlwAAA==
headers:
cache-control: ['public, max-age=60']
content-encoding: [gzip]
content-length: ['5599']
content-type: [application/json]
date: ['Fri, 02 Jan 2015 17:19:38 GMT']
server: [am/2]
vary: [Origin]
status: {code: 200, message: OK}
- request:
body: null
headers:
Accept: ['*/*']
Accept-Encoding: ['gzip, deflate']
Authorization: [!!python/unicode 'OAuth 1-35204-61921957-55796ebef403996']
Connection: [keep-alive]
User-Agent: [python-requests/2.4.3 CPython/2.7.8 Linux/3.16.0-28-generic]
method: GET
uri: https://api.soundcloud.com:443/tracks/170703457.json?client_id=93e33e327fd8a9b77becd179652272e2
response:
body:
string: !!binary |
H4sIAAAAAAAEA51V247bNhD9FYJPLWpZki+SV28G0ssC202QbtGXAMJYGllcU6RKUnadIP/eoW5w
jBZtsvDa1nBmPOfMnOEnfhKq5Bl3BooTX3BBD3EapdF6s00XvDAIDsscHPmsongTxlEYbVm0y6Ik
2yTsh4j+KK6zaHIfvNmk8Xa9W/CyM+CEVjxbJclDGlEy3TSoHBwk8syZDhfcOkpPqSuhhK2xpEza
iKNQIPNCK0fuuRUfySWOkjTZrakmCdbljS5FJShgKmsVrlYsfqDis006l2VrMEIdyavtDlIUlN/B
MZfCekD01KJpQAp1okf/IVTQgjkFVBDIoAH7Z4cGSgzgLMBpYwODjfiLIq0jbppbMNgcsCy9JT9c
KR9ISX6lviipobdPsNvOFDVYzDsjeaY6KT2sA8qewuF59nHCecIG6xGV8YTtpUOjiOAzsve6b93o
x596GOwdwWAB+8kDYb/OQNh3+xEJe++RfO9LRFsY0Q7d4i+1sIxerdEHAnNlzZVVcKa+OGRP75jV
6rigd/bIftm/YU6znhIm3JJlbz6oD+qlxslmGTAiRJVMV8y6q0TLLsLVTBMDSBxScUS4swv2LBSy
R1XU7BmEtAF67hmNZ0dugvys7lRJjkDZ+n/pAkPYWYOSxgHtkj06dgH/m1WnPIBXLByrtGG2M7gk
qAPLCpqZUYMSqRUTwb0QcndtZ8sJrzSDRyKbckxuwppi+n4WJerbVh7aZjobs+dXBHNva2jA63tj
CTQ6Q69nJRCApldg0649BlGg8hX7CQtILrXzY0kKPPcK6oygs9q51mZhCK1Y9swVUnflkkQY9hht
OOuccnr98uxTr/9ZwuNu6M++lMqkBsrWaiv85Ngxy8At/803621FozDNW8/+P0s32viNsk6z7WaW
7n+i8GXZcCyWks9SHlrRE0D477D/S+VwBgfmJtJTJ+KlVWVRqp61wcUGfuOtd6vkgbZkkH48R7tD
IMEccfnaHvnncRe2Eq4H2qm0xjpFyyYe7ZOSeFaBtLQCv73s8OsWFqnsos3pdlAvcEY/XHe4L1/g
fv39cV9UL+mP9fPPebNsaZ1O2+8u7n+MWjisTcow7cVvyDGF+qbf0byJknjnb59x6070p9tkRS0Y
2acrwU4nD9vIX3TD3TQZV/FqwcE5KGp/ZVkq8is1Fd5E+zo1qdZfCvunp7d/8M9/A1D87Dh5BwAA
headers:
access-control-allow-headers: ['Accept, Authorization, Content-Type, Origin']
access-control-allow-methods: ['GET, PUT, POST, DELETE']
access-control-allow-origin: ['*']
access-control-expose-headers: [Date]
cache-control: ['private, max-age=0, must-revalidate']
content-encoding: [gzip]
content-length: ['912']
content-type: [application/json; charset=utf-8]
date: ['Fri, 02 Jan 2015 17:19:38 GMT']
etag: ['"7c52b4eaedea7f96c5ad520d99716a45"']
server: [am/2]
set-cookie: [_session_auth_key="iuo9QNN6oaPVa3gj9XyKTHZtsxE="]
status: {code: 200, message: OK}
- request:
body: null
headers:
Accept: ['*/*']
Accept-Encoding: ['gzip, deflate']
Authorization: [!!python/unicode 'OAuth 1-35204-61921957-55796ebef403996']
Connection: [keep-alive]
User-Agent: [python-requests/2.4.3 CPython/2.7.8 Linux/3.16.0-28-generic]
method: GET
uri: https://api.soundcloud.com:443/tracks/170437996.json?client_id=93e33e327fd8a9b77becd179652272e2
response:
body:
string: !!binary |
H4sIAAAAAAAEA5VVXa+bOBD9K8ivGwImEBLebnelqlKrrlaVrrq6EhrAATfGdm2TKK3633cMIeVm
+xndh9zx8fHMmTOTz+TIZUMK4gzUR7IiHP+heZxu8v1+uyK1YeBYU4JDTBLTNKJxFG+COC+ybRHn
wR8xfvDeYJkp/eV0s8G/Dd5tBgOOK0mKJE2yNEE21fdMOqgEI4UzA1sR65AfuQ9cctuxBqmU4S2X
IMpaSYfw0vJPCEl3WRJv6YoIsK7sVcMPHPFjWlkU0yhOApoVWVpkyS0t24HhskWUHirBa6R30JaC
W1/QE3kQjhmJaZ5Y8I+qj08keCEGZgMNAmoWPBEenEG64NyBCy5qCFrlEFShLJIfg4o7JAhOjEuL
4Dk8yoacTwQf1Mz0ILg84os89GyhZwuRLUS2kCLGOhS6XwrD+oo1jY+U1QVvghCIa9RZCgVjnBQH
EBY11IOpO7CsHIxAZOectkUUcTdIZtegtWBrlD5qqwhENfSRUNaFXIauY6HkbedCpiPe7PZ5QndJ
7hMSUDExNlQOQizecNz57pG/rpkgtmXS+NBCTIzOwFfBoxfw0Qv4HgV8qZwvhNnacD35w6s0PSih
90wvrvL+6e2HOuK5YYJhjaSY8hn9WrqLvkWO7IJOabGZg89mgnFr6vn7iTdMTRpNh5Xu57Mre3lh
YO5jPdqwuw82gE2ZaG5+PShstPdVrze+Il4z6TP2vQvR1J2zoWE4KKfR54PheDZ3CzRfWzXIphZq
aMZ+jTXa6DaOyOnHjBSfxzH9OmnXGR4Pn/ttsnEFOOPT5avAf4/2LsvS5/n/ecIxTyK6DWhabJIi
2d3m6ac5+xxsNKeG7Df3L9yJ5rwr9VmecAIHZoEf3UzXFsVp5CjNBLEhLh8aZ9uM0j0NWx33DEIB
pmXrD7olX657SQu4VLjfcKMMEhuES2TcVwc4YfP8/pknaR7VxeM/Sjb63jyDcWdljguab9QwYcYi
4v0mjXOa5KGozEfDFkWsyBlOzHvrju38TJF3h3/7ff32w54+vCn7tcalN6+Vu3u/4LRo2ke+fffS
xVm+i78uollSDF3VxHVr52iy3WW3tT8HM5QfnIO68z8GFrP7zTmIFrd9igonza/Ih9ev3z6SL/8B
eQBtr9QGAAA=
headers:
access-control-allow-headers: ['Accept, Authorization, Content-Type, Origin']
access-control-allow-methods: ['GET, PUT, POST, DELETE']
access-control-allow-origin: ['*']
access-control-expose-headers: [Date]
cache-control: ['private, max-age=0, must-revalidate']
content-encoding: [gzip]
content-length: ['863']
content-type: [application/json; charset=utf-8]
date: ['Fri, 02 Jan 2015 17:19:38 GMT']
etag: ['"1da67a79619ce66a4b2cb813b646db5c"']
server: [am/2]
set-cookie: [_session_auth_key="iuo9QNN6oaPVa3gj9XyKTHZtsxE="]
status: {code: 200, message: OK}
- request:
body: null
headers:
Accept: ['*/*']
Accept-Encoding: ['gzip, deflate']
Authorization: [!!python/unicode 'OAuth 1-35204-61921957-55796ebef403996']
Connection: [keep-alive]
User-Agent: [python-requests/2.4.3 CPython/2.7.8 Linux/3.16.0-28-generic]
method: GET
uri: https://api.soundcloud.com:443/tracks/172570926.json?client_id=93e33e327fd8a9b77becd179652272e2
response:
body:
string: !!binary |
H4sIAAAAAAAEA5VV247TMBD9lcivtM1l02STt2pBCIn7cpHQSpGTTBNTxw6206is+HfGuW23gIA+
tePj8ZkzZ6b35MBESVJiFC0OZEUY/vDjYBt7SRCtSKGAGigzahATeH7o+p7rx44fpl6SbrfOEw8/
eK/ToDJ7OQi9rRfFK1J2ihomBYaCMNjaZLJpQBiacyCpUR2siDaYHlPvmWC6hhIzScUqJijPCikM
wjPNviPkKomSOIqCFeFUm6yRJdszvDDTCtwgcPwk9a9TP1lo6ZoqJipEtV3OWYH5Da0yzrQt6I7s
uAElkOcRnPeyONwRRLSgGsqZOCCkQJp8XTBzWgd4pA0K0pxXAE0OZWkjWX7CC5RzxJWyF1zSIU7S
PeUai207VdRUQ9YpTlLRcW6LyYEPwo2/F4xhxso0RisQysp0RtdWMkLIjeXo3CBH+zLoQrF2VJ68
USUox9Tg3Dx1PzFx4u5TlNdQ7jg1KEid2pg2dd1WbrRxd13VacME6FsjFdwJe6rxuO/7zZ4WkEt5
2GAf3R4oKrHAH5CmZwY1HUCfYafgIecIwmxMYN8rRZs/p7JvPn5io6koc1rV34ZbWOqonaDNopMC
DijwLNtg6syc2iVygBP6qcKOd1bQUV2mVTF/P7IS5HmD8raZz6bs2QmJXcYaNGt9GSwpOmJ8Y3H1
XqK5rPl6erQ1sAKEZWyNs0br10avFeA0HYdp6BTDs7kNtGUbLTtRFlx25aDDUKPGmZxmFnPaWSTp
/TDLyzhOcz6cPbb4hc5TglFWsvv4/OPthxevn90Ogv9u8jzXu7aT512ngbdM3l+JWybanfhh8mXq
RvWHmtEFF+X+SpYeqaHq7JL1K/M3GlUqxaDRCNFru6uiILyOkjBZQ3K84uGaU1XB5mtbkR/TFms5
PeW4DXEBdQI75U/xPT1iF+26mud53hNnj/+VsXuxUagyvVSHsxS/4T9ixgKSMIi9MI7XQWuotz0r
YGU9BdZgF9n6R2rcvuP1t1fNl9cf3n7Nmk2L63FebBf3/sFu7rgRbf8uZIu3V/72YRPOanorMgmJ
e1nPUT+KUObpD2IOhlcrQo2hRW3/NjSS+89ZcM9uW4YSp83u6N3Ll28+kx8/AShGE5b9BgAA
headers:
access-control-allow-headers: ['Accept, Authorization, Content-Type, Origin']
access-control-allow-methods: ['GET, PUT, POST, DELETE']
access-control-allow-origin: ['*']
access-control-expose-headers: [Date]
cache-control: ['private, max-age=0, must-revalidate']
content-encoding: [gzip]
content-length: ['852']
content-type: [application/json; charset=utf-8]
date: ['Fri, 02 Jan 2015 17:19:38 GMT']
etag: ['"afae57b7f16e6013976ff05ce8d23fa4"']
server: [am/2]
set-cookie: [_session_auth_key="iuo9QNN6oaPVa3gj9XyKTHZtsxE="]
status: {code: 200, message: OK}
- request:
body: null
headers:
Accept: ['*/*']
Accept-Encoding: ['gzip, deflate']
Authorization: [!!python/unicode 'OAuth 1-35204-61921957-55796ebef403996']
Connection: [keep-alive]
User-Agent: [python-requests/2.4.3 CPython/2.7.8 Linux/3.16.0-28-generic]
method: GET
uri: https://api.soundcloud.com:443/tracks/173263597.json?client_id=93e33e327fd8a9b77becd179652272e2
response:
body:
string: !!binary |
H4sIAAAAAAAEA5VU2W7bMBD8FYGvta3bh95SIE2LHi6StEWBAAIlrWVWFCmQlF0nyL93qcNxnKKH
4Qeb3F3Ozs7sA6mYKEhCjKJ5RSaE4R9/EQbzMF4tJiRXQA0UKTUYE3h+5PqeGwSOFyTxPAmXzisP
P5jXalBpn+zFK0yekKJV1DApSBIEkRd4WE3WNQhDMw4kMaqFCdEG62PtDRNMb6HAUlKxkgnK01wK
g+GpZvcYEkVLf+7PJ4RTbdJaFmzDML6DFbuej18nQFhxEgdHWHpLFRMlRjVtxlmO5Q0tU860beiO
3L69dD6vb97dvlt/urkjzh254AaUQOA7cK5lXt0RzGlA1ZQzUWFSBgJ2koPIYaqYNBrvtUGe6tO+
oM6gKOxJmh0wi3KOcYXcCy5pd06SDeUaKWhalW+phrRVHCO3xjQ6cV1mWgF6RpuGwwyZc2nrUp61
tfsCwVRjjxxcVqzCcB5HkR/hY5xmwLuZiJbzk3cMM3YA/WkJQtkBXK2/Y85wRV4/NelcD00WoHPF
mn6klpX+AUFrm/9R6oOjIJeqQDCWFAUcsK3xoU5hqTk0x5MKDjjbEsluLYQeD9MqH3/vWAGyp6W/
zJp6vBuqpwegChWG0jy+iOIQZos69p+OCopTQEEfxbWROFIrgroJbS8sB2HB2knhXMut0VMFqOpd
J8pWMbwbZ0MbNtOyFUXOZVt00+na0+7RO1jTeoIkD4OnRlsMhusunyurpKKUm4NszdbyZyMGcq8u
Pl056zfO9/WX27c3Fu5LD6A10QOhtWa4SoLo6IG/QrcPWeQ9Qqx+lPuJJFGRZx2fwaU7aqg6yehE
7M80slSIXsFdiJ7iyvB9bxl584U3XWhRqfspp6qE2Y+mJI/DNmk4PWS4lXAPtAInhdO0SNMN3eEU
7dYYDTS68+TxP8N9aSHsmiqzl6o6KfKbDvqYrgVvhRsp9oL59FDt5b46aWFC9nQHVmJn1fbP+Hi/
WlyKLz+X3levTutZg4tq3CVnef8gOLdfQnZ858TFcRCHT9tnJBQ38sCldex4ilbCJTus6vEwXiI/
xtB8axe4RnT/aQf3JNtClGg4uxcvPnxYfyOPvwAWJ2mziAYAAA==
headers:
access-control-allow-headers: ['Accept, Authorization, Content-Type, Origin']
access-control-allow-methods: ['GET, PUT, POST, DELETE']
access-control-allow-origin: ['*']
access-control-expose-headers: [Date]
cache-control: ['private, max-age=0, must-revalidate']
content-encoding: [gzip]
content-length: ['835']
content-type: [application/json; charset=utf-8]
date: ['Fri, 02 Jan 2015 17:19:38 GMT']
etag: ['"f9f43353692c61f586759a81c789f79f"']
server: [am/2]
set-cookie: [_session_auth_key="iuo9QNN6oaPVa3gj9XyKTHZtsxE="]
status: {code: 200, message: OK}
- request:
body: null
headers:
Accept: ['*/*']
Accept-Encoding: ['gzip, deflate']
Authorization: [!!python/unicode 'OAuth 1-35204-61921957-55796ebef403996']
Connection: [keep-alive]
User-Agent: [python-requests/2.4.3 CPython/2.7.8 Linux/3.16.0-28-generic]
method: GET
uri: https://api.soundcloud.com:443/tracks/157945262.json?client_id=93e33e327fd8a9b77becd179652272e2
response:
body:
string: !!binary |
H4sIAAAAAAAEA5VV247bNhD9FZYveahlXSxZa71t0wBpsWgX3aB9CSDQ0thiTZEqL1a9Qf69Q9Hy
ep0gbRcwsBzODM+cmTP6RA9ctrSiVrPmQBeU4yEtyk1eZOtsQRsNzEJbM4s+WZLmcVLGyYakabVK
q/yOfJ/gH8Y5A7qegpPV3SrPVumCtk4zy5XEjGV+lySYTvU9SMu2AmhltYMFNRYfwOQ7LrnpoMVc
SvM9l0zUjZIW3WvDn9ElKzeYebOgghlb96rlO47+E64iTtI4yUiaVWlSJekFl+mY5nKPXoPbCt5g
esv2teDGV4SnAXTPBJcHPIJsBRgTGQGA9ijFe2ORgv4aMfRbaFtvqbcnjGJCoF+rRikUm+y02jFh
sLjB6aZjBmqnBXp21g5VHO9B8qUz8SOwjt/37Bkp8lVtQUwUSifEVazl1tNFf3AnItVIlCSXIEyl
/d29sKAlsn0E8puaOjmHvQtFkadQlIcKptF8CK2hH/VH6X8/ngsgtuOGTPNAdkqTnQYgzFZkHMel
7UCzIygJ1oJZYj996C8wEia2rieP797cv/+JKGc91O/m3PyDk2Aq8qryYPQuoZyb62D010+Dsnx3
qojpmbZI5ZLbQN7TgLPQne8vHErWe07wrEEA0h8OU0m1PQ3+PM8YOh3ghBO2R/bcxCWauNFNCDry
FlRoX2jLduhpFf49Z69PwDSOJ6rj8iKOp7QdrcoXS8twWDLU1Px0jez2k7D6YeXB8wbkBBYnKkIN
dNZEGlBYx0kWTnPE5GfI4BCxgS+NcrJthHKt70Q8FWjii3wxp5clrT4FWb8o8yz66fa1BLDB0VWH
zynOjH7ocLpe2u8xfynFPE6TOF2TtKhWSZVtLlL8V/weDsKfYWL6izhfC+i27i8wsyOzTF8FecZ4
ujRIVysnsoKLifz+2uT5Zl0UeSQOTVkeIsH0HpZ/Dnv6+bzZcM5OW9yQuJKcxM2B223aeDt2xHb6
BTYrfl4nV4/j299GHH9t8eCoj0ofrvJ8pYjgE6q4y9clbu4iGtblX8P+qooFHbFrftxuso2vKHl8
OP7+8+757du/2/d1vxxwbc777ybuPwxfHBanb+INd4iyQBnMC3OmFD8PZzZxX5vZmhZ5cfluzEb/
EWDWsqbzXxOD4P6nMuKraI9Qofb8Kr9/ePj1D/r5H0foVmgWBwAA
headers:
access-control-allow-headers: ['Accept, Authorization, Content-Type, Origin']
access-control-allow-methods: ['GET, PUT, POST, DELETE']
access-control-allow-origin: ['*']
access-control-expose-headers: [Date]
cache-control: ['private, max-age=0, must-revalidate']
content-encoding: [gzip]
content-length: ['894']
content-type: [application/json; charset=utf-8]
date: ['Fri, 02 Jan 2015 17:19:38 GMT']
etag: ['"3a2806d130e1fcdc7ba3f5f3559b2102"']
server: [am/2]
set-cookie: [_session_auth_key="iuo9QNN6oaPVa3gj9XyKTHZtsxE="]
status: {code: 200, message: OK}
- request:
body: null
headers:
Accept: ['*/*']
Accept-Encoding: ['gzip, deflate']
Authorization: [!!python/unicode 'OAuth 1-35204-61921957-55796ebef403996']
Connection: [keep-alive]
User-Agent: [python-requests/2.4.3 CPython/2.7.8 Linux/3.16.0-28-generic]
method: GET
uri: https://api.soundcloud.com:443/tracks/180099816.json?client_id=93e33e327fd8a9b77becd179652272e2
response:
body:
string: !!binary |
H4sIAAAAAAAEA5VUS2+bQBD+K2gvOdQ2LME2cIsUtYdGqtpLL5HQAmPYetml+7DjRvnvneXhOlaq
phYHM8zjm5lvvmey57ImObGaVXuyIBxfaBpFWZbSzYJUGpiFumAWfeKIJiGNw2gd0CSPkzyJgg8R
/jDOGdCFD463lKZpvCC108xyJTFftk3pFpOprgNpWSmA5FY7WBBjMT2m3nHJTQs1ZlKaN1wyUVRK
WnQvDP+FLhndxHGMkAQztuhUzXcc/QdU6zCi+AQxzdfbfJ2eUZmWaS4b9OpdKXiF6S1rCsGN7+eR
7DRAUKujFIrVjyR4JBKOQecMr/CtYo1yAgJgOJwWHUEiFBlIZS1mbVmH+XrQHRNc7jFh40yLJmNx
at1lm9CVUNfeUpQndGRCoN9cePTcMWFwIr3TVcsMFE4L9Gyt7fMwLLldiVO4HxEtayW489UFK0EM
c5dOiItoy62fMvnoO7yfOsSABqT29jthQUtc0AGCb2pY/RzyaeyiBlNp3o8rJHOK4OZ+qH0T7JQO
hvm1oCF/lO9CKlmH1UesGgRgo/PrwMDCnvqzZQ8nXH6DKJ3HPEZxo6v5/4HXoMZBjR/Lvpu/TdmL
E27v2tYhs9prY81wM2OaMwWxx26gftff+mnzCqRH7Be4RJ621iw1IPUPA3Wd5tPKDO6M9XxllJN1
JZSrV8j+cOjRhOcDw5z+cEj+PBze+Xamoxy+vabYxIApcBwn+TzSwiP8+3FE6zze5DQ5H8c/0fry
JpxAYfIz1V9z86rHPwjZgVmmL5z9VDhdGRxJLYeBjC5miSJC4yjeRGm6We5dlGRPS8F0A6sffUNe
Jn3pBTuVqFMoDU7iBdPJvmMHXJkXkvmI5qO8KI6130YaTmfLtD0qvb8IeQPv6DMARo2MM5rE6VKo
LP5pLwAvyJEdwLPnKtvxVffia5zyEp5spm6LbtWjUs3qcRX3Di6Fo+z4PV2NKUmiNcrvLDfz9KIF
mQaHYmZma0bXZ6WebbfoyqxFEfT6bRDbf/I8vIj2AFFCKq+Ddw8PX76Tl9+iD6WChgYAAA==
headers:
access-control-allow-headers: ['Accept, Authorization, Content-Type, Origin']
access-control-allow-methods: ['GET, PUT, POST, DELETE']
access-control-allow-origin: ['*']
access-control-expose-headers: [Date]
cache-control: ['private, max-age=0, must-revalidate']
content-encoding: [gzip]
content-length: ['793']
content-type: [application/json; charset=utf-8]
date: ['Fri, 02 Jan 2015 17:19:38 GMT']
etag: ['"38831204ed9ab3ebf49b380963c41d63"']
server: [am/2]
set-cookie: [_session_auth_key="iuo9QNN6oaPVa3gj9XyKTHZtsxE="]
status: {code: 200, message: OK}
- request:
body: null
headers:
Accept: ['*/*']
Accept-Encoding: ['gzip, deflate']
Authorization: [!!python/unicode 'OAuth 1-35204-61921957-55796ebef403996']
Connection: [keep-alive]
User-Agent: [python-requests/2.4.3 CPython/2.7.8 Linux/3.16.0-28-generic]
method: GET
uri: https://api.soundcloud.com:443/tracks/172322693.json?client_id=93e33e327fd8a9b77becd179652272e2
response:
body:
string: !!binary |
H4sIAAAAAAAEA5VVy67bRgz9FUGbLmpZb/tau/SRLhq0aG6AooABgdJQ9tSjGWEedtUg/x6OZPn6
ug2aemVxSA7PIQ/nY3jikoVVaDW0p3AVcvpIt1meZZtdvgpbjWCR1WDJJ0vSIk6TOC2DLKnKrCrT
4NuEfhTnDOp6Cs7zfFekFMucBsuVpIRPxWa7oWyq71FaaASGldUOV6GxlJ9yd1xyc0RGqZTmBy5B
1K2Sltxrw/8mlzwrN2VaJKtQgLF1rxjvOAVMdZVxksZJFqRFVWRV8nSryxxBc3kgr8E1greU38Kh
Ftx4RD8aqyQHGezDZ8GHAfUYPAs14D4kR/rsQXB5Is8WNHZOkNVY4qS/x4B9g4x5S92M5AvC+zF1
kULBZA+rDoQhuIPT7REM1k4L8jxaO5gqjrl1Es0ahkHgmliKEWMQjetjpqJRuahDFJE9YmSgx5iz
XZ6XRbIrc7pIQINi4l46Ie7usNx6osPv3BgoGfAP/hIKOKDU3v5GWNSSenTG4L2a2r+EfH+Dy9C0
mg9zI8NMsmAalaDTqieu7kn7xgQMG2eDqXTi9AcV/KFc8JaKDz4cMXim4vfhei/3ckF+uVzWHbTY
KHWakJtrSuPboLqOtxw8nTNKSRmo8rcg27FRfwXvsVWaeVAaBRKxYTWTMBVZ23G4WU440iQdCK/z
6Gc3bnS7/D9zhmpuzHzYDP1yds1ejwj60dbTmB4fjQxoEuY0t3nuFM2TH7sLnD0i3qL0FfuBiWjo
j9ZEGklI50kHTnM6W4iCga+NcpK1Qjk2UTVhNPFNrpTTyzCsPs4yvinxqvHp8PVYf4lt73rl+nWP
p078Q360Fkh+myApq7yoshf5/ScGfxFBuJZK2W+iu5MIKeQB+pfqhjNY0Hehk7rStSHemJxYm11M
RHsrTcrtJt2VZdQ3I8M0EqAPuP5zOISfrittEDA2tBppGTlJvUuv9g7O1Fe/uhZlL8vi7vKvrDt+
WS6g7UXp012OfwEw+0wIElq1yfZpm0bZBWy+uUOw8mOGfuYesl1e0fHLzz/9VoodT9kz1P16oF25
7LiHuK+YwHhejr6Nj7yVRVYQectWXPikdX6lkra0Waz5tnx5Lhbjlt4UsBbao39EDFX3P/UR30X7
EhUp0O/rN+/e/fp7+OkzkzBI/AwHAAA=
headers:
access-control-allow-headers: ['Accept, Authorization, Content-Type, Origin']
access-control-allow-methods: ['GET, PUT, POST, DELETE']
access-control-allow-origin: ['*']
access-control-expose-headers: [Date]
cache-control: ['private, max-age=0, must-revalidate']
content-encoding: [gzip]
content-length: ['878']
content-type: [application/json; charset=utf-8]
date: ['Fri, 02 Jan 2015 17:19:38 GMT']
etag: ['"17967c5ed84d3b941bef99c44c95e9a3"']
server: [am/2]
set-cookie: [_session_auth_key="iuo9QNN6oaPVa3gj9XyKTHZtsxE="]
status: {code: 200, message: OK}
- request:
body: null
headers:
Accept: ['*/*']
Accept-Encoding: ['gzip, deflate']
Authorization: [!!python/unicode 'OAuth 1-35204-61921957-55796ebef403996']
Connection: [keep-alive]
User-Agent: [python-requests/2.4.3 CPython/2.7.8 Linux/3.16.0-28-generic]
method: GET
uri: https://api.soundcloud.com:443/tracks/166785347.json?client_id=93e33e327fd8a9b77becd179652272e2
response:
body:
string: !!binary |
H4sIAAAAAAAEA5VVyY7bOBD9FYbXsUwtlhfdMgmCBAgSYNDBXAwIlEjLjClSISl71EH+fYraWu2k
ZzF8sIuviq+2x+/4IhTDGXaGlhe8wgL+RNvtbp8mm90Kl4ZTx1lOHWDiMNqQ8EDCPYrSLNlk4Rb9
FsIH/FrLTe6dE/CN9skKs9ZQJ7TCWZxE8T6GYLquuXK0kBxnzrR8ha2D8BD6JJSwZ84gkjaiEorK
vNTKATy34hEgaZpCWKAkqXV5rZk4CcD3rFISRiSMUbTN0jALdzMre6ZGqApQTVtIUUJ4R6tcCuvz
OWIqHTcKaF45Mrq8HDFiJYsB1nBTUynUxZOjUga6dUGhu6AESq0R3ALIOihPvcyH1wVnzFvyogNX
8AQc0zclNe3tOINwFlJvWlOeqeV5ayQgz841GSG2pgbiy7VwZHmVpAWXfYFVK+XC2wnnyzlYK66M
L+frp7x8ygMEv1lQZ9yWRjRDg/AnfkMWCiU5Oj7BoBqQNVL6tkJTCkgrJB5axS36J8bIR6SyaGsE
XYfIyGrwhO/bN29j8sFKqhj6g5faMPsKHdVRfWlGpNOtQQzmwmbTHb4DQAXqvwYQ6RHeZ6Tw/Hhh
Lnmh9aX3ecLM5+4mHPT/peMObmwL/tKxUDC8laH1SwDbaCfWJ0Eeku5d9eGooBVDGxWt55YZLjlM
wdTBfg9z1zWz5cI7WIEKprT1vR0aLawpp99Xwbgepmg4LJp6Ohuj5x2n5t5Ww36d742MwtgOYeZF
PGnYBb8wdZP4HAQMpmfspzuAbT07GxgOAnDtFxi2Y5xnCwNNG7G2ulWshA6yvlZ9jpbMMgMxvXzg
7HsvP7OCjNLUn/28kcM8jL5DRfE7YPS5db/rrq/1T0KxIVFM4gTFCYhctglnofhXzp6EJSM1CD7r
w936Ps/0aejAhV6po2aB9+UR0dpCbZjqKzNAbACaGsVRnOzSQxIwuf/LhoGkpuLrr02Ff4xy20ja
FSDboJStgv5Eo/1Er9A7r6twfy81k5gtLvda8xJZ4nn/UvFAnG7aXBZxfpHEgOmzCA/hJtrF2zBo
vj0Wu2+LLFb4Rq/cz9ZdtNuzkmwfuvfho7ymgn3J63UDaj4J753ff5g0Mii2799d7fbbw3aT+mdr
1OqpqOEK9L+vJ6iYnazR/pBGm/lJm8xJ6F8o6hwtz/6ps0Dxfy4DWXh7nhrWzb8krz9+/Pwn/vE3
DcQHabEHAAA=
headers:
access-control-allow-headers: ['Accept, Authorization, Content-Type, Origin']
access-control-allow-methods: ['GET, PUT, POST, DELETE']
access-control-allow-origin: ['*']
access-control-expose-headers: [Date]
cache-control: ['private, max-age=0, must-revalidate']
content-encoding: [gzip]
content-length: ['920']
content-type: [application/json; charset=utf-8]
date: ['Fri, 02 Jan 2015 17:19:38 GMT']
etag: ['"32eed85c2543f08cb8eef0618e2399e7"']
server: [am/2]
set-cookie: [_session_auth_key="iuo9QNN6oaPVa3gj9XyKTHZtsxE="]
status: {code: 200, message: OK}
- request:
body: null
headers:
Accept: ['*/*']
Accept-Encoding: ['gzip, deflate']
Authorization: [!!python/unicode 'OAuth 1-35204-61921957-55796ebef403996']
Connection: [keep-alive]
User-Agent: [python-requests/2.4.3 CPython/2.7.8 Linux/3.16.0-28-generic]
method: GET
uri: https://api.soundcloud.com:443/tracks/172120478.json?client_id=93e33e327fd8a9b77becd179652272e2
response:
body:
string: !!binary |
H4sIAAAAAAAEA5VUa4sTMRT9KyUf/GLbedZ2BhZREQQFUUFhEYbMJG1jM8mQR5cq/ndP5lFnlxW1
LLR7cx/nnnvu/UFOQjFSEmdocyJLIvBPsk2TNM63uyVpDKeOs4o6+KRxkkdJHCX5InlWZmmZbxZP
Y3wQ5y03VR+cptmuKIolYd5QJ7RCwk2WxTGy6bblytFaclI64/mSWIf8yL0XStgjZ0iljTgIRWXV
aOXgXlnxHS7pNk6LZJcviaTWVa1mYi8Q0OPaRHGCv0WalJttudldcdkjNUId4NX5WooG+R09VFLY
0NFX8kI6bhRwnvnio25OXwk8Om5aKoU6weWsRcMtjNaBi3aOnbc1ZyxYqvoCVyol/Ji+U1LT3k7K
PZUWbXbeNEdqeeWNhOfRua6MIuG84nZNu07yNciJvI2orH0bKb1qPVvhS2oHq2BFWmS77S5Onnt9
kz+h7iZJJH31GhUlrbnsyVdeylkxJ1xgerAeuDKB6dAlgsY38nnqj3HbGNENEwssDGkVba8pDJcc
TUwZe81U7tJdLSd+wbQO4NOHWkNhYU0z/T4LxvVAwvBYd+30NmavLpwajBtiW5LJ1kIKRwgp+W1i
FJynM7nsNaYWpkrFfh/wY3AqoA2DWUFUR2dXhkOo515n3ohxFBazoJ1YW+0Va6T2rJ9G35+NruuA
nEHmpPwxrMlV6eMO9Y/31XOiDmI1egwdyCRvf1sf0TJ2LI3SfJFmZQ4551ct/xVwAAC8Iy7UvAr5
vu4e9DkDSc+AZmbegRqRrC14YapnZXCxK+x9EmMh0ywtVl4f+ea0ktQc+PpbdyA/x5PQSXqpcVqw
zF5hOBhgfyr29Iw9D6s/bci0c7PiqP0HqNF1Lalxd9qcZlGPQB58esxxkRWbeFvEq/qCa9TMMC/J
HT3zIKMH2e7uEVC82b+8/bC7ZZ/c66pdd7gu03V4EPcPmoqGsxJm9YCpZ7t4A3lP52QiEGd05A5n
zU7WZJuD2fG+TsYMvtQ52hzD1bUA95+Cj2bRAaHGSoVD9+Ldu/dfyM9fmFC20j0GAAA=
headers:
access-control-allow-headers: ['Accept, Authorization, Content-Type, Origin']
access-control-allow-methods: ['GET, PUT, POST, DELETE']
access-control-allow-origin: ['*']
access-control-expose-headers: [Date]
cache-control: ['private, max-age=0, must-revalidate']
content-encoding: [gzip]
content-length: ['791']
content-type: [application/json; charset=utf-8]
date: ['Fri, 02 Jan 2015 17:19:38 GMT']
etag: ['"b972a18ac1d4f79b31cdad19c3bc5e8e"']
server: [am/2]
set-cookie: [_session_auth_key="iuo9QNN6oaPVa3gj9XyKTHZtsxE="]
status: {code: 200, message: OK}
- request:
body: null
headers:
Accept: ['*/*']
Accept-Encoding: ['gzip, deflate']
Authorization: [!!python/unicode 'OAuth 1-35204-61921957-55796ebef403996']
Connection: [keep-alive]
User-Agent: [python-requests/2.4.3 CPython/2.7.8 Linux/3.16.0-28-generic]
method: GET
uri: https://api.soundcloud.com:443/tracks/164767462.json?client_id=93e33e327fd8a9b77becd179652272e2
response:
body:
string: !!binary |
H4sIAAAAAAAEA1xTTW/bMAz9K4IuOSyNv9Kk823YUBQYCgy97BLAkCUmJiJLBiWn8PrnR9ltltWA
AZuiyPceH9/kGZ2RtYyk9FmuJfJPsdvud/vtrlxLTaAimEZFzinzYpvlD1l5L8qqru7rci++5Pzw
vTEANelyVT7sqnK/XUszkoronaz5Yl7lXM33PbioWguyjjTCWobI9bn2ER2GDgyX8oQndMo22rvI
6U3AP5xS3W+3+yLnMlaF2PTe4BH5wjuuosyqQhS7uvha5w9XXKFThO7EWcPYWtRcP6pTYzEkRgf5
zUYgxzgvIF68Ph8kZwxAvbLozjMw4tQElKXob6FD34IxKdK0E2cqaznP+FdnvZrjsj4qG5jlMJLu
VIBmJCtrN1qbWLRgZ8mW/2tOxJj0WaIncJT0ucGZKCwp8hEXcAaCJhwWteUj+V6snrw14nkST76H
9Uqoi0JuaUEUeVYWwjvxg6FGfsULaE8mbA7uF8EdfwKJ2IFQth17Eb0g0JAUWs0NVwIdz81FO9Wi
i3Gosyz0iiKz22DMvv/+mZo/T6n1wTHehatT/ZUXgQUW5IPmbL8mTsM1coaJB3/i0YxJgEUNDKQ/
vi9owN8K2g79x9l79WYCRZ9jPbuq+xw0iie49Lja7+jZBcklr+qSOKAGlxCnQd+xR7sY7gjY95fZ
tiMhnyU5AuuhBtwEPzqjrR/Nhp2fzRxDdt0urpm2RtZv89b9W5z3lZwP/zejZllfFZ3RBPkXAAD/
/5WVy5KCMBBFf8ViLa8QIbijZqlVLl1OZUZx8AGY4Dgs/PfppokGyprHNqSTm9t9D1RNnjov2Oy1
VJMFfXoaEYhuABFhcz6b8/gekV+FoxDtG31w8T0fZH/3aJyB4XuHYmH+GqmsAnSpCD0NFm3KziDa
ol1EShSkLIxY6hZncdqX7lGq3dbb1zvn1sOmPsr2DaAFmLiU0KawX8/lJ7QQoWLCZ9JsXf6jWj/v
YwUjfa3Uwap7Ipr2kGqRslSIJHCVrOK8tVRPcYq2OFKj065DC7Ii23yFB6GS9vXk1UAug55R3R8G
zCdmYcPGXnEeB1w8aGVMBLr2/gE0tVllfNb9DAjfZjVKoqkjm0a+fyDVNQj8ZwJ8qxpVVpAxJGm2
XK7Wzu0b9S5Rp50GAAA=
headers:
access-control-allow-headers: ['Accept, Authorization, Content-Type, Origin']
access-control-allow-methods: ['GET, PUT, POST, DELETE']
access-control-allow-origin: ['*']
access-control-expose-headers: [Date]
cache-control: ['private, max-age=0, must-revalidate']
content-encoding: [gzip]
content-length: ['869']
content-type: [application/json; charset=utf-8]
date: ['Fri, 02 Jan 2015 17:19:38 GMT']
etag: ['"66dbb4584916e51f1a18d35581065139"']
server: [am/2]
set-cookie: [_session_auth_key="iuo9QNN6oaPVa3gj9XyKTHZtsxE="]
status: {code: 200, message: OK}
version: 1
Mopidy-SoundCloud-2.0.2/tests/__init__.py 0000644 0001750 0001750 00000000000 12231551106 020472 0 ustar jodal jodal 0000000 0000000 Mopidy-SoundCloud-2.0.2/tests/__main__.py 0000644 0001750 0001750 00000000102 12231551106 020456 0 ustar jodal jodal 0000000 0000000 from __future__ import unicode_literals
import nose
nose.main()
Mopidy-SoundCloud-2.0.2/tests/test_library.py 0000664 0001750 0001750 00000006546 12505250302 021463 0 ustar jodal jodal 0000000 0000000 # coding=utf-8
from __future__ import unicode_literals
import unittest
import pykka
from mopidy_soundcloud import SoundCloudExtension, actor
from mopidy_soundcloud.library import (
SoundCloudLibraryProvider, new_folder, simplify_search_query)
from mopidy_soundcloud.soundcloud import safe_url
class ApiTest(unittest.TestCase):
def setUp(self):
config = SoundCloudExtension().get_config_schema()
config['auth_token'] = '1-35204-61921957-55796ebef403996'
# using this user http://maildrop.cc/inbox/mopidytestuser
self.backend = actor.SoundCloudBackend.start(
config={'soundcloud': config},
audio=None
).proxy()
self.library = SoundCloudLibraryProvider(backend=self.backend)
def tearDown(self):
pykka.ActorRegistry.stop_all()
def test_add_folder(self):
try:
from mopidy.models import Ref
except ImportError as e:
self.skipTest(e.message)
self.assertEquals(
new_folder('Test', ['test']),
Ref(name='Test', type='directory',
uri='soundcloud:directory:test')
)
def test_mpc_search(self):
self.assertEquals(
simplify_search_query({u'any': [u'explosions in the sky']}),
'explosions in the sky'
)
def test_moped_search(self):
self.assertEquals(
simplify_search_query(
{
u'track_name': [u'explosions in the sky'],
u'any': [u'explosions in the sky']
}
),
'explosions in the sky explosions in the sky'
)
def test_simple_search(self):
self.assertEquals(
simplify_search_query('explosions in the sky'),
'explosions in the sky'
)
def test_aria_search(self):
self.assertEquals(
simplify_search_query(['explosions', 'in the sky']),
'explosions in the sky'
)
def test_only_resolves_soundcloud_uris(self):
self.assertIsNone(self.library.search(
{'uri': 'http://www.youtube.com/watch?v=wD6H6Yhluo8'}))
def test_returns_url_safe_string(self):
self.assertEquals(
safe_url('Alternative/Indie/rock/pop '),
'Alternative%2FIndie%2Frock%2Fpop+')
self.assertEquals(
safe_url('D∃∃P Hau⑀ iNDiE DᴬNCE | №➊ ²⁰¹⁴'),
'DP+Hau+iNDiE+DANCE+%7C+No+2014')
def test_default_folders(self):
try:
from mopidy.models import Ref
except ImportError as e:
self.skipTest(e.message)
self.assertEquals(
self.library.browse('soundcloud:directory'),
[
Ref(name='Explore', type='directory',
uri='soundcloud:directory:explore'),
Ref(name='Following', type='directory',
uri='soundcloud:directory:following'),
Ref(name='Groups', type='directory',
uri='soundcloud:directory:groups'),
Ref(name='Liked', type='directory',
uri='soundcloud:directory:liked'),
Ref(name='Sets', type='directory',
uri='soundcloud:directory:sets'),
Ref(name='Stream', type='directory',
uri='soundcloud:directory:stream')
]
)
Mopidy-SoundCloud-2.0.2/tests/test_extension.py 0000644 0001750 0001750 00000001075 12300231306 022015 0 ustar jodal jodal 0000000 0000000 from __future__ import unicode_literals
import unittest
from mopidy_soundcloud import SoundCloudExtension
class ExtensionTest(unittest.TestCase):
def test_get_default_config(self):
ext = SoundCloudExtension()
config = ext.get_default_config()
self.assertIn('[soundcloud]', config)
self.assertIn('enabled = True', config)
def test_get_config_schema(self):
ext = SoundCloudExtension()
schema = ext.get_config_schema()
self.assertIn('auth_token', schema)
self.assertIn('explore_songs', schema)
Mopidy-SoundCloud-2.0.2/tests/test_api.py 0000664 0001750 0001750 00000010753 12632302045 020566 0 ustar jodal jodal 0000000 0000000 from __future__ import unicode_literals
import unittest
import mock
from mopidy.models import Track
import vcr
from mopidy_soundcloud import SoundCloudExtension
from mopidy_soundcloud.soundcloud import SoundCloudClient, readable_url
class ApiTest(unittest.TestCase):
@vcr.use_cassette('tests/fixtures/sc-login.yaml')
def setUp(self):
config = SoundCloudExtension().get_config_schema()
config['auth_token'] = '1-35204-61921957-55796ebef403996'
config['explore_songs'] = 10
# using this user http://maildrop.cc/inbox/mopidytestuser
self.api = SoundCloudClient(config)
def test_resolves_string(self):
_id = self.api.parse_track_uri('soundcloud:song.38720262')
self.assertEquals(_id, '38720262')
@vcr.use_cassette('tests/fixtures/sc-login-error.yaml')
def test_responds_with_error(self):
with mock.patch('mopidy_soundcloud.soundcloud.logger.error') as d:
config = SoundCloudExtension().get_config_schema()
config['auth_token'] = '1-fake-token'
SoundCloudClient(config)
d.assert_called_once_with('Invalid "auth_token" used for '
'SoundCloud authentication!')
@vcr.use_cassette('tests/fixtures/sc-resolve-track.yaml')
def test_resolves_object(self):
trackc = {}
trackc[b'uri'] = 'soundcloud:song.38720262'
track = Track(**trackc)
id = self.api.parse_track_uri(track)
self.assertEquals(id, '38720262')
@vcr.use_cassette('tests/fixtures/sc-resolve-track-none.yaml')
def test_resolves_unknown_track_to_none(self):
track = self.api.get_track('s38720262')
self.assertIsNone(track)
@vcr.use_cassette('tests/fixtures/sc-resolve-track.yaml')
def test_resolves_Track(self):
track = self.api.get_track('38720262')
self.assertIsInstance(track, Track)
self.assertEquals(
track.uri,
'soundcloud:song/Burial Four Tet - Nova.38720262'
)
@vcr.use_cassette('tests/fixtures/sc-resolve-http.yaml')
def test_resolves_http_url(self):
track = self.api.resolve_url(
'https://soundcloud.com/bbc-radio-4/m-w-cloud'
)[0]
self.assertIsInstance(track, Track)
self.assertEquals(
track.uri,
'soundcloud:song/That Mitchell and Webb Sound The Cloud.122889665'
)
@vcr.use_cassette('tests/fixtures/sc-liked.yaml')
def test_get_user_liked(self):
tracks = self.api.get_user_liked()
self.assertIsInstance(tracks, list)
@vcr.use_cassette('tests/fixtures/sc-stream.yaml')
def test_get_user_stream(self):
tracks = self.api.get_user_stream()
self.assertIsInstance(tracks, list)
@vcr.use_cassette('tests/fixtures/sc-explore.yaml')
def test_get_explore(self):
tracks = self.api.get_explore()
self.assertIsInstance(tracks, list)
self.assertEquals(tracks[0], 'Popular+Music')
@vcr.use_cassette('tests/fixtures/sc-popular.yaml')
def test_get_explore_popular_music(self):
tracks = self.api.get_explore('1')
self.assertIsInstance(tracks, list)
self.assertIsInstance(tracks[0], Track)
@vcr.use_cassette('tests/fixtures/sc-following.yaml')
def test_get_followings(self):
tracks = self.api.get_followings()
self.assertIsInstance(tracks, list)
@vcr.use_cassette('tests/fixtures/sc-sets.yaml')
def test_get_sets(self):
tracks = self.api.get_sets()
self.assertIsInstance(tracks, list)
@vcr.use_cassette('tests/fixtures/sc-groups.yaml')
def test_get_groups(self):
tracks = self.api.get_groups()
self.assertIsInstance(tracks, list)
@vcr.use_cassette('tests/fixtures/sc-tracks.yaml')
def test_get_group_tracks(self):
tracks = self.api.get_groups(136)
self.assertIsInstance(tracks[0], Track)
def test_readeble_url(self):
self.assertEquals('Barsuk Records',
readable_url('"@"Barsuk Records'))
self.assertEquals('_Barsuk Records',
readable_url('_Barsuk \'Records\''))
@vcr.use_cassette('tests/fixtures/sc-resolve-track-id.yaml')
def test_resolves_stream_track(self):
track = self.api.get_track('38720262', True)
self.assertIsInstance(track, Track)
self.assertEquals(
track.uri,
'https://api.soundcloud.com/tracks/'
'38720262/stream?client_id=93e33e327fd8a9b77becd179652272e2'
)
Mopidy-SoundCloud-2.0.2/mopidy_soundcloud/ 0000775 0001750 0001750 00000000000 12642323173 021002 5 ustar jodal jodal 0000000 0000000 Mopidy-SoundCloud-2.0.2/mopidy_soundcloud/__init__.py 0000664 0001750 0001750 00000002435 12642322340 023112 0 ustar jodal jodal 0000000 0000000 from __future__ import unicode_literals
import os
from mopidy import config, ext
from mopidy.exceptions import ExtensionError
__version__ = '2.0.2'
class SoundCloudExtension(ext.Extension):
dist_name = 'Mopidy-SoundCloud'
ext_name = 'soundcloud'
version = __version__
def get_default_config(self):
conf_file = os.path.join(os.path.dirname(__file__), 'ext.conf')
return config.read(conf_file)
def get_config_schema(self):
schema = super(SoundCloudExtension, self).get_config_schema()
schema['explore_songs'] = config.Integer()
schema['auth_token'] = config.Secret()
schema['explore'] = config.Deprecated()
schema['explore_pages'] = config.Deprecated()
return schema
def validate_config(self, config): # no_coverage
if not config.getboolean('soundcloud', 'enabled'):
return
if not config.get('soundcloud', 'auth_token'):
raise ExtensionError(
'In order to use SoundCloud extension you must provide an '
'auth token. For more information refer to '
'https://github.com/mopidy/mopidy-soundcloud/')
def setup(self, registry):
from .actor import SoundCloudBackend
registry.add('backend', SoundCloudBackend)
Mopidy-SoundCloud-2.0.2/mopidy_soundcloud/library.py 0000664 0001750 0001750 00000016344 12632301132 023016 0 ustar jodal jodal 0000000 0000000 from __future__ import unicode_literals
import collections
import logging
import re
import urllib
from urlparse import urlparse
from mopidy import backend, models
from mopidy.models import SearchResult, Track
from mopidy_soundcloud.soundcloud import safe_url
logger = logging.getLogger(__name__)
def generate_uri(path):
return 'soundcloud:directory:%s' % urllib.quote('/'.join(path))
def new_folder(name, path):
return models.Ref.directory(
uri=generate_uri(path),
name=safe_url(name)
)
def simplify_search_query(query):
if isinstance(query, dict):
r = []
for v in query.values():
if isinstance(v, list):
r.extend(v)
else:
r.append(v)
return ' '.join(r)
if isinstance(query, list):
return ' '.join(query)
else:
return query
class SoundCloudLibraryProvider(backend.LibraryProvider):
root_directory = models.Ref.directory(
uri='soundcloud:directory',
name='SoundCloud'
)
def __init__(self, *args, **kwargs):
super(SoundCloudLibraryProvider, self).__init__(*args, **kwargs)
self.vfs = {'soundcloud:directory': collections.OrderedDict()}
self.add_to_vfs(new_folder('Explore', ['explore']))
self.add_to_vfs(new_folder('Following', ['following']))
self.add_to_vfs(new_folder('Groups', ['groups']))
self.add_to_vfs(new_folder('Liked', ['liked']))
self.add_to_vfs(new_folder('Sets', ['sets']))
self.add_to_vfs(new_folder('Stream', ['stream']))
def add_to_vfs(self, _model):
self.vfs['soundcloud:directory'][_model.uri] = _model
def list_sets(self):
sets_vfs = collections.OrderedDict()
for (name, set_id, tracks) in self.backend.remote.get_sets():
sets_list = new_folder(name, ['sets', set_id])
logger.debug('Adding set %s to vfs' % sets_list.name)
sets_vfs[set_id] = sets_list
return sets_vfs.values()
def list_liked(self):
vfs_list = collections.OrderedDict()
for data in self.backend.remote.get_user_liked():
try:
name, set_id = data
except (TypeError, ValueError):
logger.debug('Adding liked track %s to vfs' % data.name)
vfs_list[data.name] = models.Ref.track(
uri=data.uri, name=data.name
)
else:
logger.debug('Adding liked playlist %s to vfs' % name)
vfs_list[set_id] = new_folder(name, ['sets', set_id])
return vfs_list.values()
def list_user_follows(self):
sets_vfs = collections.OrderedDict()
for (name, user_id) in self.backend.remote.get_followings():
sets_list = new_folder(name, ['following', user_id])
logger.debug('Adding set %s to vfs' % sets_list.name)
sets_vfs[user_id] = sets_list
return sets_vfs.values()
def list_explore(self):
sets_vfs = collections.OrderedDict()
for eid, name in enumerate(self.backend.remote.get_explore()):
sets_list = new_folder(
name,
['explore', str(eid)]
)
logger.debug('Adding explore category %s to vfs' % sets_list.name)
sets_vfs[str(eid)] = sets_list
return sets_vfs.values()
def list_groups(self):
groups_vfs = collections.OrderedDict()
for group in self.backend.remote.get_groups():
g_list = new_folder(
group.get('name'),
['groups', str(group.get('id'))]
)
logger.debug('Adding group %s to vfs' % g_list.name)
groups_vfs[str(group.get('id'))] = g_list
return groups_vfs.values()
def tracklist_to_vfs(self, track_list):
vfs_list = collections.OrderedDict()
for temp_track in track_list:
if not isinstance(temp_track, Track):
temp_track = self.backend.remote.parse_track(temp_track)
if hasattr(temp_track, 'uri'):
vfs_list[temp_track.name] = models.Ref.track(
uri=temp_track.uri,
name=temp_track.name
)
return vfs_list.values()
def browse(self, uri):
if not self.vfs.get(uri):
(req_type, res_id) = re.match(r'.*:(\w*)(?:/(\d*))?', uri).groups()
# Sets
if 'sets' == req_type:
if res_id:
return self.tracklist_to_vfs(
self.backend.remote.get_set(res_id)
)
else:
return self.list_sets()
# Following
if 'following' == req_type:
if res_id:
return self.tracklist_to_vfs(
self.backend.remote.get_followings(res_id)
)
else:
return self.list_user_follows()
# Explore
if 'explore' == req_type:
if res_id:
return self.tracklist_to_vfs(
self.backend.remote.get_explore(res_id)
)
else:
return self.list_explore()
# Groups
if 'groups' == req_type:
if res_id:
return self.tracklist_to_vfs(
self.backend.remote.get_groups(res_id)
)
else:
return self.list_groups()
# Liked
if 'liked' == req_type:
return self.list_liked()
# User stream
if 'stream' == req_type:
return self.tracklist_to_vfs(
self.backend.remote.get_user_stream()
)
# root directory
return self.vfs.get(uri, {}).values()
def search(self, query=None, uris=None, exact=False):
# TODO Support exact search
if not query:
return
if 'uri' in query:
search_query = ''.join(query['uri'])
url = urlparse(search_query)
if 'soundcloud.com' in url.netloc:
logger.info('Resolving SoundCloud for: %s', search_query)
return SearchResult(
uri='soundcloud:search',
tracks=self.backend.remote.resolve_url(search_query)
)
else:
search_query = simplify_search_query(query)
logger.info('Searching SoundCloud for: %s', search_query)
return SearchResult(
uri='soundcloud:search',
tracks=self.backend.remote.search(search_query)
)
def lookup(self, uri):
if 'sc:' in uri:
uri = uri.replace('sc:', '')
return self.backend.remote.resolve_url(uri)
try:
track_id = self.backend.remote.parse_track_uri(uri)
track = self.backend.remote.get_track(track_id)
if track is None:
logger.info(
'Failed to lookup %s: SoundCloud track not found' % uri)
return []
return [track]
except Exception as error:
logger.error('Failed to lookup %s: %s', uri, error)
return []
Mopidy-SoundCloud-2.0.2/mopidy_soundcloud/ext.conf 0000644 0001750 0001750 00000000302 12300231306 022425 0 ustar jodal jodal 0000000 0000000 [soundcloud]
enabled = True
# Your SoundCloud auth token, you can get yours at http://www.mopidy.com/authenticate
auth_token =
# Number of songs to fetch in explore section
explore_songs = 25 Mopidy-SoundCloud-2.0.2/mopidy_soundcloud/actor.py 0000664 0001750 0001750 00000001674 12632301743 022472 0 ustar jodal jodal 0000000 0000000 from __future__ import unicode_literals
import logging
from mopidy import backend
import pykka
from .library import SoundCloudLibraryProvider
from .soundcloud import SoundCloudClient
logger = logging.getLogger(__name__)
class SoundCloudBackend(pykka.ThreadingActor, backend.Backend):
def __init__(self, config, audio):
super(SoundCloudBackend, self).__init__()
self.config = config
self.remote = SoundCloudClient(config['soundcloud'])
self.library = SoundCloudLibraryProvider(backend=self)
self.playback = SoundCloudPlaybackProvider(audio=audio, backend=self)
self.uri_schemes = ['soundcloud', 'sc']
class SoundCloudPlaybackProvider(backend.PlaybackProvider):
def translate_uri(self, uri):
track_id = self.backend.remote.parse_track_uri(uri)
track = self.backend.remote.get_track(track_id, True)
if track is None:
return None
return track.uri
Mopidy-SoundCloud-2.0.2/mopidy_soundcloud/soundcloud.py 0000664 0001750 0001750 00000024547 12632301774 023551 0 ustar jodal jodal 0000000 0000000 from __future__ import unicode_literals
import collections
import logging
import re
import string
import time
import unicodedata
from multiprocessing.pool import ThreadPool
from urllib import quote_plus
from mopidy.models import Album, Artist, Track
import requests
logger = logging.getLogger(__name__)
def safe_url(uri):
return quote_plus(
unicodedata.normalize('NFKD', unicode(uri)).encode('ASCII', 'ignore'))
def readable_url(uri):
valid_chars = "-_.() %s%s" % (string.ascii_letters, string.digits)
safe_uri = unicodedata.normalize('NFKD', unicode(uri)).encode('ASCII',
'ignore')
return re.sub('\s+', ' ',
''.join(c for c in safe_uri if c in valid_chars)).strip()
class cache(object):
# TODO: merge this to util library
def __init__(self, ctl=8, ttl=3600):
self.cache = {}
self.ctl = ctl
self.ttl = ttl
self._call_count = 1
def __call__(self, func):
def _memoized(*args):
self.func = func
now = time.time()
try:
value, last_update = self.cache[args]
age = now - last_update
if self._call_count >= self.ctl or age > self.ttl:
self._call_count = 1
raise AttributeError
self._call_count += 1
return value
except (KeyError, AttributeError):
value = self.func(*args)
self.cache[args] = (value, now)
return value
except TypeError:
return self.func(*args)
return _memoized
class SoundCloudClient(object):
CLIENT_ID = '93e33e327fd8a9b77becd179652272e2'
def __init__(self, config):
super(SoundCloudClient, self).__init__()
token = config['auth_token']
self.explore_songs = config.get('explore_songs', 10)
self.http_client = requests.Session()
self.http_client.headers.update({'Authorization': 'OAuth %s' % token})
try:
self._get('me.json')
except Exception as err:
if err.response is not None and err.response.status_code == 401:
logger.error('Invalid "auth_token" used for SoundCloud '
'authentication!')
else:
raise
@property
@cache()
def user(self):
return self._get('me.json')
@cache()
def get_user_stream(self):
# User timeline like playlist which uses undocumented api
# https://api.soundcloud.com/e1/me/stream.json?offset=0
# returns five elements per request
tracks = []
for sid in xrange(0, 2):
stream = self._get('e1/me/stream.json?offset=%s' % sid * 5)
for data in stream.get('collection'):
kind = data.get('type')
# multiple types of track with same data
if 'track' in kind:
tracks.append(self.parse_track(data.get('track')))
if kind == 'playlist':
playlist = data.get('playlist').get('tracks')
if isinstance(playlist, collections.Iterable):
tracks.extend(self.parse_results(playlist))
return self.sanitize_tracks(tracks)
@cache()
def get_explore_categories(self):
return self._get('explore/categories', 'api-v2').get('music')
def get_explore(self, query_explore_id=None):
explore = self.get_explore_categories()
if query_explore_id:
url = 'explore/{urn}?limit={limit}&offset=0&linked_partitioning=1'\
.format(
urn=explore[int(query_explore_id)],
limit=self.explore_songs
)
web_tracks = self._get(url, 'api-v2')
track_ids = map(lambda x: x.get('id'), web_tracks.get('tracks'))
return self.resolve_tracks(track_ids)
return explore
def get_groups(self, query_group_id=None):
if query_group_id:
web_tracks = self._get(
'groups/%d/tracks.json' % int(query_group_id))
tracks = []
for track in web_tracks:
if 'track' in track.get('kind'):
tracks.append(self.parse_track(track))
return tracks
else:
return self._get('me/groups.json')
def get_followings(self, query_user_id=None):
if query_user_id:
return self._get('users/%s/tracks.json' % query_user_id)
users = []
for playlist in self._get('me/followings.json?limit=60'):
name = playlist.get('username')
user_id = str(playlist.get('id'))
logger.debug('Fetched user %s with id %s' % (
name, user_id
))
users.append((name, user_id))
return users
@cache()
def get_set(self, set_id):
playlist = self._get('playlists/%s.json' % set_id)
return playlist.get('tracks', [])
def get_sets(self):
playable_sets = []
for playlist in self._get('me/playlists.json?limit=1000'):
name = playlist.get('title')
set_id = str(playlist.get('id'))
tracks = playlist.get('tracks')
logger.debug('Fetched set %s with id %s (%d tracks)' % (
name, set_id, len(tracks)
))
playable_sets.append((name, set_id, tracks))
return playable_sets
def get_user_liked(self):
# Note: As with get_user_stream, this API call is undocumented.
likes = []
liked = self._get('e1/me/likes.json?limit=1000')
for data in liked:
track = data['track']
if track:
likes.append(self.parse_track(track))
pl = data['playlist']
if pl:
likes.append((pl['title'], str(pl['id'])))
return likes
# Public
@cache()
def get_track(self, track_id, streamable=False):
logger.debug('Getting info for track with id %s' % track_id)
try:
return self.parse_track(self._get('tracks/%s.json' % track_id),
streamable)
except Exception:
return None
def parse_track_uri(self, track):
logger.debug('Parsing track %s' % (track))
if hasattr(track, "uri"):
track = track.uri
return track.split('.')[-1]
def search(self, query):
search_results = self._get(
'tracks.json?q=%s&filter=streamable&order=hotness&limit=%d' % (
quote_plus(query.encode('utf-8')), self.explore_songs))
tracks = []
for track in search_results:
tracks.append(self.parse_track(track, False))
return self.sanitize_tracks(tracks)
def parse_results(self, res):
tracks = []
for track in res:
tracks.append(self.parse_track(track))
return self.sanitize_tracks(tracks)
def resolve_url(self, uri):
return self.parse_results([self._get('resolve.json?url=%s' % uri)])
def _get(self, url, endpoint='api'):
if '?' in url:
url = '%s&client_id=%s' % (url, self.CLIENT_ID)
else:
url = '%s?client_id=%s' % (url, self.CLIENT_ID)
url = 'https://%s.soundcloud.com/%s' % (endpoint, url)
logger.debug('Requesting %s' % url)
res = self.http_client.get(url)
res.raise_for_status()
return res.json()
def sanitize_tracks(self, tracks):
return filter(None, tracks)
@cache()
def parse_track(self, data, remote_url=False):
if not data:
return []
if not data['streamable']:
logger.info(
"'%s' can't be streamed from SoundCloud" % data.get('title'))
return []
if not data['kind'] == 'track':
logger.debug('%s is not track' % data.get('title'))
return []
# NOTE kwargs dict keys must be bytestrings to work on Python < 2.6.5
# See https://github.com/mopidy/mopidy/issues/302 for details.
track_kwargs = {}
artist_kwargs = {}
album_kwargs = {}
if 'title' in data:
name = data['title']
label_name = data.get('label_name')
if bool(label_name):
track_kwargs[b'name'] = name
artist_kwargs[b'name'] = label_name
else:
track_kwargs[b'name'] = name
artist_kwargs[b'name'] = data.get('user').get('username')
album_kwargs[b'name'] = 'SoundCloud'
if 'date' in data:
track_kwargs[b'date'] = data['date']
if remote_url:
if not self.can_be_streamed(data['stream_url']):
logger.info(
"'%s' can't be streamed from SoundCloud" % data.get(
'title'))
return []
track_kwargs[b'uri'] = self.get_streamble_url(data['stream_url'])
else:
track_kwargs[b'uri'] = 'soundcloud:song/%s.%s' % (
readable_url(data.get('title')), data.get('id')
)
track_kwargs[b'length'] = int(data.get('duration', 0))
track_kwargs[b'comment'] = data.get('permalink_url', '')
if artist_kwargs:
artist = Artist(**artist_kwargs)
track_kwargs[b'artists'] = [artist]
if album_kwargs:
if 'artwork_url' in data and data['artwork_url']:
album_kwargs[b'images'] = [data['artwork_url']]
else:
image = data.get('user').get('avatar_url')
album_kwargs[b'images'] = [image]
album = Album(**album_kwargs)
track_kwargs[b'album'] = album
track = Track(**track_kwargs)
return track
@cache()
def can_be_streamed(self, url):
req = self.http_client.head(self.get_streamble_url(url))
return req.status_code == 302
def get_streamble_url(self, url):
return '%s?client_id=%s' % (url, self.CLIENT_ID)
def resolve_tracks(self, track_ids):
"""Resolve tracks concurrently emulating browser
:param track_ids:list of track ids
:return:list `Track`
"""
pool = ThreadPool(processes=16)
tracks = pool.map(self.get_track, track_ids)
pool.close()
tracks = [t for t in tracks if t is not None]
return tracks