From 07422debb9a69a2dfb3059bef80d096774f87adb Mon Sep 17 00:00:00 2001 From: Daniel <790119+DanTheMan827@users.noreply.github.com> Date: Fri, 27 Feb 2026 21:05:58 +0000 Subject: [PATCH 1/3] feat(downloads): add lossless container option This uses ffmpeg to ensure that the downloaded lossless audio is in the desired container format. --- index.html | 11 +++++++++++ js/api.js | 40 +++++++++++++++++++++++++++++++++++++++- js/downloads.js | 40 +++++++++++++++++++++++++++++++++++++++- js/settings.js | 10 ++++++++++ js/storage.js | 14 ++++++++++++++ 5 files changed, 113 insertions(+), 2 deletions(-) diff --git a/index.html b/index.html index 6a3b5c8..9506106 100644 --- a/index.html +++ b/index.html @@ -4316,6 +4316,17 @@ +
+
+ Lossless Container + Container format for lossless downloads +
+ +
Cover Art Size diff --git a/js/api.js b/js/api.js index ce18fdc..5355a9a 100644 --- a/js/api.js +++ b/js/api.js @@ -6,11 +6,12 @@ import { isTrackUnavailable, getExtensionFromBlob, } from './utils.js'; -import { trackDateSettings } from './storage.js'; +import { trackDateSettings, losslessContainerSettings } from './storage.js'; import { APICache } from './cache.js'; import { addMetadataToAudio } from './metadata.js'; import { DashDownloader } from './dash-downloader.js'; import { encodeToMp3, MP3EncodingError } from './mp3-encoder.js'; +import { ffmpeg } from './ffmpeg.js'; export const DASH_MANIFEST_UNAVAILABLE_CODE = 'DASH_MANIFEST_UNAVAILABLE'; const TIDAL_V2_TOKEN = 'txNoH4kkV41MfH25'; @@ -1208,6 +1209,43 @@ export class LosslessAPI { } } + if (quality.endsWith('LOSSLESS')) { + try { + switch (losslessContainerSettings.getContainer()) { + case 'flac': + if ((await getExtensionFromBlob(blob)) != 'flac') { + blob = await ffmpeg( + blob, + { args: ['-c:a', 'flac'] }, + 'output.flac', + 'audio/flac', + onProgress, + options.signal + ); + } + break; + case 'alac': + blob = await ffmpeg( + blob, + { args: ['-c:a', 'alac'] }, + 'output.m4a', + 'audio/m4a', + onProgress, + options.signal + ); + break; + default: + break; + } + } catch (error) { + if (error?.name === 'AbortError') { + throw error; + } + + console.error('Lossless container conversion failed:', error); + } + } + // Add metadata if track information is provided if (track) { if (onProgress) { diff --git a/js/downloads.js b/js/downloads.js index 594481f..616e5f8 100644 --- a/js/downloads.js +++ b/js/downloads.js @@ -11,11 +11,12 @@ import { getExtensionFromBlob, escapeHtml, } from './utils.js'; -import { lyricsSettings, bulkDownloadSettings, playlistSettings } from './storage.js'; +import { lyricsSettings, bulkDownloadSettings, losslessContainerSettings, playlistSettings } from './storage.js'; import { addMetadataToAudio } from './metadata.js'; import { DashDownloader } from './dash-downloader.js'; import { generateM3U, generateM3U8, generateCUE, generateNFO, generateJSON } from './playlist-generator.js'; import { encodeToMp3 } from './mp3-encoder.js'; +import { ffmpeg } from './ffmpeg.js'; const downloadTasks = new Map(); const bulkDownloadTasks = new Map(); @@ -350,6 +351,43 @@ async function downloadTrackBlob(track, quality, api, lyricsManager = null, sign blob = await encodeToMp3(blob, () => undefined, signal); } + if (quality.endsWith('LOSSLESS')) { + try { + switch (losslessContainerSettings.getContainer()) { + case 'flac': + if ((await getExtensionFromBlob(blob)) != 'flac') { + blob = await ffmpeg( + blob, + { args: ['-c:a', 'flac'] }, + 'output.flac', + 'audio/flac', + () => undefined, + signal + ); + } + break; + case 'alac': + blob = await ffmpeg( + blob, + { args: ['-c:a', 'alac'] }, + 'output.m4a', + 'audio/m4a', + () => undefined, + signal + ); + break; + default: + break; + } + } catch (error) { + if (error?.name === 'AbortError') { + throw error; + } + + console.error('Lossless container conversion failed:', error); + } + } + // Detect actual format from blob signature BEFORE adding metadata const extension = await getExtensionFromBlob(blob); diff --git a/js/settings.js b/js/settings.js index dd310ff..514fe4f 100644 --- a/js/settings.js +++ b/js/settings.js @@ -11,6 +11,7 @@ import { replayGainSettings, smoothScrollingSettings, downloadQualitySettings, + losslessContainerSettings, coverArtSizeSettings, qualityBadgeSettings, trackDateSettings, @@ -805,6 +806,15 @@ export function initializeSettings(scrobbler, player, api, ui) { }); } + const losslessContainerSetting = document.getElementById('lossless-container-setting'); + if (losslessContainerSetting) { + losslessContainerSetting.value = losslessContainerSettings.getContainer(); + + losslessContainerSetting.addEventListener('change', (e) => { + losslessContainerSettings.setContainer(e.target.value); + }); + } + // Cover Art Size setting const coverArtSizeSetting = document.getElementById('cover-art-size-setting'); if (coverArtSizeSetting) { diff --git a/js/storage.js b/js/storage.js index a152638..7a6027d 100644 --- a/js/storage.js +++ b/js/storage.js @@ -533,6 +533,20 @@ export const downloadQualitySettings = { }, }; +export const losslessContainerSettings = { + STORAGE_KEY: 'lossless-container', + getContainer() { + try { + return localStorage.getItem(this.STORAGE_KEY) || 'flac'; + } catch { + return 'flac'; + } + }, + setContainer(container) { + localStorage.setItem(this.STORAGE_KEY, container); + }, +}; + export const coverArtSizeSettings = { STORAGE_KEY: 'cover-art-size', getSize() { From 9ff62c52d445a4368dc70bda5f23ed39068d2973 Mon Sep 17 00:00:00 2001 From: Samidy Date: Sat, 28 Feb 2026 02:00:37 +0300 Subject: [PATCH 2/3] Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- js/downloads.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/downloads.js b/js/downloads.js index 616e5f8..1d8d358 100644 --- a/js/downloads.js +++ b/js/downloads.js @@ -371,7 +371,7 @@ async function downloadTrackBlob(track, quality, api, lyricsManager = null, sign blob, { args: ['-c:a', 'alac'] }, 'output.m4a', - 'audio/m4a', + 'audio/mp4', () => undefined, signal ); From fc28f9faeba475c712bc8801e7dff9d35b03e9ef Mon Sep 17 00:00:00 2001 From: Samidy Date: Sat, 28 Feb 2026 02:00:58 +0300 Subject: [PATCH 3/3] Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- js/api.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/api.js b/js/api.js index 5355a9a..5fe407e 100644 --- a/js/api.js +++ b/js/api.js @@ -1229,7 +1229,7 @@ export class LosslessAPI { blob, { args: ['-c:a', 'alac'] }, 'output.m4a', - 'audio/m4a', + 'audio/mp4', onProgress, options.signal );