Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 116 additions & 0 deletions app/helper/lastfm_profile.py
Original file line number Diff line number Diff line change
Expand Up @@ -243,3 +243,119 @@ def get_artists_by_tag(tag, limit=10):
pass

return []


def get_user_playlists(token, limit=50):
"""
Get all playlists of the current user from Spotify.
"""
url = "https://api.spotify.com/v1/me/playlists"
params = {"limit": limit}
headers = {"Authorization": f"Bearer {token}"}

try:
response = requests.get(url, params=params, headers=headers, timeout=10)
if response.status_code == 200:
data = response.json()
return data.get("items", [])
except Exception:
pass

return []


def get_playlist_tracks(token, playlist_id, limit=50):
"""
Get tracks from a specific playlist.
"""
url = f"https://api.spotify.com/v1/playlists/{playlist_id}/tracks"
params = {"limit": limit, "fields": "items(track(name,artists,uri,album))"}
headers = {"Authorization": f"Bearer {token}"}

try:
response = requests.get(url, params=params, headers=headers, timeout=10)
if response.status_code == 200:
data = response.json()
# Extract track objects from the response
tracks = []
for item in data.get("items", []):
if item.get("track"):
tracks.append(item["track"])
return tracks
except Exception:
pass

return []


def generate_playlist_from_profile_and_artist(profile, seed_artist, token, variety=5, discovery=5, limit=20):
"""
Generate a playlist combining:
- Profile mood/tags from analyzed playlist
- Seed artist for similar artists
- Variety (how many different artists)
- Discovery (balance between profile tags and seed artist)
"""
from app.helper.recommendations import get_similar_artists_lastfm, search_artist_top_tracks_spotify

collected_tracks = []
seen_uris = set()

# Calculate how many tracks from profile tags vs seed artist
# Discovery: 1 = mostly profile, 10 = mostly seed artist
profile_ratio = 1 - (discovery / 10)
profile_tracks_target = max(1, int(limit * profile_ratio))
seed_tracks_target = limit - profile_tracks_target

# Part 1: Get tracks based on profile tags
if profile and profile.get('top_tags'):
tags_to_use = profile['top_tags'][:variety]
tracks_per_tag = max(1, profile_tracks_target // len(tags_to_use)) + 1

for tag, count in tags_to_use:
if len(collected_tracks) >= profile_tracks_target:
break

tag_artists = get_artists_by_tag(tag, limit=3)
for artist in tag_artists:
if len(collected_tracks) >= profile_tracks_target:
break

tracks = search_artist_top_tracks_spotify(artist, token, limit=2)
for track in tracks:
if track and track.get('uri') not in seen_uris:
collected_tracks.append(track)
seen_uris.add(track['uri'])
if len(collected_tracks) >= profile_tracks_target:
break

# Part 2: Get tracks based on seed artist and similar artists
if seed_artist:
# Get seed artist's top tracks
seed_tracks = search_artist_top_tracks_spotify(seed_artist, token, limit=seed_tracks_target // 2 + 1)
for track in seed_tracks:
if track and track.get('uri') not in seen_uris:
collected_tracks.append(track)
seen_uris.add(track['uri'])
if len(collected_tracks) >= limit:
break

# Get similar artists
similar_artists = get_similar_artists_lastfm(seed_artist, limit=variety)
for artist in similar_artists:
if len(collected_tracks) >= limit:
break

tracks = search_artist_top_tracks_spotify(artist, token, limit=2)
for track in tracks:
if track and track.get('uri') not in seen_uris:
collected_tracks.append(track)
seen_uris.add(track['uri'])
if len(collected_tracks) >= limit:
break

# Shuffle to mix profile and seed artist tracks
import random
random.shuffle(collected_tracks)

return collected_tracks[:limit]
131 changes: 131 additions & 0 deletions app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -245,3 +245,134 @@ def generate_from_profile():
def playlist_analyzer():
"""Redirect old analyzer route to new profile page."""
return redirect(url_for('music_profile'))


@app.route('/api/playlists', methods=['GET'])
def get_user_playlists():
"""
Get all playlists of the current user.
"""
sess_access_token = session.get("access_token")
sess_token_create_time = session.get('token_create')

if not sess_access_token or not sess_token_create_time:
return jsonify({"error": "Not authenticated"}), 403

if (time.time() - sess_token_create_time) > 3500:
return jsonify({"error": "Token expired"}), 400

playlists = lastfm_profile.get_user_playlists(sess_access_token, limit=50)

# Return simplified playlist data
playlist_data = []
for pl in playlists:
playlist_data.append({
'id': pl.get('id'),
'name': pl.get('name'),
'tracks_total': pl.get('tracks', {}).get('total', 0),
'image': pl.get('images', [{}])[0].get('url') if pl.get('images') else None
})

return jsonify({"playlists": playlist_data})


@app.route('/api/playlist/<playlist_id>/analyze', methods=['GET'])
def analyze_playlist(playlist_id):
"""
Analyze a specific playlist and return its mood profile.
"""
sess_access_token = session.get("access_token")
sess_token_create_time = session.get('token_create')

if not sess_access_token or not sess_token_create_time:
return jsonify({"error": "Not authenticated"}), 403

if (time.time() - sess_token_create_time) > 3500:
return jsonify({"error": "Token expired"}), 400

# Get tracks from playlist
tracks = lastfm_profile.get_playlist_tracks(sess_access_token, playlist_id, limit=30)

if not tracks:
return jsonify({"error": "Could not fetch playlist tracks"}), 400

# Analyze tracks
profile = lastfm_profile.analyze_tracks_profile(tracks)

# Store in session for later use
session['playlist_profile'] = {
'playlist_id': playlist_id,
'scores': profile['scores'],
'top_tags': profile['top_tags'][:10],
'genres': profile['genres']
}

return jsonify({
'scores': profile['scores'],
'top_tags': profile['top_tags'][:15],
'genres': profile['genres'],
'track_count': profile['track_count']
})


@app.route('/generate-from-playlist', methods=['POST'])
def generate_from_playlist():
"""
Generate a new playlist based on:
- Mood profile from selected playlist
- Seed artist
- Variety and Discovery settings
"""
sess_access_token = session.get("access_token")
sess_token_create_time = session.get('token_create')

if not sess_access_token or not sess_token_create_time:
return jsonify({"error": "Not authenticated"}), 403

if (time.time() - sess_token_create_time) > 3500:
return jsonify({"error": "Token expired"}), 400

data = request.get_json()
playlist_id = data.get('playlist_id')
seed_artist = data.get('seed_artist', '').strip()
variety = int(data.get('variety', 5))
discovery = int(data.get('discovery', 5))
track_count = int(data.get('track_count', 15))

# Get or create profile from playlist
playlist_profile = session.get('playlist_profile')

# If no profile or different playlist, analyze it
if not playlist_profile or playlist_profile.get('playlist_id') != playlist_id:
if playlist_id:
tracks = lastfm_profile.get_playlist_tracks(sess_access_token, playlist_id, limit=30)
if tracks:
profile = lastfm_profile.analyze_tracks_profile(tracks)
playlist_profile = {
'playlist_id': playlist_id,
'scores': profile['scores'],
'top_tags': profile['top_tags'][:10],
'genres': profile['genres']
}
session['playlist_profile'] = playlist_profile

if not playlist_profile:
return jsonify({"error": "No playlist profile available"}), 400

# Generate tracks
tracks = lastfm_profile.generate_playlist_from_profile_and_artist(
playlist_profile,
seed_artist,
sess_access_token,
variety=variety,
discovery=discovery,
limit=track_count
)

uris = [t['uri'] for t in tracks if t]
return jsonify({
"songs": tracks,
"uris": json.dumps(uris),
"profile_tags": playlist_profile['top_tags'][:5],
"seed_artist": seed_artist
})
Loading