mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-04-10 21:03:01 +00:00
add music display with YTM and Spotify
This commit is contained in:
132
src/spotify_client.py
Normal file
132
src/spotify_client.py
Normal file
@@ -0,0 +1,132 @@
|
||||
import spotipy
|
||||
from spotipy.oauth2 import SpotifyOAuth
|
||||
import logging
|
||||
import json
|
||||
import os
|
||||
|
||||
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
||||
|
||||
# Define paths relative to this file's location
|
||||
CONFIG_DIR = os.path.join(os.path.dirname(__file__), '..', 'config')
|
||||
SECRETS_PATH = os.path.join(CONFIG_DIR, 'config_secrets.json')
|
||||
|
||||
class SpotifyClient:
|
||||
def __init__(self):
|
||||
self.client_id = None
|
||||
self.client_secret = None
|
||||
self.redirect_uri = None
|
||||
self.scope = "user-read-currently-playing user-read-playback-state"
|
||||
self.sp = None
|
||||
self.load_credentials()
|
||||
if self.client_id and self.client_secret and self.redirect_uri:
|
||||
self._authenticate()
|
||||
else:
|
||||
logging.warning("Spotify credentials not loaded. Cannot authenticate.")
|
||||
|
||||
|
||||
def load_credentials(self):
|
||||
if not os.path.exists(SECRETS_PATH):
|
||||
logging.error(f"Secrets file not found at {SECRETS_PATH}")
|
||||
return
|
||||
|
||||
try:
|
||||
with open(SECRETS_PATH, 'r') as f:
|
||||
secrets = json.load(f)
|
||||
music_secrets = secrets.get("music", {})
|
||||
self.client_id = music_secrets.get("SPOTIFY_CLIENT_ID")
|
||||
self.client_secret = music_secrets.get("SPOTIFY_CLIENT_SECRET")
|
||||
self.redirect_uri = music_secrets.get("SPOTIFY_REDIRECT_URI")
|
||||
if not all([self.client_id, self.client_secret, self.redirect_uri]):
|
||||
logging.warning("One or more Spotify credentials missing in config_secrets.json under the 'music' key.")
|
||||
except json.JSONDecodeError:
|
||||
logging.error(f"Error decoding JSON from {SECRETS_PATH}")
|
||||
except Exception as e:
|
||||
logging.error(f"Error loading Spotify credentials: {e}")
|
||||
|
||||
def _authenticate(self):
|
||||
"""Handles the OAuth authentication flow."""
|
||||
try:
|
||||
# Spotipy handles token caching in .cache file by default
|
||||
self.sp = spotipy.Spotify(auth_manager=SpotifyOAuth(
|
||||
client_id=self.client_id,
|
||||
client_secret=self.client_secret,
|
||||
redirect_uri=self.redirect_uri,
|
||||
scope=self.scope,
|
||||
open_browser=False # Important for headless environments
|
||||
))
|
||||
# Try making a call to ensure authentication is working or trigger refresh
|
||||
self.sp.current_user()
|
||||
logging.info("Spotify authenticated successfully.")
|
||||
except Exception as e:
|
||||
logging.error(f"Spotify authentication failed: {e}")
|
||||
self.sp = None # Ensure sp is None if auth fails
|
||||
|
||||
def is_authenticated(self):
|
||||
"""Checks if the client is authenticated."""
|
||||
# Check if sp object exists and try a lightweight API call
|
||||
if not self.sp:
|
||||
return False
|
||||
try:
|
||||
# A simple call to verify token validity
|
||||
self.sp.current_user()
|
||||
return True
|
||||
except Exception as e:
|
||||
# Log specific auth errors if needed
|
||||
logging.warning(f"Spotify token validation failed: {e}")
|
||||
return False
|
||||
|
||||
def get_auth_url(self):
|
||||
"""Gets the authorization URL for the user."""
|
||||
# Create a temporary auth manager just to get the URL
|
||||
try:
|
||||
auth_manager = SpotifyOAuth(
|
||||
client_id=self.client_id,
|
||||
client_secret=self.client_secret,
|
||||
redirect_uri=self.redirect_uri,
|
||||
scope=self.scope,
|
||||
open_browser=False
|
||||
)
|
||||
return auth_manager.get_authorize_url()
|
||||
except Exception as e:
|
||||
logging.error(f"Could not get Spotify auth URL: {e}")
|
||||
return None
|
||||
|
||||
def get_current_track(self):
|
||||
"""Fetches the currently playing track from Spotify."""
|
||||
if not self.is_authenticated():
|
||||
logging.warning("Spotify not authenticated. Cannot fetch track.")
|
||||
# Maybe try re-authenticating?
|
||||
self._authenticate()
|
||||
if not self.is_authenticated():
|
||||
return None
|
||||
|
||||
try:
|
||||
track_info = self.sp.current_playback()
|
||||
if track_info and track_info['item']:
|
||||
# Simplify structure slightly if needed, or return raw
|
||||
return track_info
|
||||
else:
|
||||
return None # Nothing playing or unavailable
|
||||
except Exception as e:
|
||||
logging.error(f"Error fetching current track from Spotify: {e}")
|
||||
# Check for specific errors like token expiration if spotipy doesn't handle it
|
||||
if "expired" in str(e).lower():
|
||||
logging.info("Spotify token might be expired, attempting refresh...")
|
||||
self._authenticate() # Try to refresh/re-authenticate
|
||||
return None
|
||||
|
||||
# Example Usage (for testing)
|
||||
# if __name__ == '__main__':
|
||||
# client = SpotifyClient()
|
||||
# if client.is_authenticated():
|
||||
# track = client.get_current_track()
|
||||
# if track:
|
||||
# print(json.dumps(track, indent=2))
|
||||
# else:
|
||||
# print("No track currently playing or error fetching.")
|
||||
# else:
|
||||
# auth_url = client.get_auth_url()
|
||||
# if auth_url:
|
||||
# print(f"Please authorize here: {auth_url}")
|
||||
# else:
|
||||
# print("Could not authenticate or get auth URL. Check credentials and config.")
|
||||
Reference in New Issue
Block a user