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())
|
||||
|
||||
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)
|
||||
m = defaultdict(lambda: list())
|
||||
m.update(dict(kwargs))
|
||||
|
@ -349,13 +350,16 @@ class PysonicApi(object):
|
|||
assert maxBitRate >= 32 and maxBitRate <= 320
|
||||
song = self.library.get_song(id)
|
||||
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'
|
||||
#if "media_length" in meta:
|
||||
# cherrypy.response.headers['X-Content-Duration'] = str(int(meta['media_length']))
|
||||
cherrypy.response.headers['X-Content-Kbitrate'] = str(to_bitrate)
|
||||
if (self.options.skip_transcode or song.get("bitrate", -1024) / 1024 == to_bitrate) \
|
||||
and format["type"] == "audio/mpeg":
|
||||
if (self.options.skip_transcode or (song.get("bitrate") and media_bitrate == to_bitrate)) \
|
||||
and song["format"] == "audio/mpeg":
|
||||
def content():
|
||||
with open(fpath, "rb") as f:
|
||||
while True:
|
||||
|
@ -507,15 +511,34 @@ class PysonicApi(object):
|
|||
"""
|
||||
response = ApiResponse()
|
||||
response.add_child("randomSongs")
|
||||
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"
|
||||
response.add_child(itemtype, _parent="randomSongs",
|
||||
**self.render_node(item, item_meta, {}, self.db.getnode(item["parent"])["metadata"]))
|
||||
children = self.library.db.get_songs(limit=size, sortby="random")
|
||||
for song in children:
|
||||
moreargs = {}
|
||||
if song["format"]:
|
||||
moreargs.update(contentType=song["format"])
|
||||
if song["albumcoverid"]:
|
||||
moreargs.update(coverArt=song["albumcoverid"])
|
||||
if song["length"]:
|
||||
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
|
||||
|
||||
@cherrypy.expose
|
||||
|
|
|
@ -78,72 +78,49 @@ class PysonicFilesystemScanner(object):
|
|||
|
||||
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 root: library root path
|
||||
:param path: scan location path, as a list of subdirs within the root
|
||||
:param dirs: dirs in the current path
|
||||
:param files: files in the current path
|
||||
"""
|
||||
# If there are no files then just bail
|
||||
if not files:
|
||||
# If this is the library root or an empty dir just bail
|
||||
if not path or not files:
|
||||
return
|
||||
# If it is the library root just bail
|
||||
if len(path) == 0:
|
||||
return
|
||||
|
||||
# Guess an artist from the dir
|
||||
artist = path[0]
|
||||
|
||||
# Guess an album from the dir, if possible
|
||||
album = None
|
||||
if len(path) > 1:
|
||||
album = path[-1]
|
||||
|
||||
with closing(self.library.db.db.cursor()) as cursor:
|
||||
# Create artist entry
|
||||
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
|
||||
artist_id, artist_dirid = self.create_or_get_artist(cursor, pid, path[0])
|
||||
|
||||
# Create album entry
|
||||
album_id = None
|
||||
album_dirid = self.create_or_get_dbdir_tree(cursor, pid, path)
|
||||
libpath = os.path.join(*path)
|
||||
album_dirid = None
|
||||
if album:
|
||||
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, path[-1]))
|
||||
album_id = cursor.lastrowid
|
||||
album_id, album_dirid = self.create_or_get_album(cursor, pid, path, artist_id)
|
||||
|
||||
libpath = os.path.join(*path)
|
||||
|
||||
new_files = False
|
||||
for file in files:
|
||||
if not any([file.endswith(".{}".format(i)) for i in MUSIC_EXTENSIONS]):
|
||||
for fname in files:
|
||||
if not any([fname.endswith(".{}".format(i)) for i in MUSIC_EXTENSIONS]):
|
||||
continue
|
||||
fpath = os.path.join(libpath, file)
|
||||
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
|
||||
new_files = self.add_music_if_new(cursor, pid, root, album_id, libpath, fname) or new_files
|
||||
|
||||
# Create cover entry TODO we can probably skip this if there were no new audio files?
|
||||
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
|
||||
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):
|
||||
"""
|
||||
Given a path like /foo/bar, return ['foo', 'bar']
|
||||
|
|
Loading…
Reference in New Issue