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)
|
# Add iTunes artist ID column to watchlist_artists (migration)
|
||||||
self._add_watchlist_itunes_id_column(cursor)
|
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()
|
conn.commit()
|
||||||
logger.info("Database initialized successfully")
|
logger.info("Database initialized successfully")
|
||||||
|
|
||||||
|
|
@ -821,6 +824,67 @@ class MusicDatabase:
|
||||||
logger.error(f"Error adding itunes_artist_id column to watchlist_artists: {e}")
|
logger.error(f"Error adding itunes_artist_id column to watchlist_artists: {e}")
|
||||||
# Don't raise - this is a migration, database can still function
|
# 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):
|
def close(self):
|
||||||
"""Close database connection (no-op since we create connections per operation)"""
|
"""Close database connection (no-op since we create connections per operation)"""
|
||||||
# Each operation creates and closes its own connection, so nothing to do here
|
# 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,
|
"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,
|
"created_at": artist.created_at.isoformat() if artist.created_at else None,
|
||||||
"updated_at": artist.updated_at.isoformat() if artist.updated_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})
|
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)
|
success = database.add_artist_to_watchlist(artist_id, artist_name)
|
||||||
|
|
||||||
if success:
|
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:
|
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)
|
artist_data = spotify_client.get_artist(artist_id)
|
||||||
if artist_data and 'images' in artist_data and artist_data['images']:
|
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)
|
# 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("""
|
cursor.execute("""
|
||||||
SELECT include_albums, include_eps, include_singles,
|
SELECT include_albums, include_eps, include_singles,
|
||||||
include_live, include_remixes, include_acoustic, include_compilations,
|
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
|
FROM watchlist_artists
|
||||||
WHERE spotify_artist_id = ?
|
WHERE spotify_artist_id = ? OR itunes_artist_id = ?
|
||||||
""", (artist_id,))
|
""", (artist_id, artist_id))
|
||||||
result = cursor.fetchone()
|
result = cursor.fetchone()
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
if not result:
|
if not result:
|
||||||
return jsonify({"success": False, "error": "Artist not found in watchlist"}), 404
|
return jsonify({"success": False, "error": "Artist not found in watchlist"}), 404
|
||||||
|
|
||||||
# Get artist info from Spotify
|
# 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 (only for Spotify artists)
|
||||||
artist_info = None
|
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:
|
try:
|
||||||
artist_data = spotify_client.sp.artist(artist_id)
|
artist_data = spotify_client.sp.artist(spotify_id)
|
||||||
if artist_data:
|
if artist_data:
|
||||||
artist_info = {
|
artist_info = {
|
||||||
'id': artist_data['id'],
|
'id': artist_data['id'],
|
||||||
|
|
@ -18053,10 +18092,10 @@ def watchlist_artist_config(artist_id):
|
||||||
SET include_albums = ?, include_eps = ?, include_singles = ?,
|
SET include_albums = ?, include_eps = ?, include_singles = ?,
|
||||||
include_live = ?, include_remixes = ?, include_acoustic = ?, include_compilations = ?,
|
include_live = ?, include_remixes = ?, include_acoustic = ?, include_compilations = ?,
|
||||||
updated_at = CURRENT_TIMESTAMP
|
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_albums), int(include_eps), int(include_singles),
|
||||||
int(include_live), int(include_remixes), int(include_acoustic), int(include_compilations),
|
int(include_live), int(include_remixes), int(include_acoustic), int(include_compilations),
|
||||||
artist_id))
|
artist_id, artist_id))
|
||||||
conn.commit()
|
conn.commit()
|
||||||
|
|
||||||
if cursor.rowcount == 0:
|
if cursor.rowcount == 0:
|
||||||
|
|
|
||||||
|
|
@ -23669,7 +23669,7 @@ async function showWatchlistModal() {
|
||||||
${artistsData.artists.map(artist => `
|
${artistsData.artists.map(artist => `
|
||||||
<div class="watchlist-artist-item"
|
<div class="watchlist-artist-item"
|
||||||
data-artist-name="${artist.artist_name.toLowerCase().replace(/"/g, '"')}"
|
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;">
|
style="cursor: pointer;">
|
||||||
${artist.image_url ? `
|
${artist.image_url ? `
|
||||||
<img src="${artist.image_url}"
|
<img src="${artist.image_url}"
|
||||||
|
|
@ -23689,7 +23689,7 @@ async function showWatchlistModal() {
|
||||||
` : ''}
|
` : ''}
|
||||||
</div>
|
</div>
|
||||||
<button class="playlist-modal-btn playlist-modal-btn-secondary watchlist-remove-btn"
|
<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)}"
|
data-artist-name="${escapeHtml(artist.artist_name)}"
|
||||||
onclick="event.stopPropagation();">
|
onclick="event.stopPropagation();">
|
||||||
Remove
|
Remove
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue