diff --git a/PKGBUILD b/PKGBUILD new file mode 100644 index 0000000..dc95100 --- /dev/null +++ b/PKGBUILD @@ -0,0 +1,35 @@ +# Maintainer: Matthew Gamble +# Contributor: Frederick Gnodtke + +pkgname=mopidy-subidy +pkgver=1.3.0 +pkgrel=2 +pkgdesc="Mopidy extension for playing music from Subsonic servers" +arch=("any") +url="https://git.hannover.ccc.de/lubiana/mopidy-subidy/releases" +license=('BSD') +depends=( + "mopidy" + "python" + "python-setuptools" + "python-pykka" + "python-pysonic" +) +source=("https://git.hannover.ccc.de/lubiana/mopidy-subidy/archive/1.0.0.tar.gz") +sha256sums=("ed78ce86da58fb42f6ddf9a8de72169d23521125b269b51054d69375b57c5b73") + +build() { + cd "mopidy-subidy" + + python setup.py build +} + +package() { + cd "mopidy-subidy" + + PYTHONHASHSEED=0 python setup.py install --root="${pkgdir}" --optimize=1 --skip-build + + install -Dm644 LICENSE "${pkgdir}/usr/share/licenses/mopidy-subidy/LICENSE" + install -Dm644 README.rst "${pkgdir}/usr/share/doc/mopidy-subidy/README.rst" + install -Dm644 CHANGELOG.rst "${pkgdir}/usr/share/doc/mopidy-subidy/CHANGELOG.rst" +} diff --git a/README.rst b/README.rst index bf10e40..dc5e316 100644 --- a/README.rst +++ b/README.rst @@ -14,6 +14,8 @@ Mopidy-Subidy :target: https://codecov.io/gh/Prior99/mopidy-subidy :alt: Test coverage +**This library is actively looking for maintainers to help out as I do not have the time or need to maintain this anymore. Please contact me if you feel that you could maintain this.** + A Subsonic backend for Mopidy using `py-sonic `_. diff --git a/mopidy_subidy/library.py b/mopidy_subidy/library.py index 6da42e0..bc5e7c2 100644 --- a/mopidy_subidy/library.py +++ b/mopidy_subidy/library.py @@ -14,6 +14,7 @@ class SubidyLibraryProvider(backend.LibraryProvider): dict(id="artists", name="Artists"), dict(id="albums", name="Albums"), dict(id="rootdirs", name="Directories"), + dict(id="random", name="Random"), ] # Create a dict with the keys being the `id`s in `vdir_templates` # and the values being objects containing the vdir `id`, @@ -52,6 +53,9 @@ class SubidyLibraryProvider(backend.LibraryProvider): def browse_rootdirs(self): return self.subsonic_api.get_rootdirs_as_refs() + def browse_random_songs(self): + return self.subsonic_api.get_random_songs_as_refs() + def browse_diritems(self, directory_id): return self.subsonic_api.get_diritems_as_refs(directory_id) @@ -82,7 +86,7 @@ class SubidyLibraryProvider(backend.LibraryProvider): def browse(self, browse_uri): if browse_uri == uri.get_vdir_uri("root"): - root_vdir_names = ["rootdirs", "artists", "albums"] + root_vdir_names = ["rootdirs", "artists", "albums", "random"] root_vdirs = [ self._vdirs[vdir_name] for vdir_name in root_vdir_names ] @@ -96,6 +100,9 @@ class SubidyLibraryProvider(backend.LibraryProvider): return self.browse_artists() elif browse_uri == uri.get_vdir_uri("albums"): return self.browse_albums() + elif browse_uri == uri.get_vdir_uri("random"): + return self.browse_random_songs() + else: uri_type = uri.get_type(browse_uri) if uri_type == uri.DIRECTORY: @@ -138,15 +145,34 @@ class SubidyLibraryProvider(backend.LibraryProvider): return SearchResult(tracks=[track]) def search_by_artist_and_album(self, artist_name, album_name): - artists = self.subsonic_api.get_raw_artists() - artist = next( - item for item in artists if artist_name in item.get("name") - ) - albums = self.subsonic_api.get_raw_albums(artist.get("id")) - album = next(item for item in albums if album_name in item.get("title")) - return SearchResult( - tracks=self.subsonic_api.get_songs_as_tracks(album.get("id")) - ) + artists = self.subsonic_api.find_raw(artist_name).get("artist") + if artists is None: + return None + tracks = [] + for artist in artists: + for album in self.subsonic_api.get_raw_albums(artist.get("id")): + if album_name in album.get("name"): + tracks.extend( + self.subsonic_api.get_songs_as_tracks(album.get("id")) + ) + return SearchResult(tracks=tracks) + + def search_by_artist(self, artist_name, exact): + result = self.subsonic_api.find_raw(artist_name) + if result is None: + return None + tracks = [] + for artist in result.get("artist"): + if exact: + if not artist.get("name") == artist_name: + continue + + tracks.extend( + self.subsonic_api.get_artist_as_songs_as_tracks_iter( + artist.get("id") + ) + ) + return SearchResult(uri=uri.get_search_uri(artist_name), tracks=tracks) def get_distinct(self, field, query): search_result = self.search(query) @@ -173,9 +199,12 @@ class SubidyLibraryProvider(backend.LibraryProvider): query.get("artist")[0], query.get("album")[0] ) if "artist" in query: - return self.subsonic_api.find_as_search_result( - query.get("artist")[0] - ) + return self.search_by_artist(query.get("artist")[0], exact) + if "comment" in query: + if query.get("comment")[0] == "random": + return SearchResult( + tracks=self.subsonic_api.get_random_songs_as_tracks() + ) if "any" in query: return self.subsonic_api.find_as_search_result(query.get("any")[0]) return SearchResult(artists=self.subsonic_api.get_artists_as_artists()) diff --git a/mopidy_subidy/playback.py b/mopidy_subidy/playback.py index 83c0a7f..972446c 100644 --- a/mopidy_subidy/playback.py +++ b/mopidy_subidy/playback.py @@ -16,3 +16,6 @@ class SubidyPlaybackProvider(backend.PlaybackProvider): 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) + + def should_download(self, uri): + return True diff --git a/mopidy_subidy/subsonic_api.py b/mopidy_subidy/subsonic_api.py index e8b4ad2..50aacf7 100644 --- a/mopidy_subidy/subsonic_api.py +++ b/mopidy_subidy/subsonic_api.py @@ -101,7 +101,7 @@ class SubsonicApi: exclude_songs=False, ): try: - response = self.connection.search2( + response = self.connection.search3( query.encode("utf-8"), MAX_SEARCH_RESULTS if not exclude_artists else 0, 0, @@ -119,7 +119,7 @@ class SubsonicApi: % response.get("status") ) return None - return response.get("searchResult2") + return response.get("searchResult3") def find_as_search_result( self, @@ -410,9 +410,30 @@ class SubsonicApi: return songs return [] - def get_raw_album_list(self, ltype, size=MAX_LIST_RESULTS): + def get_raw_random_song(self, size=MAX_LIST_RESULTS): try: - response = self.connection.getAlbumList2(ltype=ltype, size=size) + response = self.connection.getRandomSongs(size) + except Exception: + logger.warning( + "Connecting to subsonic failed when loading ramdom song list." + ) + return [] + if response.get("status") != RESPONSE_OK: + logger.warning( + "Got non-okay status code from subsonic: %s" + % response.get("status") + ) + return [] + songs = response.get("randomSongs").get("song") + if songs is not None: + return songs + return [] + + def get_more_albums(self, ltype, size=MAX_LIST_RESULTS, offset=0): + try: + response = self.connection.getAlbumList2( + ltype=ltype, size=size, offset=offset + ) except Exception: logger.warning( "Connecting to subsonic failed when loading album list." @@ -429,6 +450,24 @@ class SubsonicApi: return albums return [] + def get_raw_album_list(self, ltype, size=MAX_LIST_RESULTS): + """ + Subsonic servers don't offer any way to retrieve the total number + of albums to get, and the spec states that the max number returned + for `getAlbumList2` is 500. To get all the albums, we make a + `getAlbumList2` request each time the response contains 500 albums. If + it does not, we assume we have all the albums and return them. + """ + offset = 0 + total = [] + albums = self.get_more_albums(ltype, size, offset) + total = albums + while len(albums) == size: + offset = offset + size + albums = self.get_more_albums(ltype, size, offset) + total = total + albums + return total + def get_albums_as_refs(self, artist_id=None): albums = ( self.get_raw_album_list("alphabeticalByName") @@ -475,6 +514,16 @@ class SubsonicApi: for diritem in self.get_raw_dir(directory_id) ] + def get_random_songs_as_refs(self): + return [ + self.raw_song_to_ref(song) for song in self.get_raw_random_song(75) + ] + + def get_random_songs_as_tracks(self): + return [ + self.raw_song_to_track(song) for song in self.get_raw_random_song() + ] + def get_artists_as_artists(self): return [ self.raw_artist_to_artist(artist) diff --git a/mopidy_subidy/uri.py b/mopidy_subidy/uri.py index 57338b8..a4d8464 100644 --- a/mopidy_subidy/uri.py +++ b/mopidy_subidy/uri.py @@ -8,6 +8,7 @@ DIRECTORY = "directory" VDIR = "vdir" PREFIX = "subidy" SEARCH = "search" +RANDOM = "random" regex = re.compile(r"(\w+?):(\w+?)(?::|$)(.+?)?$") diff --git a/setup.cfg b/setup.cfg index 2bff96f..9610cce 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = Mopidy-Subidy -version = 1.0.0 +version = 1.1.0 url = https://github.com/Prior99/mopidy-subidy author = prior99 author_email = fgnodtke@cronosx.de