diff --git a/js/api.js b/js/api.js index 4ba9fa3..234d2b5 100644 --- a/js/api.js +++ b/js/api.js @@ -1,6 +1,7 @@ //js/api.js import { RATE_LIMIT_ERROR_MESSAGE, deriveTrackQuality, delay } from './utils.js'; import { APICache } from './cache.js'; +import { addMetadataToAudio } from './metadata.js'; export const DASH_MANIFEST_UNAVAILABLE_CODE = 'DASH_MANIFEST_UNAVAILABLE'; @@ -585,7 +586,7 @@ export class LosslessAPI { } async downloadTrack(id, quality = 'LOSSLESS', filename, options = {}) { - const { onProgress } = options; + const { onProgress, track } = options; try { const lookup = await this.getTrack(id, quality); @@ -613,6 +614,7 @@ export class LosslessAPI { const totalBytes = contentLength ? parseInt(contentLength, 10) : 0; let receivedBytes = 0; + let blob; if (response.body && onProgress) { const reader = response.body.getReader(); @@ -634,10 +636,9 @@ export class LosslessAPI { } } - const blob = new Blob(chunks, { type: response.headers.get('Content-Type') || 'audio/flac' }); - this.triggerDownload(blob, filename); + blob = new Blob(chunks, { type: response.headers.get('Content-Type') || 'audio/flac' }); } else { - const blob = await response.blob(); + blob = await response.blob(); if (onProgress) { onProgress({ stage: 'downloading', @@ -645,8 +646,20 @@ export class LosslessAPI { totalBytes: blob.size }); } - this.triggerDownload(blob, filename); } + + // Add metadata if track information is provided + if (track) { + if (onProgress) { + onProgress({ + stage: 'processing', + message: 'Adding metadata...' + }); + } + blob = await addMetadataToAudio(blob, track, this, quality); + } + + this.triggerDownload(blob, filename); } catch (error) { if (error.name === 'AbortError') { throw error; diff --git a/js/app.js b/js/app.js index 35de81c..dfafd45 100644 --- a/js/app.js +++ b/js/app.js @@ -8,7 +8,7 @@ import { LastFMScrobbler } from './lastfm.js'; import { LyricsManager, createLyricsPanel, showKaraokeView, showSyncedLyricsPanel, clearLyricsPanelSync } from './lyrics.js'; import { createRouter, updateTabTitle } from './router.js'; import { initializeSettings } from './settings.js'; -import { initializePlayerEvents, initializeTrackInteractions } from './events.js'; +import { initializePlayerEvents, initializeTrackInteractions, handleTrackAction } from './events.js'; import { initializeUIInteractions } from './ui-interactions.js'; import { downloadAlbumAsZip, downloadDiscography, downloadPlaylistAsZip } from './downloads.js'; import { debounce, SVG_PLAY } from './utils.js'; @@ -275,7 +275,7 @@ document.addEventListener('DOMContentLoaded', async () => { document.getElementById('download-current-btn')?.addEventListener('click', () => { if (player.currentTrack) { - downloadTrackWithMetadata(player.currentTrack, player.quality, api, lyricsManager); + handleTrackAction('download', player.currentTrack, player, api, lyricsManager); } }); diff --git a/js/downloads.js b/js/downloads.js index 7df322d..9747888 100644 --- a/js/downloads.js +++ b/js/downloads.js @@ -1,10 +1,22 @@ //js/downloads.js -import { buildTrackFilename, sanitizeForFilename, RATE_LIMIT_ERROR_MESSAGE, getTrackArtists, getTrackTitle, formatTemplate, SVG_CLOSE } from './utils.js'; +import { buildTrackFilename, sanitizeForFilename, RATE_LIMIT_ERROR_MESSAGE, getTrackArtists, getTrackTitle, formatTemplate, SVG_CLOSE, getCoverBlob } from './utils.js'; import { lyricsSettings } from './storage.js'; +import { addMetadataToAudio } from './metadata.js'; const downloadTasks = new Map(); let downloadNotificationContainer = null; +/** + * Adds a cover blob to a JSZip instance + */ +function addCoverBlobToZip(zip, folderPath, blob) { + if (!blob) return; + const path = folderPath ? `${folderPath}/cover.jpg` : 'cover.jpg'; + if (!zip.file(path)) { + zip.file(path, blob); + } +} + async function loadJSZip() { try { const module = await import('https://cdn.jsdelivr.net/npm/jszip@3.10.1/+esm'); @@ -62,7 +74,7 @@ export function addDownloadTask(trackId, track, filename, api) {
-
Starting...
+
Starting...