diff --git a/pysonic/api.py b/pysonic/api.py index 5348f27..9e14d4d 100644 --- a/pysonic/api.py +++ b/pysonic/api.py @@ -202,41 +202,39 @@ class PysonicApi(object): response.add_child("artist", _real_parent=index, id=artist["dir"], name=artist["name"]) return response - @cherrypy.expose - def savePlayQueue_view(self, id, current, position, **kwargs): - print("TODO save playlist with items {} current {} position {}".format(id, current, position)) - @cherrypy.expose @formatresponse def getAlbumList_view(self, type, size=50, offset=0, **kwargs): - albums = self.library.get_albums() + qargs = {} if type == "random": - shuffle(albums) + qargs.update(sortby="random") elif type == "alphabeticalByName": - albums.sort(key=lambda item: item.get("id3_album", item["album"] if item["album"] else "zzzzzUnsortable")) + qargs.update(sortby="name", order="asc") + elif type == "newest": + qargs.update(sortby="added", order="desc") else: raise NotImplemented() - albumset = albums[0 + int(offset):int(size) + int(offset)] + + qargs.update(limit=(offset, size)) + + albums = self.library.get_albums(**qargs) response = ApiResponse() response.add_child("albumList") - for album in albumset: - album_meta = album['metadata'] - album_kw = dict(id=album["id"], - parent=album["parent"], - isDir="true" if album['isdir'] else "false", - title=album_meta.get("id3_title", album["name"]), #TODO these cant be blank or dsub gets mad - album=album_meta.get("id3_album", album["album"]), - artist=album_meta.get("id3_artist", album["artist"]), + for album in albums: + album_kw = dict(id=album["dir"], + parent=album["artistdir"], + isDir="true", + title=album["name"], + album=album["name"], + artist=album["artistname"], + coverArt=album["coverid"] + #year=TODO # playCount="0" # created="2016-05-08T05:31:31.000Z"/>) ) - if 'cover' in album_meta: - album_kw["coverArt"] = album_meta["cover"] - if 'id3_year' in album_meta: - album_kw["year"] = album_meta['id3_year'] response.add_child("album", _parent="albumList", **album_kw) return response @@ -247,18 +245,10 @@ class PysonicApi(object): List an artist dir """ dir_id = int(id) - - cherrypy.response.headers['Content-Type'] = 'text/xml; charset=utf-8' + dirtype, dirinfo, entity = self.library.db.get_musicdir(dirid=dir_id) response = ApiResponse() response.add_child("directory") - - dirtype, dirinfo, entity = self.library.db.get_musicdir(dirid=dir_id) - - from pprint import pprint - pprint(dirinfo) - pprint(entity) - response.set_attrs(_path="directory", name=entity['name'], id=entity['id'], parent=dirinfo['parent'], playCount=420) @@ -297,53 +287,9 @@ class PysonicApi(object): type="music", **moreargs) + cherrypy.response.headers['Content-Type'] = 'text/xml; charset=utf-8' return response - def render_node(self, item, item_meta, directory, dir_meta): - """ - Given a node and it's parent directory, and meta, return a dict with the keys formatted how the subsonic clients - expect them to be - :param item: - :param item_meta: - :param directory: - :param dir_meta: - """ - raise Exception("stop using this") - child = dict(id=item["id"], - parent=item["id"], - isDir="true" if "file" not in item else "false", - title=item_meta.get("id3_title", item["name"]), - album=item_meta.get("id3_album", item["album"]), - artist=item_meta.get("id3_artist", item["artist"]), - # playCount="5", - # created="2016-04-25T07:31:33.000Z" - # genre="Other", - # path="Cosmic Gate/Sign Of The Times/03 Flatline (featuring Kyler England).mp3" - type="music") - if 'kbitrate' in item_meta: - child["bitrate"] = item_meta["kbitrate"] - if item["size"] != -1: - child["size"] = item["size"] - if "media_length" in item_meta: - child["duration"] = item_meta["media_length"] - if "albumId" in directory: - child["albumId"] = directory["id"] - if "artistId" in directory: - child["artistId"] = directory["parent"] - if "." in item["name"]: - child["suffix"] = item["name"].split(".")[-1] - if item["type"]: - child["contentType"] = item["type"] - if 'cover' in item_meta: - child["coverArt"] = item_meta["cover"] - elif 'cover' in dir_meta: - child["coverArt"] = dir_meta["cover"] - if 'track' in item_meta: - child["track"] = item_meta['track'] - if 'id3_year' in item_meta: - child["year"] = item_meta['id3_year'] - return child - @cherrypy.expose def stream_view(self, id, maxBitRate="256", **kwargs): maxBitRate = int(maxBitRate) @@ -372,7 +318,6 @@ class PysonicApi(object): # transcode_meta = "transcoded_{}_size".format(to_bitrate) # if transcode_meta in meta: # cherrypy.response.headers['Content-Length'] = str(int(meta[transcode_meta])) - print(fpath) transcode_args = ["ffmpeg", "-i", fpath, "-map", "0:0", "-b:a", "{}k".format(to_bitrate), "-v", "0", "-f", "mp3", "-"] @@ -437,7 +382,6 @@ class PysonicApi(object): yield data logging.info("\nSent {} bytes for {}".format(total, fpath)) return content() - getCoverArt_view._cp_config = {'response.stream': True} @cherrypy.expose @@ -558,7 +502,7 @@ class PysonicApi(object): :param submission: True if end of song reached. False on start of track. """ submission = True if submission == "true" else False - # TODO save played track stats + # TODO save played track stats and/or do last.fm bullshit return ApiResponse() @cherrypy.expose @@ -606,3 +550,9 @@ class PysonicApi(object): def setRating_view(self, id, rating): # rating is 1-5 pass + + @cherrypy.expose + def savePlayQueue_view(self, id, current, position, **kwargs): + print("TODO save playlist with items {} current {} position {}".format(id, current, position)) + # TODO save playlist with items ['378', '386', '384', '380', '383'] current 383 position 4471 + # id entries are strings! diff --git a/pysonic/database.py b/pysonic/database.py index 24f1fd7..39d1fb4 100644 --- a/pysonic/database.py +++ b/pysonic/database.py @@ -73,6 +73,7 @@ class PysonicDatabase(object): 'coverid' INTEGER, 'dir' INTEGER, 'name' TEXT, + 'added' INTEGER NOT NULL DEFAULT -1, UNIQUE (artistid, dir));""", """CREATE TABLE 'songs' ( 'id' INTEGER PRIMARY KEY AUTOINCREMENT, @@ -181,11 +182,31 @@ class PysonicDatabase(object): return artists @readcursor - def get_albums(self, cursor, id=None, artist=None, sortby=None, order=None): - assert order in ["asc", "desc", None] + def get_albums(self, cursor, id=None, artist=None, sortby=None, order=None, limit=None): + """ + :param limit: int or tuple of int, int. translates directly to sql logic. + """ + if order: + order = {"asc": "ASC", "desc": "DESC"}[order] + + if sortby and sortby == "random": + sortby = "RANDOM()" + albums = [] - q = "SELECT * FROM albums" + q = """ + SELECT + alb.*, + art.name as artistname, + dirs.parent as artistdir + FROM albums as alb + INNER JOIN artists as art + on alb.artistid = art.id + INNER JOIN dirs + on dirs.id = alb.dir + """ + #q = "SELECT * FROM albums" + params = [] conditions = [] @@ -199,7 +220,14 @@ class PysonicDatabase(object): q += " WHERE " + " AND ".join(conditions) if sortby: - q += " ORDER BY {} {}".format(sortby, order.upper() if order else "ASC") + q += " ORDER BY {}".format(sortby) + if order: + q += " {}".format(order) + + if limit: + q += " LIMIT {}".format(limit) if isinstance(limit, int) \ + else " LIMIT {}, {}".format(*limit) + cursor.execute(q, params) for row in cursor: albums.append(row) @@ -253,8 +281,6 @@ class PysonicDatabase(object): if limit: q += " LIMIT {}".format(limit) # TODO support limit pagination - print(q) - cursor.execute(q, params) for row in cursor: songs.append(row) diff --git a/pysonic/scanner.py b/pysonic/scanner.py index 08ab8df..42349a4 100644 --- a/pysonic/scanner.py +++ b/pysonic/scanner.py @@ -191,8 +191,8 @@ class PysonicFilesystemScanner(object): if row: album_id = row['id'] else: - cursor.execute("INSERT INTO albums (artistid, dir, name) VALUES (?, ?, ?)", - (artist_id, album_dirid, dirnames[-1])) + cursor.execute("INSERT INTO albums (artistid, dir, name, added) VALUES (?, ?, ?, ?)", + (artist_id, album_dirid, dirnames[-1], int(time()))) album_id = cursor.lastrowid return album_id, album_dirid