File > Import from URL... runs trav's `song` helper (yt-dlp --extract-audio --audio-format mp3) from inside the app. Everything at the link downloads to a private temp dir and is imported as each song lands. "and add to current playlist?" puts the batch above the selected song, else at the end of the playing playlist, else at the end of the shown one, in the link's order. Each song goes straight into Identify Track. With no AcoustID key, the proposal comes from the filename alone. Album art now opens on a single click, in a window shaped like the cover and as big as the screen allows. Also fixed the dev fixture's play journal, which crashed startup (wrong shape) and would have been ignored anyway (wrong keys). The loader now skips a journal it can't read instead of aborting. Claude-Session: https://claude.ai/code/session_01R5m9mXFPHNro78BdD69mG2
86 lines
3.3 KiB
Python
86 lines
3.3 KiB
Python
from PyQt6.QtWidgets import QWidget, QApplication
|
|
from PyQt6.QtCore import Qt, QSize
|
|
from PyQt6.QtGui import QPainter, QPixmap
|
|
|
|
# Height left free for the window's own title bar. On Wayland the frame size
|
|
# isn't known until the window is mapped, so this is a fixed allowance.
|
|
DECORATION_ALLOWANCE = 48
|
|
|
|
|
|
def fit_size(art: QSize, available: QSize,
|
|
decorations: int = DECORATION_ALLOWANCE) -> QSize:
|
|
"""The largest size with the art's own shape that fits on the screen.
|
|
|
|
Scales *up* as well as down: a 300 px cover still opens screen-tall. The
|
|
window takes the art's shape rather than the screen's, so there are no
|
|
black bars.
|
|
"""
|
|
room = QSize(max(available.width(), 1),
|
|
max(available.height() - decorations, 1))
|
|
if art.isEmpty():
|
|
return room
|
|
return art.scaled(room, Qt.AspectRatioMode.KeepAspectRatio)
|
|
|
|
|
|
def _aspect(pixmap: QPixmap | None) -> float | None:
|
|
if pixmap is None or pixmap.isNull() or pixmap.height() == 0:
|
|
return None
|
|
return pixmap.width() / pixmap.height()
|
|
|
|
|
|
class ArtWindow(QWidget):
|
|
"""Non-modal window showing the current track's album art as large as
|
|
possible. Just an album art window — closing it has no side effects."""
|
|
|
|
def __init__(self, parent=None):
|
|
super().__init__(parent, Qt.WindowType.Window)
|
|
self._pixmap: QPixmap | None = None
|
|
self.setMinimumSize(120, 120)
|
|
self.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose, False)
|
|
|
|
def show_artwork(self, pixmap: QPixmap | None, title: str, screen=None):
|
|
"""Open (or re-raise) the window, sized to the art on ``screen``."""
|
|
self.set_artwork(pixmap, title)
|
|
self.fit_to_screen(screen)
|
|
self.show()
|
|
self.raise_()
|
|
self.activateWindow()
|
|
|
|
def set_artwork(self, pixmap: QPixmap | None, title: str):
|
|
self.setWindowTitle(title or "Album Art")
|
|
reshape = (self.isVisible() and _aspect(pixmap) is not None
|
|
and _aspect(self._pixmap) is not None
|
|
and abs(_aspect(pixmap) - _aspect(self._pixmap)) > 0.01)
|
|
self._pixmap = pixmap
|
|
if reshape:
|
|
# A track change brought a different shape of cover: re-fit, or
|
|
# the new art would sit between black bars.
|
|
self.fit_to_screen()
|
|
self.update()
|
|
|
|
def fit_to_screen(self, screen=None):
|
|
if self._pixmap is None or self._pixmap.isNull():
|
|
return
|
|
screen = screen or self.screen() or QApplication.primaryScreen()
|
|
if screen is None:
|
|
return
|
|
geo = screen.availableGeometry()
|
|
size = fit_size(self._pixmap.size(), geo.size())
|
|
self.resize(size)
|
|
# Wayland ignores move() (the compositor places the window); X11
|
|
# honors it.
|
|
self.move(geo.x() + (geo.width() - size.width()) // 2,
|
|
geo.y() + (geo.height() - size.height()) // 2)
|
|
|
|
def paintEvent(self, event):
|
|
painter = QPainter(self)
|
|
painter.fillRect(self.rect(), Qt.GlobalColor.black)
|
|
if self._pixmap is None or self._pixmap.isNull():
|
|
return
|
|
scaled = self._pixmap.scaled(
|
|
self.size(), Qt.AspectRatioMode.KeepAspectRatio,
|
|
Qt.TransformationMode.SmoothTransformation)
|
|
x = (self.width() - scaled.width()) // 2
|
|
y = (self.height() - scaled.height()) // 2
|
|
painter.drawPixmap(x, y, scaled)
|