"""Pin the shared artwork resolution-upgrade + fetch helpers. Bug (Discord, user report): embedded album art came out ~600×600 while the cover.jpg in the folder was high-res. Cause: only the cover.jpg path upgraded the source CDN URL to its highest resolution (Spotify master / iTunes 3000 / Deezer 1900); the tag-embed path and the "Write Tags to File" retag path fetched the raw URL — Spotify 640, iTunes 600, Deezer 1000. Fix: one shared ``_upgrade_art_url`` + ``_fetch_art_bytes`` in ``core.metadata.artwork`` that every art path now calls, so embedded art is always the same highest resolution as the folder cover. """ from __future__ import annotations import pytest from core.metadata.artwork import _upgrade_art_url, _fetch_art_bytes # --------------------------------------------------------------------------- # _upgrade_art_url — per-source resolution bump # --------------------------------------------------------------------------- class TestUpgradeArtUrl: def test_spotify_album_art_upgraded_to_master(self): # Spotify encodes size as the hex segment after 'ab67616d0000'. # 1e02 = 300px, b273 = 640px; 82c1 = the original uploaded master. url = 'https://i.scdn.co/image/ab67616d00001e02deadbeef' assert _upgrade_art_url(url) == 'https://i.scdn.co/image/ab67616d000082c1deadbeef' def test_spotify_640_also_upgraded(self): url = 'https://i.scdn.co/image/ab67616d0000b273cafef00d' assert _upgrade_art_url(url) == 'https://i.scdn.co/image/ab67616d000082c1cafef00d' def test_itunes_600_upgraded_to_3000(self): url = 'https://is1-ssl.mzstatic.com/image/thumb/abc/600x600bb.jpg' assert _upgrade_art_url(url) == 'https://is1-ssl.mzstatic.com/image/thumb/abc/3000x3000bb.jpg' def test_itunes_100_upgraded_to_3000(self): url = 'https://is1-ssl.mzstatic.com/image/thumb/abc/100x100bb.jpg' assert '3000x3000bb' in _upgrade_art_url(url) def test_deezer_upgraded_to_1900(self): url = 'https://cdn-images.dzcdn.net/images/cover/abc/1000x1000-000000-80-0-0.jpg' assert '1900x1900' in _upgrade_art_url(url) def test_caa_thumbnail_upgraded_to_native_original(self): # #806: MusicBrainz art arrives as the /front-250 thumbnail; upgrade # to the bare /front ORIGINAL (native res, frequently 3000px+). # Flakiness of the original is handled by _fetch_art_bytes' 1200px # midpoint fallback, not by capping the URL here. url = 'https://coverartarchive.org/release/abc-123/front-250' assert _upgrade_art_url(url) == 'https://coverartarchive.org/release/abc-123/front' def test_caa_all_sizes_and_scopes_upgrade_to_original(self): assert _upgrade_art_url('https://coverartarchive.org/release/x/front-500') \ == 'https://coverartarchive.org/release/x/front' assert _upgrade_art_url('https://coverartarchive.org/release/x/front-1200') \ == 'https://coverartarchive.org/release/x/front' # release-group scope works the same (the URL shape from #806). assert _upgrade_art_url('https://coverartarchive.org/release-group/y/front-250') \ == 'https://coverartarchive.org/release-group/y/front' def test_caa_bare_front_idempotent(self): url = 'https://coverartarchive.org/release/abc/front' assert _upgrade_art_url(url) == url @pytest.mark.parametrize('url', [ 'https://lastfm.freetls.fastly.net/i/u/770x0/x.jpg', 'https://example.com/random.jpg', ]) def test_unrecognized_url_unchanged(self, url): assert _upgrade_art_url(url) == url def test_empty_and_none_unchanged(self): assert _upgrade_art_url('') == '' assert _upgrade_art_url(None) is None # --------------------------------------------------------------------------- # _fetch_art_bytes — upgrade, fetch, fall back to original on CDN refusal # --------------------------------------------------------------------------- class _FakeResponse: def __init__(self, data=b'art-bytes', ctype='image/jpeg'): self._data = data self._ctype = ctype def read(self): return self._data def info(self): ctype = self._ctype class _Info: def get_content_type(_self): return ctype return _Info() def __enter__(self): return self def __exit__(self, *a): pass class TestFetchArtBytes: def test_fetches_upgraded_url(self, monkeypatch): """The upgraded (high-res) URL is the one actually fetched.""" calls = [] def fake_urlopen(url, timeout=None): calls.append(url) return _FakeResponse(b'big-cover', 'image/png') monkeypatch.setattr('core.metadata.artwork.urllib.request.urlopen', fake_urlopen) data, mime = _fetch_art_bytes('https://i.scdn.co/image/ab67616d0000b273x') assert data == b'big-cover' assert mime == 'image/png' # Fetched the master-res URL, not the 640 original. assert calls == ['https://i.scdn.co/image/ab67616d000082c1x'] def test_falls_back_to_original_when_upgrade_refused(self, monkeypatch): upgraded = 'https://is1-ssl.mzstatic.com/image/thumb/a/3000x3000bb.jpg' original = 'https://is1-ssl.mzstatic.com/image/thumb/a/600x600bb.jpg' calls = [] def fake_urlopen(url, timeout=None): calls.append(url) if url == upgraded: raise Exception('403 Forbidden') return _FakeResponse(b'orig-cover') monkeypatch.setattr('core.metadata.artwork.urllib.request.urlopen', fake_urlopen) data, mime = _fetch_art_bytes(original) assert data == b'orig-cover' assert calls == [upgraded, original] # tried big first, then fell back def test_no_fallback_when_url_not_upgraded(self, monkeypatch): """If the upgrade is a no-op (unrecognized URL), a single failed fetch returns (None, None) — no pointless retry of the same URL.""" calls = [] def fake_urlopen(url, timeout=None): calls.append(url) raise Exception('network down') monkeypatch.setattr('core.metadata.artwork.urllib.request.urlopen', fake_urlopen) data, mime = _fetch_art_bytes('https://example.com/cover.jpg') assert (data, mime) == (None, None) assert calls == ['https://example.com/cover.jpg'] def test_empty_url_returns_none(self): assert _fetch_art_bytes('') == (None, None) assert _fetch_art_bytes(None) == (None, None) # ── #806: CAA native-res chain — original → 1200px midpoint → thumbnail ── @pytest.fixture(autouse=True) def _reset_caa_cooldown(self, monkeypatch): # The negative cache persists module-globally; isolate every test. import core.metadata.artwork as aw monkeypatch.setattr(aw, '_caa_original_down_until', 0.0) def test_caa_fetches_native_original_first(self, monkeypatch): calls = [] def fake_urlopen(url, timeout=None): calls.append(url) return _FakeResponse(b'native-3000px') monkeypatch.setattr('core.metadata.artwork.urllib.request.urlopen', fake_urlopen) data, _ = _fetch_art_bytes('https://coverartarchive.org/release/r1/front-250') assert data == b'native-3000px' assert calls == ['https://coverartarchive.org/release/r1/front'] def test_caa_flaky_original_degrades_to_1200_not_250(self, monkeypatch): """archive.org flakiness lands on the old 1200px behavior — the midpoint sits BEFORE the tiny original thumbnail in the chain.""" calls = [] def fake_urlopen(url, timeout=None): calls.append(url) if url.endswith('/front'): raise Exception('503 archive.org is having a day') return _FakeResponse(b'cdn-1200px') monkeypatch.setattr('core.metadata.artwork.urllib.request.urlopen', fake_urlopen) data, _ = _fetch_art_bytes('https://coverartarchive.org/release/r1/front-250') assert data == b'cdn-1200px' assert calls == [ 'https://coverartarchive.org/release/r1/front', 'https://coverartarchive.org/release/r1/front-1200', ] def test_caa_full_chain_ends_at_the_original_thumbnail(self, monkeypatch): calls = [] def fake_urlopen(url, timeout=None): calls.append(url) if url.endswith('/front') or url.endswith('-1200'): raise Exception('refused') return _FakeResponse(b'tiny-250px') monkeypatch.setattr('core.metadata.artwork.urllib.request.urlopen', fake_urlopen) data, _ = _fetch_art_bytes('https://coverartarchive.org/release/r1/front-250') assert data == b'tiny-250px' assert calls == [ 'https://coverartarchive.org/release/r1/front', 'https://coverartarchive.org/release/r1/front-1200', 'https://coverartarchive.org/release/r1/front-250', ] def test_caa_outage_cooldown_skips_originals_for_subsequent_fetches(self, monkeypatch): """One archive.org failure puts originals on cooldown: the NEXT track's fetch goes straight to the 1200px CDN — no 10s timeout per track during an outage (art is fetched per track).""" calls = [] def fake_urlopen(url, timeout=None): calls.append(url) if url.endswith('/front'): raise Exception('archive.org down') return _FakeResponse(b'cdn-1200px') monkeypatch.setattr('core.metadata.artwork.urllib.request.urlopen', fake_urlopen) # Track 1: pays the failed original once, falls back to 1200. _fetch_art_bytes('https://coverartarchive.org/release/r1/front-250') # Track 2: cooldown active — never touches the original. data, _ = _fetch_art_bytes('https://coverartarchive.org/release/r2/front-250') assert data == b'cdn-1200px' assert calls == [ 'https://coverartarchive.org/release/r1/front', # track 1 pays once 'https://coverartarchive.org/release/r1/front-1200', # and falls back 'https://coverartarchive.org/release/r2/front-1200', # track 2 skips straight to CDN ]