diff --git a/core/tidal_client.py b/core/tidal_client.py index 254af6eb..89bee525 100644 --- a/core/tidal_client.py +++ b/core/tidal_client.py @@ -1,3 +1,4 @@ +import os import requests import time import re @@ -119,7 +120,8 @@ class TidalClient: self.alt_base_url = "https://api.tidal.com/v1" # Alternative API base self.auth_url = "https://login.tidal.com/authorize" self.token_url = "https://auth.tidal.com/v1/oauth2/token" - self.redirect_uri = "http://127.0.0.1:8889/tidal/callback" # Default, will be updated from config + _tidal_port = int(os.environ.get('SOULSYNC_TIDAL_CALLBACK_PORT', 8889)) + self.redirect_uri = f"http://127.0.0.1:{_tidal_port}/tidal/callback" # Default, will be updated from config self.session = requests.Session() self.auth_server = None self.auth_code = None @@ -347,7 +349,7 @@ class TidalClient: pass # Suppress server logs try: - port = 8889 + port = int(os.environ.get('SOULSYNC_TIDAL_CALLBACK_PORT', 8889)) self.auth_server = HTTPServer(('localhost', port), CallbackHandler) server_thread = threading.Thread(target=self.auth_server.serve_forever) server_thread.daemon = True diff --git a/docker-compose.yml b/docker-compose.yml index 1d50375e..a757456b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -20,10 +20,15 @@ services: - SOULSYNC_CONFIG_PATH=/app/config/config.json # Set timezone (change to your timezone) - TZ=America/New_York + # OAuth callback ports. If 8888/8889 conflict with another container (e.g. Gluetun), + # change the port numbers below AND update the matching port mappings in the ports section. + # Then update the redirect URI in SoulSync → Settings → Connections to match. + - SOULSYNC_SPOTIFY_CALLBACK_PORT=8888 + - SOULSYNC_TIDAL_CALLBACK_PORT=8889 ports: - - "8008:8008" # Main web app - - "8888:8888" # Spotify OAuth callback - - "8889:8889" # Tidal OAuth callback + - "8008:8008" # Main web app + - "8888:8888" # Spotify OAuth callback — keep in sync with SOULSYNC_SPOTIFY_CALLBACK_PORT above + - "8889:8889" # Tidal OAuth callback — keep in sync with SOULSYNC_TIDAL_CALLBACK_PORT above volumes: # Persistent data volumes - ./config:/app/config diff --git a/web_server.py b/web_server.py index ea32ff17..14a48612 100644 --- a/web_server.py +++ b/web_server.py @@ -2887,25 +2887,25 @@ class WebUIDownloadMonitor: sources_str = f' (tried {len(tried_sources)} source{"s" if len(tried_sources) != 1 else ""})' if tried_sources else '' print(f"Task failed after 3 error retry attempts") task['status'] = 'failed' - # Tidal-specific error: check if this was a quality issue. - # task['username'] is popped on error-retry (line ~2866) so we can't rely on it; - # used_sources keys are formatted as "{username}_{filename}", so startswith is exact. - is_tidal = any(s.startswith('tidal_') for s in tried_sources) - if is_tidal: - tidal_quality = config_manager.get('tidal_download.quality', 'lossless') - allow_fb = config_manager.get('tidal_download.allow_fallback', True) - if tidal_quality == 'hires' and not allow_fb: - task['error_message'] = ( - f'Tidal download failed for "{track_label}" — HiRes quality is unavailable for this track ' - f'on your account or in your region. Enable "Quality Fallback" in Tidal settings to fall back to Lossless.' - ) - else: - task['error_message'] = ( - f'Tidal download failed for "{track_label}"{sources_str} — ' - f'check Tidal authentication and quality settings.' - ) - else: - task['error_message'] = f'Soulseek transfer errored 3 times for "{track_label}"{sources_str} — all sources failed or became unavailable' + # Tidal-specific error: check if this was a quality issue. + # task['username'] is popped on error-retry (line ~2866) so we can't rely on it; + # used_sources keys are formatted as "{username}_{filename}", so startswith is exact. + is_tidal = any(s.startswith('tidal_') for s in tried_sources) + if is_tidal: + tidal_quality = config_manager.get('tidal_download.quality', 'lossless') + allow_fb = config_manager.get('tidal_download.allow_fallback', True) + if tidal_quality == 'hires' and not allow_fb: + task['error_message'] = ( + f'Tidal download failed for "{track_label}" — HiRes quality is unavailable for this track ' + f'on your account or in your region. Enable "Quality Fallback" in Tidal settings to fall back to Lossless.' + ) + else: + task['error_message'] = ( + f'Tidal download failed for "{track_label}"{sources_str} — ' + f'check Tidal authentication and quality settings.' + ) + else: + task['error_message'] = f'Soulseek transfer errored 3 times for "{track_label}"{sources_str} — all sources failed or became unavailable' # CRITICAL: Notify batch manager so track is added to permanently_failed_tracks batch_id = task.get('batch_id') @@ -49711,15 +49711,16 @@ def start_oauth_callback_servers(): # Start Spotify callback server def run_spotify_server(): + spotify_port = int(os.environ.get('SOULSYNC_SPOTIFY_CALLBACK_PORT', 8888)) try: - bind_addr = ('0.0.0.0', 8888) + bind_addr = ('0.0.0.0', spotify_port) spotify_server = HTTPServer(bind_addr, SpotifyCallbackHandler) _oauth_logger.info(f"Spotify OAuth callback server listening on {bind_addr[0]}:{bind_addr[1]}") print(f"Started Spotify OAuth callback server on {bind_addr[0]}:{bind_addr[1]}") spotify_server.serve_forever() except OSError as e: - _oauth_logger.error(f"Failed to start Spotify callback server on port 8888: {e} — port may already be in use") - print(f"Failed to start Spotify callback server on port 8888: {e}") + _oauth_logger.error(f"Failed to start Spotify callback server on port {spotify_port}: {e} — port may already be in use") + print(f"Failed to start Spotify callback server on port {spotify_port}: {e}") except Exception as e: _oauth_logger.error(f"Failed to start Spotify callback server: {e}") print(f"Failed to start Spotify callback server: {e}") @@ -49789,9 +49790,10 @@ def start_oauth_callback_servers(): def run_tidal_server(): try: - tidal_server = HTTPServer(('0.0.0.0', 8889), TidalCallbackHandler) - print("Started Tidal OAuth callback server on port 8889") - print(f"Tidal server listening on all interfaces, port 8889") + tidal_port = int(os.environ.get('SOULSYNC_TIDAL_CALLBACK_PORT', 8889)) + tidal_server = HTTPServer(('0.0.0.0', tidal_port), TidalCallbackHandler) + print(f"Started Tidal OAuth callback server on port {tidal_port}") + print(f"Tidal server listening on all interfaces, port {tidal_port}") tidal_server.serve_forever() except Exception as e: print(f"Failed to start Tidal callback server: {e}")