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...