Horrible hack for subsonic support
This commit is contained in:
parent
3718d3b90c
commit
3aedfcf139
|
@ -198,7 +198,7 @@ class PysonicApi(object):
|
||||||
index = response.add_child("index", _parent="indexes", name=letter.upper())
|
index = response.add_child("index", _parent="indexes", name=letter.upper())
|
||||||
for artist in artists:
|
for artist in artists:
|
||||||
if artist["name"][0].lower() in letter:
|
if artist["name"][0].lower() in letter:
|
||||||
response.add_child("artist", _real_parent=index, id=artist["id"], name=artist["name"])
|
response.add_child("artist", _real_parent=index, id=artist["dir"], name=artist["name"])
|
||||||
return response
|
return response
|
||||||
|
|
||||||
@cherrypy.expose
|
@cherrypy.expose
|
||||||
|
@ -245,33 +245,54 @@ class PysonicApi(object):
|
||||||
"""
|
"""
|
||||||
List an artist dir
|
List an artist dir
|
||||||
"""
|
"""
|
||||||
artist_id = int(id)
|
dir_id = int(id)
|
||||||
|
|
||||||
cherrypy.response.headers['Content-Type'] = 'text/xml; charset=utf-8'
|
cherrypy.response.headers['Content-Type'] = 'text/xml; charset=utf-8'
|
||||||
|
|
||||||
response = ApiResponse()
|
response = ApiResponse()
|
||||||
response.add_child("directory")
|
response.add_child("directory")
|
||||||
|
|
||||||
artist = self.library.get_artists(id=artist_id)[0]
|
dirtype, dirinfo, entity = self.library.db.get_musicdir(dirid=dir_id)
|
||||||
children = self.library.get_albums(artist=artist_id)
|
|
||||||
response.set_attrs(_path="directory", name=artist['name'], id=artist['id'],
|
|
||||||
parent=artist['libraryid'], playCount=10)
|
|
||||||
|
|
||||||
for item in children:
|
from pprint import pprint
|
||||||
|
pprint(dirinfo)
|
||||||
|
pprint(entity)
|
||||||
|
|
||||||
|
response.set_attrs(_path="directory", name=entity['name'], id=entity['id'],
|
||||||
|
parent=dirinfo['parent'], playCount=420)
|
||||||
|
|
||||||
|
for childtype, child in entity["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 = item['metadata']
|
# item_meta = item['metadata']
|
||||||
|
moreargs = {}
|
||||||
|
if childtype == "album":
|
||||||
|
moreargs.update(name=child["name"],
|
||||||
|
isDir="true", # TODO song files in artist dir
|
||||||
|
parent=entity["id"],
|
||||||
|
coverArt=child["coverid"],
|
||||||
|
id=child["dir"])
|
||||||
|
# album=item["name"],
|
||||||
|
# title=item["name"], # TODO dupe?
|
||||||
|
# artist=artist["name"],
|
||||||
|
# coverArt=item["coverid"],
|
||||||
|
elif childtype == "song":
|
||||||
|
moreargs.update(name=child["title"],
|
||||||
|
artist=child["_artist"]["name"],
|
||||||
|
contentType=child["format"],
|
||||||
|
coverArt=entity["coverid"],
|
||||||
|
id=child["id"], # this is probably fucked ?
|
||||||
|
duration=child["length"],
|
||||||
|
isDir="false",
|
||||||
|
parent=entity["dir"],
|
||||||
|
# title=xxx
|
||||||
|
)
|
||||||
|
# duration="230" size="8409237" suffix="mp3" track="2" year="2005"/>
|
||||||
response.add_child("child", _parent="directory",
|
response.add_child("child", _parent="directory",
|
||||||
album=item["name"],
|
|
||||||
title=item["name"], # TODO dupe?
|
|
||||||
artist=artist["name"],
|
|
||||||
coverArt=item["coverid"],
|
|
||||||
id=item["id"],
|
|
||||||
isDir="false", # TODO song files in artist dir
|
|
||||||
parent=artist["id"],
|
|
||||||
size="4096",
|
size="4096",
|
||||||
type="music")
|
type="music",
|
||||||
|
**moreargs)
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
@ -284,11 +305,7 @@ class PysonicApi(object):
|
||||||
:param directory:
|
:param directory:
|
||||||
:param dir_meta:
|
:param dir_meta:
|
||||||
"""
|
"""
|
||||||
print("\n\n\n")
|
raise Exception("stop using this")
|
||||||
print(item)
|
|
||||||
print(item_meta)
|
|
||||||
print(directory)
|
|
||||||
print(dir_meta)
|
|
||||||
child = dict(id=item["id"],
|
child = dict(id=item["id"],
|
||||||
parent=item["id"],
|
parent=item["id"],
|
||||||
isDir="true" if "file" not in item else "false",
|
isDir="true" if "file" not in item else "false",
|
||||||
|
@ -328,15 +345,19 @@ class PysonicApi(object):
|
||||||
def stream_view(self, id, maxBitRate="256", **kwargs):
|
def stream_view(self, id, maxBitRate="256", **kwargs):
|
||||||
maxBitRate = int(maxBitRate)
|
maxBitRate = int(maxBitRate)
|
||||||
assert maxBitRate >= 32 and maxBitRate <= 320
|
assert maxBitRate >= 32 and maxBitRate <= 320
|
||||||
fpath = self.library.get_filepath(id)
|
song = self.library.get_song(id)
|
||||||
meta = self.library.get_file_metadata(id)
|
fpath = "library/" + song["file"]
|
||||||
to_bitrate = min(maxBitRate, self.options.max_bitrate, meta.get("media_kbitrate", 320))
|
# import pdb
|
||||||
|
# from pprint import pprint
|
||||||
|
# pdb.set_trace()
|
||||||
|
# meta = self.library.get_file_metadata(id)
|
||||||
|
to_bitrate = min(maxBitRate, self.options.max_bitrate, song.get("bitrate", 320 * 1024) / 1024)
|
||||||
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 meta.get("media_kbitrate", -1) == to_bitrate) \
|
if (self.options.skip_transcode or song.get("bitrate", -1024) / 1024 == to_bitrate) \
|
||||||
and meta["type"] == "audio/mpeg":
|
and format["type"] == "audio/mpeg":
|
||||||
def content():
|
def content():
|
||||||
with open(fpath, "rb") as f:
|
with open(fpath, "rb") as f:
|
||||||
while True:
|
while True:
|
||||||
|
@ -346,10 +367,10 @@ class PysonicApi(object):
|
||||||
yield data
|
yield data
|
||||||
return content()
|
return content()
|
||||||
else:
|
else:
|
||||||
transcode_meta = "transcoded_{}_size".format(to_bitrate)
|
# transcode_meta = "transcoded_{}_size".format(to_bitrate)
|
||||||
if transcode_meta in meta:
|
# if transcode_meta in meta:
|
||||||
cherrypy.response.headers['Content-Length'] = str(int(meta[transcode_meta]))
|
# cherrypy.response.headers['Content-Length'] = str(int(meta[transcode_meta]))
|
||||||
|
print(fpath)
|
||||||
transcode_args = ["ffmpeg", "-i", fpath, "-map", "0:0", "-b:a",
|
transcode_args = ["ffmpeg", "-i", fpath, "-map", "0:0", "-b:a",
|
||||||
"{}k".format(to_bitrate),
|
"{}k".format(to_bitrate),
|
||||||
"-v", "0", "-f", "mp3", "-"]
|
"-v", "0", "-f", "mp3", "-"]
|
||||||
|
@ -359,13 +380,13 @@ class PysonicApi(object):
|
||||||
|
|
||||||
def content(proc):
|
def content(proc):
|
||||||
length = 0
|
length = 0
|
||||||
completed = False
|
# completed = False
|
||||||
start = time()
|
start = time()
|
||||||
try:
|
try:
|
||||||
while True:
|
while True:
|
||||||
data = proc.stdout.read(16 * 1024)
|
data = proc.stdout.read(16 * 1024)
|
||||||
if not data:
|
if not data:
|
||||||
completed = True
|
# completed = True
|
||||||
break
|
break
|
||||||
yield data
|
yield data
|
||||||
length += len(data)
|
length += len(data)
|
||||||
|
@ -373,8 +394,8 @@ class PysonicApi(object):
|
||||||
proc.poll()
|
proc.poll()
|
||||||
if proc.returncode is None or proc.returncode == 0:
|
if proc.returncode is None or proc.returncode == 0:
|
||||||
logging.warning("transcoded {} in {}s".format(id, int(time() - start)))
|
logging.warning("transcoded {} in {}s".format(id, int(time() - start)))
|
||||||
if completed:
|
# if completed:
|
||||||
self.library.report_transcode(id, to_bitrate, length)
|
# self.library.report_transcode(id, to_bitrate, length)
|
||||||
else:
|
else:
|
||||||
logging.error("transcode of {} exited with code {} after {}s".format(id, proc.returncode,
|
logging.error("transcode of {} exited with code {} after {}s".format(id, proc.returncode,
|
||||||
int(time() - start)))
|
int(time() - start)))
|
||||||
|
@ -394,7 +415,8 @@ class PysonicApi(object):
|
||||||
|
|
||||||
@cherrypy.expose
|
@cherrypy.expose
|
||||||
def getCoverArt_view(self, id, **kwargs):
|
def getCoverArt_view(self, id, **kwargs):
|
||||||
fpath = self.library.get_filepath(id)
|
cover = self.library.get_cover(id)
|
||||||
|
fpath = "library/" + cover["path"]
|
||||||
type2ct = {
|
type2ct = {
|
||||||
'jpg': 'image/jpeg',
|
'jpg': 'image/jpeg',
|
||||||
'png': 'image/png',
|
'png': 'image/png',
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import os
|
import os
|
||||||
import logging
|
import logging
|
||||||
import cherrypy
|
import cherrypy
|
||||||
from sqlite3 import IntegrityError
|
from sqlite3 import DatabaseError
|
||||||
from pysonic.api import PysonicApi
|
from pysonic.api import PysonicApi
|
||||||
from pysonic.library import PysonicLibrary
|
from pysonic.library import PysonicLibrary
|
||||||
from pysonic.database import PysonicDatabase, DuplicateRootException
|
from pysonic.database import PysonicDatabase, DuplicateRootException
|
||||||
|
@ -47,7 +47,7 @@ def main():
|
||||||
for username, password in args.user:
|
for username, password in args.user:
|
||||||
try:
|
try:
|
||||||
db.add_user(username, password)
|
db.add_user(username, password)
|
||||||
except IntegrityError:
|
except DatabaseError:
|
||||||
db.update_user(username, password)
|
db.update_user(username, password)
|
||||||
|
|
||||||
# logging.warning("Libraries: {}".format([i["name"] for i in library.get_libraries()]))
|
# logging.warning("Libraries: {}".format([i["name"] for i in library.get_libraries()]))
|
||||||
|
|
|
@ -54,16 +54,23 @@ class PysonicDatabase(object):
|
||||||
'id' INTEGER PRIMARY KEY AUTOINCREMENT,
|
'id' INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
'name' TEXT,
|
'name' TEXT,
|
||||||
'path' TEXT UNIQUE);""",
|
'path' TEXT UNIQUE);""",
|
||||||
|
"""CREATE TABLE 'dirs' (
|
||||||
|
'id' INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
'library' INTEGER,
|
||||||
|
'parent' INTEGER,
|
||||||
|
'name' TEXT,
|
||||||
|
UNIQUE(parent, name)
|
||||||
|
)""",
|
||||||
"""CREATE TABLE 'artists' (
|
"""CREATE TABLE 'artists' (
|
||||||
'id' INTEGER PRIMARY KEY AUTOINCREMENT,
|
'id' INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
'libraryid' INTEGER,
|
'libraryid' INTEGER,
|
||||||
'dir' TEXT UNIQUE,
|
'dir' INTEGER UNIQUE,
|
||||||
'name' TEXT)""",
|
'name' TEXT)""",
|
||||||
"""CREATE TABLE 'albums' (
|
"""CREATE TABLE 'albums' (
|
||||||
'id' INTEGER PRIMARY KEY AUTOINCREMENT,
|
'id' INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
'artistid' INTEGER,
|
'artistid' INTEGER,
|
||||||
'coverid' INTEGER,
|
'coverid' INTEGER,
|
||||||
'dir' TEXT,
|
'dir' INTEGER,
|
||||||
'name' TEXT,
|
'name' TEXT,
|
||||||
UNIQUE (artistid, dir));""",
|
UNIQUE (artistid, dir));""",
|
||||||
"""CREATE TABLE 'songs' (
|
"""CREATE TABLE 'songs' (
|
||||||
|
@ -141,16 +148,24 @@ class PysonicDatabase(object):
|
||||||
return libs
|
return libs
|
||||||
|
|
||||||
@readcursor
|
@readcursor
|
||||||
def get_artists(self, cursor, id=None, sortby=None, order=None):
|
def get_artists(self, cursor, id=None, dirid=None, sortby=None, order=None):
|
||||||
assert order in ["asc", "desc", None]
|
assert order in ["asc", "desc", None]
|
||||||
artists = []
|
artists = []
|
||||||
q = "SELECT * FROM artists"
|
q = "SELECT * FROM artists"
|
||||||
params = []
|
params = []
|
||||||
|
conditions = []
|
||||||
if id:
|
if id:
|
||||||
q += " WHERE id = ?"
|
conditions.append("id = ?")
|
||||||
params.append(id)
|
params.append(id)
|
||||||
|
if dirid:
|
||||||
|
conditions.append("dir = ?")
|
||||||
|
params.append(dirid)
|
||||||
|
if conditions:
|
||||||
|
q += " WHERE " + " AND ".join(conditions)
|
||||||
if sortby:
|
if sortby:
|
||||||
q += " ORDER BY {} {}".format(sortby, order.upper() if order else "ASC")
|
q += " ORDER BY {} {}".format(sortby, order.upper() if order else "ASC")
|
||||||
|
print(q)
|
||||||
|
print(params)
|
||||||
cursor.execute(q, params)
|
cursor.execute(q, params)
|
||||||
for row in cursor:
|
for row in cursor:
|
||||||
artists.append(row)
|
artists.append(row)
|
||||||
|
@ -181,10 +196,86 @@ class PysonicDatabase(object):
|
||||||
albums.append(row)
|
albums.append(row)
|
||||||
return albums
|
return albums
|
||||||
|
|
||||||
|
@readcursor
|
||||||
|
def get_song(self, cursor, songid):
|
||||||
|
for item in cursor.execute("SELECT * FROM songs WHERE id=?", (songid, )):
|
||||||
|
return item
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# @readcursor
|
||||||
|
# def get_artist_by_dir(self, cursor, dirid):
|
||||||
|
# for row in cursor.execute("""
|
||||||
|
# SELECT artists.*
|
||||||
|
# FROM dirs
|
||||||
|
# INNER JOIN artists
|
||||||
|
# ON artists.dir = dirs.id
|
||||||
|
# WHERE dirs.id=?""", (dirid, )):
|
||||||
|
# return [row]
|
||||||
|
# return []
|
||||||
|
|
||||||
|
@readcursor
|
||||||
|
def get_cover(self, cursor, coverid):
|
||||||
|
cover = None
|
||||||
|
for cover in cursor.execute("SELECT * FROM covers WHERE id = ?", (coverid, )):
|
||||||
|
return cover
|
||||||
|
|
||||||
|
@readcursor
|
||||||
|
def get_musicdir(self, cursor, dirid):
|
||||||
|
"""
|
||||||
|
The world is a harsh place.
|
||||||
|
Again, this bullshit exists only to serve subsonic clients. Given a directory ID it returns a dict containing:
|
||||||
|
- the directory itself
|
||||||
|
- its parent
|
||||||
|
- its child dirs
|
||||||
|
- its child media
|
||||||
|
|
||||||
|
that's a lie, it's a tuple and it's full of BS. read the code
|
||||||
|
"""
|
||||||
|
# find directory
|
||||||
|
dirinfo = None
|
||||||
|
for dirinfo in cursor.execute("SELECT * FROM dirs WHERE id = ?", (dirid, )):
|
||||||
|
pass
|
||||||
|
assert dirinfo
|
||||||
|
|
||||||
|
ret = None
|
||||||
|
|
||||||
|
# see if it matches the artists or albums table
|
||||||
|
artist = None
|
||||||
|
for artist in cursor.execute("SELECT * FROM artists WHERE dir = ?", (dirid, )):
|
||||||
|
pass
|
||||||
|
|
||||||
|
# if artist:
|
||||||
|
# get child albums
|
||||||
|
if artist:
|
||||||
|
ret = ("artist", dirinfo, artist)
|
||||||
|
children = []
|
||||||
|
for album in cursor.execute("SELECT * FROM albums WHERE artistid = ?", (artist["id"], )):
|
||||||
|
children.append(("album", album))
|
||||||
|
ret[2]['children'] = children
|
||||||
|
return ret
|
||||||
|
|
||||||
|
# else if album:
|
||||||
|
# get child tracks
|
||||||
|
album = None
|
||||||
|
for album in cursor.execute("SELECT * FROM albums WHERE dir = ?", (dirid, )):
|
||||||
|
pass
|
||||||
|
if album:
|
||||||
|
ret = ("album", dirinfo, album)
|
||||||
|
|
||||||
|
artist_info = cursor.execute("SELECT * FROM artists WHERE id = ?", (album["artistid"], )).fetchall()[0]
|
||||||
|
|
||||||
|
children = []
|
||||||
|
for song in cursor.execute("SELECT * FROM songs WHERE albumid = ?", (album["id"], )):
|
||||||
|
song["_artist"] = artist_info
|
||||||
|
children.append(("song", song))
|
||||||
|
ret[2]['children'] = children
|
||||||
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -35,6 +35,8 @@ class PysonicLibrary(object):
|
||||||
self.get_libraries = self.db.get_libraries
|
self.get_libraries = self.db.get_libraries
|
||||||
self.get_artists = self.db.get_artists
|
self.get_artists = self.db.get_artists
|
||||||
self.get_albums = self.db.get_albums
|
self.get_albums = self.db.get_albums
|
||||||
|
self.get_song = self.db.get_song
|
||||||
|
self.get_cover = self.db.get_cover
|
||||||
|
|
||||||
self.scanner = PysonicFilesystemScanner(self)
|
self.scanner = PysonicFilesystemScanner(self)
|
||||||
logging.info("library ready")
|
logging.info("library ready")
|
||||||
|
@ -74,6 +76,9 @@ class PysonicLibrary(object):
|
||||||
"largeImageUrl": "",
|
"largeImageUrl": "",
|
||||||
"similarArtists": []}
|
"similarArtists": []}
|
||||||
|
|
||||||
|
# def get_cover(self, cover_id):
|
||||||
|
# cover = self.db.get_cover(cover_id)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -46,6 +46,7 @@ class PysonicFilesystemScanner(object):
|
||||||
root_depth = len(self.split_path(root))
|
root_depth = len(self.split_path(root))
|
||||||
for path, dirs, files in os.walk(root):
|
for path, dirs, files in os.walk(root):
|
||||||
child = self.split_path(path)[root_depth:]
|
child = self.split_path(path)[root_depth:]
|
||||||
|
# dirid = self.create_or_get_dbdir_tree(pid, child) # dumb table for Subsonic
|
||||||
self.scan_dir(pid, root, child, dirs, files)
|
self.scan_dir(pid, root, child, dirs, files)
|
||||||
|
|
||||||
logging.warning("Beginning metadata scan for library %s", pid)
|
logging.warning("Beginning metadata scan for library %s", pid)
|
||||||
|
@ -53,6 +54,28 @@ class PysonicFilesystemScanner(object):
|
||||||
|
|
||||||
logging.warning("Finished scan for library %s", pid)
|
logging.warning("Finished scan for library %s", pid)
|
||||||
|
|
||||||
|
def create_or_get_dbdir_tree(self, cursor, pid, path):
|
||||||
|
"""
|
||||||
|
Return the ID of the directory specified by `path`. The path will be created as necessary. This bullshit exists
|
||||||
|
only to serve Subsonic, and can easily be lopped off.
|
||||||
|
:param pid: root parent the path resides in
|
||||||
|
:param path: single-file tree as a list of dir names under the root parent
|
||||||
|
:type path list
|
||||||
|
"""
|
||||||
|
assert path
|
||||||
|
# with closing(self.library.db.db.cursor()) as cursor:
|
||||||
|
parent_id = 0 # 0 indicates a top level item in the library
|
||||||
|
for name in path:
|
||||||
|
parent_id = self.create_or_get_dbdir(cursor, pid, parent_id, name)
|
||||||
|
return parent_id
|
||||||
|
|
||||||
|
def create_or_get_dbdir(self, cursor, pid, parent_id, name):
|
||||||
|
for row in cursor.execute("SELECT * FROM dirs WHERE library=? and parent=? and name=?",
|
||||||
|
(pid, parent_id, name, )):
|
||||||
|
return row['id']
|
||||||
|
cursor.execute("INSERT INTO dirs (library, parent, name) VALUES (?, ?, ?)", (pid, parent_id, name))
|
||||||
|
return cursor.lastrowid
|
||||||
|
|
||||||
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.
|
||||||
|
@ -79,26 +102,29 @@ class PysonicFilesystemScanner(object):
|
||||||
|
|
||||||
with closing(self.library.db.db.cursor()) as cursor:
|
with closing(self.library.db.db.cursor()) as cursor:
|
||||||
# Create artist entry
|
# Create artist entry
|
||||||
cursor.execute("SELECT * FROM artists WHERE dir = ?", (artist, ))
|
artist_dirid = self.create_or_get_dbdir_tree(cursor, pid, [path[0]])
|
||||||
|
cursor.execute("SELECT * FROM artists WHERE dir = ?", (artist_dirid, ))
|
||||||
row = cursor.fetchone()
|
row = cursor.fetchone()
|
||||||
|
artist_id = None
|
||||||
if row:
|
if row:
|
||||||
artist_id = row['id']
|
artist_id = row['id']
|
||||||
else:
|
else:
|
||||||
cursor.execute("INSERT INTO artists (libraryid, dir, name) VALUES (?, ?, ?)",
|
cursor.execute("INSERT INTO artists (libraryid, dir, name) VALUES (?, ?, ?)",
|
||||||
(pid, artist, artist))
|
(pid, artist_dirid, artist))
|
||||||
artist_id = cursor.lastrowid
|
artist_id = cursor.lastrowid
|
||||||
|
|
||||||
# Create album entry
|
# Create album entry
|
||||||
album_id = None
|
album_id = None
|
||||||
|
album_dirid = self.create_or_get_dbdir_tree(cursor, pid, path)
|
||||||
libpath = os.path.join(*path)
|
libpath = os.path.join(*path)
|
||||||
if album:
|
if album:
|
||||||
cursor.execute("SELECT * FROM albums WHERE artistid = ? AND dir = ?", (artist_id, libpath, ))
|
cursor.execute("SELECT * FROM albums WHERE artistid = ? AND dir = ?", (artist_id, album_dirid, ))
|
||||||
row = cursor.fetchone()
|
row = cursor.fetchone()
|
||||||
if row:
|
if row:
|
||||||
album_id = row['id']
|
album_id = row['id']
|
||||||
else:
|
else:
|
||||||
cursor.execute("INSERT INTO albums (artistid, dir, name) VALUES (?, ?, ?)",
|
cursor.execute("INSERT INTO albums (artistid, dir, name) VALUES (?, ?, ?)",
|
||||||
(artist_id, libpath, path[-1]))
|
(artist_id, album_dirid, path[-1]))
|
||||||
album_id = cursor.lastrowid
|
album_id = cursor.lastrowid
|
||||||
|
|
||||||
new_files = False
|
new_files = False
|
||||||
|
|
Loading…
Reference in New Issue