tysm fable

This commit is contained in:
2026-07-02 12:44:58 -04:00
parent 8dc33fa65e
commit 649302551d
3 changed files with 87 additions and 3 deletions

View File

@ -16,7 +16,8 @@ PREVIEW_PX = 300
class AlbumArtDialog(QDialog):
def __init__(self, candidates, track_count: int, parent=None):
def __init__(self, candidates, track_count: int, album_count: int = 0,
parent=None):
super().__init__(parent)
self.setWindowTitle("Download Album Art")
self._candidates = candidates
@ -25,6 +26,9 @@ class AlbumArtDialog(QDialog):
self._fetcher.image_finished.connect(self._on_image)
self.selected_image: bytes | None = None
self.selected_mime = "image/jpeg"
# True when the user chose the whole-album button instead of just the
# selected tracks.
self.apply_to_album = False
self._image: bytes | None = None
self._mime = "image/jpeg"
@ -49,6 +53,15 @@ class AlbumArtDialog(QDialog):
self._use_btn = QPushButton("Use This Artwork")
self._use_btn.setDefault(True)
buttons.addButton(self._use_btn, QDialogButtonBox.ButtonRole.AcceptRole)
# Offered when the library's album holds more songs than the
# selection, so one download can cover the whole album.
self._album_btn = None
if album_count > track_count:
self._album_btn = QPushButton(
f"Use for All {album_count} Songs in Album")
buttons.addButton(self._album_btn,
QDialogButtonBox.ButtonRole.ActionRole)
self._album_btn.clicked.connect(self._accept_album)
self._next_btn = QPushButton("Next Result")
self._next_btn.setEnabled(len(candidates) > 1)
buttons.addButton(self._next_btn, QDialogButtonBox.ButtonRole.ActionRole)
@ -70,6 +83,8 @@ class AlbumArtDialog(QDialog):
f"<b>{candidate.artist}{candidate.album}</b>{counter}")
self._image = None
self._use_btn.setEnabled(False)
if self._album_btn is not None:
self._album_btn.setEnabled(False)
self._preview.setText("Loading…")
self._preview.setPixmap(QPixmap())
self._fetcher.fetch(candidate)
@ -96,6 +111,8 @@ class AlbumArtDialog(QDialog):
self._preview.size(), Qt.AspectRatioMode.KeepAspectRatio,
Qt.TransformationMode.SmoothTransformation))
self._use_btn.setEnabled(True)
if self._album_btn is not None:
self._album_btn.setEnabled(True)
def _accept_current(self):
if self._image is None:
@ -103,3 +120,7 @@ class AlbumArtDialog(QDialog):
self.selected_image = self._image
self.selected_mime = self._mime
self.accept()
def _accept_album(self):
self.apply_to_album = True
self._accept_current()

View File

@ -23,6 +23,16 @@ from lintunes.gui.preferences_dialog import PreferencesDialog
from lintunes.gui.track_table import format_total_time
def album_tracks(library, artist: str, album: str) -> list:
"""Every library track on the given album (case-insensitive match on
album name + album artist, falling back to track artist — the same
grouping the art download uses)."""
artist_cf, album_cf = artist.casefold(), album.casefold()
return [t for t in library.tracks.values()
if t.album.casefold() == album_cf
and (t.album_artist or t.artist).casefold() == artist_cf]
class MainWindow(QMainWindow):
def __init__(self, manager, prefs, lastfm=None, parent=None):
super().__init__(parent)
@ -364,10 +374,15 @@ class MainWindow(QMainWindow):
6000)
else:
self.statusBar().clearMessage()
whole_album = album_tracks(self._manager.library,
group["artist"], group["album"])
dialog = AlbumArtDialog(result["candidates"],
len(group["tracks"]), self)
len(group["tracks"]),
album_count=len(whole_album), parent=self)
if dialog.exec() and dialog.selected_image:
self._embed_album_art(group["tracks"], dialog.selected_image,
targets = whole_album if dialog.apply_to_album \
else group["tracks"]
self._embed_album_art(targets, dialog.selected_image,
dialog.selected_mime)
self._art_next_group()

View File

@ -381,6 +381,54 @@ class TestArtSearch:
assert not manager.undo_stack.can_undo()
class TestAlbumWideArt:
def test_album_tracks_matches_album_case_insensitively(self):
from lintunes.gui.main_window import album_tracks
library = Library()
library.tracks = {
1: Track(track_id=1, artist="Artist", album="Great Album"),
2: Track(track_id=2, artist="artist", album="great album"),
3: Track(track_id=3, artist="Someone Else", album="Great Album",
album_artist="Artist"), # album_artist wins the grouping
4: Track(track_id=4, artist="Artist", album="Other Album"),
5: Track(track_id=5, artist="Other Artist", album="Great Album"),
}
ids = {t.track_id
for t in album_tracks(library, "Artist", "Great Album")}
assert ids == {1, 2, 3}
def _dialog(self, qapp, monkeypatch, track_count, album_count):
from lintunes.art_search import AlbumArtFetcher, ArtCandidate
from lintunes.gui.album_art_dialog import AlbumArtDialog
# No network: capture the fetch and feed the image back by hand.
monkeypatch.setattr(AlbumArtFetcher, "fetch", lambda self, c: None)
candidate = ArtCandidate(artist="A", album="B", art_url="https://x")
dialog = AlbumArtDialog([candidate], track_count,
album_count=album_count)
dialog._on_image({"candidate": candidate, "image": b"img",
"mime": "image/png"})
return dialog
def test_album_button_applies_to_whole_album(self, qapp, monkeypatch):
dialog = self._dialog(qapp, monkeypatch, track_count=1, album_count=12)
assert dialog._album_btn is not None
assert dialog._album_btn.isEnabled()
assert "12" in dialog._album_btn.text()
assert not dialog.apply_to_album
dialog._accept_album()
assert dialog.apply_to_album
assert dialog.selected_image == b"img"
assert dialog.selected_mime == "image/png"
def test_no_album_button_when_selection_covers_album(self, qapp,
monkeypatch):
dialog = self._dialog(qapp, monkeypatch, track_count=5, album_count=5)
assert dialog._album_btn is None
dialog._accept_current()
assert not dialog.apply_to_album
assert dialog.selected_image == b"img"
# ---- context menu exposes the new action's signal ----
class TestDownloadArtSignal: