Vicoka / app /spotify_client.py
nexusbert's picture
Add optional SPOTIFY_MARKET configuration and update track recommendation logic to prioritize Nigerian genres
6eb40c1
import random
from typing import Any, Dict, Optional
import spotipy
from spotipy.oauth2 import SpotifyClientCredentials
from .config import get_settings
from .spotify_http import spotify_get
settings = get_settings()
def _get_spotify_client() -> spotipy.Spotify:
if not settings.spotify_client_id or not settings.spotify_client_secret:
raise RuntimeError(
"Spotify credentials not configured. "
"Set SPOTIFY_CLIENT_ID and SPOTIFY_CLIENT_SECRET."
)
auth_manager = SpotifyClientCredentials(
client_id=settings.spotify_client_id,
client_secret=settings.spotify_client_secret,
)
return spotipy.Spotify(auth_manager=auth_manager)
def get_app_access_token() -> str:
"""Get an app-only access token (Client Credentials flow)."""
sp = _get_spotify_client()
token_info = sp.auth_manager.get_access_token(as_dict=True)
return token_info["access_token"]
def get_available_genre_seeds(access_token: Optional[str] = None) -> list[str]:
"""Fetch available genre seed values for Spotify Recommendations API."""
token = access_token or get_app_access_token()
data = spotify_get("https://api.spotify.com/v1/recommendations/available-genre-seeds", token)
return data.get("genres", [])
def get_track_via_bearer(
track_id: str,
access_token: Optional[str] = None,
*,
market: Optional[str] = None,
) -> Dict[str, Any]:
"""Fetch a track using the raw Web API with a bearer token."""
token = access_token or get_app_access_token()
params = {"market": market} if market else None
data = spotify_get(f"https://api.spotify.com/v1/tracks/{track_id}", token, params=params)
return {
"id": data.get("id"),
"name": data.get("name"),
"artists": [a.get("name") for a in data.get("artists", [])],
"external_url": (data.get("external_urls") or {}).get("spotify"),
"preview_url": data.get("preview_url"),
"album": (data.get("album") or {}).get("name"),
"is_playable": data.get("is_playable"),
"linked_from": data.get("linked_from"),
"restrictions": data.get("restrictions"),
}
def emotion_to_spotify_params(
emotion: str, source: str = "text"
) -> Dict[str, Any]:
"""Map emotion to Spotify search/recommendation parameters."""
emotion = emotion.lower()
params: Dict[str, Any] = {
"seed_genres": ["pop"],
"target_valence": 0.5,
"target_energy": 0.5,
}
if emotion in {"joy", "happy", "happiness"}:
params.update(seed_genres=["pop", "dance", "electronic"], target_valence=0.9, target_energy=0.8)
elif emotion in {"sad", "sadness"}:
params.update(seed_genres=["acoustic", "blues", "folk"], target_valence=0.2, target_energy=0.4)
elif emotion in {"anger", "angry"}:
params.update(seed_genres=["rock", "metal", "punk"], target_valence=0.3, target_energy=0.9)
elif emotion in {"fear"}:
params.update(seed_genres=["ambient", "classical"], target_valence=0.3, target_energy=0.3)
elif emotion in {"surprise"}:
params.update(seed_genres=["electronic", "indie", "alternative"], target_valence=0.7, target_energy=0.7)
elif emotion in {"neutral"}:
params.update(seed_genres=["pop", "indie"], target_valence=0.5, target_energy=0.5)
elif emotion in {"disgust"}:
params.update(seed_genres=["alternative", "rock"], target_valence=0.4, target_energy=0.6)
return params
def recommend_track_for_emotion(emotion: str, source: str = "text") -> Dict[str, Any]:
sp = _get_spotify_client()
params = emotion_to_spotify_params(emotion, source=source)
try:
recs = sp.recommendations(limit=1, **params)
tracks = recs.get("tracks", [])
if not tracks:
return {}
track = tracks[0]
return {
"id": track["id"],
"name": track["name"],
"artists": [a["name"] for a in track.get("artists", [])],
"preview_url": track.get("preview_url"),
"external_url": track.get("external_urls", {}).get("spotify"),
}
except Exception:
pass
token = get_app_access_token()
market = getattr(settings, "spotify_market", "NG") or "NG"
emotion_norm = (emotion or "").lower()
seed_genres = params.get("seed_genres") or ["afrobeats"]
genre = seed_genres[0] if isinstance(seed_genres, list) and seed_genres else "afrobeats"
queries = [
f"genre:afrobeats",
f"genre:afropop",
f"genre:afrobeat",
f"genre:world-music",
f"naija {emotion_norm}",
f"nigerian {emotion_norm}",
f"{emotion_norm} afrobeats",
f"genre:{genre}",
]
items: list[dict] = []
for q in queries:
data = spotify_get(
"https://api.spotify.com/v1/search",
token,
params={"q": q, "type": "track", "limit": 10, "market": market},
)
batch = (data.get("tracks") or {}).get("items") or []
if batch:
items.extend(batch)
if len(items) >= 10:
break
if not items:
return {}
track = random.choice(items)
return {
"id": track.get("id"),
"name": track.get("name"),
"artists": [a.get("name") for a in track.get("artists", [])],
"preview_url": track.get("preview_url"),
"external_url": (track.get("external_urls") or {}).get("spotify"),
}