Fix iTunes album tracks failing on region-restricted releases
iTunes API can return collection metadata without song tracks for
region-restricted albums. The _lookup fallback only checked if results
was empty, so a collection-only response was accepted and cached as
{'items': []}. All future lookups returned the cached empty result.
Three fixes:
- get_album_tracks now checks for actual song items and tries fallback
storefronts when only collection metadata is returned
- Skip cached results with empty items array (prevents stale cache hits)
- Backend returns descriptive 404 error, frontend surfaces it in toast
This commit is contained in:
parent
8c96e0e197
commit
71bff55c6a
3 changed files with 38 additions and 4 deletions
|
|
@ -735,7 +735,7 @@ class iTunesClient:
|
||||||
# Check cache for album tracks listing
|
# Check cache for album tracks listing
|
||||||
cache = get_metadata_cache()
|
cache = get_metadata_cache()
|
||||||
cached = cache.get_entity('itunes', 'album', f"{album_id}_tracks")
|
cached = cache.get_entity('itunes', 'album', f"{album_id}_tracks")
|
||||||
if cached:
|
if cached and cached.get('items'):
|
||||||
return cached
|
return cached
|
||||||
|
|
||||||
results = self._lookup(id=album_id, entity='song')
|
results = self._lookup(id=album_id, entity='song')
|
||||||
|
|
@ -743,6 +743,39 @@ class iTunesClient:
|
||||||
if not results:
|
if not results:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
# Check if results contain actual song tracks (not just collection metadata).
|
||||||
|
# iTunes sometimes returns only the collection/album info without songs
|
||||||
|
# (region-restricted tracks). The _lookup fallback only checks if results
|
||||||
|
# is empty, so a collection-only response bypasses fallback storefronts.
|
||||||
|
has_songs = any(
|
||||||
|
item.get('wrapperType') == 'track' and item.get('kind') == 'song'
|
||||||
|
for item in results
|
||||||
|
)
|
||||||
|
if not has_songs:
|
||||||
|
# Try fallback storefronts for actual song tracks
|
||||||
|
logger.info(f"Album {album_id} returned collection info but no songs, trying fallback storefronts")
|
||||||
|
for fallback in self.FALLBACK_COUNTRIES:
|
||||||
|
if fallback == self.country:
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
fb_results = self.session.get(
|
||||||
|
self.LOOKUP_URL,
|
||||||
|
params={'id': album_id, 'entity': 'song', 'country': fallback},
|
||||||
|
timeout=15
|
||||||
|
)
|
||||||
|
if fb_results.status_code == 200:
|
||||||
|
fb_data = fb_results.json().get('results', [])
|
||||||
|
if any(i.get('wrapperType') == 'track' and i.get('kind') == 'song' for i in fb_data):
|
||||||
|
logger.info(f"Found song tracks via fallback storefront: {fallback}")
|
||||||
|
results = fb_data
|
||||||
|
has_songs = True
|
||||||
|
break
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
if not has_songs:
|
||||||
|
logger.warning(f"Album {album_id} has no song tracks in any storefront")
|
||||||
|
return None
|
||||||
|
|
||||||
# First result is usually the album/collection info
|
# First result is usually the album/collection info
|
||||||
# Extract album information to include in each track (like Spotify does)
|
# Extract album information to include in each track (like Spotify does)
|
||||||
album_info = None
|
album_info = None
|
||||||
|
|
|
||||||
|
|
@ -11653,8 +11653,8 @@ def get_artist_album_tracks(artist_id, album_id):
|
||||||
|
|
||||||
# Get album tracks
|
# Get album tracks
|
||||||
tracks_data = client.get_album_tracks(resolved_album_id)
|
tracks_data = client.get_album_tracks(resolved_album_id)
|
||||||
if not tracks_data or 'items' not in tracks_data:
|
if not tracks_data or 'items' not in tracks_data or len(tracks_data['items']) == 0:
|
||||||
return jsonify({"error": "No tracks found for album"}), 404
|
return jsonify({"error": "No tracks found for album — it may be region-restricted or unavailable on this metadata source"}), 404
|
||||||
|
|
||||||
# Handle both dict and object responses from spotify_client.get_album()
|
# Handle both dict and object responses from spotify_client.get_album()
|
||||||
if isinstance(album_data, dict):
|
if isinstance(album_data, dict):
|
||||||
|
|
|
||||||
|
|
@ -37520,7 +37520,8 @@ async function createArtistAlbumVirtualPlaylist(album, albumType) {
|
||||||
if (response.status === 401) {
|
if (response.status === 401) {
|
||||||
throw new Error('Spotify not authenticated. Please check your API settings.');
|
throw new Error('Spotify not authenticated. Please check your API settings.');
|
||||||
}
|
}
|
||||||
throw new Error(`Failed to load album tracks: ${response.status}`);
|
const errData = await response.json().catch(() => ({}));
|
||||||
|
throw new Error(errData.error || `Failed to load album tracks: ${response.status}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue