tysm fable
This commit is contained in:
@ -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()
|
||||
|
||||
@ -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()
|
||||
|
||||
|
||||
@ -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:
|
||||
|
||||
Reference in New Issue
Block a user