diff --git a/core/personalized_playlists.py b/core/personalized_playlists.py index 7114fbbb..7bf2fd99 100644 --- a/core/personalized_playlists.py +++ b/core/personalized_playlists.py @@ -191,7 +191,8 @@ class PersonalizedPlaylistsService: album_cover_url, duration_ms, popularity, - release_date + release_date, + track_data_json FROM discovery_pool WHERE release_date IS NOT NULL AND CAST(SUBSTR(release_date, 1, 4) AS INTEGER) BETWEEN ? AND ? @@ -200,7 +201,17 @@ class PersonalizedPlaylistsService: """, (start_year, end_year, limit * 10)) rows = cursor.fetchall() - all_tracks = [dict(row) for row in rows] + all_tracks = [] + for row in rows: + track_dict = dict(row) + # Parse track_data_json if available + if track_dict.get('track_data_json'): + try: + import json + track_dict['track_data_json'] = json.loads(track_dict['track_data_json']) + except: + pass + all_tracks.append(track_dict) if not all_tracks: logger.warning(f"No tracks found for {decade}s") @@ -336,7 +347,8 @@ class PersonalizedPlaylistsService: album_cover_url, duration_ms, popularity, - artist_genres + artist_genres, + track_data_json FROM discovery_pool WHERE artist_genres IS NOT NULL """) @@ -386,6 +398,12 @@ class PersonalizedPlaylistsService: 'duration_ms': row[5], 'popularity': row[6] } + # Parse track_data_json if available + if row[8]: # track_data_json column + try: + track_dict['track_data_json'] = json.loads(row[8]) + except: + pass matching_tracks.append(track_dict) except Exception as e: logger.debug(f"Error parsing genres for track: {e}") @@ -470,7 +488,8 @@ class PersonalizedPlaylistsService: album_name, album_cover_url, duration_ms, - popularity + popularity, + track_data_json FROM discovery_pool WHERE popularity >= 60 ORDER BY popularity DESC, RANDOM() @@ -478,7 +497,17 @@ class PersonalizedPlaylistsService: """, (limit * 3,)) # Get 3x more for diversity filtering rows = cursor.fetchall() - all_tracks = [dict(row) for row in rows] + all_tracks = [] + for row in rows: + track_dict = dict(row) + # Parse track_data_json if available + if track_dict.get('track_data_json'): + try: + import json + track_dict['track_data_json'] = json.loads(track_dict['track_data_json']) + except: + pass + all_tracks.append(track_dict) # Apply diversity constraint: max 2 tracks per album, max 3 per artist tracks_by_album = {} @@ -523,7 +552,8 @@ class PersonalizedPlaylistsService: album_name, album_cover_url, duration_ms, - popularity + popularity, + track_data_json FROM discovery_pool WHERE popularity < 40 ORDER BY RANDOM() @@ -531,7 +561,18 @@ class PersonalizedPlaylistsService: """, (limit,)) rows = cursor.fetchall() - return [dict(row) for row in rows] + tracks = [] + for row in rows: + track_dict = dict(row) + # Parse track_data_json if available + if track_dict.get('track_data_json'): + try: + import json + track_dict['track_data_json'] = json.loads(track_dict['track_data_json']) + except: + pass + tracks.append(track_dict) + return tracks except Exception as e: logger.error(f"Error getting hidden gems: {e}") @@ -555,14 +596,26 @@ class PersonalizedPlaylistsService: album_name, album_cover_url, duration_ms, - popularity + popularity, + track_data_json FROM discovery_pool ORDER BY RANDOM() LIMIT ? """, (limit,)) rows = cursor.fetchall() - return [dict(row) for row in rows] + tracks = [] + for row in rows: + track_dict = dict(row) + # Parse track_data_json if available + if track_dict.get('track_data_json'): + try: + import json + track_dict['track_data_json'] = json.loads(track_dict['track_data_json']) + except: + pass + tracks.append(track_dict) + return tracks except Exception as e: logger.error(f"Error getting discovery shuffle: {e}") @@ -728,7 +781,8 @@ class PersonalizedPlaylistsService: album_name, album_cover_url, duration_ms, - popularity + popularity, + track_data_json FROM discovery_pool WHERE artist_name LIKE ? OR track_name LIKE ? ORDER BY RANDOM() @@ -736,7 +790,18 @@ class PersonalizedPlaylistsService: """, (f'%{category}%', f'%{category}%', limit)) rows = cursor.fetchall() - return [dict(row) for row in rows] + tracks = [] + for row in rows: + track_dict = dict(row) + # Parse track_data_json if available + if track_dict.get('track_data_json'): + try: + import json + track_dict['track_data_json'] = json.loads(track_dict['track_data_json']) + except: + pass + tracks.append(track_dict) + return tracks except Exception as e: logger.error(f"Error getting discovery tracks by category: {e}") diff --git a/core/seasonal_discovery.py b/core/seasonal_discovery.py index fc6f32a0..57982d65 100644 --- a/core/seasonal_discovery.py +++ b/core/seasonal_discovery.py @@ -332,6 +332,15 @@ class SeasonalDiscoveryService: rows = cursor.fetchall() for row in rows: + import json + # Parse track_data_json if it's a string + track_data_json = row['track_data_json'] + if isinstance(track_data_json, str): + try: + track_data_json = json.loads(track_data_json) + except: + track_data_json = {} + seasonal_tracks.append({ 'spotify_track_id': row['spotify_track_id'], 'track_name': row['track_name'], @@ -340,7 +349,7 @@ class SeasonalDiscoveryService: 'album_cover_url': row['album_cover_url'], 'duration_ms': row['duration_ms'], 'popularity': row['popularity'], - 'track_data_json': row['track_data_json'] + 'track_data_json': track_data_json # Now parsed as dict }) return seasonal_tracks @@ -643,6 +652,19 @@ class SeasonalDiscoveryService: # Use track's actual artist, not album artist track_artist = track['artists'][0]['name'] if track.get('artists') else album['artist_name'] + # Enhance track object with full album data (including total_tracks) + enhanced_track = { + **track, + 'album': { + 'id': album_data['id'], + 'name': album_data.get('name', 'Unknown Album'), + 'images': album_data.get('images', []), + 'release_date': album_data.get('release_date', ''), + 'album_type': album_data.get('album_type', 'album'), + 'total_tracks': album_data.get('total_tracks', 0) + } + } + track_data = { 'spotify_track_id': track['id'], 'track_name': track['name'], @@ -650,7 +672,8 @@ class SeasonalDiscoveryService: 'album_name': album['album_name'], 'popularity': album.get('popularity', 50), 'album_cover_url': album.get('album_cover_url'), - 'duration_ms': track.get('duration_ms', 0) + 'duration_ms': track.get('duration_ms', 0), + 'track_data_json': enhanced_track # Add full track data with album info } all_tracks.append(track_data) diff --git a/core/watchlist_scanner.py b/core/watchlist_scanner.py index 3bb4ee04..e868fe9a 100644 --- a/core/watchlist_scanner.py +++ b/core/watchlist_scanner.py @@ -523,12 +523,14 @@ class WatchlistScanner: album_release_date = album.get('release_date', '') album_images = album.get('images', []) album_type = album.get('album_type', 'album') # 'album', 'single', or 'ep' + total_tracks = album.get('total_tracks', 0) else: album_name = album.name album_id = album.id album_release_date = album.release_date album_images = album.images if hasattr(album, 'images') else [] album_type = album.album_type if hasattr(album, 'album_type') else 'album' + total_tracks = album.total_tracks if hasattr(album, 'total_tracks') else 0 # Create Spotify track data structure spotify_track_data = { @@ -540,7 +542,8 @@ class WatchlistScanner: 'id': album_id, 'release_date': album_release_date, 'images': album_images, - 'album_type': album_type # Store album type for category filtering + 'album_type': album_type, # Store album type for category filtering + 'total_tracks': total_tracks # Store track count for accurate categorization }, 'duration_ms': track_duration, 'explicit': track_explicit, diff --git a/web_server.py b/web_server.py index 78f2d260..a2480ece 100644 --- a/web_server.py +++ b/web_server.py @@ -7691,7 +7691,7 @@ def _process_wishlist_automatically(): import json filtered_tracks = [] for track in wishlist_tracks: - # Extract album_type from spotify_data JSON (after sanitization) + # Extract track count from spotify_data JSON (after sanitization) spotify_data = track.get('spotify_data', {}) if isinstance(spotify_data, str): try: @@ -7700,14 +7700,27 @@ def _process_wishlist_automatically(): spotify_data = {} album_data = spotify_data.get('album', {}) + total_tracks = album_data.get('total_tracks') album_type = album_data.get('album_type', 'album').lower() - if current_cycle == 'singles': - if album_type in ['single', 'ep']: - filtered_tracks.append(track) - elif current_cycle == 'albums': - if album_type == 'album': - filtered_tracks.append(track) + # Categorize by track count if available, otherwise use album_type + # Single: 1 track, EP: 2-5 tracks, Album: 6+ tracks + is_single_or_ep = False + is_album = False + + if total_tracks is not None and total_tracks > 0: + # Use track count (most accurate) + is_single_or_ep = total_tracks < 6 + is_album = total_tracks >= 6 + else: + # Fall back to Spotify's album_type + is_single_or_ep = album_type in ['single', 'ep'] + is_album = album_type == 'album' + + if current_cycle == 'singles' and is_single_or_ep: + filtered_tracks.append(track) + elif current_cycle == 'albums' and is_album: + filtered_tracks.append(track) print(f"🔄 [Auto-Wishlist] Current cycle: {current_cycle}") print(f"📊 [Auto-Wishlist] Filtered {len(filtered_tracks)}/{len(wishlist_tracks)} tracks for '{current_cycle}' category") @@ -7911,22 +7924,30 @@ def get_wishlist_stats(): albums_count = 0 for track in raw_tracks: - # Extract album_type from spotify_data JSON + # Extract track count from spotify_data JSON spotify_data = track.get('spotify_data', {}) if isinstance(spotify_data, str): import json spotify_data = json.loads(spotify_data) album_data = spotify_data.get('album', {}) + total_tracks = album_data.get('total_tracks') album_type = album_data.get('album_type', 'album').lower() - if album_type in ['single', 'ep']: - singles_count += 1 - elif album_type == 'album': - albums_count += 1 + # Categorize by track count if available, otherwise use album_type + # Single: 1 track, EP: 2-5 tracks, Album: 6+ tracks + if total_tracks is not None and total_tracks > 0: + # Use track count (most accurate) + if total_tracks >= 6: + albums_count += 1 + else: + singles_count += 1 else: - # Default unknown types to albums - albums_count += 1 + # Fall back to Spotify's album_type + if album_type in ['single', 'ep']: + singles_count += 1 + else: + albums_count += 1 total_count = singles_count + albums_count @@ -8127,7 +8148,7 @@ def get_wishlist_tracks(): import json filtered_tracks = [] for track in sanitized_tracks: - # Extract album_type from spotify_data JSON (same as stats endpoint) + # Extract track count from spotify_data JSON (same as stats endpoint) spotify_data = track.get('spotify_data', {}) if isinstance(spotify_data, str): try: @@ -8136,16 +8157,27 @@ def get_wishlist_tracks(): spotify_data = {} album_data = spotify_data.get('album', {}) + total_tracks = album_data.get('total_tracks') album_type = album_data.get('album_type', 'album').lower() - if category == 'singles': - # Singles category includes 'single' and 'ep' - if album_type in ['single', 'ep']: - filtered_tracks.append(track) - elif category == 'albums': - # Albums category includes only 'album' - if album_type == 'album': - filtered_tracks.append(track) + # Categorize by track count if available, otherwise use album_type + # Single: 1 track, EP: 2-5 tracks, Album: 6+ tracks + is_single_or_ep = False + is_album = False + + if total_tracks is not None and total_tracks > 0: + # Use track count (most accurate) + is_single_or_ep = total_tracks < 6 + is_album = total_tracks >= 6 + else: + # Fall back to Spotify's album_type + is_single_or_ep = album_type in ['single', 'ep'] + is_album = album_type == 'album' + + if category == 'singles' and is_single_or_ep: + filtered_tracks.append(track) + elif category == 'albums' and is_album: + filtered_tracks.append(track) print(f"📊 Wishlist filter: {len(filtered_tracks)}/{len(sanitized_tracks)} tracks in '{category}' category") return jsonify({"tracks": filtered_tracks, "category": category}) @@ -15251,7 +15283,8 @@ def get_seasonal_playlist(season_key): album_name, album_cover_url, duration_ms, - popularity + popularity, + track_data_json FROM seasonal_tracks WHERE spotify_track_id = ? """, (track_id,)) @@ -15259,7 +15292,15 @@ def get_seasonal_playlist(season_key): result = cursor.fetchone() if result: - tracks.append(dict(result)) + track_dict = dict(result) + # Parse track_data_json if available + if track_dict.get('track_data_json'): + try: + import json + track_dict['track_data_json'] = json.loads(track_dict['track_data_json']) + except: + pass + tracks.append(track_dict) else: # Try discovery_pool as fallback cursor.execute(""" @@ -15270,14 +15311,23 @@ def get_seasonal_playlist(season_key): album_name, album_cover_url, duration_ms, - popularity + popularity, + track_data_json FROM discovery_pool WHERE spotify_track_id = ? """, (track_id,)) result = cursor.fetchone() if result: - tracks.append(dict(result)) + track_dict = dict(result) + # Parse track_data_json if available + if track_dict.get('track_data_json'): + try: + import json + track_dict['track_data_json'] = json.loads(track_dict['track_data_json']) + except: + pass + tracks.append(track_dict) config = SEASONAL_CONFIG[season_key]