Compare commits
20 commits
Author | SHA1 | Date | |
---|---|---|---|
ebb2dab571 | |||
62429f22bd | |||
![]() |
4bc641e95a | ||
![]() |
5ce65f0a08 | ||
![]() |
b5e0de1e4d | ||
![]() |
e8493923c1 | ||
![]() |
32f74f28e9 | ||
![]() |
cf588481c6 | ||
![]() |
876bec6711 | ||
![]() |
7064dd9e50 | ||
![]() |
81a62cdca4 | ||
![]() |
1bc9e35d83 | ||
![]() |
6857653cdb | ||
![]() |
511a101c2c | ||
![]() |
f7090127fe | ||
![]() |
31023236ae | ||
![]() |
eff25672d9 | ||
![]() |
584209c134 | ||
![]() |
ee3e36408d | ||
![]() |
713845090c |
7 changed files with 137 additions and 18 deletions
35
PKGBUILD
Normal file
35
PKGBUILD
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
# Maintainer: Matthew Gamble <git@matthewgamble.net>
|
||||||
|
# Contributor: Frederick Gnodtke <fgnodtke at cronosx dot de>
|
||||||
|
|
||||||
|
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"
|
||||||
|
}
|
|
@ -14,6 +14,8 @@ Mopidy-Subidy
|
||||||
:target: https://codecov.io/gh/Prior99/mopidy-subidy
|
:target: https://codecov.io/gh/Prior99/mopidy-subidy
|
||||||
:alt: Test coverage
|
: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
|
A Subsonic backend for Mopidy using `py-sonic
|
||||||
<https://github.com/crustymonkey/py-sonic>`_.
|
<https://github.com/crustymonkey/py-sonic>`_.
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,7 @@ class SubidyLibraryProvider(backend.LibraryProvider):
|
||||||
dict(id="artists", name="Artists"),
|
dict(id="artists", name="Artists"),
|
||||||
dict(id="albums", name="Albums"),
|
dict(id="albums", name="Albums"),
|
||||||
dict(id="rootdirs", name="Directories"),
|
dict(id="rootdirs", name="Directories"),
|
||||||
|
dict(id="random", name="Random"),
|
||||||
]
|
]
|
||||||
# Create a dict with the keys being the `id`s in `vdir_templates`
|
# Create a dict with the keys being the `id`s in `vdir_templates`
|
||||||
# and the values being objects containing the vdir `id`,
|
# and the values being objects containing the vdir `id`,
|
||||||
|
@ -52,6 +53,9 @@ class SubidyLibraryProvider(backend.LibraryProvider):
|
||||||
def browse_rootdirs(self):
|
def browse_rootdirs(self):
|
||||||
return self.subsonic_api.get_rootdirs_as_refs()
|
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):
|
def browse_diritems(self, directory_id):
|
||||||
return self.subsonic_api.get_diritems_as_refs(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):
|
def browse(self, browse_uri):
|
||||||
if browse_uri == uri.get_vdir_uri("root"):
|
if browse_uri == uri.get_vdir_uri("root"):
|
||||||
root_vdir_names = ["rootdirs", "artists", "albums"]
|
root_vdir_names = ["rootdirs", "artists", "albums", "random"]
|
||||||
root_vdirs = [
|
root_vdirs = [
|
||||||
self._vdirs[vdir_name] for vdir_name in root_vdir_names
|
self._vdirs[vdir_name] for vdir_name in root_vdir_names
|
||||||
]
|
]
|
||||||
|
@ -96,6 +100,9 @@ class SubidyLibraryProvider(backend.LibraryProvider):
|
||||||
return self.browse_artists()
|
return self.browse_artists()
|
||||||
elif browse_uri == uri.get_vdir_uri("albums"):
|
elif browse_uri == uri.get_vdir_uri("albums"):
|
||||||
return self.browse_albums()
|
return self.browse_albums()
|
||||||
|
elif browse_uri == uri.get_vdir_uri("random"):
|
||||||
|
return self.browse_random_songs()
|
||||||
|
|
||||||
else:
|
else:
|
||||||
uri_type = uri.get_type(browse_uri)
|
uri_type = uri.get_type(browse_uri)
|
||||||
if uri_type == uri.DIRECTORY:
|
if uri_type == uri.DIRECTORY:
|
||||||
|
@ -138,15 +145,34 @@ class SubidyLibraryProvider(backend.LibraryProvider):
|
||||||
return SearchResult(tracks=[track])
|
return SearchResult(tracks=[track])
|
||||||
|
|
||||||
def search_by_artist_and_album(self, artist_name, album_name):
|
def search_by_artist_and_album(self, artist_name, album_name):
|
||||||
artists = self.subsonic_api.get_raw_artists()
|
artists = self.subsonic_api.find_raw(artist_name).get("artist")
|
||||||
artist = next(
|
if artists is None:
|
||||||
item for item in artists if artist_name in item.get("name")
|
return None
|
||||||
)
|
tracks = []
|
||||||
albums = self.subsonic_api.get_raw_albums(artist.get("id"))
|
for artist in artists:
|
||||||
album = next(item for item in albums if album_name in item.get("title"))
|
for album in self.subsonic_api.get_raw_albums(artist.get("id")):
|
||||||
return SearchResult(
|
if album_name in album.get("name"):
|
||||||
tracks=self.subsonic_api.get_songs_as_tracks(album.get("id"))
|
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):
|
def get_distinct(self, field, query):
|
||||||
search_result = self.search(query)
|
search_result = self.search(query)
|
||||||
|
@ -173,9 +199,12 @@ class SubidyLibraryProvider(backend.LibraryProvider):
|
||||||
query.get("artist")[0], query.get("album")[0]
|
query.get("artist")[0], query.get("album")[0]
|
||||||
)
|
)
|
||||||
if "artist" in query:
|
if "artist" in query:
|
||||||
return self.subsonic_api.find_as_search_result(
|
return self.search_by_artist(query.get("artist")[0], exact)
|
||||||
query.get("artist")[0]
|
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:
|
if "any" in query:
|
||||||
return self.subsonic_api.find_as_search_result(query.get("any")[0])
|
return self.subsonic_api.find_as_search_result(query.get("any")[0])
|
||||||
return SearchResult(artists=self.subsonic_api.get_artists_as_artists())
|
return SearchResult(artists=self.subsonic_api.get_artists_as_artists())
|
||||||
|
|
|
@ -16,3 +16,6 @@ class SubidyPlaybackProvider(backend.PlaybackProvider):
|
||||||
censored_url = self.subsonic_api.get_censored_song_stream_uri(song_id)
|
censored_url = self.subsonic_api.get_censored_song_stream_uri(song_id)
|
||||||
logger.debug("Loading song from subsonic with url: '%s'" % censored_url)
|
logger.debug("Loading song from subsonic with url: '%s'" % censored_url)
|
||||||
return self.subsonic_api.get_song_stream_uri(song_id)
|
return self.subsonic_api.get_song_stream_uri(song_id)
|
||||||
|
|
||||||
|
def should_download(self, uri):
|
||||||
|
return True
|
||||||
|
|
|
@ -101,7 +101,7 @@ class SubsonicApi:
|
||||||
exclude_songs=False,
|
exclude_songs=False,
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
response = self.connection.search2(
|
response = self.connection.search3(
|
||||||
query.encode("utf-8"),
|
query.encode("utf-8"),
|
||||||
MAX_SEARCH_RESULTS if not exclude_artists else 0,
|
MAX_SEARCH_RESULTS if not exclude_artists else 0,
|
||||||
0,
|
0,
|
||||||
|
@ -119,7 +119,7 @@ class SubsonicApi:
|
||||||
% response.get("status")
|
% response.get("status")
|
||||||
)
|
)
|
||||||
return None
|
return None
|
||||||
return response.get("searchResult2")
|
return response.get("searchResult3")
|
||||||
|
|
||||||
def find_as_search_result(
|
def find_as_search_result(
|
||||||
self,
|
self,
|
||||||
|
@ -410,9 +410,30 @@ class SubsonicApi:
|
||||||
return songs
|
return songs
|
||||||
return []
|
return []
|
||||||
|
|
||||||
def get_raw_album_list(self, ltype, size=MAX_LIST_RESULTS):
|
def get_raw_random_song(self, size=MAX_LIST_RESULTS):
|
||||||
try:
|
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:
|
except Exception:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
"Connecting to subsonic failed when loading album list."
|
"Connecting to subsonic failed when loading album list."
|
||||||
|
@ -429,6 +450,24 @@ class SubsonicApi:
|
||||||
return albums
|
return albums
|
||||||
return []
|
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):
|
def get_albums_as_refs(self, artist_id=None):
|
||||||
albums = (
|
albums = (
|
||||||
self.get_raw_album_list("alphabeticalByName")
|
self.get_raw_album_list("alphabeticalByName")
|
||||||
|
@ -475,6 +514,16 @@ class SubsonicApi:
|
||||||
for diritem in self.get_raw_dir(directory_id)
|
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):
|
def get_artists_as_artists(self):
|
||||||
return [
|
return [
|
||||||
self.raw_artist_to_artist(artist)
|
self.raw_artist_to_artist(artist)
|
||||||
|
|
|
@ -8,6 +8,7 @@ DIRECTORY = "directory"
|
||||||
VDIR = "vdir"
|
VDIR = "vdir"
|
||||||
PREFIX = "subidy"
|
PREFIX = "subidy"
|
||||||
SEARCH = "search"
|
SEARCH = "search"
|
||||||
|
RANDOM = "random"
|
||||||
|
|
||||||
regex = re.compile(r"(\w+?):(\w+?)(?::|$)(.+?)?$")
|
regex = re.compile(r"(\w+?):(\w+?)(?::|$)(.+?)?$")
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[metadata]
|
[metadata]
|
||||||
name = Mopidy-Subidy
|
name = Mopidy-Subidy
|
||||||
version = 1.0.0
|
version = 1.1.0
|
||||||
url = https://github.com/Prior99/mopidy-subidy
|
url = https://github.com/Prior99/mopidy-subidy
|
||||||
author = prior99
|
author = prior99
|
||||||
author_email = fgnodtke@cronosx.de
|
author_email = fgnodtke@cronosx.de
|
||||||
|
|
Loading…
Add table
Reference in a new issue