Version 1

This commit is contained in:
2026-04-10 23:59:23 +02:00
parent 9d7adf42c1
commit d55859c593
17 changed files with 743 additions and 490 deletions

View File

@@ -41,9 +41,9 @@ class DraggableLibraryList(QListWidget):
class LibraryPanel(QWidget):
song_selected = pyqtSignal(dict)
add_to_playlist = pyqtSignal(dict)
scan_requested = pyqtSignal()
song_selected = pyqtSignal(dict)
add_to_playlist = pyqtSignal(dict)
scan_requested = pyqtSignal()
edit_tags_requested = pyqtSignal(dict)
send_mail_requested = pyqtSignal(dict)
@@ -51,6 +51,7 @@ class LibraryPanel(QWidget):
super().__init__(parent)
self._all_songs: list[dict] = []
self._filtered: list[dict] = []
self._bpm_scan_running = False
self._search_timer = QTimer(self)
self._search_timer.setSingleShot(True)
self._search_timer.setInterval(150)
@@ -70,6 +71,12 @@ class LibraryPanel(QWidget):
header.addWidget(lbl)
header.addStretch()
self._btn_bpm_scan = QPushButton("♩ BPM alle")
self._btn_bpm_scan.setFixedHeight(24)
self._btn_bpm_scan.setToolTip("Analysér BPM på alle sange uden BPM (kører i baggrunden)")
self._btn_bpm_scan.clicked.connect(self._start_bulk_bpm_scan)
header.addWidget(self._btn_bpm_scan)
btn_manage = QPushButton("⚙ Mapper")
btn_manage.setFixedHeight(24)
btn_manage.setToolTip("Tilføj, fjern og scan musikbiblioteker")
@@ -172,7 +179,6 @@ class LibraryPanel(QWidget):
dance_levels = song.get("dance_levels", [])
missing = song.get("file_missing", False)
# Byg dans-streng med niveau hvis tilgængeligt
dance_parts = []
for i, d in enumerate(dances):
lvl = dance_levels[i] if i < len(dance_levels) else ""
@@ -183,13 +189,97 @@ class LibraryPanel(QWidget):
bpm = song.get("bpm", 0)
bpm_str = f"{bpm} BPM" if bpm else "? BPM"
line2 = f" {song.get('artist','')} · {bpm_str} · {song.get('file_format','').upper()}{dance_str}"
item = QListWidgetItem(f"{line1}\n{line2}")
row_widget = QWidget()
row_widget.setStyleSheet("background: transparent;")
row_layout = QHBoxLayout(row_widget)
row_layout.setContentsMargins(2, 2, 2, 2)
row_layout.setSpacing(8)
lbl = QLabel(f"{line1}\n{line2}")
lbl.setWordWrap(False)
row_layout.addWidget(lbl, stretch=1)
btn_danse = QPushButton("Danse")
btn_danse.setFixedHeight(30)
btn_danse.setFixedWidth(70)
btn_danse.setToolTip("Rediger dans-tags")
btn_danse.setStyleSheet(
"QPushButton { background: #e8a020; color: #111; border-radius: 4px; "
"font-weight: bold; font-size: 12px; border: none; }"
"QPushButton:hover { background: #f0b030; }"
)
btn_danse.clicked.connect(lambda _, s=song: self.edit_tags_requested.emit(s))
row_layout.addWidget(btn_danse)
item = QListWidgetItem()
item.setData(Qt.ItemDataRole.UserRole, song)
if missing:
item.setForeground(QColor("#5a6070"))
elif q and any(q in d.lower() for d in dances):
item.setForeground(QColor("#e8a020"))
row_widget.adjustSize()
hint = row_widget.sizeHint()
hint.setHeight(max(hint.height(), 52))
item.setSizeHint(hint)
self._list.addItem(item)
self._list.setItemWidget(item, row_widget)
def _start_bulk_bpm_scan(self):
"""Start BPM-analyse på alle sange uden BPM i baggrundstråd med lav prioritet."""
if self._bpm_scan_running:
return
songs_without_bpm = [s for s in self._all_songs
if not s.get("bpm") and not s.get("file_missing")]
if not songs_without_bpm:
self._btn_bpm_scan.setText("♩ Alle har BPM")
return
self._bpm_scan_running = True
self._btn_bpm_scan.setText(f"♩ Scanner 0/{len(songs_without_bpm)}...")
self._btn_bpm_scan.setEnabled(False)
from PyQt6.QtCore import QThread, pyqtSignal as _sig
class BulkBpmWorker(QThread):
progress = _sig(int, int, str) # done, total, title
finished = _sig()
def __init__(self, songs):
super().__init__()
self._songs = songs
def run(self):
from local.tag_reader import analyze_and_save_bpm
total = len(self._songs)
for i, song in enumerate(self._songs, start=1):
if self.isInterruptionRequested():
break
try:
bpm = analyze_and_save_bpm(song["local_path"], song["id"])
if bpm:
song["bpm"] = int(round(bpm))
except Exception:
pass
self.progress.emit(i, total, song.get("title", ""))
self.finished.emit()
self._bulk_bpm_worker = BulkBpmWorker(songs_without_bpm)
def on_progress(done, total, title):
self._btn_bpm_scan.setText(f"{done}/{total}...")
# Opdater sangen i listen
for s in self._all_songs:
if s.get("title") == title and s.get("bpm"):
break
self._do_search()
def on_finished():
self._bpm_scan_running = False
self._btn_bpm_scan.setEnabled(True)
self._btn_bpm_scan.setText("♩ BPM alle")
self._do_search()
self._bulk_bpm_worker.progress.connect(on_progress)
self._bulk_bpm_worker.finished.connect(on_finished)
self._bulk_bpm_worker.start()
self._bulk_bpm_worker.setPriority(QThread.Priority.LowestPriority)
# ── Handlinger ────────────────────────────────────────────────────────────