db struktur
This commit is contained in:
@@ -159,11 +159,11 @@ def run_acoustid_scan(db_path: str, api_key: str = "", on_progress=None, stop_ev
|
|||||||
conn.row_factory = sqlite3.Row
|
conn.row_factory = sqlite3.Row
|
||||||
|
|
||||||
rows = conn.execute("""
|
rows = conn.execute("""
|
||||||
SELECT id, local_path, title, artist
|
SELECT s.id, s.title, s.artist, f.local_path
|
||||||
FROM songs
|
FROM songs s
|
||||||
WHERE (mbid IS NULL OR mbid = '')
|
JOIN files f ON f.song_id = s.id AND f.file_missing = 0
|
||||||
AND file_missing = 0
|
WHERE (s.mbid IS NULL OR s.mbid = '')
|
||||||
AND local_path IS NOT NULL AND local_path != ''
|
AND f.local_path IS NOT NULL AND f.local_path != ''
|
||||||
ORDER BY RANDOM()
|
ORDER BY RANDOM()
|
||||||
LIMIT ?
|
LIMIT ?
|
||||||
""", (MAX_PER_SESSION,)).fetchall()
|
""", (MAX_PER_SESSION,)).fetchall()
|
||||||
@@ -284,4 +284,4 @@ class AcoustIDWorker:
|
|||||||
self._stop_event.set()
|
self._stop_event.set()
|
||||||
|
|
||||||
def is_running(self) -> bool:
|
def is_running(self) -> bool:
|
||||||
return self._running
|
return self._running
|
||||||
@@ -11,6 +11,62 @@ from pathlib import Path
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
SUPPORTED = {'.mp3', '.flac', '.m4a', '.ogg', '.wav', '.aiff', '.wma'}
|
SUPPORTED = {'.mp3', '.flac', '.m4a', '.ogg', '.wav', '.aiff', '.wma'}
|
||||||
|
import uuid as _uuid_module
|
||||||
|
|
||||||
|
|
||||||
|
def _find_or_create_song_conn(conn, title, artist, album, bpm,
|
||||||
|
duration_sec, mbid, acoustid) -> str:
|
||||||
|
"""Find eller opret sang via eksisterende forbindelse."""
|
||||||
|
if mbid:
|
||||||
|
row = conn.execute("SELECT id FROM songs WHERE mbid=?", (mbid,)).fetchone()
|
||||||
|
if row:
|
||||||
|
return row["id"]
|
||||||
|
if acoustid:
|
||||||
|
row = conn.execute("SELECT id FROM songs WHERE acoustid=?", (acoustid,)).fetchone()
|
||||||
|
if row:
|
||||||
|
if mbid:
|
||||||
|
conn.execute("UPDATE songs SET mbid=? WHERE id=? AND mbid IS NULL", (mbid, row["id"]))
|
||||||
|
return row["id"]
|
||||||
|
if title:
|
||||||
|
row = conn.execute(
|
||||||
|
"SELECT id FROM songs WHERE title=? AND artist=?", (title, artist)
|
||||||
|
).fetchone()
|
||||||
|
if row:
|
||||||
|
if mbid:
|
||||||
|
conn.execute("UPDATE songs SET mbid=? WHERE id=? AND mbid IS NULL", (mbid, row["id"]))
|
||||||
|
return row["id"]
|
||||||
|
new_id = str(_uuid_module.uuid4())
|
||||||
|
conn.execute(
|
||||||
|
"INSERT INTO songs (id, title, artist, album, bpm, duration_sec, mbid, acoustid) "
|
||||||
|
"VALUES (?,?,?,?,?,?,?,?)",
|
||||||
|
(new_id, title, artist, album, bpm, duration_sec, mbid or None, acoustid or None)
|
||||||
|
)
|
||||||
|
return new_id
|
||||||
|
|
||||||
|
|
||||||
|
def _upsert_file_conn(conn, song_id, local_path, file_format,
|
||||||
|
file_modified_at, extra_tags) -> str:
|
||||||
|
"""Opret eller opdater fil-post via eksisterende forbindelse."""
|
||||||
|
existing = conn.execute(
|
||||||
|
"SELECT id FROM files WHERE local_path=?", (local_path,)
|
||||||
|
).fetchone()
|
||||||
|
if existing:
|
||||||
|
conn.execute("""
|
||||||
|
UPDATE files SET song_id=?, file_missing=0,
|
||||||
|
file_format=?, file_modified_at=?, extra_tags=?
|
||||||
|
WHERE id=?
|
||||||
|
""", (song_id, file_format, file_modified_at, extra_tags, existing["id"]))
|
||||||
|
return existing["id"]
|
||||||
|
else:
|
||||||
|
file_id = str(_uuid_module.uuid4())
|
||||||
|
conn.execute(
|
||||||
|
"INSERT INTO files (id, song_id, local_path, file_format, file_modified_at, extra_tags) "
|
||||||
|
"VALUES (?,?,?,?,?,?)",
|
||||||
|
(file_id, song_id, local_path, file_format, file_modified_at, extra_tags)
|
||||||
|
)
|
||||||
|
return file_id
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def is_supported(path) -> bool:
|
def is_supported(path) -> bool:
|
||||||
@@ -33,7 +89,6 @@ def scan_library(library_id: int, library_path: str, db_path: str,
|
|||||||
"""
|
"""
|
||||||
import sqlite3
|
import sqlite3
|
||||||
from local.tag_reader import read_tags
|
from local.tag_reader import read_tags
|
||||||
from local.local_db import find_or_create_song, upsert_file
|
|
||||||
|
|
||||||
base = Path(library_path)
|
base = Path(library_path)
|
||||||
if not base.exists():
|
if not base.exists():
|
||||||
@@ -84,16 +139,16 @@ def scan_library(library_id: int, library_path: str, db_path: str,
|
|||||||
acoustid = tags.get("acoustid", "")
|
acoustid = tags.get("acoustid", "")
|
||||||
duration_sec = tags.get("duration_sec", 0)
|
duration_sec = tags.get("duration_sec", 0)
|
||||||
file_format = tags.get("file_format", fp.suffix.lstrip(".").lower())
|
file_format = tags.get("file_format", fp.suffix.lstrip(".").lower())
|
||||||
extra_tags = tags.get("extra_tags", "{}")
|
import json as _json
|
||||||
|
_extra = tags.get("extra_tags", {})
|
||||||
|
extra_tags = _json.dumps(_extra) if isinstance(_extra, dict) else (_extra or "{}")
|
||||||
|
|
||||||
# Find eller opret sang i global katalog
|
# Find eller opret sang — alt via samme conn
|
||||||
song_id = find_or_create_song(
|
song_id = _find_or_create_song_conn(
|
||||||
title=title, artist=artist, album=album,
|
conn, title, artist, album, bpm, duration_sec, mbid, acoustid
|
||||||
bpm=bpm, duration_sec=duration_sec,
|
|
||||||
mbid=mbid, acoustid=acoustid,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Opdater BPM på sangen hvis vi har bedre data
|
# Opdater BPM
|
||||||
if bpm and bpm > 0:
|
if bpm and bpm > 0:
|
||||||
conn.execute(
|
conn.execute(
|
||||||
"UPDATE songs SET bpm=? WHERE id=? AND (bpm=0 OR bpm IS NULL)",
|
"UPDATE songs SET bpm=? WHERE id=? AND (bpm=0 OR bpm IS NULL)",
|
||||||
@@ -101,13 +156,7 @@ def scan_library(library_id: int, library_path: str, db_path: str,
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Opret eller opdater fil-post
|
# Opret eller opdater fil-post
|
||||||
upsert_file(
|
_upsert_file_conn(conn, song_id, path_str, file_format, mtime, extra_tags)
|
||||||
song_id=song_id,
|
|
||||||
local_path=path_str,
|
|
||||||
file_format=file_format,
|
|
||||||
file_modified_at=mtime,
|
|
||||||
extra_tags=extra_tags,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Dans-tags fra fil
|
# Dans-tags fra fil
|
||||||
file_dances = tags.get("dances", [])
|
file_dances = tags.get("dances", [])
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ class LibraryManagerDialog(QDialog):
|
|||||||
conn = sqlite3.connect(self._db_path)
|
conn = sqlite3.connect(self._db_path)
|
||||||
conn.row_factory = sqlite3.Row
|
conn.row_factory = sqlite3.Row
|
||||||
libs = conn.execute(
|
libs = conn.execute(
|
||||||
"SELECT id, path, last_full_scan FROM libraries "
|
"SELECT id, path FROM libraries "
|
||||||
"WHERE is_active=1 ORDER BY path"
|
"WHERE is_active=1 ORDER BY path"
|
||||||
).fetchall()
|
).fetchall()
|
||||||
|
|
||||||
@@ -87,15 +87,16 @@ class LibraryManagerDialog(QDialog):
|
|||||||
bpm_missing = {}
|
bpm_missing = {}
|
||||||
for lib in libs:
|
for lib in libs:
|
||||||
counts[lib["id"]] = conn.execute(
|
counts[lib["id"]] = conn.execute(
|
||||||
"SELECT COUNT(*) FROM songs "
|
"SELECT COUNT(*) FROM files "
|
||||||
"WHERE library_id=? AND file_missing=0",
|
"WHERE file_missing=0 AND local_path LIKE ?",
|
||||||
(lib["id"],)
|
(lib["path"] + "%",)
|
||||||
).fetchone()[0]
|
).fetchone()[0]
|
||||||
bpm_missing[lib["id"]] = conn.execute(
|
bpm_missing[lib["id"]] = conn.execute(
|
||||||
"SELECT COUNT(*) FROM songs "
|
"SELECT COUNT(*) FROM files f "
|
||||||
"WHERE library_id=? AND file_missing=0 "
|
"JOIN songs s ON s.id = f.song_id "
|
||||||
"AND (bpm IS NULL OR bpm=0)",
|
"WHERE f.file_missing=0 AND f.local_path LIKE ? "
|
||||||
(lib["id"],)
|
"AND (s.bpm IS NULL OR s.bpm=0)",
|
||||||
|
(lib["path"] + "%",)
|
||||||
).fetchone()[0]
|
).fetchone()[0]
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
@@ -122,9 +123,7 @@ class LibraryManagerDialog(QDialog):
|
|||||||
lib_id = lib["id"]
|
lib_id = lib["id"]
|
||||||
path = lib["path"]
|
path = lib["path"]
|
||||||
exists = Path(path).exists()
|
exists = Path(path).exists()
|
||||||
last = lib.get("last_full_scan") or "aldrig"
|
last = "—"
|
||||||
if isinstance(last, str) and len(last) > 16:
|
|
||||||
last = last[:16]
|
|
||||||
|
|
||||||
frame = QFrame()
|
frame = QFrame()
|
||||||
frame.setObjectName("track_display")
|
frame.setObjectName("track_display")
|
||||||
@@ -246,11 +245,12 @@ class LibraryManagerDialog(QDialog):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
conn = sqlite3.connect(self._db_path)
|
conn = sqlite3.connect(self._db_path)
|
||||||
# Slet sange fra biblioteket
|
# Marker filer fra denne mappe som missing
|
||||||
conn.execute(
|
conn.execute(
|
||||||
"DELETE FROM songs WHERE library_id=?", (lib["id"],)
|
"UPDATE files SET file_missing=1 WHERE local_path LIKE ?",
|
||||||
|
(lib["path"] + "%",)
|
||||||
)
|
)
|
||||||
# Slet selve biblioteks-rækken helt
|
# Slet selve biblioteks-rækken
|
||||||
conn.execute(
|
conn.execute(
|
||||||
"DELETE FROM libraries WHERE id=?", (lib["id"],)
|
"DELETE FROM libraries WHERE id=?", (lib["id"],)
|
||||||
)
|
)
|
||||||
@@ -297,4 +297,4 @@ class LibraryManagerDialog(QDialog):
|
|||||||
for w in list(self._workers.values()):
|
for w in list(self._workers.values()):
|
||||||
w.cancel()
|
w.cancel()
|
||||||
w.wait(2000) # Vent max 2 sek på at tråden stopper
|
w.wait(2000) # Vent max 2 sek på at tråden stopper
|
||||||
event.accept()
|
event.accept()
|
||||||
@@ -505,19 +505,19 @@ class MainWindow(QMainWindow):
|
|||||||
conn.row_factory = sqlite3.Row
|
conn.row_factory = sqlite3.Row
|
||||||
rows = conn.execute("""
|
rows = conn.execute("""
|
||||||
SELECT s.id, s.title, s.artist, s.album, s.bpm,
|
SELECT s.id, s.title, s.artist, s.album, s.bpm,
|
||||||
s.duration_sec, s.local_path, s.file_format,
|
s.duration_sec,
|
||||||
s.file_missing,
|
f.id as file_id, f.local_path, f.file_format, f.file_missing,
|
||||||
GROUP_CONCAT(d.name, ',') AS dance_names,
|
GROUP_CONCAT(d.name, ',') AS dance_names,
|
||||||
GROUP_CONCAT(COALESCE(dl.name,''), ',') AS dance_levels,
|
GROUP_CONCAT(COALESCE(dl.name,''), ',') AS dance_levels,
|
||||||
GROUP_CONCAT(COALESCE(d.choreographer,''), ',') AS dance_choreographers,
|
GROUP_CONCAT(COALESCE(d.choreographer,''), ',') AS dance_choreographers,
|
||||||
GROUP_CONCAT(DISTINCT ad.name) AS alt_dance_names
|
GROUP_CONCAT(DISTINCT ad.name) AS alt_dance_names
|
||||||
FROM songs s
|
FROM songs s
|
||||||
|
JOIN files f ON f.song_id = s.id AND f.file_missing = 0
|
||||||
LEFT JOIN song_dances sd ON sd.song_id = s.id
|
LEFT JOIN song_dances sd ON sd.song_id = s.id
|
||||||
LEFT JOIN dances d ON d.id = sd.dance_id
|
LEFT JOIN dances d ON d.id = sd.dance_id
|
||||||
LEFT JOIN dance_levels dl ON dl.id = d.level_id
|
LEFT JOIN dance_levels dl ON dl.id = d.level_id
|
||||||
LEFT JOIN song_alt_dances sad ON sad.song_id = s.id
|
LEFT JOIN song_alt_dances sad ON sad.song_id = s.id
|
||||||
LEFT JOIN dances ad ON ad.id = sad.dance_id
|
LEFT JOIN dances ad ON ad.id = sad.dance_id
|
||||||
WHERE s.file_missing = 0
|
|
||||||
GROUP BY s.id
|
GROUP BY s.id
|
||||||
ORDER BY s.artist, s.title
|
ORDER BY s.artist, s.title
|
||||||
""").fetchall()
|
""").fetchall()
|
||||||
@@ -536,6 +536,7 @@ class MainWindow(QMainWindow):
|
|||||||
"album": row["album"],
|
"album": row["album"],
|
||||||
"bpm": row["bpm"],
|
"bpm": row["bpm"],
|
||||||
"duration_sec": row["duration_sec"],
|
"duration_sec": row["duration_sec"],
|
||||||
|
"file_id": row["file_id"],
|
||||||
"local_path": row["local_path"],
|
"local_path": row["local_path"],
|
||||||
"file_format": row["file_format"],
|
"file_format": row["file_format"],
|
||||||
"file_missing": bool(row["file_missing"]),
|
"file_missing": bool(row["file_missing"]),
|
||||||
@@ -1476,4 +1477,4 @@ class MainWindow(QMainWindow):
|
|||||||
self._watchdog_proc.terminate()
|
self._watchdog_proc.terminate()
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
event.accept()
|
event.accept()
|
||||||
Reference in New Issue
Block a user