Add genres

This commit is contained in:
Dave Pedu 2018-04-04 16:44:05 -07:00
parent 5f3b2e471b
commit a3c354d4ef
5 changed files with 105 additions and 16 deletions

View File

@ -523,9 +523,8 @@ class PysonicApi(object):
def getGenres_view(self, **kwargs): def getGenres_view(self, **kwargs):
response = ApiResponse() response = ApiResponse()
response.add_child("genres") response.add_child("genres")
response.add_child("genre", _parent="genres", value="Death Metal", songCount=420, albumCount=69) for row in self.library.db.get_genres():
response.add_child("genre", _parent="genres", value="Metal", songCount=52, albumCount=3) response.add_child("genre", _parent="genres", value=row["name"], songCount=420, albumCount=69)
response.add_child("genre", _parent="genres", value="Punk", songCount=34, albumCount=3)
return response return response
@cherrypy.expose @cherrypy.expose

View File

@ -59,6 +59,9 @@ class PysonicDatabase(object):
'name' TEXT, 'name' TEXT,
UNIQUE(parent, name) UNIQUE(parent, name)
)""", )""",
"""CREATE TABLE 'genres' (
'id' INTEGER PRIMARY KEY AUTOINCREMENT,
'name' TEXT UNIQUE)""",
"""CREATE TABLE 'artists' ( """CREATE TABLE 'artists' (
'id' INTEGER PRIMARY KEY AUTOINCREMENT, 'id' INTEGER PRIMARY KEY AUTOINCREMENT,
'libraryid' INTEGER, 'libraryid' INTEGER,
@ -75,6 +78,7 @@ class PysonicDatabase(object):
'id' INTEGER PRIMARY KEY AUTOINCREMENT, 'id' INTEGER PRIMARY KEY AUTOINCREMENT,
'library' INTEGER, 'library' INTEGER,
'albumid' BOOLEAN, 'albumid' BOOLEAN,
'genre' INTEGER DEFAULT NULL,
'file' TEXT UNIQUE, -- path from the library root 'file' TEXT UNIQUE, -- path from the library root
'size' INTEGER NOT NULL DEFAULT -1, 'size' INTEGER NOT NULL DEFAULT -1,
'title' TEXT NOT NULL, 'title' TEXT NOT NULL,
@ -202,10 +206,76 @@ class PysonicDatabase(object):
return albums return albums
@readcursor @readcursor
def get_song(self, cursor, songid): def get_songs(self, cursor, id=None, genre=None, sortby=None, order=None, limit=None):
for item in cursor.execute("SELECT * FROM songs WHERE id=?", (songid, )): # TODO make this query massively uglier by joining albums and artists so that artistid etc can be a filter
return item # or maybe lookup those IDs in the library layer?
return None 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

View File

@ -83,7 +83,7 @@ class PysonicLibrary(object):
return cover return cover
def get_song(self, song_id): 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] library = self.db.get_libraries(song["library"])[0]
song['_fullpath'] = os.path.join(library["path"], song["file"]) song['_fullpath'] = os.path.join(library["path"], song["file"])
return song return song

View File

@ -210,11 +210,17 @@ class PysonicFilesystemScanner(object):
writer.execute(q, params) writer.execute(q, params)
# If the metadata has an artist or album name, update the relevant items # If the metadata has an artist or album name, update the relevant items
# TODO ignore metadata if theyre blank
if "album" in meta: if "album" in meta:
writer.execute("UPDATE albums SET name=? WHERE id=?", (meta["album"], row["albumid"])) writer.execute("UPDATE albums SET name=? WHERE id=?", (meta["album"], row["albumid"]))
if "artist" in meta: if "artist" in meta:
album = writer.execute("SELECT artistid FROM albums WHERE id=?", (row['albumid'], )).fetchone() album = writer.execute("SELECT artistid FROM albums WHERE id=?", (row['albumid'], )).fetchone()
writer.execute("UPDATE artists SET name=? WHERE id=?", (meta["artist"], album["artistid"])) 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 # Commit every 50 items
processed += 1 processed += 1
@ -225,6 +231,13 @@ class PysonicFilesystemScanner(object):
if processed != 0: if processed != 0:
writer.execute("COMMIT") 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): def scan_file_metadata(self, fpath):
""" """
Scan the file for metadata. Scan the file for metadata.
@ -283,6 +296,10 @@ class PysonicFilesystemScanner(object):
meta["year"] = audio['TDRC'].text[0].year meta["year"] = audio['TDRC'].text[0].year
except (KeyError, IndexError): except (KeyError, IndexError):
pass pass
try:
meta["genre"] = audio['TCON'].text[0]
except (KeyError, IndexError):
pass
logging.info("got all media info from %s", fpath) logging.info("got all media info from %s", fpath)
return meta return meta

View File

@ -1,9 +1,12 @@
beautifulsoup4==4.6.0 beautifulsoup4==4.6.0
cheroot==5.8.3 bs4==0.0.1
CherryPy==11.0.0 cheroot==6.0.0
lxml==3.8.0 CherryPy==14.0.1
mutagen==1.38 lxml==4.2.1
portend==2.1.2 more-itertools==4.1.0
pytz==2017.2 mutagen==1.40.0
six==1.10.0 portend==2.2
tempora==1.8 pysonic==0.0.1
pytz==2018.3
six==1.11.0
tempora==1.11