Merge pull request #251 from DanTheMan827/lossless-container-option
feat(downloads): add lossless container option
This commit is contained in:
commit
e8ad19b2d7
5 changed files with 113 additions and 2 deletions
11
index.html
11
index.html
|
|
@ -4332,6 +4332,17 @@
|
||||||
<option value="LOW">AAC 96kbps</option>
|
<option value="LOW">AAC 96kbps</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="setting-item">
|
||||||
|
<div class="info">
|
||||||
|
<span class="label">Lossless Container</span>
|
||||||
|
<span class="description">Container format for lossless downloads</span>
|
||||||
|
</div>
|
||||||
|
<select id="lossless-container-setting">
|
||||||
|
<option value="flac">FLAC</option>
|
||||||
|
<option value="alac">Apple Lossless</option>
|
||||||
|
<option value="nochange">Don't change</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
<div class="setting-item">
|
<div class="setting-item">
|
||||||
<div class="info">
|
<div class="info">
|
||||||
<span class="label">Cover Art Size</span>
|
<span class="label">Cover Art Size</span>
|
||||||
|
|
|
||||||
40
js/api.js
40
js/api.js
|
|
@ -6,11 +6,12 @@ import {
|
||||||
isTrackUnavailable,
|
isTrackUnavailable,
|
||||||
getExtensionFromBlob,
|
getExtensionFromBlob,
|
||||||
} from './utils.js';
|
} from './utils.js';
|
||||||
import { trackDateSettings } from './storage.js';
|
import { trackDateSettings, losslessContainerSettings } from './storage.js';
|
||||||
import { APICache } from './cache.js';
|
import { APICache } from './cache.js';
|
||||||
import { addMetadataToAudio } from './metadata.js';
|
import { addMetadataToAudio } from './metadata.js';
|
||||||
import { DashDownloader } from './dash-downloader.js';
|
import { DashDownloader } from './dash-downloader.js';
|
||||||
import { encodeToMp3, MP3EncodingError } from './mp3-encoder.js';
|
import { encodeToMp3, MP3EncodingError } from './mp3-encoder.js';
|
||||||
|
import { ffmpeg } from './ffmpeg.js';
|
||||||
|
|
||||||
export const DASH_MANIFEST_UNAVAILABLE_CODE = 'DASH_MANIFEST_UNAVAILABLE';
|
export const DASH_MANIFEST_UNAVAILABLE_CODE = 'DASH_MANIFEST_UNAVAILABLE';
|
||||||
const TIDAL_V2_TOKEN = 'txNoH4kkV41MfH25';
|
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/mp4',
|
||||||
|
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
|
// Add metadata if track information is provided
|
||||||
if (track) {
|
if (track) {
|
||||||
if (onProgress) {
|
if (onProgress) {
|
||||||
|
|
|
||||||
|
|
@ -11,11 +11,12 @@ import {
|
||||||
getExtensionFromBlob,
|
getExtensionFromBlob,
|
||||||
escapeHtml,
|
escapeHtml,
|
||||||
} from './utils.js';
|
} from './utils.js';
|
||||||
import { lyricsSettings, bulkDownloadSettings, playlistSettings } from './storage.js';
|
import { lyricsSettings, bulkDownloadSettings, losslessContainerSettings, playlistSettings } from './storage.js';
|
||||||
import { addMetadataToAudio } from './metadata.js';
|
import { addMetadataToAudio } from './metadata.js';
|
||||||
import { DashDownloader } from './dash-downloader.js';
|
import { DashDownloader } from './dash-downloader.js';
|
||||||
import { generateM3U, generateM3U8, generateCUE, generateNFO, generateJSON } from './playlist-generator.js';
|
import { generateM3U, generateM3U8, generateCUE, generateNFO, generateJSON } from './playlist-generator.js';
|
||||||
import { encodeToMp3 } from './mp3-encoder.js';
|
import { encodeToMp3 } from './mp3-encoder.js';
|
||||||
|
import { ffmpeg } from './ffmpeg.js';
|
||||||
|
|
||||||
const downloadTasks = new Map();
|
const downloadTasks = new Map();
|
||||||
const bulkDownloadTasks = 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);
|
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/mp4',
|
||||||
|
() => 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
|
// Detect actual format from blob signature BEFORE adding metadata
|
||||||
const extension = await getExtensionFromBlob(blob);
|
const extension = await getExtensionFromBlob(blob);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import {
|
||||||
replayGainSettings,
|
replayGainSettings,
|
||||||
smoothScrollingSettings,
|
smoothScrollingSettings,
|
||||||
downloadQualitySettings,
|
downloadQualitySettings,
|
||||||
|
losslessContainerSettings,
|
||||||
coverArtSizeSettings,
|
coverArtSizeSettings,
|
||||||
qualityBadgeSettings,
|
qualityBadgeSettings,
|
||||||
trackDateSettings,
|
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
|
// Cover Art Size setting
|
||||||
const coverArtSizeSetting = document.getElementById('cover-art-size-setting');
|
const coverArtSizeSetting = document.getElementById('cover-art-size-setting');
|
||||||
if (coverArtSizeSetting) {
|
if (coverArtSizeSetting) {
|
||||||
|
|
|
||||||
|
|
@ -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 = {
|
export const coverArtSizeSettings = {
|
||||||
STORAGE_KEY: 'cover-art-size',
|
STORAGE_KEY: 'cover-art-size',
|
||||||
getSize() {
|
getSize() {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue