Implement retry functionality, with a sleep to help with ratelimiting

This commit is contained in:
EffakT
2025-05-27 08:42:48 +12:00
committed by GitHub
parent ffc501a48d
commit 8aec18a580
+62 -59
View File
@@ -210,90 +210,93 @@ def get_rating_from_popularity(popularity):
def process_track(track_id, artist_name, album, track_name): def process_track(track_id, artist_name, album, track_name):
def search_spotify(query):
# Declare global variables
global FOUND_AND_UPDATED, UNMATCHED_TRACKS, NOT_FOUND, TOTAL_TRACKS
def search_spotify(query, max_retries=3):
spotify_url = f"https://api.spotify.com/v1/search?q={query}&type=track&limit=1" spotify_url = f"https://api.spotify.com/v1/search?q={query}&type=track&limit=1"
headers = {"Authorization": f"Bearer {SPOTIFY_TOKEN}"} headers = {"Authorization": f"Bearer {SPOTIFY_TOKEN}"}
try: time.sleep(1)
response = requests.get(spotify_url, headers=headers)
except requests.exceptions.ConnectionError:
logging.error(f"{LIGHT_RED}Spotify Error: Unable to reach server.{RESET}")
sys.exit(1)
if response.status_code != 200: for attempt in range(max_retries):
if response.status_code == 429:
logging.error(
f"{LIGHT_RED}Spotify Error {response.status_code}: Retry after {BOLD}{response.headers.get('Retry-After', 'some time')}s{RESET}"
)
else:
logging.error(
f"{LIGHT_RED}Spotify Error {response.status_code}: {response.text}{RESET}"
)
sys.exit(1)
try: try:
response = requests.get(spotify_url, headers=headers, timeout=10)
# Handle rate limiting
if response.status_code == 429:
retry_after = int(response.headers.get('Retry-After', 5))
logging.warning(f"Rate limited. Retrying after {retry_after} seconds...")
time.sleep(retry_after)
continue
# Handle server errors with retry
if response.status_code >= 500:
wait_time = (attempt + 1) * 2 # Exponential backoff factor
logging.warning(f"Spotify server error {response.status_code}. Attempt {attempt + 1}/{max_retries}. Waiting {wait_time}s...")
time.sleep(wait_time)
continue
response.raise_for_status()
return response.json() return response.json()
except ValueError as e:
logging.error( except (requests.exceptions.ConnectionError, requests.exceptions.Timeout) as e:
f"{LIGHT_RED}Spotify Error: Error decoding JSON from Spotify API: {e}{RESET}" wait_time = (attempt + 1) * 2
) logging.warning(f"Connection error: {e}. Attempt {attempt + 1}/{max_retries}. Waiting {wait_time}s...")
sys.exit(1) time.sleep(wait_time)
continue
except requests.exceptions.RequestException as e:
logging.error(f"Request failed: {e}")
break
# If we get here, all retries failed
logging.error(f"Failed after {max_retries} attempts for query: {query}")
return None
def remove_parentheses_content(s): def remove_parentheses_content(s):
"""Remove content inside parentheses from a string."""
return re.sub(r"\s*\(.*?\)\s*", " ", s).strip() return re.sub(r"\s*\(.*?\)\s*", " ", s).strip()
encoded_track_name = url_encode(track_name) search_attempts = [
encoded_artist_name = url_encode(artist_name) # Primary attempt with all info
encoded_album = url_encode(album) lambda: f"{url_encode(track_name)}%20artist:{url_encode(artist_name)}%20album:{url_encode(album)}",
# Primary Search (with album) # Secondary attempt without album
spotify_data = search_spotify( lambda: f"{url_encode(remove_parentheses_content(track_name))}%20artist:{url_encode(artist_name)}",
f"{encoded_track_name}%20artist:{encoded_artist_name}%20album:{encoded_album}"
)
found_track = len(spotify_data.get("tracks", {}).get("items", [])) > 0 # Tertiary attempt with modified track name
lambda: f"{url_encode(track_name.replace('Part', 'Pt.'))}%20artist:{url_encode(artist_name)}"
]
if not found_track: spotify_data = None
# Secondary Search (without album and parentheses content) for attempt in search_attempts:
sanitized_track_name = url_encode(remove_parentheses_content(track_name)) spotify_data = search_spotify(attempt())
spotify_data = search_spotify( if spotify_data and spotify_data.get("tracks", {}).get("items"):
f"{sanitized_track_name}%20artist:{encoded_artist_name}" break
)
found_track = len(spotify_data.get("tracks", {}).get("items", [])) > 0
if not found_track: if spotify_data and spotify_data.get("tracks", {}).get("items"):
# Tertiary Search (replace 'Part' with 'Pt.') # Success case - process the track
modified_track_name = track_name.replace("Part", "Pt.") track = spotify_data["tracks"]["items"][0]
encoded_modified_track_name = url_encode(modified_track_name) popularity = track.get("popularity", 0)
spotify_data = search_spotify(
f"{encoded_modified_track_name}%20artist:{encoded_artist_name}"
)
found_track = len(spotify_data.get("tracks", {}).get("items", []))
if found_track:
popularity = spotify_data["tracks"]["items"][0].get("popularity", 0)
rating = get_rating_from_popularity(popularity) rating = get_rating_from_popularity(popularity)
popularity_str = f"{popularity} " if 0 <= popularity <= 9 else str(popularity) popularity_str = f"{popularity} " if 0 <= popularity <= 9 else str(popularity)
message = f" p:{LIGHT_CYAN}{popularity_str}{RESET} → r:{LIGHT_BLUE}{rating}{RESET} | {LIGHT_GREEN}{track_name}{RESET}"
logging.info(message) logging.info(f" p:{LIGHT_CYAN}{popularity_str}{RESET} → r:{LIGHT_BLUE}{rating}{RESET} | {LIGHT_GREEN}{track_name}{RESET}")
if PREVIEW != 1: if PREVIEW != 1:
try:
nav_url = f"{NAV_BASE_URL}/rest/setRating?u={NAV_USER}&p=enc:{HEX_ENCODED_PASS}&v=1.12.0&c=myapp&id={track_id}&rating={rating}" nav_url = f"{NAV_BASE_URL}/rest/setRating?u={NAV_USER}&p=enc:{HEX_ENCODED_PASS}&v=1.12.0&c=myapp&id={track_id}&rating={rating}"
requests.get(nav_url) requests.get(nav_url, timeout=5)
global FOUND_AND_UPDATED
FOUND_AND_UPDATED += 1 FOUND_AND_UPDATED += 1
except requests.exceptions.RequestException as e:
logging.error(f"Failed to update rating in Navidrome: {e}")
else: else:
logging.info( logging.info(f" p:{LIGHT_RED}??{RESET} → r:{LIGHT_BLUE}0{RESET} | {LIGHT_RED}(not found) {track_name}{RESET}")
f" p:{LIGHT_RED}??{RESET} → r:{LIGHT_BLUE}0{RESET} | {LIGHT_RED}(not found) {track_name}{RESET}"
)
global UNMATCHED_TRACKS
UNMATCHED_TRACKS.append(f"{artist_name} - {album} - {track_name}") UNMATCHED_TRACKS.append(f"{artist_name} - {album} - {track_name}")
global NOT_FOUND
NOT_FOUND += 1 NOT_FOUND += 1
global TOTAL_TRACKS
TOTAL_TRACKS += 1 TOTAL_TRACKS += 1
def process_album(album_id): def process_album(album_id):
nav_url = f"{NAV_BASE_URL}/rest/getAlbum?id={album_id}&u={NAV_USER}&p=enc:{HEX_ENCODED_PASS}&v=1.12.0&c=spotify_sync&f=json" nav_url = f"{NAV_BASE_URL}/rest/getAlbum?id={album_id}&u={NAV_USER}&p=enc:{HEX_ENCODED_PASS}&v=1.12.0&c=spotify_sync&f=json"
response = requests.get(nav_url).json() response = requests.get(nav_url).json()