Fix watchlist artist config and add image at insert when Itunes source.
This commit is contained in:
parent
47c45ddea7
commit
905e98016f
3 changed files with 116 additions and 13 deletions
|
|
@ -291,6 +291,9 @@ class MusicDatabase:
|
|||
# Add iTunes artist ID column to watchlist_artists (migration)
|
||||
self._add_watchlist_itunes_id_column(cursor)
|
||||
|
||||
# Make spotify_artist_id nullable for iTunes-only artists (migration)
|
||||
self._fix_watchlist_spotify_id_nullable(cursor)
|
||||
|
||||
conn.commit()
|
||||
logger.info("Database initialized successfully")
|
||||
|
||||
|
|
@ -821,6 +824,67 @@ class MusicDatabase:
|
|||
logger.error(f"Error adding itunes_artist_id column to watchlist_artists: {e}")
|
||||
# Don't raise - this is a migration, database can still function
|
||||
|
||||
def _fix_watchlist_spotify_id_nullable(self, cursor):
|
||||
"""
|
||||
Make spotify_artist_id nullable in watchlist_artists table.
|
||||
This allows adding iTunes-only artists without Spotify IDs.
|
||||
|
||||
Since SQLite doesn't support modifying column constraints directly,
|
||||
we need to recreate the table if the constraint needs to be changed.
|
||||
"""
|
||||
try:
|
||||
# Check if spotify_artist_id is currently NOT NULL
|
||||
cursor.execute("SELECT sql FROM sqlite_master WHERE type='table' AND name='watchlist_artists'")
|
||||
result = cursor.fetchone()
|
||||
|
||||
if result and 'spotify_artist_id TEXT UNIQUE NOT NULL' in result[0]:
|
||||
logger.info("Migrating watchlist_artists table to make spotify_artist_id nullable...")
|
||||
|
||||
# Create new table with nullable spotify_artist_id
|
||||
cursor.execute("""
|
||||
CREATE TABLE watchlist_artists_new (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
spotify_artist_id TEXT UNIQUE,
|
||||
artist_name TEXT NOT NULL,
|
||||
date_added TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
last_scan_timestamp TIMESTAMP,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
image_url TEXT,
|
||||
include_albums INTEGER DEFAULT 1,
|
||||
include_eps INTEGER DEFAULT 1,
|
||||
include_singles INTEGER DEFAULT 1,
|
||||
include_live INTEGER DEFAULT 0,
|
||||
include_remixes INTEGER DEFAULT 0,
|
||||
include_acoustic INTEGER DEFAULT 0,
|
||||
include_compilations INTEGER DEFAULT 0,
|
||||
itunes_artist_id TEXT
|
||||
)
|
||||
""")
|
||||
|
||||
# Copy data from old table
|
||||
cursor.execute("""
|
||||
INSERT INTO watchlist_artists_new
|
||||
SELECT * FROM watchlist_artists
|
||||
""")
|
||||
|
||||
# Drop old table
|
||||
cursor.execute("DROP TABLE watchlist_artists")
|
||||
|
||||
# Rename new table
|
||||
cursor.execute("ALTER TABLE watchlist_artists_new RENAME TO watchlist_artists")
|
||||
|
||||
# Recreate indexes
|
||||
cursor.execute("CREATE INDEX IF NOT EXISTS idx_watchlist_spotify_id ON watchlist_artists (spotify_artist_id)")
|
||||
|
||||
logger.info("Successfully migrated watchlist_artists table - spotify_artist_id is now nullable")
|
||||
else:
|
||||
logger.debug("watchlist_artists table already has nullable spotify_artist_id or custom schema")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error making spotify_artist_id nullable in watchlist_artists: {e}")
|
||||
# Don't raise - this is a migration, database can still function
|
||||
|
||||
def close(self):
|
||||
"""Close database connection (no-op since we create connections per operation)"""
|
||||
# Each operation creates and closes its own connection, so nothing to do here
|
||||
|
|
|
|||
|
|
@ -17423,7 +17423,8 @@ def get_watchlist_artists():
|
|||
"last_scan_timestamp": artist.last_scan_timestamp.isoformat() if artist.last_scan_timestamp else None,
|
||||
"created_at": artist.created_at.isoformat() if artist.created_at else None,
|
||||
"updated_at": artist.updated_at.isoformat() if artist.updated_at else None,
|
||||
"image_url": artist.image_url # Cached during watchlist scans
|
||||
"image_url": artist.image_url, # Cached during watchlist scans
|
||||
"itunes_artist_id": artist.itunes_artist_id # For iTunes-only artists
|
||||
})
|
||||
|
||||
return jsonify({"success": True, "artists": artists_data})
|
||||
|
|
@ -17446,9 +17447,42 @@ def add_to_watchlist():
|
|||
success = database.add_artist_to_watchlist(artist_id, artist_name)
|
||||
|
||||
if success:
|
||||
# Fetch and cache artist image immediately from Spotify
|
||||
# Detect ID type: iTunes IDs are purely numeric, Spotify IDs are alphanumeric
|
||||
is_itunes_id = artist_id.isdigit()
|
||||
|
||||
# Fetch and cache artist image immediately
|
||||
try:
|
||||
if spotify_client and spotify_client.is_authenticated():
|
||||
if is_itunes_id:
|
||||
# For iTunes artists, fetch image from iTunes API
|
||||
# We look up 'album' entity because 'artist' entity doesn't always have artwork
|
||||
# We fallback to the first album's artwork as the artist image
|
||||
try:
|
||||
itunes_url = f"https://itunes.apple.com/lookup?id={artist_id}&entity=album&limit=5"
|
||||
print(f"🔍 Fetching iTunes artist image: {itunes_url}")
|
||||
resp = requests.get(itunes_url, timeout=5)
|
||||
|
||||
image_url = None
|
||||
if resp.status_code == 200:
|
||||
data = resp.json()
|
||||
results = data.get('results', [])
|
||||
|
||||
# Iterate results to find one with artwork
|
||||
for res in results:
|
||||
if 'artworkUrl100' in res:
|
||||
# Get highest res by replacing dimensions
|
||||
# iTunes artwork URLs usually end in .../100x100bb.jpg
|
||||
image_url = res['artworkUrl100'].replace('100x100', '600x600')
|
||||
break
|
||||
|
||||
if image_url:
|
||||
database.update_watchlist_artist_image(artist_id, image_url)
|
||||
print(f"✅ Cached iTunes artist image for {artist_name}")
|
||||
else:
|
||||
print(f"⚠️ No artwork found for iTunes artist {artist_name}")
|
||||
except Exception as itunes_error:
|
||||
print(f"⚠️ Error fetching iTunes artwork: {itunes_error}")
|
||||
elif spotify_client and spotify_client.is_authenticated():
|
||||
# For Spotify artists, fetch from Spotify API
|
||||
artist_data = spotify_client.get_artist(artist_id)
|
||||
if artist_data and 'images' in artist_data and artist_data['images']:
|
||||
# Get medium-sized image (usually the second one, or first if only one)
|
||||
|
|
@ -17974,21 +18008,26 @@ def watchlist_artist_config(artist_id):
|
|||
cursor.execute("""
|
||||
SELECT include_albums, include_eps, include_singles,
|
||||
include_live, include_remixes, include_acoustic, include_compilations,
|
||||
artist_name, image_url
|
||||
artist_name, image_url, spotify_artist_id, itunes_artist_id
|
||||
FROM watchlist_artists
|
||||
WHERE spotify_artist_id = ?
|
||||
""", (artist_id,))
|
||||
WHERE spotify_artist_id = ? OR itunes_artist_id = ?
|
||||
""", (artist_id, artist_id))
|
||||
result = cursor.fetchone()
|
||||
conn.close()
|
||||
|
||||
if not result:
|
||||
return jsonify({"success": False, "error": "Artist not found in watchlist"}), 404
|
||||
|
||||
# Determine if this is an iTunes or Spotify artist
|
||||
is_itunes_artist = artist_id.isdigit()
|
||||
spotify_id = result[9] # spotify_artist_id from query
|
||||
itunes_id = result[10] # itunes_artist_id from query
|
||||
|
||||
# Get artist info from Spotify
|
||||
# Get artist info from Spotify (only for Spotify artists)
|
||||
artist_info = None
|
||||
if spotify_client and spotify_client.is_authenticated():
|
||||
if not is_itunes_artist and spotify_client and spotify_client.is_authenticated() and spotify_id:
|
||||
try:
|
||||
artist_data = spotify_client.sp.artist(artist_id)
|
||||
artist_data = spotify_client.sp.artist(spotify_id)
|
||||
if artist_data:
|
||||
artist_info = {
|
||||
'id': artist_data['id'],
|
||||
|
|
@ -18053,10 +18092,10 @@ def watchlist_artist_config(artist_id):
|
|||
SET include_albums = ?, include_eps = ?, include_singles = ?,
|
||||
include_live = ?, include_remixes = ?, include_acoustic = ?, include_compilations = ?,
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
WHERE spotify_artist_id = ?
|
||||
WHERE spotify_artist_id = ? OR itunes_artist_id = ?
|
||||
""", (int(include_albums), int(include_eps), int(include_singles),
|
||||
int(include_live), int(include_remixes), int(include_acoustic), int(include_compilations),
|
||||
artist_id))
|
||||
artist_id, artist_id))
|
||||
conn.commit()
|
||||
|
||||
if cursor.rowcount == 0:
|
||||
|
|
|
|||
|
|
@ -23669,7 +23669,7 @@ async function showWatchlistModal() {
|
|||
${artistsData.artists.map(artist => `
|
||||
<div class="watchlist-artist-item"
|
||||
data-artist-name="${artist.artist_name.toLowerCase().replace(/"/g, '"')}"
|
||||
data-artist-id="${artist.spotify_artist_id}"
|
||||
data-artist-id="${artist.spotify_artist_id || artist.itunes_artist_id}"
|
||||
style="cursor: pointer;">
|
||||
${artist.image_url ? `
|
||||
<img src="${artist.image_url}"
|
||||
|
|
@ -23689,7 +23689,7 @@ async function showWatchlistModal() {
|
|||
` : ''}
|
||||
</div>
|
||||
<button class="playlist-modal-btn playlist-modal-btn-secondary watchlist-remove-btn"
|
||||
data-artist-id="${artist.spotify_artist_id}"
|
||||
data-artist-id="${artist.spotify_artist_id || artist.itunes_artist_id}"
|
||||
data-artist-name="${escapeHtml(artist.artist_name)}"
|
||||
onclick="event.stopPropagation();">
|
||||
Remove
|
||||
|
|
|
|||
Loading…
Reference in a new issue