Album art download: one-click apply to every song on the album

The confirmation dialog gains a "Use for All N Songs in Album" button
(shown when the library's album has more songs than the selection); it
embeds the chosen art into every track matching the album, not just the
right-clicked ones. Album membership matches case-insensitively on album
name + album artist (falling back to track artist), the same grouping the
search already uses.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-02 15:54:24 -04:00
parent 649302551d
commit b69a25fa51

View File

@ -397,36 +397,40 @@ class TestAlbumWideArt:
for t in album_tracks(library, "Artist", "Great Album")} for t in album_tracks(library, "Artist", "Great Album")}
assert ids == {1, 2, 3} assert ids == {1, 2, 3}
def _dialog(self, qapp, monkeypatch, track_count, album_count): def _dialog(self, monkeypatch, image, track_count, album_count):
from lintunes.art_search import AlbumArtFetcher, ArtCandidate from lintunes.art_search import AlbumArtFetcher, ArtCandidate
from lintunes.gui.album_art_dialog import AlbumArtDialog from lintunes.gui.album_art_dialog import AlbumArtDialog
# No network: capture the fetch and feed the image back by hand. # No network: neuter the fetch and feed the image back by hand.
monkeypatch.setattr(AlbumArtFetcher, "fetch", lambda self, c: None) monkeypatch.setattr(AlbumArtFetcher, "fetch", lambda self, c: None)
candidate = ArtCandidate(artist="A", album="B", art_url="https://x") candidate = ArtCandidate(artist="A", album="B", art_url="https://x")
dialog = AlbumArtDialog([candidate], track_count, dialog = AlbumArtDialog([candidate], track_count,
album_count=album_count) album_count=album_count)
dialog._on_image({"candidate": candidate, "image": b"img", dialog._on_image({"candidate": candidate, "image": image,
"mime": "image/png"}) "mime": "image/jpeg"})
return dialog return dialog
def test_album_button_applies_to_whole_album(self, qapp, monkeypatch): def test_album_button_applies_to_whole_album(self, qapp, monkeypatch,
dialog = self._dialog(qapp, monkeypatch, track_count=1, album_count=12) jpeg_bytes):
dialog = self._dialog(monkeypatch, jpeg_bytes,
track_count=1, album_count=12)
assert dialog._album_btn is not None assert dialog._album_btn is not None
assert dialog._album_btn.isEnabled() assert dialog._album_btn.isEnabled()
assert "12" in dialog._album_btn.text() assert "12" in dialog._album_btn.text()
assert not dialog.apply_to_album assert not dialog.apply_to_album
dialog._accept_album() dialog._accept_album()
assert dialog.apply_to_album assert dialog.apply_to_album
assert dialog.selected_image == b"img" assert dialog.selected_image == jpeg_bytes
assert dialog.selected_mime == "image/png" assert dialog.selected_mime == "image/jpeg"
def test_no_album_button_when_selection_covers_album(self, qapp, def test_no_album_button_when_selection_covers_album(self, qapp,
monkeypatch): monkeypatch,
dialog = self._dialog(qapp, monkeypatch, track_count=5, album_count=5) jpeg_bytes):
dialog = self._dialog(monkeypatch, jpeg_bytes,
track_count=5, album_count=5)
assert dialog._album_btn is None assert dialog._album_btn is None
dialog._accept_current() dialog._accept_current()
assert not dialog.apply_to_album assert not dialog.apply_to_album
assert dialog.selected_image == b"img" assert dialog.selected_image == jpeg_bytes
# ---- context menu exposes the new action's signal ---- # ---- context menu exposes the new action's signal ----