diff --git a/pysonic/api.py b/pysonic/api.py index dd77fbc..937b09d 100644 --- a/pysonic/api.py +++ b/pysonic/api.py @@ -523,9 +523,8 @@ class PysonicApi(object): def getGenres_view(self, **kwargs): response = ApiResponse() response.add_child("genres") - response.add_child("genre", _parent="genres", value="Death Metal", songCount=420, albumCount=69) - response.add_child("genre", _parent="genres", value="Metal", songCount=52, albumCount=3) - response.add_child("genre", _parent="genres", value="Punk", songCount=34, albumCount=3) + for row in self.library.db.get_genres(): + response.add_child("genre", _parent="genres", value=row["name"], songCount=420, albumCount=69) return response @cherrypy.expose diff --git a/pysonic/database.py b/pysonic/database.py index ccfe2a8..24f1fd7 100644 --- a/pysonic/database.py +++ b/pysonic/database.py @@ -59,6 +59,9 @@ class PysonicDatabase(object): 'name' TEXT, UNIQUE(parent, name) )""", + """CREATE TABLE 'genres' ( + 'id' INTEGER PRIMARY KEY AUTOINCREMENT, + 'name' TEXT UNIQUE)""", """CREATE TABLE 'artists' ( 'id' INTEGER PRIMARY KEY AUTOINCREMENT, 'libraryid' INTEGER, @@ -75,6 +78,7 @@ class PysonicDatabase(object): 'id' INTEGER PRIMARY KEY AUTOINCREMENT, 'library' INTEGER, 'albumid' BOOLEAN, + 'genre' INTEGER DEFAULT NULL, 'file' TEXT UNIQUE, -- path from the library root 'size' INTEGER NOT NULL DEFAULT -1, 'title' TEXT NOT NULL, @@ -202,10 +206,76 @@ class PysonicDatabase(object): return albums @readcursor - def get_song(self, cursor, songid): - for item in cursor.execute("SELECT * FROM songs WHERE id=?", (songid, )): - return item - return None + def get_songs(self, cursor, id=None, genre=None, sortby=None, order=None, limit=None): + # TODO make this query massively uglier by joining albums and artists so that artistid etc can be a filter + # or maybe lookup those IDs in the library layer? + if order: + order = {"asc": "ASC", "desc": "DESC"}[order] + + if sortby and sortby == "random": + sortby = "RANDOM()" + + songs = [] + + q = """ + SELECT + s.*, + alb.name as albumname, + alb.coverid as albumcoverid, + art.name as artistname, + g.name as genrename + FROM songs as s + INNER JOIN albums as alb + on s.albumid == alb.id + INNER JOIN artists as art + on alb.artistid = art.id + LEFT JOIN genres as g + on s.genre == g.id + """ + + params = [] + + conditions = [] + if id: + conditions.append("s.id = ?") + params.append(id) + if genre: + conditions.append("g.name = ?") + params.append(genre) + if conditions: + q += " WHERE " + " AND ".join(conditions) + + if sortby: + q += " ORDER BY {}".format(sortby) + if order: + q += " {}".format(order) + + if limit: + q += " LIMIT {}".format(limit) # TODO support limit pagination + + print(q) + + cursor.execute(q, params) + for row in cursor: + songs.append(row) + return songs + + @readcursor + def get_genres(self, cursor, genre_id=None): + genres = [] + q = "SELECT * FROM genres" + params = [] + conditions = [] + if genre_id: + conditions.append("id = ?") + params.append(genre_id) + if conditions: + q += " WHERE " + " AND ".join(conditions) + cursor.execute(q, params) + for row in cursor: + genres.append(row) + return genres + diff --git a/pysonic/library.py b/pysonic/library.py index 611214f..8b1ba29 100644 --- a/pysonic/library.py +++ b/pysonic/library.py @@ -83,7 +83,7 @@ class PysonicLibrary(object): return cover def get_song(self, song_id): - song = self.db.get_song(song_id) + song = self.db.get_songs(id=song_id)[0] library = self.db.get_libraries(song["library"])[0] song['_fullpath'] = os.path.join(library["path"], song["file"]) return song diff --git a/pysonic/scanner.py b/pysonic/scanner.py index 87f31ad..b38a9b4 100644 --- a/pysonic/scanner.py +++ b/pysonic/scanner.py @@ -210,11 +210,17 @@ class PysonicFilesystemScanner(object): writer.execute(q, params) # If the metadata has an artist or album name, update the relevant items + # TODO ignore metadata if theyre blank if "album" in meta: writer.execute("UPDATE albums SET name=? WHERE id=?", (meta["album"], row["albumid"])) if "artist" in meta: album = writer.execute("SELECT artistid FROM albums WHERE id=?", (row['albumid'], )).fetchone() writer.execute("UPDATE artists SET name=? WHERE id=?", (meta["artist"], album["artistid"])) + if "genre" in meta: + genre_name = meta["genre"].strip() + if genre_name: + genre_id = self.get_genre_id(writer, meta["genre"]) + writer.execute("UPDATE songs SET genre=? WHERE id=?", (genre_id, row['id'])) # Commit every 50 items processed += 1 @@ -225,6 +231,13 @@ class PysonicFilesystemScanner(object): if processed != 0: writer.execute("COMMIT") + def get_genre_id(self, cursor, genre_name): + genre_name = genre_name.title().strip() # normalize + for row in cursor.execute("SELECT * FROM genres WHERE name=?", (genre_name, )): + return row['id'] + cursor.execute("INSERT INTO genres (name) VALUES (?)", (genre_name, )) + return cursor.lastrowid + def scan_file_metadata(self, fpath): """ Scan the file for metadata. @@ -283,6 +296,10 @@ class PysonicFilesystemScanner(object): meta["year"] = audio['TDRC'].text[0].year except (KeyError, IndexError): pass + try: + meta["genre"] = audio['TCON'].text[0] + except (KeyError, IndexError): + pass logging.info("got all media info from %s", fpath) return meta diff --git a/requirements.txt b/requirements.txt index 5777cbf..2d29bb7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,9 +1,12 @@ beautifulsoup4==4.6.0 -cheroot==5.8.3 -CherryPy==11.0.0 -lxml==3.8.0 -mutagen==1.38 -portend==2.1.2 -pytz==2017.2 -six==1.10.0 -tempora==1.8 +bs4==0.0.1 +cheroot==6.0.0 +CherryPy==14.0.1 +lxml==4.2.1 +more-itertools==4.1.0 +mutagen==1.40.0 +portend==2.2 +pysonic==0.0.1 +pytz==2018.3 +six==1.11.0 +tempora==1.11