Add Spotify integration with improved token management and user interface

- Implemented Spotify OAuth handling in spotify_client_manager.py to ensure valid access tokens for users.
- Created helper functions in spotify_helper.py for refreshing tokens and retrieving user information.
- Developed manage_spotify.html template for connecting and managing Spotify accounts, displaying connection status and token information.
- Added logging for token management processes to enhance debugging and monitoring.
- Introduced a debug client for Spotify interactions to facilitate easier testing and development.
This commit is contained in:
Christian Krakau-Louis
2025-05-27 21:40:37 +02:00
parent d2772b88fe
commit 2f55f898ed
44 changed files with 2317 additions and 1884 deletions
+79 -8
View File
@@ -5,6 +5,7 @@ import random
import time
from flask import current_app
from musicround.models import Song, db
from musicround.helpers.metadata import get_song_metadata_by_isrc
logger = logging.getLogger(__name__)
@@ -161,11 +162,55 @@ class DeezerClient:
if not preview_url:
self.logger.warning(f"Track {track_info.get('title')} has no preview URL")
return None
# Extract ISRC
isrc = track_info.get('isrc')
# Check if this track is already in our database
existing_song = Song.query.filter_by(deezer_id=str(track_info['id'])).first()
# Check if this track is already in our database by Deezer ID or ISRC
existing_song = None
if isrc:
existing_song = Song.query.filter_by(isrc=isrc).first()
if not existing_song:
existing_song = Song.query.filter_by(deezer_id=str(track_info['id'])).first()
if existing_song:
self.logger.info(f"Track {track_info.get('title')} already exists in database")
self.logger.info(f"Track {track_info.get('title')} (Deezer ID: {track_info['id']}, ISRC: {isrc}) already exists in database with ID {existing_song.id}")
# If ISRC was missing and we found it now, update the existing record
if isrc and not existing_song.isrc:
existing_song.isrc = isrc
if existing_song.deezer_id is None: # If it was matched by ISRC but didn't have deezer_id
existing_song.deezer_id = str(track_info['id'])
try:
db.session.commit()
self.logger.info(f"Updated ISRC for existing song {existing_song.id} to {isrc}")
except Exception as e:
db.session.rollback()
self.logger.error(f"Error updating ISRC for existing song {existing_song.id}: {e}")
# Optionally, trigger metadata refresh if ISRC is now available or if desired
if existing_song.isrc:
try:
app_context = current_app._get_current_object()
updated_metadata = get_song_metadata_by_isrc(existing_song.isrc, app=app_context)
if updated_metadata:
# Update song fields from aggregated metadata
existing_song.title = updated_metadata.get('title', existing_song.title)
existing_song.artist = updated_metadata.get('artist_name', existing_song.artist)
existing_song.year = updated_metadata.get('year', existing_song.year)
existing_song.genre = updated_metadata.get('genre', existing_song.genre)
# ... update other relevant fields ...
if updated_metadata.get('spotify_id') and not existing_song.spotify_id:
existing_song.spotify_id = updated_metadata.get('spotify_id')
if updated_metadata.get('cover_url') and not existing_song.cover_url: # Prioritize existing cover if any
existing_song.cover_url = updated_metadata.get('cover_url')
if updated_metadata.get('preview_url') and not existing_song.preview_url: # Prioritize existing preview if any
existing_song.preview_url = updated_metadata.get('preview_url')
db.session.commit()
self.logger.info(f"Refreshed metadata for existing song {existing_song.id} using ISRC {existing_song.isrc}")
except Exception as e:
db.session.rollback()
self.logger.error(f"Error refreshing metadata for existing song {existing_song.id}: {e}")
return existing_song
# Get additional artist details if needed
@@ -184,27 +229,53 @@ class DeezerClient:
# Get highest quality cover image
cover_url = album_info.get('cover_xl') or album_info.get('cover_big') or album_info.get('cover_medium', '')
# Get genre from Last.fm
genre = self.get_genre_from_lastfm(artist_name, track_info.get('title', ''), lastfm_api_key)
# Get genre from Last.fm (can be removed if metadata aggregation handles it)
# genre = self.get_genre_from_lastfm(artist_name, track_info.get('title', ''), lastfm_api_key)
genre = None # Will be populated by metadata aggregation if ISRC is present
# Create new Song object
new_song = Song(
deezer_id=str(track_info['id']),
spotify_id=None, # We don't have Spotify ID for Deezer tracks
spotify_id=None, # Will be populated by metadata aggregation if ISRC is present
title=track_info.get('title', ''),
artist=artist_name,
genre=genre,
year=release_year,
preview_url=preview_url,
cover_url=cover_url,
popularity=track_info.get('rank', 0),
popularity=track_info.get('rank', 0), # Deezer 'rank' can be used as popularity
isrc=isrc, # Save the ISRC
used_count=0
)
try:
db.session.add(new_song)
db.session.commit()
self.logger.info(f"Imported track '{new_song.title}' by {new_song.artist}")
self.logger.info(f"Imported track '{new_song.title}' by {new_song.artist} with Deezer ID {new_song.deezer_id} and ISRC {new_song.isrc}")
# If ISRC is present, fetch and update with aggregated metadata
if new_song.isrc:
try:
app_context = current_app._get_current_object()
aggregated_metadata = get_song_metadata_by_isrc(new_song.isrc, app=app_context)
if aggregated_metadata:
new_song.title = aggregated_metadata.get('title', new_song.title)
new_song.artist = aggregated_metadata.get('artist_name', new_song.artist)
new_song.year = aggregated_metadata.get('year', new_song.year)
new_song.genre = aggregated_metadata.get('genre', new_song.genre)
new_song.spotify_id = aggregated_metadata.get('spotify_id', new_song.spotify_id)
# Update cover and preview URLs if they are better or missing
if aggregated_metadata.get('cover_url'):
new_song.cover_url = aggregated_metadata.get('cover_url')
if aggregated_metadata.get('preview_url'):
new_song.preview_url = aggregated_metadata.get('preview_url')
# Potentially update popularity if a more universal score is available
# new_song.popularity = aggregated_metadata.get('popularity', new_song.popularity)
db.session.commit()
self.logger.info(f"Updated new song {new_song.id} with aggregated metadata using ISRC {new_song.isrc}")
except Exception as e:
db.session.rollback()
self.logger.error(f"Error updating new song {new_song.id} with aggregated metadata: {e}")
return new_song
except Exception as e:
db.session.rollback()