Partial shuffle support
This commit is contained in:
parent
b6daecdd5e
commit
efcef1f5df
|
@ -118,12 +118,12 @@ class PysonicApi(object):
|
||||||
doc.append(albumlist)
|
doc.append(albumlist)
|
||||||
|
|
||||||
for album in albumset:
|
for album in albumset:
|
||||||
album_meta = self.library.db.decode_metadata(album['metadata'])
|
album_meta = album['metadata']
|
||||||
tag = doc.new_tag("album",
|
tag = doc.new_tag("album",
|
||||||
id=album["id"],
|
id=album["id"],
|
||||||
parent=album["parent"],
|
parent=album["parent"],
|
||||||
isDir="true" if album['isdir'] else "false",
|
isDir="true" if album['isdir'] else "false",
|
||||||
title=album_meta.get("id3_title", album["name"]),
|
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"]),
|
album=album_meta.get("id3_album", album["album"]),
|
||||||
artist=album_meta.get("id3_artist", album["artist"]),
|
artist=album_meta.get("id3_artist", album["artist"]),
|
||||||
# X year="2014"
|
# X year="2014"
|
||||||
|
@ -151,7 +151,7 @@ class PysonicApi(object):
|
||||||
dirtag = doc.new_tag("directory")
|
dirtag = doc.new_tag("directory")
|
||||||
|
|
||||||
directory = self.library.get_dir(dir_id)
|
directory = self.library.get_dir(dir_id)
|
||||||
dir_meta = self.db.decode_metadata(directory["metadata"])
|
dir_meta = directory["metadata"]
|
||||||
children = self.library.get_dir_children(dir_id)
|
children = self.library.get_dir_children(dir_id)
|
||||||
dirtag.attrs.update(name=directory['name'], id=directory['id'],
|
dirtag.attrs.update(name=directory['name'], id=directory['id'],
|
||||||
parent=directory['parent'], playCount=10)
|
parent=directory['parent'], playCount=10)
|
||||||
|
@ -161,7 +161,7 @@ class PysonicApi(object):
|
||||||
# omit not dirs and media in browser
|
# omit not dirs and media in browser
|
||||||
if not item["isdir"] and item["type"] not in MUSIC_TYPES:
|
if not item["isdir"] and item["type"] not in MUSIC_TYPES:
|
||||||
continue
|
continue
|
||||||
item_meta = self.db.decode_metadata(item['metadata'])
|
item_meta = item['metadata']
|
||||||
dirtag.append(self.render_node(doc, item, item_meta, directory, dir_meta))
|
dirtag.append(self.render_node(doc, item, item_meta, directory, dir_meta))
|
||||||
yield doc.prettify()
|
yield doc.prettify()
|
||||||
|
|
||||||
|
@ -327,23 +327,34 @@ class PysonicApi(object):
|
||||||
|
|
||||||
@cherrypy.expose
|
@cherrypy.expose
|
||||||
def getStarred_view(self, **kwargs):
|
def getStarred_view(self, **kwargs):
|
||||||
children = self.library.get_starred(cherrypy.request.login)
|
|
||||||
|
|
||||||
cherrypy.response.headers['Content-Type'] = 'text/xml; charset=utf-8'
|
cherrypy.response.headers['Content-Type'] = 'text/xml; charset=utf-8'
|
||||||
doc, root = self.response()
|
doc, root = self.response()
|
||||||
|
|
||||||
tag = doc.new_tag("starred")
|
tag = doc.new_tag("starred")
|
||||||
|
|
||||||
#directory = self.library.get_dir(dir_id)
|
|
||||||
#dir_meta = self.db.decode_metadata(directory["metadata"])
|
|
||||||
#children = self.library.get_dir_children(dir_id)
|
|
||||||
root.append(tag)
|
root.append(tag)
|
||||||
|
|
||||||
|
children = self.library.get_starred(cherrypy.request.login)
|
||||||
for item in children:
|
for item in children:
|
||||||
# omit not dirs and media in browser
|
# omit not dirs and media in browser
|
||||||
if not item["isdir"] and item["type"] not in MUSIC_TYPES:
|
if not item["isdir"] and item["type"] not in MUSIC_TYPES:
|
||||||
continue
|
continue
|
||||||
item_meta = self.db.decode_metadata(item['metadata'])
|
item_meta = item['metadata']
|
||||||
|
itemtype = "song" if item["type"] in MUSIC_TYPES else "album"
|
||||||
|
tag.append(self.render_node(doc, item, item_meta, {}, {}, tagname=itemtype))
|
||||||
|
yield doc.prettify()
|
||||||
|
|
||||||
|
@cherrypy.expose
|
||||||
|
def getRandomSongs_view(self, size=50, genre=None, fromYear=0, toYear=0, **kwargs):
|
||||||
|
cherrypy.response.headers['Content-Type'] = 'text/xml; charset=utf-8'
|
||||||
|
doc, root = self.response()
|
||||||
|
tag = doc.new_tag("randomSongs")
|
||||||
|
root.append(tag)
|
||||||
|
|
||||||
|
children = self.library.get_songs(size, shuffle=True)
|
||||||
|
for item in children:
|
||||||
|
# omit not dirs and media in browser
|
||||||
|
if not item["isdir"] and item["type"] not in MUSIC_TYPES:
|
||||||
|
continue
|
||||||
|
item_meta = item['metadata']
|
||||||
itemtype = "song" if item["type"] in MUSIC_TYPES else "album"
|
itemtype = "song" if item["type"] in MUSIC_TYPES else "album"
|
||||||
tag.append(self.render_node(doc, item, item_meta, {}, {}, tagname=itemtype))
|
tag.append(self.render_node(doc, item, item_meta, {}, {}, tagname=itemtype))
|
||||||
yield doc.prettify()
|
yield doc.prettify()
|
||||||
|
|
|
@ -87,13 +87,56 @@ class PysonicDatabase(object):
|
||||||
|
|
||||||
# Virtual file tree
|
# Virtual file tree
|
||||||
def getnode(self, node_id):
|
def getnode(self, node_id):
|
||||||
with closing(self.db.cursor()) as cursor:
|
return self.getnodes(node_id=node_id)[0]
|
||||||
return cursor.execute("SELECT * FROM nodes WHERE id=?;", (node_id, )).fetchone()
|
|
||||||
|
def _populate_meta(self, node):
|
||||||
|
node['metadata'] = self.decode_metadata(node['metadata'])
|
||||||
|
return node
|
||||||
|
|
||||||
|
def getnodes(self, *parent_ids, node_id=None, types=None, limit=None, order=None):
|
||||||
|
"""
|
||||||
|
Find nodes that match the passed paramters.
|
||||||
|
:param parent_ids: one or more parents to find children of
|
||||||
|
:type parent_ids: int
|
||||||
|
:param node_id: single node id to return
|
||||||
|
:type node_id: int
|
||||||
|
:param types: filter by type column
|
||||||
|
:type types: list
|
||||||
|
:param limit: number of records to limit to
|
||||||
|
:param order: one of ("rand") to select ordering mode
|
||||||
|
"""
|
||||||
|
query = "SELECT * FROM nodes WHERE "
|
||||||
|
qargs = []
|
||||||
|
|
||||||
|
def add_filter(name, values):
|
||||||
|
nonlocal query
|
||||||
|
nonlocal qargs
|
||||||
|
query += "{} in (".format(name)
|
||||||
|
for value in (values if type(values) in [list, tuple] else [values]):
|
||||||
|
query += "?, "
|
||||||
|
qargs += [value]
|
||||||
|
query = query.rstrip(", ")
|
||||||
|
query += ") AND"
|
||||||
|
|
||||||
|
if node_id:
|
||||||
|
add_filter("id", node_id)
|
||||||
|
if parent_ids:
|
||||||
|
add_filter("parent", parent_ids)
|
||||||
|
if types:
|
||||||
|
add_filter("type", types)
|
||||||
|
|
||||||
|
query = query.rstrip(" AND")
|
||||||
|
|
||||||
|
if order:
|
||||||
|
query += "ORDER BY "
|
||||||
|
if order == "rand":
|
||||||
|
query += "RANDOM()"
|
||||||
|
|
||||||
|
if limit: # TODO 2-item tuple limit
|
||||||
|
query += " limit {}".format(limit)
|
||||||
|
|
||||||
def getnodes(self, *parent_ids):
|
|
||||||
with closing(self.db.cursor()) as cursor:
|
with closing(self.db.cursor()) as cursor:
|
||||||
return list(chain(*[cursor.execute("SELECT * FROM nodes WHERE parent=?;", (parent_id, )).fetchall()
|
return list(map(self._populate_meta, cursor.execute(query, qargs).fetchall()))
|
||||||
for parent_id in parent_ids]))
|
|
||||||
|
|
||||||
def addnode(self, parent_id, fspath, name):
|
def addnode(self, parent_id, fspath, name):
|
||||||
fullpath = os.path.join(fspath, name)
|
fullpath = os.path.join(fspath, name)
|
||||||
|
@ -132,9 +175,9 @@ class PysonicDatabase(object):
|
||||||
def get_metadata(self, node_id):
|
def get_metadata(self, node_id):
|
||||||
keys_in_table = ["title", "album", "artist", "type"]
|
keys_in_table = ["title", "album", "artist", "type"]
|
||||||
node = self.getnode(node_id)
|
node = self.getnode(node_id)
|
||||||
metadata = self.decode_metadata(node["metadata"])
|
meta = node["metadata"]
|
||||||
metadata.update({item: node[item] for item in ["title", "album", "artist", "type"]})
|
meta.update({item: node[item] for item in keys_in_table})
|
||||||
return metadata
|
return meta
|
||||||
|
|
||||||
def decode_metadata(self, metadata):
|
def decode_metadata(self, metadata):
|
||||||
if metadata:
|
if metadata:
|
||||||
|
@ -169,7 +212,10 @@ class PysonicDatabase(object):
|
||||||
query = "INSERT INTO stars (userid, nodeid) VALUES (?, ?);"
|
query = "INSERT INTO stars (userid, nodeid) VALUES (?, ?);"
|
||||||
else:
|
else:
|
||||||
query = "DELETE FROM stars WHERE userid=? and nodeid=?;"
|
query = "DELETE FROM stars WHERE userid=? and nodeid=?;"
|
||||||
cursor.execute(query, (user_id, node_id))
|
try:
|
||||||
|
cursor.execute(query, (user_id, node_id))
|
||||||
|
except sqlite3.IntegrityError:
|
||||||
|
pass
|
||||||
|
|
||||||
def get_starred_items(self, for_user_id=None):
|
def get_starred_items(self, for_user_id=None):
|
||||||
with closing(self.db.cursor()) as cursor:
|
with closing(self.db.cursor()) as cursor:
|
||||||
|
@ -178,6 +224,5 @@ class PysonicDatabase(object):
|
||||||
if for_user_id:
|
if for_user_id:
|
||||||
q += """ AND userid=?"""
|
q += """ AND userid=?"""
|
||||||
qargs += [int(for_user_id)]
|
qargs += [int(for_user_id)]
|
||||||
print(q)
|
return list(map(self._populate_meta,
|
||||||
print(qargs)
|
cursor.execute(q, qargs).fetchall()))
|
||||||
return cursor.execute(q, qargs).fetchall()
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import os
|
import os
|
||||||
import json
|
|
||||||
import logging
|
import logging
|
||||||
from pysonic.scanner import PysonicFilesystemScanner
|
from pysonic.scanner import PysonicFilesystemScanner
|
||||||
|
from pysonic.types import MUSIC_TYPES
|
||||||
|
|
||||||
|
|
||||||
LETTER_GROUPS = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t",
|
LETTER_GROUPS = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t",
|
||||||
|
@ -43,21 +43,21 @@ class PysonicLibrary(object):
|
||||||
|
|
||||||
def add_dir(self, dir_path):
|
def add_dir(self, dir_path):
|
||||||
dir_path = os.path.abspath(os.path.normpath(dir_path))
|
dir_path = os.path.abspath(os.path.normpath(dir_path))
|
||||||
libraries = [self.db.decode_metadata(i['metadata'])['fspath'] for i in self.db.getnodes(-1)]
|
libraries = [i['metadata']['fspath'] for i in self.db.getnodes(-1)]
|
||||||
if dir_path in libraries:
|
if dir_path in libraries:
|
||||||
raise DuplicateRootException("Dir already in library")
|
raise DuplicateRootException("Dir already in library")
|
||||||
else:
|
else:
|
||||||
new_root = self.db._addnode(-1, 'New Library', is_dir=True)
|
new_root = self.db._addnode(-1, 'New Library', is_dir=True)
|
||||||
self.db.update_metadata(new_root['id'], fspath=dir_path)
|
self.db.update_metadata(new_root['id'], fspath=dir_path)
|
||||||
|
|
||||||
@memoize
|
#@memoize
|
||||||
def get_libraries(self):
|
def get_libraries(self):
|
||||||
"""
|
"""
|
||||||
Libraries are top-level nodes
|
Libraries are top-level nodes
|
||||||
"""
|
"""
|
||||||
return self.db.getnodes(-1)
|
return self.db.getnodes(-1)
|
||||||
|
|
||||||
@memoize
|
#@memoize
|
||||||
def get_artists(self):
|
def get_artists(self):
|
||||||
# Assume artists are second level dirs
|
# Assume artists are second level dirs
|
||||||
return self.db.getnodes(*[item["id"] for item in self.get_libraries()])
|
return self.db.getnodes(*[item["id"] for item in self.get_libraries()])
|
||||||
|
@ -68,18 +68,18 @@ class PysonicLibrary(object):
|
||||||
def get_dir_children(self, dirid):
|
def get_dir_children(self, dirid):
|
||||||
return self.db.getnodes(dirid)
|
return self.db.getnodes(dirid)
|
||||||
|
|
||||||
@memoize
|
#@memoize
|
||||||
def get_albums(self):
|
def get_albums(self):
|
||||||
return self.db.getnodes(*[item["id"] for item in self.get_artists()])
|
return self.db.getnodes(*[item["id"] for item in self.get_artists()])
|
||||||
|
|
||||||
@memoize
|
#@memoize
|
||||||
def get_filepath(self, nodeid):
|
def get_filepath(self, nodeid):
|
||||||
parents = [self.db.getnode(nodeid)]
|
parents = [self.db.getnode(nodeid)]
|
||||||
while parents[-1]['parent'] != -1:
|
while parents[-1]['parent'] != -1:
|
||||||
parents.append(self.db.getnode(parents[-1]['parent']))
|
parents.append(self.db.getnode(parents[-1]['parent']))
|
||||||
root = parents.pop()
|
root = parents.pop()
|
||||||
parents.reverse()
|
parents.reverse()
|
||||||
return os.path.join(json.loads(root['metadata'])['fspath'], *[i['name'] for i in parents])
|
return os.path.join(root['metadata']['fspath'], *[i['name'] for i in parents])
|
||||||
|
|
||||||
def get_file_metadata(self, nodeid):
|
def get_file_metadata(self, nodeid):
|
||||||
return self.db.get_metadata(nodeid)
|
return self.db.get_metadata(nodeid)
|
||||||
|
@ -105,3 +105,6 @@ class PysonicLibrary(object):
|
||||||
|
|
||||||
def get_starred(self, username):
|
def get_starred(self, username):
|
||||||
return self.db.get_starred_items(self.db.get_user(username)["id"])
|
return self.db.get_starred_items(self.db.get_user(username)["id"])
|
||||||
|
|
||||||
|
def get_songs(self, limit=50, shuffle=True):
|
||||||
|
return self.db.getnodes(types=MUSIC_TYPES, limit=limit, order="rand")
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import json
|
|
||||||
import logging
|
import logging
|
||||||
import mimetypes
|
import mimetypes
|
||||||
from time import time
|
from time import time
|
||||||
|
@ -28,7 +27,7 @@ class PysonicFilesystemScanner(object):
|
||||||
logging.warning("Beginning library rescan")
|
logging.warning("Beginning library rescan")
|
||||||
start = time()
|
start = time()
|
||||||
for parent in self.library.get_libraries():
|
for parent in self.library.get_libraries():
|
||||||
meta = json.loads(parent["metadata"])
|
meta = parent["metadata"]
|
||||||
logging.info("Scanning {}".format(meta["fspath"]))
|
logging.info("Scanning {}".format(meta["fspath"]))
|
||||||
|
|
||||||
def recurse_dir(path, parent):
|
def recurse_dir(path, parent):
|
||||||
|
@ -70,7 +69,7 @@ class PysonicFilesystemScanner(object):
|
||||||
artist = artist_dir["name"]
|
artist = artist_dir["name"]
|
||||||
for album_dir in self.library.db.getnodes(artist_dir["id"]):
|
for album_dir in self.library.db.getnodes(artist_dir["id"]):
|
||||||
album = album_dir["name"]
|
album = album_dir["name"]
|
||||||
album_meta = self.library.db.get_metadata(album_dir["id"])
|
album_meta = album_dir["metadata"]
|
||||||
for track_file in self.library.db.getnodes(album_dir["id"]):
|
for track_file in self.library.db.getnodes(album_dir["id"]):
|
||||||
title = track_file["name"]
|
title = track_file["name"]
|
||||||
if not track_file["title"]:
|
if not track_file["title"]:
|
||||||
|
@ -106,9 +105,9 @@ class PysonicFilesystemScanner(object):
|
||||||
artist = artist_dir["name"]
|
artist = artist_dir["name"]
|
||||||
for album_dir in self.library.db.getnodes(artist_dir["id"]):
|
for album_dir in self.library.db.getnodes(artist_dir["id"]):
|
||||||
album = album_dir["name"]
|
album = album_dir["name"]
|
||||||
album_meta = self.library.db.get_metadata(album_dir["id"])
|
album_meta = album_dir["metadata"]
|
||||||
for track_file in self.library.db.getnodes(album_dir["id"]):
|
for track_file in self.library.db.getnodes(album_dir["id"]):
|
||||||
track_meta = self.library.db.decode_metadata(track_file['metadata'])
|
track_meta = track_file['metadata']
|
||||||
title = track_file["name"]
|
title = track_file["name"]
|
||||||
fpath = self.library.get_filepath(track_file["id"])
|
fpath = self.library.get_filepath(track_file["id"])
|
||||||
if track_meta.get('id3_done', False) or track_file.get("type", "x") not in MUSIC_TYPES:
|
if track_meta.get('id3_done', False) or track_file.get("type", "x") not in MUSIC_TYPES:
|
||||||
|
|
Loading…
Reference in New Issue