feat(downloads): prefetch data while downloading to improve performance and update taglib-wasm
This commit is contained in:
parent
1173388ee3
commit
0f20106076
11 changed files with 131 additions and 99 deletions
10
bun.lock
10
bun.lock
|
|
@ -16,18 +16,18 @@
|
||||||
"cookie-session": "^2.1.1",
|
"cookie-session": "^2.1.1",
|
||||||
"dashjs": "^5.1.1",
|
"dashjs": "^5.1.1",
|
||||||
"fuse.js": "^7.1.0",
|
"fuse.js": "^7.1.0",
|
||||||
"jose": "^6.1.3",
|
"jose": "^6.2.0",
|
||||||
"npm": "^11.11.0",
|
"npm": "^11.11.0",
|
||||||
"pocketbase": "^0.26.8",
|
"pocketbase": "^0.26.8",
|
||||||
"taglib-wasm": "^0.9.0",
|
"taglib-wasm": "^1.0.5",
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@neutralinojs/neu": "^11.7.0",
|
"@neutralinojs/neu": "^11.7.0",
|
||||||
"@types/node": "^25.3.3",
|
"@types/node": "^25.3.5",
|
||||||
"eslint": "^9.39.3",
|
"eslint": "^9.39.3",
|
||||||
"eslint-config-prettier": "^10.1.8",
|
"eslint-config-prettier": "^10.1.8",
|
||||||
"globals": "^17.4.0",
|
"globals": "^17.4.0",
|
||||||
"htmlhint": "^1.9.1",
|
"htmlhint": "^1.9.2",
|
||||||
"miniflare": "^4.20260301.1",
|
"miniflare": "^4.20260301.1",
|
||||||
"prettier": "^3.8.1",
|
"prettier": "^3.8.1",
|
||||||
"stylelint": "^16.26.1",
|
"stylelint": "^16.26.1",
|
||||||
|
|
@ -1279,7 +1279,7 @@
|
||||||
|
|
||||||
"table": ["table@6.9.0", "", { "dependencies": { "ajv": "^8.0.1", "lodash.truncate": "^4.4.2", "slice-ansi": "^4.0.0", "string-width": "^4.2.3", "strip-ansi": "^6.0.1" } }, "sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A=="],
|
"table": ["table@6.9.0", "", { "dependencies": { "ajv": "^8.0.1", "lodash.truncate": "^4.4.2", "slice-ansi": "^4.0.0", "string-width": "^4.2.3", "strip-ansi": "^6.0.1" } }, "sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A=="],
|
||||||
|
|
||||||
"taglib-wasm": ["taglib-wasm@0.9.0", "", { "dependencies": { "@msgpack/msgpack": "^3.1.3" }, "peerDependencies": { "typescript": ">=4.5.0" } }, "sha512-E6Z/rGT6vE+9HuRnklSJNvEBdq+VyVVrXvMJ3o7/4oY3tsBwLYp949SgmkTUSegTgDzBjguTN74XVeEKtPVSOA=="],
|
"taglib-wasm": ["taglib-wasm@1.0.5", "", { "dependencies": { "@msgpack/msgpack": "^3.1.3" }, "peerDependencies": { "typescript": ">=4.5.0" }, "optionalPeers": ["typescript"] }, "sha512-kuDHX78FbjLOqldWxBBkEgjyyDagYRGcYqr4g6ObkmEMO203Sp1R0KxkELoH9VkZxsOVR8yoSWaSaG9f5oTqyQ=="],
|
||||||
|
|
||||||
"tcp-port-used": ["tcp-port-used@1.0.2", "", { "dependencies": { "debug": "4.3.1", "is2": "^2.0.6" } }, "sha512-l7ar8lLUD3XS1V2lfoJlCBaeoaWo/2xfYt81hM7VlvR4RrMVFqfmzfhLVk40hAb368uitje5gPtBRL1m/DGvLA=="],
|
"tcp-port-used": ["tcp-port-used@1.0.2", "", { "dependencies": { "debug": "4.3.1", "is2": "^2.0.6" } }, "sha512-l7ar8lLUD3XS1V2lfoJlCBaeoaWo/2xfYt81hM7VlvR4RrMVFqfmzfhLVk40hAb368uitje5gPtBRL1m/DGvLA=="],
|
||||||
|
|
||||||
|
|
|
||||||
10
js/api.js
10
js/api.js
|
|
@ -8,11 +8,10 @@ import {
|
||||||
} from './utils.js';
|
} from './utils.js';
|
||||||
import { trackDateSettings, losslessContainerSettings } 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, prefetchMetadataObjects } 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, loadFfmpeg } from './ffmpeg.js';
|
import { ffmpeg, loadFfmpeg } from './ffmpeg.js';
|
||||||
import { initTagLib } from './taglib.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';
|
||||||
|
|
@ -1110,12 +1109,11 @@ export class LosslessAPI {
|
||||||
}
|
}
|
||||||
|
|
||||||
async downloadTrack(id, quality = 'HI_RES_LOSSLESS', filename, options = {}) {
|
async downloadTrack(id, quality = 'HI_RES_LOSSLESS', filename, options = {}) {
|
||||||
// Initialize taglib in the background.
|
|
||||||
initTagLib().catch(console.error);
|
|
||||||
|
|
||||||
// Load ffmpeg in the background.
|
// Load ffmpeg in the background.
|
||||||
loadFfmpeg().catch(console.error);
|
loadFfmpeg().catch(console.error);
|
||||||
|
|
||||||
const { onProgress, track } = options;
|
const { onProgress, track } = options;
|
||||||
|
const prefetchPromises = prefetchMetadataObjects(track, this);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// MP3_320 is not a native TIDAL quality, we download LOSSLESS and convert
|
// MP3_320 is not a native TIDAL quality, we download LOSSLESS and convert
|
||||||
|
|
@ -1271,7 +1269,7 @@ export class LosslessAPI {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
blob = await addMetadataToAudio(blob, enrichedTrack, this, quality);
|
blob = await addMetadataToAudio(blob, enrichedTrack, this, quality, prefetchPromises);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Detect actual format and fix filename extension if needed
|
// Detect actual format and fix filename extension if needed
|
||||||
|
|
|
||||||
|
|
@ -12,12 +12,11 @@ import {
|
||||||
escapeHtml,
|
escapeHtml,
|
||||||
} from './utils.js';
|
} from './utils.js';
|
||||||
import { lyricsSettings, bulkDownloadSettings, losslessContainerSettings, playlistSettings } from './storage.js';
|
import { lyricsSettings, bulkDownloadSettings, losslessContainerSettings, playlistSettings } from './storage.js';
|
||||||
import { addMetadataToAudio } from './metadata.js';
|
import { addMetadataToAudio, prefetchMetadataObjects } 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, loadFfmpeg } from './ffmpeg.js';
|
import { ffmpeg, loadFfmpeg } from './ffmpeg.js';
|
||||||
import { initTagLib } from './taglib.js';
|
|
||||||
|
|
||||||
const downloadTasks = new Map();
|
const downloadTasks = new Map();
|
||||||
const bulkDownloadTasks = new Map();
|
const bulkDownloadTasks = new Map();
|
||||||
|
|
@ -270,12 +269,11 @@ function removeBulkDownloadTask(notifEl) {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function downloadTrackBlob(track, quality, api, lyricsManager = null, signal = null) {
|
async function downloadTrackBlob(track, quality, api, lyricsManager = null, signal = null) {
|
||||||
// Initialize taglib in the background.
|
|
||||||
initTagLib().catch(console.error);
|
|
||||||
|
|
||||||
// Load ffmpeg in the background.
|
// Load ffmpeg in the background.
|
||||||
loadFfmpeg().catch(console.error);
|
loadFfmpeg().catch(console.error);
|
||||||
|
|
||||||
|
const prefetchPromises = prefetchMetadataObjects(track, api);
|
||||||
|
|
||||||
let enrichedTrack = {
|
let enrichedTrack = {
|
||||||
...track,
|
...track,
|
||||||
artist: track.artist || (track.artists && track.artists.length > 0 ? track.artists[0] : null),
|
artist: track.artist || (track.artists && track.artists.length > 0 ? track.artists[0] : null),
|
||||||
|
|
@ -408,7 +406,7 @@ async function downloadTrackBlob(track, quality, api, lyricsManager = null, sign
|
||||||
const extension = await getExtensionFromBlob(blob);
|
const extension = await getExtensionFromBlob(blob);
|
||||||
|
|
||||||
// Add metadata to the blob
|
// Add metadata to the blob
|
||||||
blob = await addMetadataToAudio(blob, enrichedTrack, api, quality);
|
blob = await addMetadataToAudio(blob, enrichedTrack, api, quality, prefetchPromises);
|
||||||
|
|
||||||
return { blob, extension };
|
return { blob, extension };
|
||||||
}
|
}
|
||||||
|
|
|
||||||
4
js/global.d.ts
vendored
Normal file
4
js/global.d.ts
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
declare module '*?url' {
|
||||||
|
const content: string;
|
||||||
|
export default content;
|
||||||
|
}
|
||||||
|
|
@ -42,6 +42,16 @@ function getFullArtistString(track) {
|
||||||
return knownArtists.join('; ') || null;
|
return knownArtists.join('; ') || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function prefetchMetadataObjects(track, api) {
|
||||||
|
const _tagLib = initTagLib().catch(console.error);
|
||||||
|
const coverFetch = track?.album?.cover
|
||||||
|
? getCoverBlob(api, track.album.cover).catch(console.error)
|
||||||
|
: Promise.resolve(null);
|
||||||
|
const lyricsFetch = managers?.lyricsManager?.fetchLyrics?.(track.id, track)?.catch(console.error);
|
||||||
|
|
||||||
|
return { _tagLib, coverFetch, lyricsFetch };
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds metadata tags to audio files (FLAC, M4A or MP3)
|
* Adds metadata tags to audio files (FLAC, M4A or MP3)
|
||||||
* @param {Blob} audioBlob - The audio file blob
|
* @param {Blob} audioBlob - The audio file blob
|
||||||
|
|
@ -50,32 +60,42 @@ function getFullArtistString(track) {
|
||||||
* @param {string} quality - Audio quality
|
* @param {string} quality - Audio quality
|
||||||
* @returns {Promise<Blob>} - Audio blob with embedded metadata
|
* @returns {Promise<Blob>} - Audio blob with embedded metadata
|
||||||
*/
|
*/
|
||||||
export async function addMetadataToAudio(audioBlob, track, api, _quality) {
|
export async function addMetadataToAudio(audioBlob, track, api, _quality, prefetchPromises) {
|
||||||
const tagLib = await initTagLib();
|
const { _tagLib, coverFetch, lyricsFetch } = prefetchPromises;
|
||||||
const file = await tagLib.open(await audioBlob.arrayBuffer());
|
|
||||||
|
|
||||||
|
console.time('Get audio array buffer');
|
||||||
|
const audioBuffer = await audioBlob.arrayBuffer();
|
||||||
|
console.timeEnd('Get audio array buffer');
|
||||||
|
|
||||||
|
console.time('Open file with taglib');
|
||||||
|
const tagLib = await _tagLib;
|
||||||
|
const file = await tagLib.open(audioBuffer);
|
||||||
|
console.timeEnd('Open file with taglib');
|
||||||
|
|
||||||
|
console.time('Tagging file');
|
||||||
try {
|
try {
|
||||||
const isMp4 = file.isMP4();
|
const isMp4 = file.isMP4();
|
||||||
|
|
||||||
const discNumber = track.volumeNumber ?? track.discNumber;
|
const discNumber = track.volumeNumber ?? track.discNumber;
|
||||||
const lyricsFetch = managers?.lyricsManager?.fetchLyrics?.(track.id, track);
|
|
||||||
const coverFetch = getCoverBlob(api, track.album.cover);
|
|
||||||
|
|
||||||
// Add standard tags
|
// Add standard tags
|
||||||
if (track.title) {
|
if (track.title) {
|
||||||
file.setProperty('TITLE', getTrackTitle(track));
|
file.setProperty('TITLE', getTrackTitle(track));
|
||||||
}
|
}
|
||||||
|
|
||||||
const artistStr = getFullArtistString(track);
|
const artistStr = getFullArtistString(track);
|
||||||
if (artistStr) {
|
if (artistStr) {
|
||||||
file.setProperty('ARTIST', artistStr);
|
file.setProperty('ARTIST', artistStr);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (track.album?.title) {
|
if (track.album?.title) {
|
||||||
file.setProperty('ALBUM', track.album.title);
|
file.setProperty('ALBUM', track.album.title);
|
||||||
}
|
}
|
||||||
|
|
||||||
const albumArtist = track.album?.artist?.name || track.artist?.name;
|
const albumArtist = track.album?.artist?.name || track.artist?.name;
|
||||||
if (albumArtist) {
|
if (albumArtist) {
|
||||||
file.setProperty('ALBUMARTIST', albumArtist);
|
file.setProperty('ALBUMARTIST', albumArtist);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (track.trackNumber) {
|
if (track.trackNumber) {
|
||||||
let trackString = String(track.trackNumber);
|
let trackString = String(track.trackNumber);
|
||||||
|
|
||||||
|
|
@ -89,6 +109,7 @@ export async function addMetadataToAudio(audioBlob, track, api, _quality) {
|
||||||
file.setProperty('TRACKNUMBER', String(track.trackNumber));
|
file.setProperty('TRACKNUMBER', String(track.trackNumber));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isMp4 && track.album?.numberOfTracks) {
|
if (!isMp4 && track.album?.numberOfTracks) {
|
||||||
file.setProperty('TRACKTOTAL', String(track.album.numberOfTracks));
|
file.setProperty('TRACKTOTAL', String(track.album.numberOfTracks));
|
||||||
}
|
}
|
||||||
|
|
@ -103,6 +124,7 @@ export async function addMetadataToAudio(audioBlob, track, api, _quality) {
|
||||||
file.setProperty('BPM', String(Math.round(bpm)));
|
file.setProperty('BPM', String(Math.round(bpm)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (track.replayGain) {
|
if (track.replayGain) {
|
||||||
const { albumReplayGain, albumPeakAmplitude, trackReplayGain, trackPeakAmplitude } = track.replayGain;
|
const { albumReplayGain, albumPeakAmplitude, trackReplayGain, trackPeakAmplitude } = track.replayGain;
|
||||||
if (albumReplayGain) file.setProperty('REPLAYGAIN_ALBUM_GAIN', String(albumReplayGain));
|
if (albumReplayGain) file.setProperty('REPLAYGAIN_ALBUM_GAIN', String(albumReplayGain));
|
||||||
|
|
@ -113,6 +135,7 @@ export async function addMetadataToAudio(audioBlob, track, api, _quality) {
|
||||||
|
|
||||||
const releaseDateStr =
|
const releaseDateStr =
|
||||||
track.album?.releaseDate || (track.streamStartDate ? track.streamStartDate.split('T')[0] : '');
|
track.album?.releaseDate || (track.streamStartDate ? track.streamStartDate.split('T')[0] : '');
|
||||||
|
|
||||||
if (releaseDateStr) {
|
if (releaseDateStr) {
|
||||||
try {
|
try {
|
||||||
const year = new Date(releaseDateStr).getFullYear();
|
const year = new Date(releaseDateStr).getFullYear();
|
||||||
|
|
@ -127,6 +150,7 @@ export async function addMetadataToAudio(audioBlob, track, api, _quality) {
|
||||||
if (track.copyright) {
|
if (track.copyright) {
|
||||||
file.setProperty('COPYRIGHT', track.copyright);
|
file.setProperty('COPYRIGHT', track.copyright);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (track.isrc) {
|
if (track.isrc) {
|
||||||
file.setProperty('ISRC', track.isrc);
|
file.setProperty('ISRC', track.isrc);
|
||||||
|
|
||||||
|
|
@ -134,6 +158,7 @@ export async function addMetadataToAudio(audioBlob, track, api, _quality) {
|
||||||
file.setMP4Item('xid ', `:isrc:${track.isrc}`);
|
file.setMP4Item('xid ', `:isrc:${track.isrc}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (track.explicit) {
|
if (track.explicit) {
|
||||||
if (isMp4) {
|
if (isMp4) {
|
||||||
file.setMP4Item('rtng', '1');
|
file.setMP4Item('rtng', '1');
|
||||||
|
|
@ -142,6 +167,7 @@ export async function addMetadataToAudio(audioBlob, track, api, _quality) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
if (track.album?.cover) {
|
if (track.album?.cover) {
|
||||||
const coverBlob = await coverFetch;
|
const coverBlob = await coverFetch;
|
||||||
const coverBuffer = new Uint8Array(await coverBlob.arrayBuffer());
|
const coverBuffer = new Uint8Array(await coverBlob.arrayBuffer());
|
||||||
|
|
@ -157,6 +183,9 @@ export async function addMetadataToAudio(audioBlob, track, api, _quality) {
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('Error setting cover metadata.', track, e);
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const lyrics = await lyricsFetch;
|
const lyrics = await lyricsFetch;
|
||||||
|
|
@ -170,16 +199,28 @@ export async function addMetadataToAudio(audioBlob, track, api, _quality) {
|
||||||
//}
|
//}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn('Error fetching lyrics', track, e);
|
console.warn('Error setting lyrics metadata', track, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
await file.save();
|
console.timeEnd('Tagging file');
|
||||||
|
|
||||||
return new Blob([file.getFileBuffer()], { type: audioBlob.type, name: audioBlob.name });
|
console.time('Saving in-memory buffer');
|
||||||
|
await file.save();
|
||||||
|
console.timeEnd('Saving in-memory buffer');
|
||||||
|
|
||||||
|
console.time('Saving blob');
|
||||||
|
const blob = new Blob([file.getFileBuffer()], { type: audioBlob.type, name: audioBlob.name });
|
||||||
|
console.timeEnd('Saving blob');
|
||||||
|
|
||||||
|
return blob;
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
} finally {
|
} finally {
|
||||||
// Always dispose, even if there was an error.
|
// Always dispose, even if there was an error.
|
||||||
file.dispose();
|
file.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return audioBlob;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
29
js/taglib.js
29
js/taglib.js
|
|
@ -1,29 +0,0 @@
|
||||||
import { TagLib as _TagLib } from 'taglib-wasm';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @type {typeof import('taglib-wasm').TagLib}
|
|
||||||
*/
|
|
||||||
export const TagLib = _TagLib;
|
|
||||||
import TagLibWasm from '!/taglib-wasm/dist/taglib-web.wasm?url';
|
|
||||||
|
|
||||||
export { TagLibWasm };
|
|
||||||
|
|
||||||
let tagLib = null;
|
|
||||||
const wasmBinary = fetch(TagLibWasm).then((r) => r.arrayBuffer());
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @returns {ReturnType<typeof TagLib.initialize>}
|
|
||||||
*/
|
|
||||||
export async function initTagLib() {
|
|
||||||
if (tagLib) return await tagLib;
|
|
||||||
|
|
||||||
tagLib = TagLib.initialize({
|
|
||||||
wasmBinary: await wasmBinary,
|
|
||||||
legacyMode: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log('TagLib initialized', { tagLib: await tagLib, TagLibWasm });
|
|
||||||
|
|
||||||
return await tagLib;
|
|
||||||
}
|
|
||||||
19
js/taglib.ts
Normal file
19
js/taglib.ts
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
import { TagLib } from 'taglib-wasm';
|
||||||
|
import { fetchBlobURL } from './utils';
|
||||||
|
import _TagLibWasm from '!/taglib-wasm/dist/taglib-web.wasm?url';
|
||||||
|
|
||||||
|
let tagLib: Promise<TagLib> | null = null;
|
||||||
|
|
||||||
|
export async function initTagLib(): Promise<TagLib> {
|
||||||
|
if (tagLib) return await tagLib;
|
||||||
|
|
||||||
|
const TagLibWasm = await fetchBlobURL(_TagLibWasm);
|
||||||
|
|
||||||
|
tagLib = TagLib.initialize({
|
||||||
|
wasmUrl: TagLibWasm,
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('TagLib initialized', { tagLib: await tagLib, TagLibWasm });
|
||||||
|
|
||||||
|
return await tagLib;
|
||||||
|
}
|
||||||
|
|
@ -533,3 +533,11 @@ export const getShareUrl = (path) => {
|
||||||
const safePath = path.startsWith('/') ? path : `/${path}`;
|
const safePath = path.startsWith('/') ? path : `/${path}`;
|
||||||
return `${baseUrl}${safePath}`;
|
return `${baseUrl}${safePath}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export function fetchBlob(url) {
|
||||||
|
return fetch(url).then((d) => d.blob());
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchBlobURL(url) {
|
||||||
|
return await URL.createObjectURL(await fetchBlob(url));
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,11 +30,11 @@
|
||||||
"homepage": "https://github.com/SamidyFR/monochrome#readme",
|
"homepage": "https://github.com/SamidyFR/monochrome#readme",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@neutralinojs/neu": "^11.7.0",
|
"@neutralinojs/neu": "^11.7.0",
|
||||||
"@types/node": "^25.3.3",
|
"@types/node": "^25.3.5",
|
||||||
"eslint": "^9.39.3",
|
"eslint": "^9.39.3",
|
||||||
"eslint-config-prettier": "^10.1.8",
|
"eslint-config-prettier": "^10.1.8",
|
||||||
"globals": "^17.4.0",
|
"globals": "^17.4.0",
|
||||||
"htmlhint": "^1.9.1",
|
"htmlhint": "^1.9.2",
|
||||||
"miniflare": "^4.20260301.1",
|
"miniflare": "^4.20260301.1",
|
||||||
"prettier": "^3.8.1",
|
"prettier": "^3.8.1",
|
||||||
"stylelint": "^16.26.1",
|
"stylelint": "^16.26.1",
|
||||||
|
|
@ -61,9 +61,9 @@
|
||||||
"cookie-session": "^2.1.1",
|
"cookie-session": "^2.1.1",
|
||||||
"dashjs": "^5.1.1",
|
"dashjs": "^5.1.1",
|
||||||
"fuse.js": "^7.1.0",
|
"fuse.js": "^7.1.0",
|
||||||
"jose": "^6.1.3",
|
"jose": "^6.2.0",
|
||||||
"npm": "^11.11.0",
|
"npm": "^11.11.0",
|
||||||
"pocketbase": "^0.26.8",
|
"pocketbase": "^0.26.8",
|
||||||
"taglib-wasm": "^0.9.0"
|
"taglib-wasm": "^1.0.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
21
tsconfig.json
Normal file
21
tsconfig.json
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2022",
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "Bundler",
|
||||||
|
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
||||||
|
"types": ["vite/client", "node"],
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"!/*": ["node_modules/*"]
|
||||||
|
},
|
||||||
|
"allowJs": true,
|
||||||
|
"checkJs": false,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"verbatimModuleSyntax": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"noEmit": true
|
||||||
|
},
|
||||||
|
"include": ["js/**/*.ts", "js/**/*.d.ts"]
|
||||||
|
}
|
||||||
|
|
@ -74,34 +74,6 @@ export default defineConfig(({ mode }) => {
|
||||||
includeAssets: ['discord.html'],
|
includeAssets: ['discord.html'],
|
||||||
manifest: false, // Use existing public/manifest.json
|
manifest: false, // Use existing public/manifest.json
|
||||||
}),
|
}),
|
||||||
{
|
|
||||||
name: 'ignore-taglib',
|
|
||||||
resolveId(id) {
|
|
||||||
if (
|
|
||||||
id == './dist/taglib-wrapper.js' ||
|
|
||||||
id == '../../build/taglib-wrapper.js' ||
|
|
||||||
id == '../../dist/taglib-wrapper.js'
|
|
||||||
) {
|
|
||||||
return path.resolve('node_modules/taglib-wasm/dist/taglib-wrapper.js');
|
|
||||||
}
|
|
||||||
|
|
||||||
return id;
|
|
||||||
},
|
|
||||||
load(id) {
|
|
||||||
if (id.endsWith('taglib-wasm/dist/src/worker-pool.js')) {
|
|
||||||
return 'export const getGlobalWorkerPool = () => { throw new Error("Worker pool is not supported in this environment"); }; export class TagLibWorkerPool { constructor() { throw new Error("Worker pool is not supported in this environment"); } } export function createWorkerPool() { throw new Error("Worker pool is not supported in this environment"); } export function terminateGlobalWorkerPool() { throw new Error("Worker pool is not supported in this environment"); }';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (id.endsWith('taglib-wasm/dist/src/runtime/wasmer-sdk-loader.js')) {
|
|
||||||
return [
|
|
||||||
'export const initializeWasmer = null;',
|
|
||||||
'export const loadWasmerWasi = null',
|
|
||||||
'export const isWasmerAvailable = null;',
|
|
||||||
'export const WasmerExecutionError = null;',
|
|
||||||
].join('\n');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue