diff --git a/js/api.js b/js/api.js index e44f867..f523251 100644 --- a/js/api.js +++ b/js/api.js @@ -1216,7 +1216,7 @@ export class LosslessAPI { if ((await getExtensionFromBlob(blob)) != 'flac') { blob = await ffmpeg( blob, - { args: ['-c:a', 'copy'] }, + { args: ['-vn', '-map_metadata', '-1', '-map', '0:a', '-c:a', 'flac'] }, 'output.flac', 'audio/flac', onProgress, diff --git a/js/downloads.js b/js/downloads.js index f3c16ce..00edae2 100644 --- a/js/downloads.js +++ b/js/downloads.js @@ -195,11 +195,21 @@ export function updateDownloadProgress(trackId, progress) { const percent = progress.totalBytes ? Math.round((progress.receivedBytes / progress.totalBytes) * 100) : 0; progressFill.style.width = `${percent}%`; + progressFill.style.background = 'var(--highlight)'; const receivedMB = (progress.receivedBytes / (1024 * 1024)).toFixed(1); const totalMB = progress.totalBytes ? (progress.totalBytes / (1024 * 1024)).toFixed(1) : '?'; statusEl.textContent = `Downloading: ${receivedMB}MB / ${totalMB}MB (${percent}%)`; + } else if (progress.stage === 'encoding') { + const percent = progress.progress ? Math.round(progress.progress) : 0; + progressFill.style.width = `${percent}%`; + progressFill.style.background = '#3b82f6'; // Blue for encoding + statusEl.textContent = `Converting: ${percent}%`; + } else if (progress.stage === 'finalizing' || progress.stage === 'processing') { + progressFill.style.width = '100%'; + progressFill.style.background = '#3b82f6'; + statusEl.textContent = progress.message || 'Processing...'; } } @@ -268,7 +278,7 @@ function removeBulkDownloadTask(notifEl) { }, 300); } -async function downloadTrackBlob(track, quality, api, lyricsManager = null, signal = null) { +async function downloadTrackBlob(track, quality, api, lyricsManager = null, signal = null, onProgress = null) { let enrichedTrack = { ...track, artist: track.artist || (track.artists && track.artists.length > 0 ? track.artists[0] : null), @@ -343,7 +353,7 @@ async function downloadTrackBlob(track, quality, api, lyricsManager = null, sign // Fallback if (downloadQuality !== 'LOSSLESS') { console.warn('Falling back to LOSSLESS (16-bit) download.'); - return downloadTrackBlob(track, 'LOSSLESS', api, lyricsManager, signal); + return downloadTrackBlob(track, 'LOSSLESS', api, lyricsManager, signal, onProgress); } throw dashError; } @@ -357,7 +367,7 @@ async function downloadTrackBlob(track, quality, api, lyricsManager = null, sign // Convert to MP3 320kbps if requested if (quality === 'MP3_320') { - blob = await encodeToMp3(blob, () => undefined, signal); + blob = await encodeToMp3(blob, onProgress || (() => undefined), signal); } if (quality.endsWith('LOSSLESS')) { @@ -367,10 +377,10 @@ async function downloadTrackBlob(track, quality, api, lyricsManager = null, sign if ((await getExtensionFromBlob(blob)) != 'flac') { blob = await ffmpeg( blob, - { args: ['-c:a', 'copy'] }, + { args: ['-vn', '-map_metadata', '-1', '-map', '0:a', '-c:a', 'flac'] }, 'output.flac', 'audio/flac', - () => undefined, + onProgress, signal ); } @@ -381,7 +391,7 @@ async function downloadTrackBlob(track, quality, api, lyricsManager = null, sign { args: ['-c:a', 'alac'] }, 'output.m4a', 'audio/mp4', - () => undefined, + onProgress, signal ); break; @@ -556,7 +566,9 @@ async function bulkDownloadToZipStream( updateBulkDownloadProgress(notification, i, tracks.length, trackTitle); try { - const { blob, extension } = await downloadTrackBlob(track, quality, api, null, signal); + const { blob, extension } = await downloadTrackBlob(track, quality, api, null, signal, (p) => { + updateBulkDownloadProgress(notification, i, tracks.length, trackTitle, p); + }); const filename = buildTrackFilename(track, quality, extension); const discNumber = discLayout.resolveDiscNumber(i); yield { @@ -698,7 +710,9 @@ async function bulkDownloadToZipBlob( updateBulkDownloadProgress(notification, i, tracks.length, trackTitle); try { - const { blob, extension } = await downloadTrackBlob(track, quality, api, null, signal); + const { blob, extension } = await downloadTrackBlob(track, quality, api, null, signal, (p) => { + updateBulkDownloadProgress(notification, i, tracks.length, trackTitle, p); + }); const filename = buildTrackFilename(track, quality, extension); const discNumber = discLayout.resolveDiscNumber(i); yield { @@ -841,7 +855,9 @@ async function bulkDownloadToZipNeutralino( updateBulkDownloadProgress(notification, i, tracks.length, trackTitle); try { - const { blob, extension } = await downloadTrackBlob(track, quality, api, null, signal); + const { blob, extension } = await downloadTrackBlob(track, quality, api, null, signal, (p) => { + updateBulkDownloadProgress(notification, i, tracks.length, trackTitle, p); + }); const filename = buildTrackFilename(track, quality, extension); const discNumber = discLayout.resolveDiscNumber(i); yield { @@ -1303,12 +1319,21 @@ function createBulkDownloadNotification(type, name, _totalItems) { return notifEl; } -function updateBulkDownloadProgress(notifEl, current, total, currentItem) { +function updateBulkDownloadProgress(notifEl, current, total, currentItem, ffmpegProgress = null) { const progressFill = notifEl.querySelector('.download-progress-fill'); const statusEl = notifEl.querySelector('.download-status'); + if (ffmpegProgress && (ffmpegProgress.stage === 'encoding' || ffmpegProgress.stage === 'finalizing')) { + const percent = ffmpegProgress.progress ? Math.round(ffmpegProgress.progress) : 100; + progressFill.style.width = `${percent}%`; + progressFill.style.background = '#3b82f6'; // Blue for encoding + statusEl.textContent = `Converting ${current}/${total}: ${percent}%`; + return; + } + const percent = total > 0 ? Math.round((current / total) * 100) : 0; progressFill.style.width = `${percent}%`; + progressFill.style.background = 'var(--highlight)'; statusEl.textContent = `${current}/${total} - ${currentItem}`; }