diff --git a/docker-compose.yaml b/docker-compose.yaml index 4e18af0..c8be331 100755 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -4,6 +4,7 @@ services: spotizerr: volumes: - ./creds:/app/creds + - ./config:/app/config - ./downloads:/app/downloads # <-- Change this for your music library dir ports: - 7171:7171 diff --git a/static/js/artist.js b/static/js/artist.js index cecdf47..98e3336 100644 --- a/static/js/artist.js +++ b/static/js/artist.js @@ -57,7 +57,7 @@ function renderArtist(artistData, artistId) { downloadArtistBtn = document.createElement('button'); downloadArtistBtn.id = 'downloadArtistBtn'; downloadArtistBtn.className = 'download-btn download-btn--main'; - downloadArtistBtn.textContent = 'Download All Albums'; + downloadArtistBtn.textContent = 'Download All Discography'; document.getElementById('artist-header').appendChild(downloadArtistBtn); } diff --git a/static/js/playlist.js b/static/js/playlist.js index 5643967..d419c83 100644 --- a/static/js/playlist.js +++ b/static/js/playlist.js @@ -72,7 +72,7 @@ function renderPlaylist(playlist) { downloadPlaylistBtn.id = 'downloadPlaylistBtn'; downloadPlaylistBtn.textContent = 'Download Whole Playlist'; downloadPlaylistBtn.className = 'download-btn download-btn--main'; - // Insert the button into the header container (e.g. after the description) + // Insert the button into the header container. const headerContainer = document.getElementById('playlist-header'); headerContainer.appendChild(downloadPlaylistBtn); } @@ -97,6 +97,36 @@ function renderPlaylist(playlist) { }); }); + // --- Add "Download Playlist's Albums" Button --- + let downloadAlbumsBtn = document.getElementById('downloadAlbumsBtn'); + if (!downloadAlbumsBtn) { + downloadAlbumsBtn = document.createElement('button'); + downloadAlbumsBtn.id = 'downloadAlbumsBtn'; + downloadAlbumsBtn.textContent = "Download Playlist's Albums"; + downloadAlbumsBtn.className = 'download-btn download-btn--main'; + // Insert the new button into the header container. + const headerContainer = document.getElementById('playlist-header'); + headerContainer.appendChild(downloadAlbumsBtn); + } + downloadAlbumsBtn.addEventListener('click', () => { + // Remove individual track download buttons (but leave this album button). + document.querySelectorAll('.download-btn').forEach(btn => { + if (btn.id !== 'downloadAlbumsBtn') btn.remove(); + }); + + downloadAlbumsBtn.disabled = true; + downloadAlbumsBtn.textContent = 'Queueing...'; + + downloadPlaylistAlbums(playlist) + .then(() => { + downloadAlbumsBtn.textContent = 'Queued!'; + }) + .catch(err => { + showError('Failed to queue album downloads: ' + err.message); + downloadAlbumsBtn.disabled = false; + }); + }); + // Render tracks list const tracksList = document.getElementById('tracks-list'); tracksList.innerHTML = ''; // Clear any existing content @@ -166,20 +196,16 @@ function showError(message) { */ function attachDownloadListeners() { document.querySelectorAll('.download-btn').forEach((btn) => { - // Skip the whole playlist button. - if (btn.id === 'downloadPlaylistBtn') return; + // Skip the whole playlist and album download buttons. + if (btn.id === 'downloadPlaylistBtn' || btn.id === 'downloadAlbumsBtn') return; btn.addEventListener('click', (e) => { e.stopPropagation(); const url = e.currentTarget.dataset.url; const type = e.currentTarget.dataset.type; const name = e.currentTarget.dataset.name || extractName(url); - const albumType = e.currentTarget.dataset.albumType; - - // Remove the button after click + // Remove the button immediately after click. e.currentTarget.remove(); - - // Start the download for this track. - startDownload(url, type, { name }, albumType); + startDownload(url, type, { name }); }); }); } @@ -187,7 +213,6 @@ function attachDownloadListeners() { /** * Initiates the whole playlist download by calling the playlist endpoint. */ -// playlist.js async function downloadWholePlaylist(playlist) { const url = playlist.external_urls.spotify; try { @@ -198,12 +223,45 @@ async function downloadWholePlaylist(playlist) { } } +/** + * Initiates album downloads for each unique album in the playlist. + */ +async function downloadPlaylistAlbums(playlist) { + // Use a Map to ensure each album is processed only once (by album ID). + const albumMap = new Map(); + playlist.tracks.items.forEach(item => { + const album = item.track.album; + if (album && album.id) { + albumMap.set(album.id, album); + } + }); + + const uniqueAlbums = Array.from(albumMap.values()); + if (uniqueAlbums.length === 0) { + showError('No albums found in this playlist.'); + return; + } + + try { + // Start album downloads concurrently. + await Promise.all(uniqueAlbums.map(album => + downloadQueue.startAlbumDownload( + album.external_urls.spotify, + { name: album.name } + ) + )); + } catch (error) { + // Propagate any errors. + throw error; + } +} + /** * Starts the download process by building the API URL, * fetching download details, and then adding the download to the queue. */ async function startDownload(url, type, item, albumType) { - // Retrieve configuration (if any) from localStorage + // Retrieve configuration (if any) from localStorage. const config = JSON.parse(localStorage.getItem('activeConfig')) || {}; const { fallback = false,