Refactor and misc bugfix
This commit is contained in:
parent
a3c354d4ef
commit
33e501928e
|
@ -62,6 +62,7 @@ class ApiResponse(object):
|
||||||
self.data = defaultdict(lambda: list())
|
self.data = defaultdict(lambda: list())
|
||||||
|
|
||||||
def add_child(self, _type, _parent="", _real_parent=None, **kwargs):
|
def add_child(self, _type, _parent="", _real_parent=None, **kwargs):
|
||||||
|
kwargs = {k: v for k, v in kwargs.items() if v or type(v) is int} # filter out empty keys (0 is ok)
|
||||||
parent = _real_parent if _real_parent else self.get_child(_parent)
|
parent = _real_parent if _real_parent else self.get_child(_parent)
|
||||||
m = defaultdict(lambda: list())
|
m = defaultdict(lambda: list())
|
||||||
m.update(dict(kwargs))
|
m.update(dict(kwargs))
|
||||||
|
@ -349,13 +350,16 @@ class PysonicApi(object):
|
||||||
assert maxBitRate >= 32 and maxBitRate <= 320
|
assert maxBitRate >= 32 and maxBitRate <= 320
|
||||||
song = self.library.get_song(id)
|
song = self.library.get_song(id)
|
||||||
fpath = song["_fullpath"]
|
fpath = song["_fullpath"]
|
||||||
to_bitrate = min(maxBitRate, self.options.max_bitrate, song.get("bitrate", 320 * 1024) / 1024)
|
media_bitrate = song.get("bitrate") / 1024 if song.get("bitrate") else 320
|
||||||
|
to_bitrate = min(maxBitRate,
|
||||||
|
self.options.max_bitrate,
|
||||||
|
media_bitrate)
|
||||||
cherrypy.response.headers['Content-Type'] = 'audio/mpeg'
|
cherrypy.response.headers['Content-Type'] = 'audio/mpeg'
|
||||||
#if "media_length" in meta:
|
#if "media_length" in meta:
|
||||||
# cherrypy.response.headers['X-Content-Duration'] = str(int(meta['media_length']))
|
# cherrypy.response.headers['X-Content-Duration'] = str(int(meta['media_length']))
|
||||||
cherrypy.response.headers['X-Content-Kbitrate'] = str(to_bitrate)
|
cherrypy.response.headers['X-Content-Kbitrate'] = str(to_bitrate)
|
||||||
if (self.options.skip_transcode or song.get("bitrate", -1024) / 1024 == to_bitrate) \
|
if (self.options.skip_transcode or (song.get("bitrate") and media_bitrate == to_bitrate)) \
|
||||||
and format["type"] == "audio/mpeg":
|
and song["format"] == "audio/mpeg":
|
||||||
def content():
|
def content():
|
||||||
with open(fpath, "rb") as f:
|
with open(fpath, "rb") as f:
|
||||||
while True:
|
while True:
|
||||||
|
@ -507,15 +511,34 @@ class PysonicApi(object):
|
||||||
"""
|
"""
|
||||||
response = ApiResponse()
|
response = ApiResponse()
|
||||||
response.add_child("randomSongs")
|
response.add_child("randomSongs")
|
||||||
children = self.library.get_songs(size, shuffle=True)
|
children = self.library.db.get_songs(limit=size, sortby="random")
|
||||||
for item in children:
|
for song in children:
|
||||||
# omit not dirs and media in browser
|
moreargs = {}
|
||||||
if not item["isdir"] and item["type"] not in MUSIC_TYPES:
|
if song["format"]:
|
||||||
continue
|
moreargs.update(contentType=song["format"])
|
||||||
item_meta = item['metadata']
|
if song["albumcoverid"]:
|
||||||
itemtype = "song" if item["type"] in MUSIC_TYPES else "album"
|
moreargs.update(coverArt=song["albumcoverid"])
|
||||||
response.add_child(itemtype, _parent="randomSongs",
|
if song["length"]:
|
||||||
**self.render_node(item, item_meta, {}, self.db.getnode(item["parent"])["metadata"]))
|
moreargs.update(duration=song["length"])
|
||||||
|
if song["track"]:
|
||||||
|
moreargs.update(track=song["track"])
|
||||||
|
if song["year"]:
|
||||||
|
moreargs.update(year=song["year"])
|
||||||
|
|
||||||
|
file_extension = song["file"].split(".")[-1]
|
||||||
|
|
||||||
|
response.add_child("song",
|
||||||
|
_parent="randomSongs",
|
||||||
|
title=song["title"],
|
||||||
|
album=song["albumname"],
|
||||||
|
artist=song["artistname"],
|
||||||
|
id=song["id"],
|
||||||
|
isDir="false",
|
||||||
|
parent=song["albumid"],
|
||||||
|
size=song["size"],
|
||||||
|
suffix=file_extension,
|
||||||
|
type="music",
|
||||||
|
**moreargs)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
@cherrypy.expose
|
@cherrypy.expose
|
||||||
|
|
|
@ -78,72 +78,49 @@ class PysonicFilesystemScanner(object):
|
||||||
|
|
||||||
def scan_dir(self, pid, root, path, dirs, files):
|
def scan_dir(self, pid, root, path, dirs, files):
|
||||||
"""
|
"""
|
||||||
Scan a single directory in the library.
|
Scan a single directory in the library. Actually, this ignores all dirs that don't contain files. Dirs are
|
||||||
|
interpreted as follows:
|
||||||
|
- The library root is ignored
|
||||||
|
- Empty dirs are ignored
|
||||||
|
- Dirs containing files are assumed to be an album
|
||||||
|
- Top level dirs in the library are assumed to be artists
|
||||||
|
- Any dirs not following the above rules are transparently ignored
|
||||||
|
- Files placed in an artist dir is an unhandled edge case TODO
|
||||||
|
- Any files with an image extension in an album dir will be assumed to be the cover regardless of naming
|
||||||
|
- TODO ignore dotfiles/dirs
|
||||||
:param pid: parent id
|
:param pid: parent id
|
||||||
:param root: library root path
|
:param root: library root path
|
||||||
:param path: scan location path, as a list of subdirs within the root
|
:param path: scan location path, as a list of subdirs within the root
|
||||||
:param dirs: dirs in the current path
|
:param dirs: dirs in the current path
|
||||||
:param files: files in the current path
|
:param files: files in the current path
|
||||||
"""
|
"""
|
||||||
# If there are no files then just bail
|
# If this is the library root or an empty dir just bail
|
||||||
if not files:
|
if not path or not files:
|
||||||
return
|
return
|
||||||
# If it is the library root just bail
|
# If it is the library root just bail
|
||||||
if len(path) == 0:
|
if len(path) == 0:
|
||||||
return
|
return
|
||||||
|
|
||||||
# Guess an artist from the dir
|
|
||||||
artist = path[0]
|
|
||||||
|
|
||||||
# Guess an album from the dir, if possible
|
# Guess an album from the dir, if possible
|
||||||
album = None
|
album = None
|
||||||
if len(path) > 1:
|
if len(path) > 1:
|
||||||
album = path[-1]
|
album = path[-1]
|
||||||
|
|
||||||
with closing(self.library.db.db.cursor()) as cursor:
|
with closing(self.library.db.db.cursor()) as cursor:
|
||||||
# Create artist entry
|
artist_id, artist_dirid = self.create_or_get_artist(cursor, pid, path[0])
|
||||||
artist_dirid = self.create_or_get_dbdir_tree(cursor, pid, [path[0]])
|
|
||||||
cursor.execute("SELECT * FROM artists WHERE dir = ?", (artist_dirid, ))
|
|
||||||
row = cursor.fetchone()
|
|
||||||
artist_id = None
|
|
||||||
if row:
|
|
||||||
artist_id = row['id']
|
|
||||||
else:
|
|
||||||
cursor.execute("INSERT INTO artists (libraryid, dir, name) VALUES (?, ?, ?)",
|
|
||||||
(pid, artist_dirid, artist))
|
|
||||||
artist_id = cursor.lastrowid
|
|
||||||
|
|
||||||
# Create album entry
|
|
||||||
album_id = None
|
album_id = None
|
||||||
album_dirid = self.create_or_get_dbdir_tree(cursor, pid, path)
|
album_dirid = None
|
||||||
libpath = os.path.join(*path)
|
|
||||||
if album:
|
if album:
|
||||||
cursor.execute("SELECT * FROM albums WHERE artistid = ? AND dir = ?", (artist_id, album_dirid, ))
|
album_id, album_dirid = self.create_or_get_album(cursor, pid, path, artist_id)
|
||||||
row = cursor.fetchone()
|
|
||||||
if row:
|
libpath = os.path.join(*path)
|
||||||
album_id = row['id']
|
|
||||||
else:
|
|
||||||
cursor.execute("INSERT INTO albums (artistid, dir, name) VALUES (?, ?, ?)",
|
|
||||||
(artist_id, album_dirid, path[-1]))
|
|
||||||
album_id = cursor.lastrowid
|
|
||||||
|
|
||||||
new_files = False
|
new_files = False
|
||||||
for file in files:
|
for fname in files:
|
||||||
if not any([file.endswith(".{}".format(i)) for i in MUSIC_EXTENSIONS]):
|
if not any([fname.endswith(".{}".format(i)) for i in MUSIC_EXTENSIONS]):
|
||||||
continue
|
continue
|
||||||
fpath = os.path.join(libpath, file)
|
new_files = self.add_music_if_new(cursor, pid, root, album_id, libpath, fname) or new_files
|
||||||
cursor.execute("SELECT id FROM songs WHERE file=?", (fpath, ))
|
|
||||||
|
|
||||||
if not cursor.fetchall():
|
|
||||||
# We leave most fields blank now and return later
|
|
||||||
cursor.execute("INSERT INTO songs (library, albumid, file, size, title) "
|
|
||||||
"VALUES (?, ?, ?, ?, ?)",
|
|
||||||
(pid,
|
|
||||||
album_id,
|
|
||||||
fpath,
|
|
||||||
os.stat(os.path.join(root, fpath)).st_size,
|
|
||||||
file, ))
|
|
||||||
new_files = True
|
|
||||||
|
|
||||||
# Create cover entry TODO we can probably skip this if there were no new audio files?
|
# Create cover entry TODO we can probably skip this if there were no new audio files?
|
||||||
if album_id:
|
if album_id:
|
||||||
|
@ -161,6 +138,64 @@ class PysonicFilesystemScanner(object):
|
||||||
if new_files: # Commit after each dir IF audio files were found. no audio == dump the artist
|
if new_files: # Commit after each dir IF audio files were found. no audio == dump the artist
|
||||||
cursor.execute("COMMIT")
|
cursor.execute("COMMIT")
|
||||||
|
|
||||||
|
def add_music_if_new(self, cursor, pid, root_dir, album_id, fdir, fname):
|
||||||
|
fpath = os.path.join(fdir, fname)
|
||||||
|
cursor.execute("SELECT id FROM songs WHERE file=?", (fpath, ))
|
||||||
|
if not cursor.fetchall():
|
||||||
|
# We leave most fields blank now and return later
|
||||||
|
# TODO probably not here but track file sizes and mark them for rescan on change
|
||||||
|
cursor.execute("INSERT INTO songs (library, albumid, file, size, title) "
|
||||||
|
"VALUES (?, ?, ?, ?, ?)",
|
||||||
|
(pid,
|
||||||
|
album_id,
|
||||||
|
fpath,
|
||||||
|
os.stat(os.path.join(root_dir, fpath)).st_size,
|
||||||
|
fname, ))
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def create_or_get_artist(self, cursor, pid, dirname):
|
||||||
|
"""
|
||||||
|
Retrieve, creating if necessary, directory information about an artist. Return tuple contains the artist's ID
|
||||||
|
and the dir id associated with the artist.
|
||||||
|
:param cursor: sqlite cursor to use
|
||||||
|
:param pid: root parent id we're working int
|
||||||
|
:param dirname: name of the artist dir
|
||||||
|
:return tuple:
|
||||||
|
"""
|
||||||
|
artist_dirid = self.create_or_get_dbdir_tree(cursor, pid, [dirname])
|
||||||
|
cursor.execute("SELECT * FROM artists WHERE dir = ?", (artist_dirid, ))
|
||||||
|
row = cursor.fetchone()
|
||||||
|
artist_id = None
|
||||||
|
if row:
|
||||||
|
artist_id = row['id']
|
||||||
|
else:
|
||||||
|
cursor.execute("INSERT INTO artists (libraryid, dir, name) VALUES (?, ?, ?)",
|
||||||
|
(pid, artist_dirid, dirname))
|
||||||
|
artist_id = cursor.lastrowid
|
||||||
|
return artist_id, artist_dirid
|
||||||
|
|
||||||
|
def create_or_get_album(self, cursor, pid, dirnames, artist_id):
|
||||||
|
"""
|
||||||
|
Retrieve, creating if necessary, directory information about an album. Return tuple contains the albums's ID
|
||||||
|
and the dir id associated with the album.
|
||||||
|
:param cursor: sqlite cursor to use
|
||||||
|
:param pid: root parent id we're working int
|
||||||
|
:param dirnames: list of directories from the root to the album dir
|
||||||
|
:param artist_id: id of the artist the album belongs to
|
||||||
|
:return tuple:
|
||||||
|
"""
|
||||||
|
album_dirid = self.create_or_get_dbdir_tree(cursor, pid, dirnames)
|
||||||
|
cursor.execute("SELECT * FROM albums WHERE artistid = ? AND dir = ?", (artist_id, album_dirid, ))
|
||||||
|
row = cursor.fetchone()
|
||||||
|
if row:
|
||||||
|
album_id = row['id']
|
||||||
|
else:
|
||||||
|
cursor.execute("INSERT INTO albums (artistid, dir, name) VALUES (?, ?, ?)",
|
||||||
|
(artist_id, album_dirid, dirnames[-1]))
|
||||||
|
album_id = cursor.lastrowid
|
||||||
|
return album_id, album_dirid
|
||||||
|
|
||||||
def split_path(self, path):
|
def split_path(self, path):
|
||||||
"""
|
"""
|
||||||
Given a path like /foo/bar, return ['foo', 'bar']
|
Given a path like /foo/bar, return ['foo', 'bar']
|
||||||
|
|
Loading…
Reference in New Issue