From dbbd8889dfa14f29b1d86f9b4ffe7846d2ff97ce Mon Sep 17 00:00:00 2001 From: "architect.in.git" Date: Tue, 22 Apr 2025 21:51:06 -0600 Subject: [PATCH] i dont have time for this --- routes/utils/album.py | 24 +++++- routes/utils/artist.py | 14 ++- routes/utils/celery_queue_manager.py | 16 ++-- routes/utils/celery_tasks.py | 15 ++-- routes/utils/playlist.py | 24 +++++- routes/utils/track.py | 24 +++++- static/js/queue.js | 123 ++++++++++++++++++++++++++- templates/config.html | 4 +- 8 files changed, 208 insertions(+), 36 deletions(-) diff --git a/routes/utils/album.py b/routes/utils/album.py index bb8ba90..2a13a43 100755 --- a/routes/utils/album.py +++ b/routes/utils/album.py @@ -6,7 +6,6 @@ from deezspot.deezloader import DeeLogin from pathlib import Path def download_album( - service, url, main, fallback=None, @@ -22,8 +21,24 @@ def download_album( progress_callback=None ): try: - # DEBUG: Print parameters - print(f"DEBUG: album.py received - service={service}, main={main}, fallback={fallback}") + # Detect URL source (Spotify or Deezer) from URL + is_spotify_url = 'open.spotify.com' in url.lower() + is_deezer_url = 'deezer.com' in url.lower() + + # Determine service exclusively from URL + if is_spotify_url: + service = 'spotify' + elif is_deezer_url: + service = 'deezer' + else: + # If URL can't be detected, raise an error + error_msg = "Invalid URL: Must be from open.spotify.com or deezer.com" + print(f"ERROR: {error_msg}") + raise ValueError(error_msg) + + print(f"DEBUG: album.py - URL detection: is_spotify_url={is_spotify_url}, is_deezer_url={is_deezer_url}") + print(f"DEBUG: album.py - Service determined from URL: {service}") + print(f"DEBUG: album.py - Credentials: main={main}, fallback={fallback}") # Load Spotify client credentials if available spotify_client_id = None @@ -49,6 +64,8 @@ def download_album( except Exception as e: print(f"Error loading Spotify search credentials: {e}") + # For Spotify URLs: check if fallback is enabled, if so use the fallback logic, + # otherwise download directly from Spotify if service == 'spotify': if fallback: if quality is None: @@ -186,6 +203,7 @@ def download_album( max_retries=max_retries ) print(f"DEBUG: Album download completed successfully using Spotify main") + # For Deezer URLs: download directly from Deezer elif service == 'deezer': if quality is None: quality = 'FLAC' diff --git a/routes/utils/artist.py b/routes/utils/artist.py index 2074e65..470fd1a 100644 --- a/routes/utils/artist.py +++ b/routes/utils/artist.py @@ -87,6 +87,16 @@ def download_artist_albums(url, album_type="album,single,compilation", request_a logger.info(f"Fetching artist info for ID: {artist_id}") + # Detect URL source (only Spotify is supported for artists) + is_spotify_url = 'open.spotify.com' in url.lower() + is_deezer_url = 'deezer.com' in url.lower() + + # Artist functionality only works with Spotify URLs currently + if not is_spotify_url: + error_msg = "Invalid URL: Artist functionality only supports open.spotify.com URLs" + logger.error(error_msg) + raise ValueError(error_msg) + # Get artist info with albums artist_data = get_spotify_info(artist_id, "artist") @@ -152,8 +162,7 @@ def download_artist_albums(url, album_type="album,single,compilation", request_a "name": album_name, "artist": album_artist, "type": "album", - "service": "spotify", - # Add reference to parent artist request if needed + # URL source will be automatically detected in the download functions "parent_artist_url": url, "parent_request_type": "artist" } @@ -162,7 +171,6 @@ def download_artist_albums(url, album_type="album,single,compilation", request_a task_data = { "download_type": "album", "type": "album", # Type for the download task - "service": "spotify", # Default to Spotify since we're using Spotify API "url": album_url, # Important: use the album URL, not artist URL "retry_url": album_url, # Use album URL for retry logic, not artist URL "name": album_name, diff --git a/routes/utils/celery_queue_manager.py b/routes/utils/celery_queue_manager.py index 3317814..010665a 100644 --- a/routes/utils/celery_queue_manager.py +++ b/routes/utils/celery_queue_manager.py @@ -120,9 +120,6 @@ class CeleryDownloadQueueManager: # Extract original request or use empty dict original_request = task.get("orig_request", task.get("original_request", {})) - # Determine service (spotify or deezer) from config or request - service = original_request.get("service", config_params.get("service", "spotify")) - # Debug retry_url if present if "retry_url" in task: logger.debug(f"Task has retry_url: {task['retry_url']}") @@ -133,21 +130,20 @@ class CeleryDownloadQueueManager: "type": task.get("type", download_type), "name": task.get("name", ""), "artist": task.get("artist", ""), - "service": service, "url": task.get("url", ""), # Preserve retry_url if present "retry_url": task.get("retry_url", ""), - # Use config values but allow override from request - "main": original_request.get("main", - config_params['spotify'] if service == 'spotify' else config_params['deezer']), + # Use main account from config + "main": original_request.get("main", config_params['deezer']), + # Set fallback if enabled in config "fallback": original_request.get("fallback", - config_params['spotify'] if config_params['fallback'] and service == 'spotify' else None), + config_params['spotify'] if config_params['fallback'] else None), - "quality": original_request.get("quality", - config_params['spotifyQuality'] if service == 'spotify' else config_params['deezerQuality']), + # Use default quality settings + "quality": original_request.get("quality", config_params['deezerQuality']), "fall_quality": original_request.get("fall_quality", config_params['spotifyQuality']), diff --git a/routes/utils/celery_tasks.py b/routes/utils/celery_tasks.py index c5cf856..ef3416c 100644 --- a/routes/utils/celery_tasks.py +++ b/routes/utils/celery_tasks.py @@ -893,12 +893,11 @@ def download_track(self, **task_data): custom_track_format = task_data.get("custom_track_format", config_params.get("customTrackFormat", "%tracknum%. %music%")) pad_tracks = task_data.get("pad_tracks", config_params.get("tracknum_padding", True)) - # Execute the download + # Execute the download - service is now determined from URL download_track_func( - service=service, url=url, main=main, - fallback=fallback, + fallback=fallback if fallback_enabled else None, quality=quality, fall_quality=fall_quality, real_time=real_time, @@ -961,12 +960,11 @@ def download_album(self, **task_data): custom_track_format = task_data.get("custom_track_format", config_params.get("customTrackFormat", "%tracknum%. %music%")) pad_tracks = task_data.get("pad_tracks", config_params.get("tracknum_padding", True)) - # Execute the download + # Execute the download - service is now determined from URL download_album_func( - service=service, url=url, main=main, - fallback=fallback, + fallback=fallback if fallback_enabled else None, quality=quality, fall_quality=fall_quality, real_time=real_time, @@ -1034,12 +1032,11 @@ def download_playlist(self, **task_data): retry_delay_increase = task_data.get("retry_delay_increase", config_params.get("retry_delay_increase", 5)) max_retries = task_data.get("max_retries", config_params.get("maxRetries", 3)) - # Execute the download + # Execute the download - service is now determined from URL download_playlist_func( - service=service, url=url, main=main, - fallback=fallback, + fallback=fallback if fallback_enabled else None, quality=quality, fall_quality=fall_quality, real_time=real_time, diff --git a/routes/utils/playlist.py b/routes/utils/playlist.py index 45bb4ff..6830460 100755 --- a/routes/utils/playlist.py +++ b/routes/utils/playlist.py @@ -6,7 +6,6 @@ from deezspot.deezloader import DeeLogin from pathlib import Path def download_playlist( - service, url, main, fallback=None, @@ -22,8 +21,24 @@ def download_playlist( progress_callback=None, ): try: - # DEBUG: Print parameters - print(f"DEBUG: playlist.py received - service={service}, main={main}, fallback={fallback}") + # Detect URL source (Spotify or Deezer) from URL + is_spotify_url = 'open.spotify.com' in url.lower() + is_deezer_url = 'deezer.com' in url.lower() + + # Determine service exclusively from URL + if is_spotify_url: + service = 'spotify' + elif is_deezer_url: + service = 'deezer' + else: + # If URL can't be detected, raise an error + error_msg = "Invalid URL: Must be from open.spotify.com or deezer.com" + print(f"ERROR: {error_msg}") + raise ValueError(error_msg) + + print(f"DEBUG: playlist.py - URL detection: is_spotify_url={is_spotify_url}, is_deezer_url={is_deezer_url}") + print(f"DEBUG: playlist.py - Service determined from URL: {service}") + print(f"DEBUG: playlist.py - Credentials: main={main}, fallback={fallback}") # Load Spotify client credentials if available spotify_client_id = None @@ -49,6 +64,8 @@ def download_playlist( except Exception as e: print(f"Error loading Spotify search credentials: {e}") + # For Spotify URLs: check if fallback is enabled, if so use the fallback logic, + # otherwise download directly from Spotify if service == 'spotify': if fallback: if quality is None: @@ -181,6 +198,7 @@ def download_playlist( max_retries=max_retries ) print(f"DEBUG: Playlist download completed successfully using Spotify main") + # For Deezer URLs: download directly from Deezer elif service == 'deezer': if quality is None: quality = 'FLAC' diff --git a/routes/utils/track.py b/routes/utils/track.py index c163c33..ffa84aa 100755 --- a/routes/utils/track.py +++ b/routes/utils/track.py @@ -6,7 +6,6 @@ from deezspot.deezloader import DeeLogin from pathlib import Path def download_track( - service, url, main, fallback=None, @@ -22,8 +21,24 @@ def download_track( progress_callback=None ): try: - # DEBUG: Print parameters - print(f"DEBUG: track.py received - service={service}, main={main}, fallback={fallback}") + # Detect URL source (Spotify or Deezer) from URL + is_spotify_url = 'open.spotify.com' in url.lower() + is_deezer_url = 'deezer.com' in url.lower() + + # Determine service exclusively from URL + if is_spotify_url: + service = 'spotify' + elif is_deezer_url: + service = 'deezer' + else: + # If URL can't be detected, raise an error + error_msg = "Invalid URL: Must be from open.spotify.com or deezer.com" + print(f"ERROR: {error_msg}") + raise ValueError(error_msg) + + print(f"DEBUG: track.py - URL detection: is_spotify_url={is_spotify_url}, is_deezer_url={is_deezer_url}") + print(f"DEBUG: track.py - Service determined from URL: {service}") + print(f"DEBUG: track.py - Credentials: main={main}, fallback={fallback}") # Load Spotify client credentials if available spotify_client_id = None @@ -49,6 +64,8 @@ def download_track( except Exception as e: print(f"Error loading Spotify search credentials: {e}") + # For Spotify URLs: check if fallback is enabled, if so use the fallback logic, + # otherwise download directly from Spotify if service == 'spotify': if fallback: if quality is None: @@ -166,6 +183,7 @@ def download_track( retry_delay_increase=retry_delay_increase, max_retries=max_retries ) + # For Deezer URLs: download directly from Deezer elif service == 'deezer': if quality is None: quality = 'FLAC' diff --git a/static/js/queue.js b/static/js/queue.js index 44987fd..6539145 100644 --- a/static/js/queue.js +++ b/static/js/queue.js @@ -551,6 +551,9 @@ createQueueItem(item, type, prgFile, queueId) {
${defaultMessage}
+ + +
@@ -620,13 +623,80 @@ createQueueItem(item, type, prgFile, queueId) { break; case 'error': entry.element.classList.add('error'); + // Show detailed error information in the error-details container if available + if (entry.lastStatus && entry.element) { + const errorDetailsContainer = entry.element.querySelector(`#error-details-${entry.uniqueId}-${entry.prgFile}`); + if (errorDetailsContainer) { + // Format the error details + let errorDetailsHTML = ''; + + // Add error message + errorDetailsHTML += `
${entry.lastStatus.error || entry.lastStatus.message || 'Unknown error'}
`; + + // Add parent information if available + if (entry.lastStatus.parent) { + const parent = entry.lastStatus.parent; + let parentInfo = ''; + + if (parent.type === 'album') { + parentInfo = `
From album: "${parent.title}" by ${parent.artist || 'Unknown artist'}
`; + } else if (parent.type === 'playlist') { + parentInfo = `
From playlist: "${parent.name}" by ${parent.owner || 'Unknown creator'}
`; + } + + if (parentInfo) { + errorDetailsHTML += parentInfo; + } + } + + // Add source URL if available + if (entry.lastStatus.url) { + errorDetailsHTML += ``; + } + + // Add retry button if this error can be retried + if (entry.lastStatus.can_retry !== false && (!entry.retryCount || entry.retryCount < this.MAX_RETRIES)) { + errorDetailsHTML += ``; + } + + // Display the error details + errorDetailsContainer.innerHTML = errorDetailsHTML; + errorDetailsContainer.style.display = 'block'; + + // Add event listener to retry button if present + const retryBtn = errorDetailsContainer.querySelector('.retry-btn'); + if (retryBtn) { + retryBtn.addEventListener('click', (e) => { + const queueId = e.target.getAttribute('data-queueid'); + if (queueId) { + const logElement = entry.element.querySelector('.log'); + this.retryDownload(queueId, logElement); + } + }); + } + } + } break; case 'complete': case 'done': entry.element.classList.add('complete'); + // Hide error details if present + if (entry.element) { + const errorDetailsContainer = entry.element.querySelector(`#error-details-${entry.uniqueId}-${entry.prgFile}`); + if (errorDetailsContainer) { + errorDetailsContainer.style.display = 'none'; + } + } break; case 'cancelled': entry.element.classList.add('cancelled'); + // Hide error details if present + if (entry.element) { + const errorDetailsContainer = entry.element.querySelector(`#error-details-${entry.uniqueId}-${entry.prgFile}`); + if (errorDetailsContainer) { + errorDetailsContainer.style.display = 'none'; + } + } break; } } @@ -1081,7 +1151,15 @@ createQueueItem(item, type, prgFile, queueId) { return `${trackName}${artist ? ` by ${artist}` : ''} was skipped: ${data.reason || 'Unknown reason'}`; case 'error': + // Enhanced error message handling using the new format let errorMsg = `Error: ${data.error || data.message || 'Unknown error'}`; + + // Add position information for tracks in collections + if (data.current_track && data.total_tracks) { + errorMsg = `Error on track ${data.current_track}/${data.total_tracks}: ${data.error || data.message || 'Unknown error'}`; + } + + // Add retry information if available if (data.retry_count !== undefined) { errorMsg += ` (Attempt ${data.retry_count}/${this.MAX_RETRIES})`; } else if (data.can_retry !== undefined) { @@ -1091,6 +1169,21 @@ createQueueItem(item, type, prgFile, queueId) { errorMsg += ` (Max retries reached)`; } } + + // Add parent information if this is a track with a parent + if (data.type === 'track' && data.parent) { + if (data.parent.type === 'album') { + errorMsg += `\nFrom album: "${data.parent.title}" by ${data.parent.artist || 'Unknown artist'}`; + } else if (data.parent.type === 'playlist') { + errorMsg += `\nFrom playlist: "${data.parent.name}" by ${data.parent.owner || 'Unknown creator'}`; + } + } + + // Add URL for troubleshooting if available + if (data.url) { + errorMsg += `\nSource: ${data.url}`; + } + return errorMsg; case 'retrying': @@ -1168,8 +1261,30 @@ createQueueItem(item, type, prgFile, queueId) { entry.isRetrying = true; logElement.textContent = 'Retrying download...'; + // Determine if we should use parent information for retry + let useParent = false; + let parentType = null; + let parentUrl = null; + + // Check if we have parent information in the lastStatus + if (entry.lastStatus && entry.lastStatus.parent) { + const parent = entry.lastStatus.parent; + if (parent.type && parent.url) { + useParent = true; + parentType = parent.type; + parentUrl = parent.url; + console.log(`Using parent info for retry: ${parentType} with URL: ${parentUrl}`); + } + } + // Find a retry URL from various possible sources const getRetryUrl = () => { + // If using parent, return parent URL + if (useParent && parentUrl) { + return parentUrl; + } + + // Otherwise use the standard fallback options if (entry.requestUrl) return entry.requestUrl; // If we have lastStatus with original_request, check there @@ -1200,10 +1315,12 @@ createQueueItem(item, type, prgFile, queueId) { // Close any existing polling interval this.clearPollingInterval(queueId); - console.log(`Retrying download for ${entry.type} with URL: ${retryUrl}`); + // Determine which type to use for the API endpoint + const apiType = useParent ? parentType : entry.type; + console.log(`Retrying download using type: ${apiType} with URL: ${retryUrl}`); - // Build the API URL based on the entry's type - const apiUrl = `/api/${entry.type}/download?url=${encodeURIComponent(retryUrl)}`; + // Build the API URL based on the determined type + const apiUrl = `/api/${apiType}/download?url=${encodeURIComponent(retryUrl)}`; // Add name and artist if available for better progress display let fullRetryUrl = apiUrl; diff --git a/templates/config.html b/templates/config.html index a15f67b..7cd30b7 100644 --- a/templates/config.html +++ b/templates/config.html @@ -116,7 +116,7 @@ - + @@ -169,7 +169,7 @@ - +