Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Soundcloud backend #339

Closed
wants to merge 32 commits into from
Closed
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
081c83f
Initial Soundcloud backend
Mar 14, 2013
c1f0292
Tune searching in such way results are not sorted into artists
Mar 14, 2013
af7fb08
Only add streamable tracks
Mar 14, 2013
5332647
PEP8 fixes
Mar 14, 2013
da56b18
Don't cache favorites, add more resolvers for artist
Mar 14, 2013
10eb425
Add Soundcloud branding
Mar 14, 2013
ab98e35
Added sets
Mar 14, 2013
7836140
Add user stream playlist
Mar 15, 2013
6bf854a
Too many problems with special characters on some clients
Mar 15, 2013
ed14c47
Use oAuth login, add Explore section as playlists
Mar 15, 2013
b8055b5
Fix: documentation for settings
Mar 16, 2013
e894c96
Fix: handle 404, missing or deleted tracks
Mar 16, 2013
f24b0fb
fix: capitalize class
Mar 16, 2013
12a3448
add: Basic SounCloud JSON track parser tests
Mar 16, 2013
a2ce6d0
Fix: Library behavior on searching
Mar 19, 2013
bcd2eee
fix: variable naming
Mar 19, 2013
57895e1
Fix: Libray search for mpris
Mar 19, 2013
d7f093b
Fix: SoundCloud naming
Mar 22, 2013
272c185
Fix: removing // from URI
Mar 22, 2013
7f94568
Review: resolve uri
Mar 22, 2013
3386353
Merge branch 'develop' of git://github.com/mopidy/mopidy.git into sou…
Mar 22, 2013
9f4dfb9
Fix: Double class, super methods
Mar 22, 2013
8e50a94
Merge branch 'soundcloud/resolve_uri' into soundcloud_backend
Mar 23, 2013
023a038
Fix: docs
Mar 23, 2013
1ab2740
Add: More debug information
Mar 23, 2013
ea17017
Fix: Playlist resolving
Mar 23, 2013
5dcd5fd
Fix: Only add albumart if album name is defined
Mar 23, 2013
df3cf02
Fix: SoundCloud api unavailable, leaves backend in broken state
Mar 24, 2013
f27d2fe
Add: Playlists tests
Mar 24, 2013
a5b864b
Add: Custom audio implementation
Mar 26, 2013
9aed118
Fix: Rename variable SC to http_client
Mar 26, 2013
14286bf
Merge branch 'develop' of git://github.com/mopidy/mopidy.git into sou…
Mar 31, 2013
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ install:
- "sudo wget -q -O /etc/apt/sources.list.d/mopidy.list http://apt.mopidy.com/mopidy.list"
- "sudo apt-get update || true"
- "sudo apt-get install $(apt-cache depends mopidy | awk '$2 !~ /mopidy/ {print $2}')"
- "sudo apt-get install python-requests"

before_script:
- "rm $VIRTUAL_ENV/lib/python$TRAVIS_PYTHON_VERSION/no-global-site-packages.txt"
Expand Down
8 changes: 8 additions & 0 deletions docs/modules/backends/soundcloud.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
.. soundcloud-backend:

*************************************************
:mod:`mopidy.backends.soundcloud` -- SoundCloud backend
*************************************************
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

*** rows should have equal length with the header text.


.. automodule:: mopidy.backends.soudcloud
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/soudcloud/soundcloud/

:synopsis: Backend for the SoundCloud music streaming service
25 changes: 25 additions & 0 deletions mopidy/backends/soundcloud/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
"""A backend for playing music from Soundcloud.

This backend handles URIs starting with ``soundcloud:``.

See :ref:`music-from-soundcloud-storage` for further instructions on using this
backend.

**Issues:**

https://github.com/mopidy/mopidy/issues?labels=Soundcloud+backend
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've created a label now. Please do a s/Soundcloud/SoundCloud/


**Dependencies:**

.. literalinclude:: ../../../requirements/soundcloud.txt

**Settings:**

- :attr:`mopidy.settings.SOUNDCLOUD_AUTH_TOKEN`
- :attr:`mopidy.settings.SOUNDCLOUD_EXPLORE`
"""

from __future__ import unicode_literals

# flake8: noqa
from .actor import SoundcloudBackend
33 changes: 33 additions & 0 deletions mopidy/backends/soundcloud/actor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
from __future__ import unicode_literals

import logging
import pykka

from mopidy import settings
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sort this block of imports. Should be in the opposite order.

from mopidy.backends import base


from .library import SoundcloudLibraryProvider
from .playlists import SoundcloudPlaylistsProvider
from .soundcloud import SoundCloudClient

logger = logging.getLogger('mopidy.backends.soundcloud')


class SoundcloudBackend(pykka.ThreadingActor, base.Backend):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/Soundcloud/SoundCloud/

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And same for the rest of the class names. I won't comment on all of them.

def __init__(self, audio):
super(SoundcloudBackend, self).__init__()

if not settings.SOUNDCLOUD_AUTH_TOKEN:
logger.error(("In order to use SoundCloud backend "
"you must provide settings.SOUNDCLOUD_AUTH_TOKEN. "
"Get yours at http://www.mopidy.com/authenticate"))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens if you ignore this error and just try to use the SoundCloud backend? I guess something will crash with an AttributeError on backend.sc_api now being available, which isn't very nice. The backend should shut down properly after logging the error.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just ran into this case. Because I got a traceback about sc_api missing, it took focus away from the error message right above, stating what the root cause was.

else:
self.sc_api = SoundCloudClient(
settings.SOUNDCLOUD_AUTH_TOKEN)

self.library = SoundcloudLibraryProvider(backend=self)
self.playback = base.BasePlaybackProvider(audio=audio, backend=self)
self.playlists = SoundcloudPlaylistsProvider(backend=self)

self.uri_schemes = ['soundcloud']
44 changes: 44 additions & 0 deletions mopidy/backends/soundcloud/library.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
from __future__ import unicode_literals

import logging

from mopidy.backends import base
from mopidy.models import SearchResult

logger = logging.getLogger('mopidy.backends.soundcloud')


class SoundcloudLibraryProvider(base.BaseLibraryProvider):
def __init__(self, *args, **kwargs):
super(SoundcloudLibraryProvider, self).__init__(*args, **kwargs)

def find_exact(self, **query):
return self.search(**query)

def search(self, **query):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should maybe add some TODOs here if there are more stuff than we can make work with the SoundCloud search API. I'm thinking of things like:

  • exact searches
  • search specific fields like artist, title, album, year, etc.
  • search for multiple terms (e.g. {'any': ['foo', 'bar']} returning sound files matching both 'foo' and 'bar')

If some of these features are not supported by SoundCloud, it should probably be documented in a docstring on the function.

print(query)
if not query:
return

for (field, val) in query.iteritems():

# TODO: Devise method for searching SoundCloud via artists
if field == "album" and query['album'] == "SoundCloud":
return SearchResult(
uri='soundcloud:search',
tracks=self.backend.sc_api.search(query['artist']) or [])
elif field == "any":
return SearchResult(
uri='soundcloud:search',
tracks=self.backend.sc_api.search(val[0]) or [])
else:
return []

def lookup(self, uri):
try:
id = uri.split('//')[1]
logger.debug(u'SoundCloud track id for %s: %s' % (uri, id))
return [self.backend.sc_api.get_track(id, True)]
except Exception as error:
logger.debug(u'Failed to lookup %s: %s', uri, error)
return []
74 changes: 74 additions & 0 deletions mopidy/backends/soundcloud/playlists.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
from __future__ import unicode_literals

import logging

from mopidy import settings
from mopidy.backends import base, listener
from mopidy.models import Playlist

logger = logging.getLogger('mopidy.backends.soundcloud.playlists')


class SoundcloudPlaylistsProvider(base.BasePlaylistsProvider):

def __init__(self, *args, **kwargs):
super(SoundcloudPlaylistsProvider, self).__init__(*args, **kwargs)
self.refresh()

def create(self, name):
pass # TODO

def delete(self, uri):
pass # TODO

def lookup(self, uri):
for playlist in self._playlists:
if playlist.uri == uri:
return playlist

def refresh(self):
logger.info('Loading playlists from SoundCloud')
username = self.backend.sc_api.get_user().get('username')
playlists = []

liked = Playlist(
uri='soundcloud:playlist-liked',
name="%s's liked on SoundCloud" % username,
tracks=self.backend.sc_api.get_favorites()
)
playlists.append(liked)

stream = Playlist(
uri='soundcloud:playlist-user',
name="%s's stream on SoundCloud" % username,
tracks=self.backend.sc_api.get_user_stream()
)
playlists.append(stream)

for (name, uri, tracks) in self.backend.sc_api.get_sets():
scset = Playlist(
uri='soundcloud:%s' % uri,
name=name,
tracks=tracks
)
playlists.append(scset)

for cat in settings.SOUNDCLOUD_EXPLORE:

(category, section) = cat.split('/')
logger.info('Fetching Explore playlist %s from SoundCloud' % section)
tracks = self.backend.sc_api.get_explore_category(
category, section)

exp = Playlist(
uri='soundcloud:cat-%s' % section.lower(),
name='Explore %s on SoundCloud' % section,
tracks=tracks
)
playlists.append(exp)

self._playlists = playlists
listener.BackendListener.send('playlists_loaded')

def save(self, playlist):
pass # TODO
Loading