-
Notifications
You must be signed in to change notification settings - Fork 1.8k
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
Subsonic API #2321
Subsonic API #2321
Conversation
Interesting! Any comments about how this works in general? How broadly have you observed its compatibility? Also, it looks like changes to a few other files got wrapped up in this PR. |
I edited the first post. Some comments on why I had to change others files (feel free to guide me on how I could have done it better):
Also, pagination is done entirely in python, which is rather inefficient. We'll need to to some work as stated in #750. |
Got it; thanks for elaborating! The one thing that looks like it might be a problem is the dependency on And, to talk more about the long-term picture: I'm really interested in moving toward AURA, our new API that should replace our hacked-together initial API. There's even been some talk about constructing an AURA-to-Subsonic "adapter," more or less exactly like this, to connect to clients that support that API already. Would you be interested in exploring that direction? |
I'll move reusable part of And regarding
So the real question is "Do you have a real need for a new API ?" |
Also, to fix appveyor build, I would need to modify |
@sampsyo as you see I made some test trying to get |
OK! Factoring out the random functionality sounds like a fine strategy if we can do it cleanly. Here's my motivation for focusing on AURA in the future: interoperation. I'd like to be able to unify music software of all stripes, not just beets, so people don't have to invent their own APIs or use the cumbersome ones that already exist. In particular, it should be easy to get new tools—new players, new caching layers, etc.—up and running. That rules out XML in my opinion, as you're discovering with the lxml dependency hell. 😃 I know that's something of a dream that's low on practicality, so I don't object to adding a Subsonic API directly to beets for now. I'm just letting you know that, maybe someday, we might want to supplant it with the more grand vision. Back to the matter at hand: I think we need to avoid checking other people's code into this repository… maybe we should just mark the tests as skippable if lxml isn't available? That's what we've done for the tests that require GStreamer, for example. One other design point: might it make sense to make this its own plugin? It doesn't seem to share much with the existing web plugin, except that they both use Flask. It might be simpler to explain and use the different set of requirements for two different plugins. |
Got it! We can move subsonic to it's own plugin, but that would make beets listen on another port. I think of the |
Yeah, we'd like to have an extensible server mode—see #718. That direction would allow plugins to extend the web functionality, instead of needing to roll all possible web-related functionality into a single monolithic plugin. |
appveyor.yml
Outdated
- PYTHON: C:\Python35 | ||
TOX_ENV: py35-test |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this change still needed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, I see now—you're using this flag to disable tests on the command line.
Instead would you mind looking into using @unittest.skipIf()
or self.skipTest()
in the tests themselves? This makes for a graceful degradation: developers can just type nosetests
, regardless of whether they're on Windows or not, and have the appropriate set of tests skipped based on their current configuration. There are a few examples of this in the current test suite in case that's helpful.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's what I wanted to do firstly, but that implies that we totally remove xmlunittest
from tox deps. I fear that this test case will be forgotten if we do that.
Also, we still want this to run on travis, so do I just add the dependency in .travis.yml
file ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, that's really tricky. So to summarize, the new tests have a new dependency from pip, so it makes sense to list that in the tox config. But that library has trouble installing on all platforms, because of the transitive dependency on lxml. 🙁
This is similar to (but not exactly the same as) the trouble we have with running tests against code that uses GStreamer on Travis. You can't install python-gobject from pip (who knows why), so you need to get it from the apt repositories. Then, to use that dependency in tests within tox's virtualenv, you have to use the "site packages" option, which lets all system-installed dependencies leak into the test environment. Clearly far from ideal, but it's the only way we've been able to make it work.
It looks like we're not the first to hit problems with lxml on AppVeyor:
http://help.appveyor.com/discussions/problems/2351-python-lxml-module
http://help.appveyor.com/discussions/problems/5330-pip-install-lxml-fails-with-missing-symbols
I don't, at the moment, see any good way around this. (Sometimes, CI seems like more effort than it's worth… 😢) I wish there were a way to make xmlunittest use the built-in ETree instead of lxml. I'll keep thinking about the right solution here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We came to the same conclusions 😃
In fact I would just need a pure python lib that would implement C14N, but lxml
is the only lib to do this for now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ideally, I think, we'd have the tox config add the dependency only on non-Windows platforms. It looks like platform-specific settings are being designed for tox, but aren't there yet? http://tox.readthedocs.io/en/latest/config-v2.html
That page does a pretty good job explaining why our use case is hard to accomplish in tox today. 👎
docs/plugins/web.rst
Outdated
It also implements a subset of `subsonic api`_ that allows any subsonic client | ||
to play music from beets. | ||
|
||
.. _subsonic api: http://www.subsonic.org/pages/api.jsp |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The docs should probably list the required dependencies (whether we use a separate plugin or expand the current web plugin).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll modify the doc when we'll be happy with everything else .
I took a look at #718, the ideas there are really good. But for now we still just have the web plugin all by himself, and I think that, for now, just extending it for further APIs is the way to go (simpler to dev, simpler for users). |
OK, cool. I get the convenience argument, and I now see that this doesn't have any extra dependencies beyond what we require for the web plugin itself. (In part because you hand-rolled your own XML serialization! Wow!) One small argument on the other side is that I imagine the Subsonic API would, in an ideal world, prefer to "live" on a different port (4040?) by default. Putting all the APIs under one roof means that they all get our funky joke port, 8337, by default. That's not the biggest deal in the world, but it wouldn't be the case for separate plugins. |
Regarding subsonic case, I don't think there is a "default" port to use. Other APIs may indeed use such ports, and would require some other Flask App instead of Flask Blueprints. And you're right, there is no new dependency for this API, only the ones already there for the |
I agree with the statement of there is not a 'default' port to use. Sure subsonic, libresonic, madsonic all point their ports to 4040 by default. However; most of the installs that I've seen and used change those ports to 80/443 or something personal to the user. |
I moved random functionnalities to a dedicated |
I think that we can consider this PR as mergeable now, tests with real clients are conclusive. |
OK; thank you! I'm still a little dissatisfied with the unfortunate impact the tests have on the Tox config, but I'll keep looking for a solution for this… |
@magne4000 : I don't see the tests being skipped if lxml is missing as per #2321 (comment) Or did i miss it? |
xmlunittest can't be installed without lxml previously installed, and testing xmlunittest is the real functionnality that we want to test for presence. |
beetsplug/web/subsonic.py
Outdated
if wrap or isinstance(d, dict): | ||
xml = u"{}</{}>".format(xml, root) | ||
|
||
return xml |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why all the string building instead of using the built-in XML builder in the standard library?
(I'm not a project maintainer, just jumping on here because I am also working with and potentially modifying the web API.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's something I based on some existing code, made some improvements.
I didn't want to completelly rewrite it, because it works and it's concise.
beetsplug/web/__init__.py
Outdated
@@ -309,6 +309,7 @@ def __init__(self): | |||
'host': u'127.0.0.1', | |||
'port': 8337, | |||
'cors': '', | |||
'subsonic': True |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If subsonic introduces a bunch of new dependencies, perhaps the default should be False?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No more deps than web
plugin, so no risks on that side.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, I misread the lxml comments, sorry.
To future-proof against future web plugin extensibility, perhaps it makes sense to namespace the subsonic API under |
beetsplug/web/__init__.py
Outdated
if self.config['subsonic']: | ||
from .subsonic import subsonic_routes | ||
app.register_blueprint(subsonic_routes) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems like this strategy could potentially be used to implement the extensible web plugin, #718!
Imagine implementing this as a separate plugin called subsonic
. In that plugin's __init__.py
, you could do something like this:
# beets/beetsplug/subsonic/__init__.py
from beets.beetsplug.web import register_blueprint
subsonic_routes = Blueprint('subsonic', 'subsonic')
@subsonic_routes.route('/rest/getMusicFolders.view', methods=['GET', 'POST'])
def v_music_folders():
pass # whatever
register_blueprint(subsonic_routes)
On the web/__init__.py
side, it would work like this:
# beets/beetsplug/web/__init__.py
_BLUEPRINTS = []
def register_blueprint(blueprint):
_BLUEPRINTS.append(blueprint)
class WebPlugin(BeetsPlugin):
# ...
def commands(self):
# ...
def func(lib, opts, args):
# ...
for blueprint in _BLUEPRINTS:
app.register_blueprint(blueprint)
This would require almost no changes to your patch, except that you would have to enable subsonic as an independent plugin. But when you run beet web
, it will (I think?) include the subsonic routes.
@sampsyo what do you think? Am I wrong about how plugin imports and bootstrapping works?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And then eventually the AURA stuff could just be implemented as a separate plugin namespaced under /aura/
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, this seems like it could work! The more typical strategy for beets would be to define a method on the plugin class to get a set of blueprints, which could also work here—the blueprint would still be defined at the module level, but it could be registered in a callback. That's worth exploring!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll find a proper way to do it 😃
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@irskep subsonic API is defined to run under |
Moved to dedicated plugin (with corresponding doc). |
Wow, I don't know about you, but this seems so much better to me. But reading the code, it occurred to me...why not just import Sorry for not thinking of this earlier. |
@irskep that's indeed simpler ! I'll document this in Web plugin then. |
As a random person who happens to be commenting on your PR, this looks good to me. Nice separation of new plugin, existing web plugin, and core library changes. |
@magne4000 which clients do not support arbitrary prefixes? As far as I am aware, you should be able to prefix the rest api at least one level down. |
wow, this is pretty awesome! i think the only thing that needs to be fixed here is to remove the conflicting file, but otherwise - is there a reason why this shouldn't be merged? |
Very excited by this so I thought I'd give it a spin! Unfortunately, both POST requests fail with the same traceback:
|
Whatever happened to this PR? How come it never got merged? |
I'd still be interested! If anyone's excited to take it up, it would be great to split out all the components of this large change. There are changes to the random plugin, some CI testing changes, some core field additions, and then the plugin itself. It would be truly great to be able to review and tackle those pieces one at a time so we can be confident in the core changes. |
I probably won't have time anytime soon to update this PR, so if someone wants to take over, feel free to do so. |
I've just came across this PR a few days after reviving another similar project (https://github.com/dangmai/beetsonic) which I've forked at https://github.com/sdrik/beetsonic. If there is still interest in updating/merging this PR, I'm willing to volunteer. |
@sdrik (or anyone else willing to continue this work) it seems the original repo for this PR no longer exists. You may be better off taking what code you can from here and opening your own PR. Also, as @sampsyo suggested, it would be ideal to try and split this up a bit into separate PRs. Reviewing a ~1,300 line PR can be exhausting 😄 |
This PR adds Subsonic API support (version
1.10.1
of the protocol).With it you can connect with any subsonic client as long as
web
plugin is enabled (if client ask for login/password, put anything, those are not read).NB: encoding parameters for stream mode are not taken into account, file is streamed as-is.
Implemented methods
ping
getLicense
getMusicFolders
getIndexes
getMusicDirectory
getArtists
getArtist
getAlbum
getSong
getAlbumList
getAlbumList2
getRandomSongs
search2
search3
Unimplemented methods:
All unimplemented methods generate a valid XML/JSON that should be interpreted by any client, so that they can show an appropriate error message without any crash.
getGenres
getVideos
getVideoInfo
getArtistInfo
getArtistInfo2
getAlbumInfo
getAlbumInfo2
getSimilarSongs
getSimilarSongs2
getTopSongs
getSongsByGenre
getNowPlaying
getStarred
getStarred2
search
getPlaylists
getPlaylist
createPlaylist
updatePlaylist
deletePlaylist
stream
download
hls
getCaptions
getCoverArt
getLyrics
getAvatar
star
unstar
setRating
scrobble
getShares
createShare
updateShare
deleteShare
getPodcasts
getNewestPodcasts
refreshPodcasts
createPodcastChannel
deletePodcastChannel
deletePodcastEpisode
downloadPodcastEpisode
jukeboxControl
getInternetRadioStations
getChatMessages
addChatMessage
getUser
getUsers
createUser
updateUser
deleteUser
changePassword
getBookmarks
createBookmark
deleteBookmark
getPlayQueue
savePlayQueue