Merge branch 'master' of github.com:Prior99/mopidy-subidy
This commit is contained in:
commit
2ce911a231
3 changed files with 98 additions and 20 deletions
|
@ -23,12 +23,14 @@ The following things are supported:
|
||||||
* Browsing all artists/albums/tracks
|
* Browsing all artists/albums/tracks
|
||||||
* Searching for any terms
|
* Searching for any terms
|
||||||
* Browsing playlists
|
* Browsing playlists
|
||||||
|
* Searching explicitly for one of: artists, albums, tracks
|
||||||
|
|
||||||
The following things are **not** supported:
|
The following things are **not** supported:
|
||||||
|
|
||||||
* Creating, editing and deleting playlists
|
* Creating, editing and deleting playlists
|
||||||
* Advanced searching. This means everything except 'any', 'artist', 'artist' and 'album'
|
|
||||||
* Subsonics smart playlists
|
* Subsonics smart playlists
|
||||||
|
* Searching for a combination of filters (artist and album, artist and track, etc.)
|
||||||
|
* Browsing more than 2 levels deep in the Subsonic directory tree
|
||||||
|
|
||||||
## Contributors
|
## Contributors
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
from mopidy import backend
|
from mopidy import backend
|
||||||
from mopidy_subidy import uri
|
from mopidy_subidy import uri
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
class SubidyPlaybackProvider(backend.PlaybackProvider):
|
class SubidyPlaybackProvider(backend.PlaybackProvider):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
|
@ -7,4 +10,7 @@ class SubidyPlaybackProvider(backend.PlaybackProvider):
|
||||||
self.subsonic_api = self.backend.subsonic_api
|
self.subsonic_api = self.backend.subsonic_api
|
||||||
|
|
||||||
def translate_uri(self, translate_uri):
|
def translate_uri(self, translate_uri):
|
||||||
return self.subsonic_api.get_song_stream_uri(uri.get_song_id(translate_uri))
|
song_id = uri.get_song_id(translate_uri)
|
||||||
|
censored_url = self.subsonic_api.get_censored_song_stream_uri(song_id)
|
||||||
|
logger.debug("Loading song from subsonic with url: '%s'" % censored_url)
|
||||||
|
return self.subsonic_api.get_song_stream_uri(song_id)
|
||||||
|
|
|
@ -41,18 +41,29 @@ class SubsonicApi():
|
||||||
template = '%s/stream.view?id=%s&u=%s&p=%s&c=mopidy&v=1.14'
|
template = '%s/stream.view?id=%s&u=%s&p=%s&c=mopidy&v=1.14'
|
||||||
return template % (self.url, song_id, self.username, self.password)
|
return template % (self.url, song_id, self.username, self.password)
|
||||||
|
|
||||||
|
def get_censored_song_stream_uri(self, song_id):
|
||||||
|
template = '%s/stream.view?id=%s&u=******&p=******&c=mopidy&v=1.14'
|
||||||
|
return template % (self.url, song_id)
|
||||||
|
|
||||||
def find_raw(self, query, exclude_artists=False, exclude_albums=False, exclude_songs=False):
|
def find_raw(self, query, exclude_artists=False, exclude_albums=False, exclude_songs=False):
|
||||||
response = self.connection.search2(
|
try:
|
||||||
query.encode('utf-8'),
|
response = self.connection.search2(
|
||||||
MAX_SEARCH_RESULTS if not exclude_artists else 0, 0,
|
query.encode('utf-8'),
|
||||||
MAX_SEARCH_RESULTS if not exclude_albums else 0, 0,
|
MAX_SEARCH_RESULTS if not exclude_artists else 0, 0,
|
||||||
MAX_SEARCH_RESULTS if not exclude_songs else 0, 0)
|
MAX_SEARCH_RESULTS if not exclude_albums else 0, 0,
|
||||||
|
MAX_SEARCH_RESULTS if not exclude_songs else 0, 0)
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning('Connecting to subsonic failed when searching.')
|
||||||
|
return None
|
||||||
if response.get('status') != RESPONSE_OK:
|
if response.get('status') != RESPONSE_OK:
|
||||||
|
logger.warning('Got non-okay status code from subsonic: %s' % response.get('status'))
|
||||||
return None
|
return None
|
||||||
return response.get('searchResult2')
|
return response.get('searchResult2')
|
||||||
|
|
||||||
def find_as_search_result(self, query, exclude_artists=False, exclude_albums=False, exclude_songs=False):
|
def find_as_search_result(self, query, exclude_artists=False, exclude_albums=False, exclude_songs=False):
|
||||||
result = self.find_raw(query)
|
result = self.find_raw(query)
|
||||||
|
if result is None:
|
||||||
|
return None
|
||||||
return SearchResult(
|
return SearchResult(
|
||||||
uri=uri.get_search_uri(query),
|
uri=uri.get_search_uri(query),
|
||||||
artists=[self.raw_artist_to_artist(artist) for artist in result.get('artist') or []],
|
artists=[self.raw_artist_to_artist(artist) for artist in result.get('artist') or []],
|
||||||
|
@ -61,48 +72,88 @@ class SubsonicApi():
|
||||||
|
|
||||||
|
|
||||||
def get_raw_artists(self):
|
def get_raw_artists(self):
|
||||||
response = self.connection.getIndexes()
|
try:
|
||||||
|
response = self.connection.getIndexes()
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning('Connecting to subsonic failed when loading list of artists.')
|
||||||
|
return []
|
||||||
if response.get('status') != RESPONSE_OK:
|
if response.get('status') != RESPONSE_OK:
|
||||||
return None
|
logger.warning('Got non-okay status code from subsonic: %s' % response.get('status'))
|
||||||
|
return []
|
||||||
letters = response.get('indexes').get('index')
|
letters = response.get('indexes').get('index')
|
||||||
if letters is not None:
|
if letters is not None:
|
||||||
artists = [artist for letter in letters for artist in letter.get('artist')]
|
artists = [artist for letter in letters for artist in letter.get('artist') or []]
|
||||||
return artists
|
return artists
|
||||||
return None
|
logger.warning('Subsonic does not seem to have any artists in it\'s library.')
|
||||||
|
return []
|
||||||
|
|
||||||
def get_song_by_id(self, song_id):
|
def get_song_by_id(self, song_id):
|
||||||
response = self.connection.getSong(song_id)
|
try:
|
||||||
|
response = self.connection.getSong(song_id)
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning('Connecting to subsonic failed when loading song by id.')
|
||||||
|
return None
|
||||||
if response.get('status') != RESPONSE_OK:
|
if response.get('status') != RESPONSE_OK:
|
||||||
|
logger.warning('Got non-okay status code from subsonic: %s' % response.get('status'))
|
||||||
return None
|
return None
|
||||||
return self.raw_song_to_track(response.get('song')) if response.get('song') is not None else None
|
return self.raw_song_to_track(response.get('song')) if response.get('song') is not None else None
|
||||||
|
|
||||||
def get_album_by_id(self, album_id):
|
def get_album_by_id(self, album_id):
|
||||||
response = self.connection.getAlbum(album_id)
|
try:
|
||||||
|
response = self.connection.getAlbum(album_id)
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning('Connecting to subsonic failed when loading album by id.')
|
||||||
|
return None
|
||||||
if response.get('status') != RESPONSE_OK:
|
if response.get('status') != RESPONSE_OK:
|
||||||
|
logger.warning('Got non-okay status code from subsonic: %s' % response.get('status'))
|
||||||
return None
|
return None
|
||||||
return self.raw_album_to_album(response.get('album')) if response.get('album') is not None else None
|
return self.raw_album_to_album(response.get('album')) if response.get('album') is not None else None
|
||||||
|
|
||||||
def get_artist_by_id(self, artist_id):
|
def get_artist_by_id(self, artist_id):
|
||||||
response = self.connection.getArtist(artist_id)
|
try:
|
||||||
|
response = self.connection.getArtist(artist_id)
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning('Connecting to subsonic failed when loading artist by id.')
|
||||||
|
return None
|
||||||
if response.get('status') != RESPONSE_OK:
|
if response.get('status') != RESPONSE_OK:
|
||||||
|
logger.warning('Got non-okay status code from subsonic: %s' % response.get('status'))
|
||||||
return None
|
return None
|
||||||
return self.raw_artist_to_artist(response.get('artist')) if response.get('artist') is not None else None
|
return self.raw_artist_to_artist(response.get('artist')) if response.get('artist') is not None else None
|
||||||
|
|
||||||
def get_raw_playlists(self):
|
def get_raw_playlists(self):
|
||||||
response = self.connection.getPlaylists()
|
try:
|
||||||
|
response = self.connection.getPlaylists()
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning('Connecting to subsonic failed when loading list of playlists.')
|
||||||
|
return []
|
||||||
if response.get('status') != RESPONSE_OK:
|
if response.get('status') != RESPONSE_OK:
|
||||||
return None
|
logger.warning('Got non-okay status code from subsonic: %s' % response.get('status'))
|
||||||
return response.get('playlists').get('playlist')
|
return []
|
||||||
|
playlists = response.get('playlists').get('playlist')
|
||||||
|
if playlists is None:
|
||||||
|
logger.warning('Subsonic does not seem to have any playlists in it\'s library.')
|
||||||
|
return []
|
||||||
|
return playlists
|
||||||
|
|
||||||
def get_raw_playlist(self, playlist_id):
|
def get_raw_playlist(self, playlist_id):
|
||||||
response = self.connection.getPlaylist(playlist_id)
|
try:
|
||||||
|
response = self.connection.getPlaylist(playlist_id)
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning('Connecting to subsonic failed when loading playlist.')
|
||||||
|
return None
|
||||||
if response.get('status') != RESPONSE_OK:
|
if response.get('status') != RESPONSE_OK:
|
||||||
|
logger.warning('Got non-okay status code from subsonic: %s' % response.get('status'))
|
||||||
return None
|
return None
|
||||||
return response.get('playlist')
|
return response.get('playlist')
|
||||||
|
|
||||||
def get_raw_dir(self, parent_id):
|
def get_raw_dir(self, parent_id):
|
||||||
response = self.connection.getMusicDirectory(parent_id)
|
try:
|
||||||
|
response = self.connection.getMusicDirectory(parent_id)
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning('Connecting to subsonic failed when listing content of music directory.')
|
||||||
|
return None
|
||||||
if response.get('status') != RESPONSE_OK:
|
if response.get('status') != RESPONSE_OK:
|
||||||
|
logger.warning('Got non-okay status code from subsonic: %s' % response.get('status'))
|
||||||
return None
|
return None
|
||||||
directory = response.get('directory')
|
directory = response.get('directory')
|
||||||
if directory is not None:
|
if directory is not None:
|
||||||
|
@ -144,14 +195,20 @@ class SubsonicApi():
|
||||||
|
|
||||||
def get_playlist_as_songs_as_refs(self, playlist_id):
|
def get_playlist_as_songs_as_refs(self, playlist_id):
|
||||||
playlist = self.get_raw_playlist(playlist_id)
|
playlist = self.get_raw_playlist(playlist_id)
|
||||||
|
if playlist is None:
|
||||||
|
return None
|
||||||
return [self.raw_song_to_ref(song) for song in playlist.get('entry')]
|
return [self.raw_song_to_ref(song) for song in playlist.get('entry')]
|
||||||
|
|
||||||
def raw_song_to_ref(self, song):
|
def raw_song_to_ref(self, song):
|
||||||
|
if song is None:
|
||||||
|
return None
|
||||||
return Ref.track(
|
return Ref.track(
|
||||||
name=song.get('title') or UNKNOWN_SONG,
|
name=song.get('title') or UNKNOWN_SONG,
|
||||||
uri=uri.get_song_uri(song.get('id')))
|
uri=uri.get_song_uri(song.get('id')))
|
||||||
|
|
||||||
def raw_song_to_track(self, song):
|
def raw_song_to_track(self, song):
|
||||||
|
if song is None:
|
||||||
|
return None
|
||||||
return Track(
|
return Track(
|
||||||
name=song.get('title') or UNKNOWN_SONG,
|
name=song.get('title') or UNKNOWN_SONG,
|
||||||
uri=uri.get_song_uri(song.get('id')),
|
uri=uri.get_song_uri(song.get('id')),
|
||||||
|
@ -166,13 +223,18 @@ class SubsonicApi():
|
||||||
uri=uri.get_artist_uri(song.get('artistId')))],
|
uri=uri.get_artist_uri(song.get('artistId')))],
|
||||||
album=Album(
|
album=Album(
|
||||||
name=song.get('album'),
|
name=song.get('album'),
|
||||||
uri=uri.get_album_uri('albumId')))
|
uri=uri.get_album_uri(song.get('albumId'))))
|
||||||
|
|
||||||
def raw_album_to_ref(self, album):
|
def raw_album_to_ref(self, album):
|
||||||
|
if album is None:
|
||||||
|
return None
|
||||||
return Ref.album(
|
return Ref.album(
|
||||||
name=album.get('title') or album.get('name') or UNKNOWN_ALBUM,
|
name=album.get('title') or album.get('name') or UNKNOWN_ALBUM,
|
||||||
uri=uri.get_album_uri(album.get('id')))
|
uri=uri.get_album_uri(album.get('id')))
|
||||||
|
|
||||||
def raw_album_to_album(self, album):
|
def raw_album_to_album(self, album):
|
||||||
|
if album is None:
|
||||||
|
return None
|
||||||
return Album(
|
return Album(
|
||||||
name=album.get('title') or album.get('name') or UNKNOWN_ALBUM,
|
name=album.get('title') or album.get('name') or UNKNOWN_ALBUM,
|
||||||
uri=uri.get_album_uri(album.get('id')),
|
uri=uri.get_album_uri(album.get('id')),
|
||||||
|
@ -181,16 +243,22 @@ class SubsonicApi():
|
||||||
uri=uri.get_artist_uri(album.get('artistId')))])
|
uri=uri.get_artist_uri(album.get('artistId')))])
|
||||||
|
|
||||||
def raw_artist_to_ref(self, artist):
|
def raw_artist_to_ref(self, artist):
|
||||||
|
if artist is None:
|
||||||
|
return None
|
||||||
return Ref.artist(
|
return Ref.artist(
|
||||||
name=artist.get('name') or UNKNOWN_ARTIST,
|
name=artist.get('name') or UNKNOWN_ARTIST,
|
||||||
uri=uri.get_artist_uri(artist.get('id')))
|
uri=uri.get_artist_uri(artist.get('id')))
|
||||||
|
|
||||||
def raw_artist_to_artist(self, artist):
|
def raw_artist_to_artist(self, artist):
|
||||||
|
if artist is None:
|
||||||
|
return None
|
||||||
return Artist(
|
return Artist(
|
||||||
name=artist.get('name') or UNKNOWN_ARTIST,
|
name=artist.get('name') or UNKNOWN_ARTIST,
|
||||||
uri=uri.get_artist_uri(artist.get('id')))
|
uri=uri.get_artist_uri(artist.get('id')))
|
||||||
|
|
||||||
def raw_playlist_to_playlist(self, playlist):
|
def raw_playlist_to_playlist(self, playlist):
|
||||||
|
if playlist is None:
|
||||||
|
return None
|
||||||
entries = playlist.get('entry')
|
entries = playlist.get('entry')
|
||||||
tracks = [self.raw_song_to_track(song) for song in entries] if entries is not None else None
|
tracks = [self.raw_song_to_track(song) for song in entries] if entries is not None else None
|
||||||
return Playlist(
|
return Playlist(
|
||||||
|
@ -199,6 +267,8 @@ class SubsonicApi():
|
||||||
tracks=tracks)
|
tracks=tracks)
|
||||||
|
|
||||||
def raw_playlist_to_ref(self, playlist):
|
def raw_playlist_to_ref(self, playlist):
|
||||||
|
if playlist is None:
|
||||||
|
return None
|
||||||
return Ref.playlist(
|
return Ref.playlist(
|
||||||
uri=uri.get_playlist_uri(playlist.get('id')),
|
uri=uri.get_playlist_uri(playlist.get('id')),
|
||||||
name=playlist.get('name'))
|
name=playlist.get('name'))
|
||||||
|
|
Loading…
Add table
Reference in a new issue