Code Monkey home page Code Monkey logo

supysonic's Introduction

Supysonic

Supysonic is a Python implementation of the Subsonic server API.

Build Status codecov Python

Current supported features are:

  • browsing (by folders or tags)
  • streaming of various audio file formats
  • transcoding
  • user or random playlists
  • cover art
  • starred tracks/albums and ratings
  • Last.fm scrobbling
  • ListenBrainz scrobbling
  • Jukebox mode

Supysonic currently targets the version 1.12.0 of the Subsonic API. For more details, go check the API implementation status.

Documentation

Full documentation is available at https://supysonic.readthedocs.io/

Quickstart

Use the following commands to install Supysonic, create an admin user, define a library folder, scan it and start serving on port 5722 using Gunicorn.

$ pip install supysonic
$ pip install gunicorn
$ supysonic-cli user add MyUserName
$ supysonic-cli user setroles --admin MyUserName
$ supysonic-cli folder add MyLibrary /home/username/Music
$ supysonic-cli folder scan MyLibrary
$ supysonic-server

You should now be able to enjoy your music with the client of your choice!

But using only the above commands will use a default configuration and especially storing the database in a temporary directory. Head over to the documentaiton for full setup instructions, plus other options if you don't want to use Gunicorn.

Note that there's also an optional daemon that watches for library changes and provides support for other features such as the jukebox mode.

Development stuff

For those wishing to collaborate on the project, since Supysonic uses Flask you can use its development server which provides automatic reloading and in-browser debugging among other things. To start said server:

$ export FLASK_APP="supysonic.web:create_application()"
$ export FLASK_ENV=development
$ flask run

And there's also the tests (which require lxml to run):

$ pip install lxml
$ python -m unittest
$ python -m unittest tests.net.suite

The last command runs a few tests that make HTTP requests to remote third-party services (namely Last.fm, ListenBrainz and ChartLyrics).

supysonic's People

Contributors

alch-emi avatar andrenam avatar baldurmen avatar davebiffuk avatar hhm0 avatar jelmer avatar lolspark avatar m35 avatar mvn23 avatar ogarcia avatar powerexploit avatar pr0ps avatar sanderd avatar shuddertrix avatar spl0k avatar thecarlhall avatar vincentdcmps avatar vithyze avatar whisprin avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

supysonic's Issues

monitor music folder with inotify

Just an idea for a new feature.
How about monitoring the music folders for changes using inotify? So that new files or updated files would be added instantly without re-scanning the whole library?

There is pyinotify that could be used for that.

I realize this feature would be Linux specific and wouldn't work on any other operating system. But it would really be a great improvement and I would guess most people run it on Linux. If not, it could be optional.

feature request: playlists with arbitrarily orderable and non-unique playlist items

Currently, playlist items are automatically sorted by ids, and may not have more than one entry with the same song ID.

It would be wonderful to support having playlists with arbitrarily ordered items and the same item occuring possibly more than once.

The current status of uniquified and id-sorted playlists seems to be coded in two levels: the API, and the DB schema.

In the API, upon creating and updating the song UUIDs to put/add are put in a python set, which sorts them and uniquifies them.

In the schema, playlist items are also unordered and do not have a unique key to differentiate one entry from another with the same song.

Perhaps an ever-increasing integer column can be added to the playlist items table, as the playlist item ID, and this ID can be used as the playlist sort order too, since the subsonic API only appends items to the end of a playlist? I am not a DBA though, so take this idea with a grain of salt if you'd like :-)

As far as the API code, it seems on a quick glance that only the updatePlaylist method needs to be changed, to remove an item not only by song ID but by playlist item ID too.

folder scan: unsupported format character

When I try to run a folder scan in the CLI, I get the following exception:

supysonic> folder scan owncloud-martin
Traceback (most recent call last):
  File "bin/supysonic-cli", line 234, in <module>
    cli.cmdloop()
  File "/usr/lib/python2.7/cmd.py", line 142, in cmdloop
    stop = self.onecmd(line)
  File "/usr/lib/python2.7/cmd.py", line 221, in onecmd
    return func(arg)
  File "bin/supysonic-cli", line 51, in method
    return func(** { key: vars(args)[key] for key in vars(args) if key != 'action' })
  File "bin/supysonic-cli", line 150, in folder_scan
    scanner.scan(folder, TimedProgressDisplay(folder.name))
  File "/opt/supysonic/supysonic/scanner.py", line 60, in scan
    self.scan_file(path)
  File "/opt/supysonic/supysonic/scanner.py", line 140, in scan_file
    trroot = self.__find_root_folder(path)
  File "/opt/supysonic/supysonic/scanner.py", line 212, in __find_root_folder
    count = folders.count()
  File "/usr/local/lib/python2.7/dist-packages/storm/store.py", line 1264, in count
    return int(self._aggregate(lambda expr: Count(expr, distinct), expr))
  File "/usr/local/lib/python2.7/dist-packages/storm/store.py", line 1253, in _aggregate
    result = self._store._connection.execute(select)
  File "/usr/local/lib/python2.7/dist-packages/storm/databases/mysql.py", line 106, in execute
    return Connection.execute(self, statement, params, noresult)
  File "/usr/local/lib/python2.7/dist-packages/storm/database.py", line 241, in execute
    raw_cursor = self.raw_execute(statement, params)
  File "/usr/local/lib/python2.7/dist-packages/storm/database.py", line 374, in raw_execute
    self._run_execution(raw_cursor, args, params, statement)
  File "/usr/local/lib/python2.7/dist-packages/storm/database.py", line 388, in _run_execution
    self._check_disconnect(raw_cursor.execute, *args)
  File "/usr/local/lib/python2.7/dist-packages/storm/database.py", line 454, in _check_disconnect
    return function(*args, **kwargs)
  File "/usr/lib/python2.7/dist-packages/MySQLdb/cursors.py", line 159, in execute
    query = query % db.literal(args)
ValueError: unsupported format character ''' (0x27) at index 58

Be nice with gunicorn

In web.py (or in any other python module, I really don't mind) please consider adding

"application = create_application()"

so it's possible to use supysonic with gunicorn with something like

$ gunicorn web:application

Thank you.

Transcoder processes hangs on dropped connection

Hi.
First of all - thanks! Great software!

I found only one big problem up to this moment:
When stream fails (connection closed by the client) server shows exception, but transcoder is working. So it hangs (because output stream already closed).

Maybe we can add some exception handler for closed stream and send SIGTERM to the transcoder, or even redirect it's output to /dev/null (if it can't be closed by SIGTERM)

Unhandled exceptions starting FCGI

Unhandled exceptions starting the fcgi socket

[supysonic@alarm ~]$ source/supysonic.fcgi
Unhandled exception in thread started by 
sys.excepthook is missing
lost sys.stderr
Unhandled exception in thread started by 
sys.excepthook is missing
lost sys.stderr
Unhandled exception in thread started by 
sys.excepthook is missing
lost sys.stderr
Unhandled exception in thread started by 
sys.excepthook is missing
lost sys.stderr
Unhandled exception in thread started by 
sys.excepthook is missing
lost sys.stderr

Genres Support

Any chance of bumping the API to v1.9 and supporting genres?

DatabaseError when creating new user

I get sqlite3.DatabaseError: file is encrypted or is not a database when trying to add a new user. The database exists, is not encrypted, is correctly referenced in the supysonic config file.

Here is the output:

pi@pi ~/supysonic/bin $ ./supysonic-cli 
supysonic> user add johndoe -p password
Traceback (most recent call last):
  File "./supysonic-cli", line 233, in <module>
    cli.cmdloop()
  File "/usr/lib/python2.7/cmd.py", line 142, in cmdloop
    stop = self.onecmd(line)
  File "/usr/lib/python2.7/cmd.py", line 221, in onecmd
    return func(arg)
  File "./supysonic-cli", line 51, in method
    return func(** { key: vars(args)[key] for key in vars(args) if key != 'action' })
  File "./supysonic-cli", line 190, in user_add
    status = UserManager.add(self.__store, name, password, email, admin)
  File "/usr/local/lib/python2.7/dist-packages/supysonic/managers/user.py", line 53, in add
    if store.find(User, User.name == name).one():
  File "/usr/lib/python2.7/dist-packages/storm/store.py", line 1142, in one
    result = self._store._connection.execute(select)
  File "/usr/lib/python2.7/dist-packages/storm/database.py", line 238, in execute
    raw_cursor = self.raw_execute(statement, params)
  File "/usr/lib/python2.7/dist-packages/storm/databases/sqlite.py", line 154, in raw_execute
    return Connection.raw_execute(self, statement, params)
  File "/usr/lib/python2.7/dist-packages/storm/database.py", line 322, in raw_execute
    self._check_disconnect(raw_cursor.execute, *args)
  File "/usr/lib/python2.7/dist-packages/storm/database.py", line 371, in _check_disconnect
    return function(*args, **kwargs)
sqlite3.DatabaseError: file is encrypted or is not a database

Possibly relevant:

pi@pi ~/supysonic/bin $ sqlite -version
2.8.17
pi@pi ~/supysonic/bin $ python -V
Python 2.7.3

Feature request: More specific transcoding options

The transcoding options present are useful, but I have suggestions to give more versatility to the features.
First, some formats such as Opus has bitrate up to 510 and are not bound to powers of 2, so max bitrate input would be better if it was a textbox rather than a combobox.
Also, there are instances when a specific format does not work on a client (flac on HTML5), and want other formats sent out raw. Currently on the transcoding configuration, only the destination format is specified and all audio format are transcoded into such. Being able to specify a original format to be transcoded is desirable, and if there is no definition for a specific format, it can maybe fallback to a definition with no original format input (if available) or sent out raw.

Transcoding flac to mp3 does not change file extension

Transcoding flac to mp3 does not change the file extension. I'm downloading the files with DSub and even though they are transcoded fine and end up being mp3 files, they are still named *.flac:

$ file "01-Pints of Guinness Make You Strong.complete.flac"
> 01-Pints of Guinness Make You Strong.complete.flac: MPEG ADTS, layer III, v1, 320 kbps, 44.1 kHz, JntStereo

I'm using the recommended transcoding options on the wiki:

transcoder_mp3_mp3 = lame --quiet --mp3input -b %outrate %srcpath -
transcoder = ffmpeg -i %srcpath -ab %outratek -v 0 -f %outfmt -
decoder_mp3 = mpg123 --quiet -w - %srcpath
decoder_ogg = oggdec -o %srcpath
decoder_flac = flac -d -c -s %srcpath
encoder_mp3 = lame --quiet -b %outrate - -
encoder_ogg = oggenc2 -q -M %outrate -

This is not a critical bug since most players won't care and will play the files anyway, but I wondered for a while why the transcoding was not working even though I could see flac and lame running on the server.

Add user on QNAP

Hi I installed supysonic on my QNAP TS-412 (ARM),

but if I try to add an user I get the following error. There are no special characters in the password.

[/opt] # /opt/supysonic/bin/supysonic-cli user add --admin pingumania
Password:
Confirm password:
Traceback (most recent call last):
  File "/opt/supysonic/bin/supysonic-cli", line 231, in <module>
    cli.onecmd(' '.join(sys.argv[1:]))
  File "/share/MD0_DATA/.qpkg/Python/lib/python2.7/cmd.py", line 219, in onecmd
    return func(arg)
  File "/opt/supysonic/bin/supysonic-cli", line 51, in method
    return func(** { key: vars(args)[key] for key in vars(args) if key != 'action' })
  File "/opt/supysonic/bin/supysonic-cli", line 190, in user_add
    status = UserManager.add(self.__store, name, password, email, admin)
  File "/share/MD0_DATA/.qpkg/Python/lib/python2.7/site-packages/supysonic/managers/user.py", line 53, in add
    if store.find(User, User.name == name).one():
  File "/opt/local/lib/python2.7/site-packages/storm/store.py", line 1158, in one
    result = self._store._connection.execute(select)
  File "/opt/local/lib/python2.7/site-packages/storm/database.py", line 238, in execute
    statement = self.compile(statement, state)
  File "/opt/local/lib/python2.7/site-packages/storm/databases/sqlite.py", line 57, in compile_select_sqlite
    statement = compile_select(compile, select, state)
  File "/opt/local/lib/python2.7/site-packages/storm/expr.py", line 666, in compile_select
    tokens.append(compile(select.columns, state))
LookupError: unknown encoding:

jamstash doesn't play

I have set up supysonic for testing -- what a fascinating project! -- and it's working with DSub. I'm not terribly experienced but I have successfully run supysonic with uwsgi and with gunicorn, with bits and pieces of gathered info. Apache didn't work for me but I'm sure I was doing it wrong.

With DSub, it's working both directly (IP:port) in my LAN, and through nginx reverse proxy via domain name --
as I've always run Subsonic --but with jamstash it won't work with either. The artist list loads up, and I can browse music, but when I try to play something, nothing happens.

I'm not sure how to generate appropriate logs, or where to look. The supysonic log is empty.

License

What license is supysonic under? It'd be great if you can ship a license file.

random play fails with empty library

Traceback (most recent call last):
  File "/usr/share/pyshared/flask/app.py", line 1836, in __call__
    return self.wsgi_app(environ, start_response)
  File "/usr/share/pyshared/flask/app.py", line 1820, in wsgi_app
    response = self.make_response(self.handle_exception(e))
  File "/usr/share/pyshared/flask/app.py", line 1403, in handle_exception
    reraise(exc_type, exc_value, tb)
  File "/usr/share/pyshared/flask/app.py", line 1817, in wsgi_app
    response = self.full_dispatch_request()
  File "/usr/share/pyshared/flask/app.py", line 1477, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "/usr/share/pyshared/flask/app.py", line 1381, in handle_user_exception
    reraise(exc_type, exc_value, tb)
  File "/usr/share/pyshared/flask/app.py", line 1475, in full_dispatch_request
    rv = self.dispatch_request()
  File "/usr/share/pyshared/flask/app.py", line 1461, in dispatch_request
    return self.view_functions[rule.endpoint](**req.view_args)
  File "/home/jelmer/supysonic/api/albums_songs.py", line 64, in album_list
    x = random.choice(xrange(count))
  File "/usr/lib/python2.7/random.py", line 273, in choice
    return seq[int(self.random() * len(seq))]  # raises IndexError if seq is empty
IndexError: xrange object index out of range

problem linking lsat.fm account

I'm using the cgi script. When I try to link my lastfm account I get the following in the log:

    Exception on /user/lastfm/link [GET]
Traceback (most recent call last):
  File "/usr/local/lib/python2.7/dist-packages/flask/app.py", line 1817, in wsgi_app
    response = self.full_dispatch_request()
  File "/usr/local/lib/python2.7/dist-packages/flask/app.py", line 1477, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "/usr/local/lib/python2.7/dist-packages/flask/app.py", line 1381, in handle_user_exception
    reraise(exc_type, exc_value, tb)
  File "/usr/local/lib/python2.7/dist-packages/flask/app.py", line 1475, in full_dispatch_request
    rv = self.dispatch_request()
  File "/usr/local/lib/python2.7/dist-packages/flask/app.py", line 1461, in dispatch_request
    return self.view_functions[rule.endpoint](**req.view_args)
  File "/usr/lib/cgi-bin/supysonic/frontend/user.py", line 188, in lastfm_reg
    status, error = lfm.link_account(token)
  File "/usr/lib/cgi-bin/supysonic/lastfm.py", line 37, in link_account
    res = self.__api_request(False, method = 'auth.getSession', token = token)
  File "/usr/lib/cgi-bin/supysonic/lastfm.py", line 92, in __api_request
    if 'error' in r.json:
TypeError: argument of type 'instancemethod' is not iterable

Capitalization-unaware search

Currently, the search method finds only for strings that matches exactly, which means that the user has to type the query with exact capitalization.
It would be great if this could be worked out.

UnicodeEncodeError: 'ascii' codec can't encode character u'\xe8' in position 88: ordinal not in range(128)

This is a solved problem, and it's really not a supysonic issue (ie webserver configuration) but I spent a bit of time hunting it down and thought it'd be useful to document.

TL;DR

The solution is already noted in the README.md - see the green in this commit

Details

Using the DSub android client, browsing to a song titled "Aprรจs Moi" (notice the UTF-8 character), the client would fail with the message:

Error: A problem occurred communication with the server. ...

ie, the server returned a 500.

Then looking in the subsonic.log the error message in the title of this git issue was logged.

I noticed this issue didn't occur when running supysonic as stand alone, only from my Apache2 WSGI endpoint.

Similar to what's mentioned in the README.md, for me it was solved by adding:

export LANG="en_US.utf8"
export LC_ALL="en_US.UTF-8"

before the export LANG line in my /etc/apache2/envvars file and issuing a sudo service apache2 restart.

Improve Flask usage

  • Blueprints and/or pluggable views
  • flask.json.jsonify()
  • API errors as exceptions
  • Flask-style config? (breaks compatibility) using a mix of flask-style and ini files
  • Flask-style CLI?
  • Handle content type when creating the response rather than in after_request handlers

Threaded or/and external scan

Currently, collection scan is only done in a request context, so the user's browser will keep "loading" until Supysonic is done scanning.
Move the scanning process to its own thread so the request can return as soon as it starts, and not when it's done.

It might also be a good idea to create a command-line script to scan. It could then be used by cron or such.

Scanner creates duplicate folders in database

Problem:
Multiple folder entries (with empty name) are created if two folders share a first word.

In the example below, both "Album I" and "Album II" start with the same name.

What's happening:

  • Scan file /tmp/test/Album I/test.mp3
    • Track doesn't exist yet, = added
    • Folder doesn't exist yet, = added
  • Scan file /tmp/test/Album I/test2.mp3
    • Track doesn't exist yet, = added
    • Folder already exists (exact match, Folder.path = /tmp/test/Album I) -> no action needed
  • Scan file /tmp/test/Album II/test.mp3
    • Track doesn't exist yet, = added
    • Folder: no exact match found. Find last (= longest) match where '/tmp/test/Album II' LIKE Folder.path||'%'
      the longest match = /tmp/test/Album I

Reproduction scenario:
Python 2.7, centos 7, sqlite

# setup
mkdir /tmp/test
mkdir /tmp/test/Artist
mkdir "/tmp/test/Artist/Album I"
mkdir "/tmp/test/Artist/Album II"

cp sometaggedmp3.mp3 "/tmp/test/Artist/Album I/test.mp3"
cp sometaggedmp3.mp3 "/tmp/test/Artist/Album I/test2.mp3"
cp sometaggedmp3.mp3 "/tmp/test/Artist/Album II/test.mp3"
cp sometaggedmp3.mp3 "/tmp/test/Artist/Album II/test2.mp3"

# first scan
python supysonic-cli
folder add TestLibrary /tmp/test
folder scan TestLibrary
exit

# See erroneously inserted folders @ db
sqlite3 /var/supysonic/supysonic.db
SQLite version 3.7.17 2013-05-20 00:56:22
Enter ".help" for instructions
Enter SQL statements terminated with a ";"
sqlite> SELECT * FROM folder;
6f28fba3-1d75-4b62-be32-013678e28367|1|test|/tmp/test|2016-02-12 01:57:03|0|1455238626|
9f16c505-4da6-4b91-8610-f63b0658d81a|0|Artist|/tmp/test/Artist|2016-02-12 01:57:06|0|0|6f28fba3-1d75-4b62-be32-013678e28367
958eee7e-324e-4d4b-ac74-aacf99a84d27|0|Album I|/tmp/test/Artist/Album I|2016-02-12 01:57:06|1|0|9f16c505-4da6-4b91-8610-f63b0658d81a
0d937f59-8234-47a7-98e0-cc0486553933|0||/tmp/test/Artist/Album I/|2016-02-12 01:57:06|1|0|958eee7e-324e-4d4b-ac74-aacf99a84d27
016f276a-323f-48dc-82e9-0208e81729a4|0||/tmp/test/Artist/Album I/|2016-02-12 01:57:06|1|0|958eee7e-324e-4d4b-ac74-aacf99a84d27
sqlite>

# Add extra file to "Album II"
cp sometaggedmp3.mp3 "/tmp/test/Artist/Album II/test3.mp3"
python supysonic-cli
folder scan TestLibrary

# An extra wrong record added
sqlite3 /var/supysonic/supysonic.db
SQLite version 3.7.17 2013-05-20 00:56:22
Enter ".help" for instructions
Enter SQL statements terminated with a ";"
sqlite> SELECT * FROM folder;
5dc83780-cadf-4512-aff7-bfb2a761d7cf|1|test|/tmp/test|2016-02-12 02:04:53|0|1455239102|
85520cd1-8e69-4a2e-84db-4d7ab458c436|0|Artist|/tmp/test/Artist|2016-02-12 02:04:56|0|0|5dc83780-cadf-4512-aff7-bfb2a761d7cf
82ede624-5826-47ae-9369-6d5ab9f322c3|0|Album I|/tmp/test/Artist/Album I|2016-02-12 02:04:56|0|0|85520cd1-8e69-4a2e-84db-4d7ab458c436
14d57814-db9b-40dd-bb88-c8dd5261f674|0||/tmp/test/Artist/Album I/|2016-02-12 02:04:56|0|0|82ede624-5826-47ae-9369-6d5ab9f322c3
2f1f9019-55a1-4c4a-a5f6-c91fa3befce4|0||/tmp/test/Artist/Album I/|2016-02-12 02:04:56|0|0|82ede624-5826-47ae-9369-6d5ab9f322c3
3cbdd51d-bf10-44ca-b1e8-93cd6faf8b8d|0||/tmp/test/Artist/Album I/|2016-02-12 02:05:02|0|0|82ede624-5826-47ae-9369-6d5ab9f322c

PolySonic won't connect to supysonic

PolySonic is a decent, clean-looking client software for Subsonic API. It runs as a Google Chrome extension.
When I try to connect it to an instance of supysonic, it would return an error without proper explanation. On the server side, I only get a line of log (when debugging):

10.8.8.1 - - [13/Aug/2015 02:05:05] "GET /rest/ping.view?u=test&p=test&v=1.11.0&c=PolySonic&f=json HTTP/1.1" 200 -

and nothing afterwards.

Since other clients such as Jamstash, Perisonic, DSub and Subsonic on Android works, it would not be a major problem. However, I assume a slightest chance of some compatibility issue since PolySonic works with the official server software.

I could not determine if it is a problem on the server side or client side. Some differences between supysonic and Subsonic that I was able to find out was:

  • Different indentation of JSON: one space versus three spaces
  • Subsonic uses the Content-Type: application/json; charset=utf-8 while supysonic was missing the charset part.
  • Subsonic uses gzipped response for default and sends its response with chunked transfer.
  • Different API versions: 1.8.0 v 1.12.0. (Although I feel skeptical about the effect of this difference)

You may refer to the code at: https://github.com/dough10/PolySonic.

"Folders" not selectable in DSub

I've noticed that unlike Subsonic, Supysonic doesn't allow per-folder browsing. On my Subsonic 6.1.1 server, I have an Audiobooks folder, a Podcasts folder (I use manolomartinez/greg to download & tag podcasts), and a Music folder. I'll switch between them depending on what I want.

I tend to think of the "Folders" each as a Library in their own right.

Supysonic lets me add folders but seems to treat them all as one big library. DSub shows everything all jumbled together.

With Subsonic, DSub lets me pick an individual "Folder" to operate in which is much more convenient. (Additionally, although I don't have Jamstash playback working as per #58 , it does list the supysonic "folders" in its drop-down, but it also doesn't work to switch between them; it does nothing to change what I'm seeing.)

Would it be possible to add this capability to Supysonic?

badly formed hex UUID string after moving to storm

With the move from SQLAlchemy to Storm, supysonic errors out like this. I dropped the db and created it anew, so I can't provide further info. Backtrace:

Exception on /user/login [POST]
Traceback (most recent call last):
  File "/usr/lib/python2.7/site-packages/flask/app.py", line 1817, in wsgi_app
    response = self.full_dispatch_request()
  File "/usr/lib/python2.7/site-packages/flask/app.py", line 1477, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "/usr/lib/python2.7/site-packages/flask/app.py", line 1381, in handle_user_exception
    reraise(exc_type, exc_value, tb)
  File "/usr/lib/python2.7/site-packages/flask/app.py", line 1475, in full_dispatch_request
    rv = self.dispatch_request()
  File "/usr/lib/python2.7/site-packages/flask/app.py", line 1461, in dispatch_request
    return self.view_functions[rule.endpoint](**req.view_args)
  File "./supysonic/frontend/user.py", line 232, in login
    status, user = UserManager.try_auth(store, name, password)
  File "./supysonic/managers/user.py", line 85, in try_auth
    user = store.find(User, User.name == name).one()
  File "/usr/lib/python2.7/site-packages/storm/store.py", line 1163, in one
    return self._load_objects(result, values)
  File "/usr/lib/python2.7/site-packages/storm/store.py", line 989, in _load_objects
    return self._find_spec.load_objects(self._store, result, values)
  File "/usr/lib/python2.7/site-packages/storm/store.py", line 1734, in load_objects
    values[values_start:values_end])
  File "/usr/lib/python2.7/site-packages/storm/store.py", line 699, in _load_object
    variable = columns[i].variable_factory(value=value, from_db=True)
  File "/usr/lib/python2.7/site-packages/storm/variables.py", line 506, in parse_set
    value = uuid.UUID(value)
  File "/usr/lib/python2.7/uuid.py", line 134, in __init__
    raise ValueError('badly formed hexadecimal UUID string')
ValueError: badly formed hexadecimal UUID string

Feature request: Setting the default transcoded format when not specified

Most subsonic clients does not specify the destination format of the transcoded format.
When using flac files, they do not transcode with designated bitrate (since they are not lossy) and does meaninglessly run the transcoder process.
Furthermore, most browsers do not support flac audio, so they cannot be played on HTML5 players such as Jamstash.
Whereas this can be detoured out by setting a specific format with transcoder settings, it seems to be a good practice to be able to specify a default target format.
Also, a option to always transcode a specific format into another specified format when requested by some user-agent or application name may also be lovely.

EDIT: Nevermind: I just noticed the options on /user/me. Sorry for that.

Database other than SQLite does not work properly

As with Issue #33, MySQL Database does not work correctly with supysonic. Neither PostgreSQL does work correctly, but with another error.

Commands input (almost) as exactly:

git clone https://github.com/spl0k/supysonic.git app
virtualenv2 virtualenv
virtualenv/bin/pip install flask storm psycopg2==2.4 pillow simplejson requests mutagen watchdog
virtualenv/bin/pip install -e app/
sudo -upostgres createuser supysonic
sudo -upostgres createdb -E utf8 -T postgres -O supysonic supysonic
psql -dsupysonic -Usupysonic -f app/schema/postgresql.sql
virtualenv/bin/supysonic-cli folder add music /data/files/music
virtualenv/bin/supysonic-cli folder scan

Turns out to be:

$ virtualenv/bin/supysonic-cli folder scan
Traceback (most recent call last):
  File "virtualenv/bin/supysonic-cli", line 6, in <module>
    exec(compile(open(__file__).read(), __file__, 'exec'))
  File "/home/tki/app/bin/supysonic-cli", line 232, in <module>
    cli.onecmd(' '.join(sys.argv[1:]))
  File "/usr/lib/python2.7/cmd.py", line 221, in onecmd
    return func(arg)
  File "/home/tki/app/bin/supysonic-cli", line 51, in method
    return func(** { key: vars(args)[key] for key in vars(args) if key != 'action' })
  File "/home/tki/app/bin/supysonic-cli", line 153, in folder_scan
    scanner.scan(folder, TimedProgressDisplay(folder.name))
  File "/home/tki/app/supysonic/scanner.py", line 60, in scan
    self.scan_file(path)
  File "/home/tki/app/supysonic/scanner.py", line 140, in scan_file
    trroot = self.__find_root_folder(path)
  File "/home/tki/app/supysonic/scanner.py", line 211, in __find_root_folder
    count = folders.count()
  File "/home/tki/virtualenv/lib/python2.7/site-packages/storm/store.py", line 1264, in count
    return int(self._aggregate(lambda expr: Count(expr, distinct), expr))
  File "/home/tki/virtualenv/lib/python2.7/site-packages/storm/store.py", line 1253, in _aggregate
    result = self._store._connection.execute(select)
  File "/home/tki/virtualenv/lib/python2.7/site-packages/storm/databases/postgres.py", line 266, in execute
    return Connection.execute(self, statement, params, noresult)
  File "/home/tki/virtualenv/lib/python2.7/site-packages/storm/database.py", line 241, in execute
    raw_cursor = self.raw_execute(statement, params)
  File "/home/tki/virtualenv/lib/python2.7/site-packages/storm/databases/postgres.py", line 276, in raw_execute
    return Connection.raw_execute(self, statement, params)
  File "/home/tki/virtualenv/lib/python2.7/site-packages/storm/database.py", line 374, in raw_execute
    self._run_execution(raw_cursor, args, params, statement)
  File "/home/tki/virtualenv/lib/python2.7/site-packages/storm/database.py", line 388, in _run_execution
    self._check_disconnect(raw_cursor.execute, *args)
  File "/home/tki/virtualenv/lib/python2.7/site-packages/storm/database.py", line 454, in _check_disconnect
    return function(*args, **kwargs)
IndexError: tuple index out of range

My /etc/supysonic:

[base]
database_uri = postgres://supysonic@localhost/supysonic
scanner_extensions = mp3 ogg oga aac m4a flac wav wma aif aiff ape mpc shn

[webapp]
log_level = WARNING

[transcoding]
transcoder = ffmpeg -i %srcpath -ab %outratek -v 0 -f %outfmt -

[mimetypes]
mp3 = audio/mpeg
ogg = audio/ogg

[daemon]
log_level = WARNING

Please notice that I am using psycopg2==2.4, thus the issue has nothing to do with this.
When I tried with a local SQLite DB, the procedure worked wonderfully.

Cheers to you and collaborators for this interesting project. I am fairly excited to the fact that I can use Subsonic API implementation without using JVM.

Traceback during scan

Traceback (most recent call last):
  File "/usr/lib/python2.7/pdb.py", line 1314, in main
    pdb._runscript(mainpyfile)
  File "/usr/lib/python2.7/pdb.py", line 1233, in _runscript
    self.run(statement)
  File "/usr/lib/python2.7/bdb.py", line 400, in run
    exec cmd in globals, locals
  File "<string>", line 1, in <module>
  File "cli.py", line 3, in <module>
    import sys, cmd, argparse, getpass
  File "/usr/lib/python2.7/cmd.py", line 142, in cmdloop
    stop = self.onecmd(line)
  File "/usr/lib/python2.7/cmd.py", line 221, in onecmd
    return func(arg)
  File "cli.py", line 27, in method
    return func(** { key: vars(args)[key] for key in vars(args) if key != 'action' })
  File "cli.py", line 110, in folder_scan
    FolderManager.scan(folder.id, s)
  File "managers/folder.py", line 94, in scan
    scanner.scan(folder)
  File "scanner.py", line 28, in scan
    self.__scan_file(os.path.join(root, f), folder)
  File "scanner.py", line 77, in __scan_file
    tr.album    = self.__find_album(self.__try_read_tag(tag, 'artist'), self.__try_read_tag(tag, 'album'))
  File "scanner.py", line 83, in __find_album
    ar = self.__find_artist(artist)
  File "scanner.py", line 94, in __find_artist
    ar = filter(lambda a: a.name.lower() == artist.lower(), self.__artists)
  File "scanner.py", line 94, in <lambda>
    ar = filter(lambda a: a.name.lower() == artist.lower(), self.__artists)
AttributeError: 'NoneType' object has no attribute 'lower'

This happens because artist is None in this context, because back in __scan_file, self.__try_read_tag(tag, 'artist') returns None. (I guess I have a broken MP3 file without an artist tag...) Adding a default, i.e. changing to self.__try_read_tag(tag, 'artist', '') seems to help, but I don't know what the right solution is.

BTW: I'm really psyched to have found something I can run on my Sheevaplug that doesn't require something as heavy as Java!

Error reading config with extra newlines

I created a configuration file with the appropriate values, but my file editor added an additional newline to the end of the file. This caused the config parser to error out with the error below:

$ python main.py 
Traceback (most recent call last):
  File "main.py", line 3, in <module>
    import config
  File "/home/william/Documents/supysonic/config.py", line 20, in <module>
    config_dict[spl[0].strip()] = eval(spl[1])
IndexError: list index out of range

If you print out the line after the split, it looked like this:

['DATABASE_URI', '"sqlite:////var/supysonic/supysonic.db"\n']
['CACHE_DIR', '"/tmp/supysonic"\n']
['LOG_FILE', '"/var/log/supysonic"\n']
['\n']

There should be a way to handle or ignore these additional newlines. Maybe add:
if len(spl) == 2: or something similar.

Support for .ogg and .flac and transcoding?

Hi spl0k,

I was very happy when I found your project and I'm using it on my raspberry pi now.
I'm playing around with it and started to work on support for .ogg and .flac files and on transcoding using ffmpeg/libav. I've got it partially working on archlinux but it needs testing. Are you interested in a patch/pull request? Maybe we can work together?

And another question: what license do you want to use for supysonic?
GPL, Apache, MIT, ... ?

Error on scanning after removing last played music on PostgreSQL

$ mv some-album-played-last/ ../music_backup/
$ supysonic-cli folder scan
Scanning 'Music': 0% (1/116)
Scanning 'Music': 100% (116/116)
Traceback (most recent call last):
  File "virtualenv/bin/supysonic-cli", line 6, in <module>
    exec(compile(open(__file__).read(), __file__, 'exec'))
  File "app/bin/supysonic-cli", line 232, in <module>
    cli.onecmd(' '.join(sys.argv[1:]))
  File "/usr/lib/python2.7/cmd.py", line 221, in onecmd
    return func(arg)
  File "app/bin/supysonic-cli", line 51, in method
    return func(** { key: vars(args)[key] for key in vars(args) if key != 'action' })
  File "app/bin/supysonic-cli", line 153, in folder_scan
    scanner.scan(folder, TimedProgressDisplay(folder.name))
  File "app/supysonic/scanner.py", line 98, in scan
    folders += f.children
  File "virtualenv/lib/python2.7/site-packages/storm/references.py", line 288, in __iter__
    return self.find().__iter__()
  File "virtualenv/lib/python2.7/site-packages/storm/references.py", line 282, in find
    result = store.find(self._target_cls, where, *args, **kwargs)
  File "virtualenv/lib/python2.7/site-packages/storm/store.py", line 221, in find
    self.flush()
  File "virtualenv/lib/python2.7/site-packages/storm/store.py", line 501, in flush
    self._flush_one(obj_info)
  File "virtualenv/lib/python2.7/site-packages/storm/store.py", line 517, in _flush_one
    self._connection.execute(expr, noresult=True)
  File "virtualenv/lib/python2.7/site-packages/storm/databases/postgres.py", line 266, in execute
    return Connection.execute(self, statement, params, noresult)
  File "virtualenv/lib/python2.7/site-packages/storm/database.py", line 241, in execute
    raw_cursor = self.raw_execute(statement, params)
  File "virtualenv/lib/python2.7/site-packages/storm/databases/postgres.py", line 276, in raw_execute
    return Connection.raw_execute(self, statement, params)
  File "virtualenv/lib/python2.7/site-packages/storm/database.py", line 374, in raw_execute
    self._run_execution(raw_cursor, args, params, statement)
  File "virtualenv/lib/python2.7/site-packages/storm/database.py", line 388, in _run_execution
    self._check_disconnect(raw_cursor.execute, *args)
  File "virtualenv/lib/python2.7/site-packages/storm/database.py", line 454, in _check_disconnect
    return function(*args, **kwargs)
psycopg2.IntegrityError: update or delete on table "track" violates foreign key constraint "user_last_play_id_fkey" on table "user"
DETAIL:  Key (id)=(79f79da0-5aa0-4090-af09-e464a66a7d7f) is still referenced from table "user".

Exception Exception: Exception("There's still something to check. Did you run Scanner.finish()?",) in <bound method Scanner.__del__ of <supysonic.scanner.Scanner instance at 0xb604ffd0>> ignored

happens on folder sync after removing album containing last played song.
I use PostgreSQL for my database backend, but this maybe can happen also on other backends although I haven't tested.

Duplicate albums if there are multiple artists

It seems that supysonic returns multiple album entries when some tracks have different artists. In this case, we get one album per track artist, all with the same name. In this case, it would be nice if supysonic supported the album artist metadata field, in order to group albums by album artists.

Provide systemd-friendly interface for watcher

While lots of linux distribution supported the classic init.d, systemd is becoming more and more popular among many distributions. Starting from jessie/15.04 builds of Debian and Ubuntu, systemd is fully supported, and even more systems are populated with systemd based daemon administration.

The thing is that systemd works the best with undaemonized processes where the main process won't fork but instead emit the output to stdout/stderr. It is difficult to utilize the current implementation to fit into a systemd service.

Thus, I suggest providing a simpler interface -- where python watcher.py or some simple script would persist with the terminal -- along with current one.

FCGI Support

Would be great to have FCGI support to use in nginx like envs.

User/groupbased folders

Hey,

nice to be have an feature to set privileges for folder to an group/user to allow or restrict access.

greetings
Sam

Supysonic fails to start

When launching, it fails to start with error:
$ python cgi-bin/server.py
Traceback (most recent call last):
File "cgi-bin/server.py", line 22, in
from supysonic.web import create_application
ImportError: No module named supysonic.web

Support creating playlists from m3u playlist files

I'd like to be able to create playlists from .m3u files. I've written a very rough patch to do this on my own repo of supysonic here: c13b3ae. I just added a route to upload playlists to the frontend to implement it. The path for the uploads and the upload form are both hardcoded and ugly and the error handling is pretty awful, so it should probably be cleaned up before being pulled in. I can do that if there's interest in it.

One other issue related to error handling - due to the way the database is organized, multiple copies of the same song cannot be in the same playlist. I'm not sure how subsonic itself handles it, maybe that's consistent, but to me it's annoying. I'd like the option to have multiple of the same songs. I may end up changing this as well.

About #65

@spl0k Do you like the PR #65?

I'm thinking to touch to all message flashing to add categories and have different colors that depends of importance of message, but I don't know if you like the changes.

Greetings.

CLI is raising error when called

Was following instructions in #52 (comment) and the following error took place:

$ PYTHONPATH=.. ./supysonic-cli --help
Traceback (most recent call last):
  File "./supysonic-cli", line 26, in <module>
    from supysonic.managers.folder import FolderManager
  File "<supysonic-location>/managers/folder.py", line 23, in <module>
    from supysonic.scanner import Scanner
  File "<supysonic-location>/scanner.py", line 206
    self.__store.find(User, User.last_play_id == tr.id).set(User.last_play_id = None)
SyntaxError: keyword can't be an expression

This is caused by commit 1de9e2b (currently the latest commit); reverting to the commit before it (783e9b5) fixes this for now.

Support other DB backends

OK, we use SQLAlchemy, so we should be able to use almost any DBMS.
But for now it has only been tested on SQLite, and I know there are some SQLite-specific queries (example).

Problem with passwords

I'm working on the unit tests and discover a bug caused by __decode_password in user manager. I think that is old code that can be deleted without problem, but at this moment cause one issue that is what your password cannot start with enc:.

The second bug is that you cannot use non ascii characters in passwords. This is caused by the construction hashlib.sha1(salt + password).hexdigest() in the return. The problem is that the password is unicode but salt not and yu gives an UnicodeEncodeError like this.

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "supysonic/managers/user.py", line 153, in __encrypt_password
    return hashlib.sha1(salt + password).hexdigest(), salt
UnicodeEncodeError: 'ascii' codec can't encode character u'\xf1' in position 6: ordinal not in range(128)

The safest method that I foud to fix this is change the return to manage password and salt as utf8:

return hashlib.sha1(salt.encode('utf-8') + password.encode('utf-8')).hexdigest(), salt

storm crashed on scan

OS: Debian Jessie
Supysonic version: commit 2b3155c

python-storm crashes when I try to scan my music folder.

foo@bar:~# supysonic-cli folder scan Musique
Scanning 'Musique': 0% (1/10797)
Scanning 'Musique': 2% (315/10797)
Scanning 'Musique': 5% (636/10797)
Scanning 'Musique': 8% (949/10797)
Scanning 'Musique': 11% (1272/10797)
Scanning 'Musique': 14% (1563/10797)
Scanning 'Musique': 16% (1831/10797)
Scanning 'Musique': 19% (2131/10797)
Scanning 'Musique': 22% (2455/10797)
Scanning 'Musique': 25% (2772/10797)
Scanning 'Musique': 28% (3091/10797)
Scanning 'Musique': 31% (3403/10797)
Scanning 'Musique': 34% (3717/10797)
Scanning 'Musique': 37% (4043/10797)
Scanning 'Musique': 40% (4359/10797)
Scanning 'Musique': 43% (4688/10797)
Scanning 'Musique': 46% (5000/10797)
Scanning 'Musique': 49% (5313/10797)
Scanning 'Musique': 52% (5631/10797)
Scanning 'Musique': 55% (5955/10797)
Scanning 'Musique': 58% (6276/10797)
Scanning 'Musique': 61% (6594/10797)
Scanning 'Musique': 64% (6911/10797)
Scanning 'Musique': 66% (7230/10797)
Scanning 'Musique': 69% (7547/10797)
Scanning 'Musique': 72% (7864/10797)
Scanning 'Musique': 75% (8172/10797)
Scanning 'Musique': 78% (8484/10797)
Scanning 'Musique': 81% (8811/10797)
Scanning 'Musique': 84% (9129/10797)
Scanning 'Musique': 87% (9440/10797)
Scanning 'Musique': 90% (9767/10797)
Scanning 'Musique': 93% (10098/10797)
Scanning 'Musique': 96% (10415/10797)
Scanning 'Musique': 99% (10729/10797)
Scanning 'Musique': 100% (10797/10797)
Traceback (most recent call last):
  File "/usr/local/bin/supysonic-cli", line 233, in <module>
    cli.onecmd(' '.join(sys.argv[1:]))
  File "/usr/lib/python2.7/cmd.py", line 221, in onecmd
    return func(arg)
  File "/usr/local/bin/supysonic-cli", line 51, in method
    return func(** { key: vars(args)[key] for key in vars(args) if key != 'action' })
  File "/usr/local/bin/supysonic-cli", line 151, in folder_scan
    scanner.scan(folder, TimedProgressDisplay(folder.name))
  File "/usr/local/lib/python2.7/dist-packages/supysonic/scanner.py", line 94, in scan
    self.remove_file(track.path)
  File "/usr/local/lib/python2.7/dist-packages/supysonic/scanner.py", line 201, in remove_file
    tr = self.__store.find(Track, Track.path == path).one()
  File "/usr/lib/python2.7/dist-packages/storm/store.py", line 1145, in one
    raise NotOneError("one() used with more than one result available")
storm.exceptions.NotOneError: one() used with more than one result available
Exception Exception: Exception("There's still something to check. Did you run Scanner.finish()?",) in <bound method Scanner.__del__ of <supysonic.scanner.Scanner instance at 0x7f7efcaa27e8>> ignored

i've tried to delete the folder to try to fix the problem but it seems the fact that the scan does not finish correctly makes it impossible to do.

foo@bar:~# supysonic-cli folder delete Musique
Traceback (most recent call last):
  File "/usr/local/bin/supysonic-cli", line 233, in <module>
    cli.onecmd(' '.join(sys.argv[1:]))
  File "/usr/lib/python2.7/cmd.py", line 221, in onecmd
    return func(arg)
  File "/usr/local/bin/supysonic-cli", line 51, in method
    return func(** { key: vars(args)[key] for key in vars(args) if key != 'action' })
  File "/usr/local/bin/supysonic-cli", line 126, in folder_delete
    ret = FolderManager.delete_by_name(self.__store, name)
  File "/usr/local/lib/python2.7/dist-packages/supysonic/managers/folder.py", line 104, in delete_by_name
    return FolderManager.delete(store, folder.id)
  File "/usr/local/lib/python2.7/dist-packages/supysonic/managers/folder.py", line 88, in delete
    scanner.remove_file(track.path)
  File "/usr/local/lib/python2.7/dist-packages/supysonic/scanner.py", line 201, in remove_file
    tr = self.__store.find(Track, Track.path == path).one()
  File "/usr/lib/python2.7/dist-packages/storm/store.py", line 1145, in one
    raise NotOneError("one() used with more than one result available")
storm.exceptions.NotOneError: one() used with more than one result available
Exception Exception: Exception("There's still something to check. Did you run Scanner.finish()?",) in <bound method Scanner.__del__ of <supysonic.scanner.Scanner instance at 0x7f5f7b4f8d88>> ignored

I "fixed" the problem by deleting the DB and recreating it from scratch, but I made a dump before so I can test things out if you want.

Fun fact: if that can help, Dsub was still working but one artist (I had only one album from her and I replaced it from a mp3 source to a FLAC source a few days ago) was shown multiple times.

I've been doing that a lot lately (replacing mp3 albums by FLAC ones) and it seems that it is what triggered that bug for me.

Migrate to Python3

Python2 is slowly being deprecated everywhere and I think migrating to python3 would be a very nice thing for this program.

If you think this would be nice, I can help port some part of it.

tracks with empty album tags cause problems.

I think that tracks with an empty album tag cause problems...

E.g.: From one artist I have just one track. In the clients (Ultra Sonic on android, Jamstash in Chrome), the artist is shown but the track cannot be displayed when clicking the artist (Jamstash: no reaction, Ultra Sonic: Error - Didn't understand the reply. Please check the server adress.). However, everything works fine when I change the album tag from empty to e.g. "none".

For another artist I have single tracks (empty album tag) and a couple of albums. When I click on the Artist in the clients, the same happens as described above. Neither the tracks nor the albums can be shown.

Drop "daemon-runner" wrapper in favor of strait "python-daemon" for the watcher

Hi!

While trying to run the watcher, I got his dependency error:

ImportError: No module named daemon.runner

This is of course the result of me not installing supysonic via pip but with Debian packages.

Sadly, is does not seem like daemon-runner is packaged for Debian. python-daemon, the library under the daemon-runner wrapper is though.

I was thus wondering how hard it would be to drop the wrapper and use the base library instead.

I'm not closed to the idea of doing the PR myself, I've just never touched that python lib before and was wondering if you could explain you design choice.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.