| 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"), |
| } |
|
|
|
|
|
|
|
|