fix(downloads): cache ffmpeg core js and wasm
This creates a blob url outside of the worker for for the core .js and .wasm files so they aren't downloaded on each run.
This commit is contained in:
parent
50a5b79d70
commit
44a7c3b61c
7 changed files with 60 additions and 21 deletions
3
bun.lock
3
bun.lock
|
|
@ -5,6 +5,7 @@
|
|||
"": {
|
||||
"name": "monochrome",
|
||||
"dependencies": {
|
||||
"@ffmpeg/core": "^0.12.10",
|
||||
"@ffmpeg/ffmpeg": "^0.12.15",
|
||||
"@ffmpeg/util": "^0.12.2",
|
||||
"@kawarp/core": "^1.1.1",
|
||||
|
|
@ -328,6 +329,8 @@
|
|||
|
||||
"@eslint/plugin-kit": ["@eslint/plugin-kit@0.4.1", "", { "dependencies": { "@eslint/core": "^0.17.0", "levn": "^0.4.1" } }, "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA=="],
|
||||
|
||||
"@ffmpeg/core": ["@ffmpeg/core@0.12.10", "", {}, "sha512-dzNplnn2Nxle2c2i2rrDhqcB19q9cglCkWnoMTDN9Q9l3PvdjZWd1HfSPjCNWc/p8Q3CT+Es9fWOR0UhAeYQZA=="],
|
||||
|
||||
"@ffmpeg/ffmpeg": ["@ffmpeg/ffmpeg@0.12.15", "", { "dependencies": { "@ffmpeg/types": "^0.12.4" } }, "sha512-1C8Obr4GsN3xw+/1Ww6PFM84wSQAGsdoTuTWPOj2OizsRDLT4CXTaVjPhkw6ARyDus1B9X/L2LiXHqYYsGnRFw=="],
|
||||
|
||||
"@ffmpeg/types": ["@ffmpeg/types@0.12.4", "", {}, "sha512-k9vJQNBGTxE5AhYDtOYR5rO5fKsspbg51gbcwtbkw2lCdoIILzklulcjJfIDwrtn7XhDeF2M+THwJ2FGrLeV6A=="],
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ 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';
|
||||
import { ffmpeg, loadFfmpeg } from './ffmpeg.js';
|
||||
import { initTagLib } from './taglib.js';
|
||||
|
||||
export const DASH_MANIFEST_UNAVAILABLE_CODE = 'DASH_MANIFEST_UNAVAILABLE';
|
||||
|
|
@ -1112,6 +1112,9 @@ export class LosslessAPI {
|
|||
async downloadTrack(id, quality = 'HI_RES_LOSSLESS', filename, options = {}) {
|
||||
// Initialize taglib in the background.
|
||||
initTagLib().catch(console.error);
|
||||
|
||||
// Load ffmpeg in the background.
|
||||
loadFfmpeg().catch(console.error);
|
||||
const { onProgress, track } = options;
|
||||
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ 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';
|
||||
import { ffmpeg, loadFfmpeg } from './ffmpeg.js';
|
||||
import { initTagLib } from './taglib.js';
|
||||
|
||||
const downloadTasks = new Map();
|
||||
|
|
@ -273,6 +273,9 @@ async function downloadTrackBlob(track, quality, api, lyricsManager = null, sign
|
|||
// Initialize taglib in the background.
|
||||
initTagLib().catch(console.error);
|
||||
|
||||
// Load ffmpeg in the background.
|
||||
loadFfmpeg().catch(console.error);
|
||||
|
||||
let enrichedTrack = {
|
||||
...track,
|
||||
artist: track.artist || (track.artists && track.artists.length > 0 ? track.artists[0] : null),
|
||||
|
|
|
|||
44
js/ffmpeg.js
44
js/ffmpeg.js
|
|
@ -1,3 +1,8 @@
|
|||
import { toBlobURL } from '@ffmpeg/util';
|
||||
const ffmpegBase = 'https://unpkg.com/@ffmpeg/core/dist/esm';
|
||||
const coreJs = `${ffmpegBase}/ffmpeg-core.js`;
|
||||
const coreWasm = `${ffmpegBase}/ffmpeg-core.wasm`;
|
||||
|
||||
class FfmpegError extends Error {
|
||||
constructor(message) {
|
||||
super(message);
|
||||
|
|
@ -6,6 +11,20 @@ class FfmpegError extends Error {
|
|||
}
|
||||
}
|
||||
|
||||
export function loadFfmpeg() {
|
||||
return (
|
||||
loadFfmpeg.promise ||
|
||||
(loadFfmpeg.promise = (async () => {
|
||||
const data = {
|
||||
coreURL: await toBlobURL(coreJs, 'text/javascript'),
|
||||
wasmURL: await toBlobURL(coreWasm, 'application/wasm'),
|
||||
};
|
||||
|
||||
return data;
|
||||
})())
|
||||
);
|
||||
}
|
||||
|
||||
async function ffmpegWorker(
|
||||
audioBlob,
|
||||
args = {},
|
||||
|
|
@ -15,6 +34,7 @@ async function ffmpegWorker(
|
|||
signal = null
|
||||
) {
|
||||
const audioData = await audioBlob.arrayBuffer();
|
||||
const assets = loadFfmpeg();
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const worker = new Worker(new URL('./ffmpeg.worker.js', import.meta.url), { type: 'module' });
|
||||
|
|
@ -57,18 +77,20 @@ async function ffmpegWorker(
|
|||
reject(new FfmpegError('Worker failed: ' + error.message));
|
||||
};
|
||||
|
||||
// Transfer audio data to worker
|
||||
worker.postMessage(
|
||||
{
|
||||
audioData,
|
||||
...args,
|
||||
output: {
|
||||
name: outputName,
|
||||
mime: outputMime,
|
||||
(async () => {
|
||||
worker.postMessage(
|
||||
{
|
||||
audioData,
|
||||
...args,
|
||||
output: {
|
||||
name: outputName,
|
||||
mime: outputMime,
|
||||
},
|
||||
loadOptions: await assets,
|
||||
},
|
||||
},
|
||||
[audioData]
|
||||
);
|
||||
[audioData]
|
||||
);
|
||||
})();
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
import { FFmpeg } from '@ffmpeg/ffmpeg';
|
||||
import { toBlobURL } from '@ffmpeg/util';
|
||||
|
||||
let ffmpeg = null;
|
||||
let loadingPromise = null;
|
||||
|
||||
async function loadFFmpeg() {
|
||||
async function loadFFmpeg(loadOptions = {}) {
|
||||
if (loadingPromise) return loadingPromise;
|
||||
|
||||
loadingPromise = (async () => {
|
||||
|
|
@ -25,11 +24,7 @@ async function loadFFmpeg() {
|
|||
|
||||
self.postMessage({ type: 'progress', stage: 'loading', message: 'Loading FFmpeg...' });
|
||||
|
||||
const baseURL = 'https://unpkg.com/@ffmpeg/core@0.12.6/dist/esm';
|
||||
await ffmpeg.load({
|
||||
coreURL: await toBlobURL(`${baseURL}/ffmpeg-core.js`, 'text/javascript'),
|
||||
wasmURL: await toBlobURL(`${baseURL}/ffmpeg-core.wasm`, 'application/wasm'),
|
||||
});
|
||||
await ffmpeg.load(loadOptions);
|
||||
})();
|
||||
|
||||
return loadingPromise;
|
||||
|
|
@ -45,10 +40,12 @@ self.onmessage = async (e) => {
|
|||
},
|
||||
encodeStartMessage = 'Encoding...',
|
||||
encodeEndMessage = 'Finalizing...',
|
||||
loadOptions = {},
|
||||
} = e.data;
|
||||
|
||||
try {
|
||||
await loadFFmpeg();
|
||||
console.log(loadOptions);
|
||||
await loadFFmpeg(loadOptions);
|
||||
|
||||
self.postMessage({ type: 'progress', stage: 'encoding', message: encodeStartMessage });
|
||||
|
||||
|
|
|
|||
10
package-lock.json
generated
10
package-lock.json
generated
|
|
@ -9,6 +9,7 @@
|
|||
"version": "1.0.0",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@ffmpeg/core": "^0.12.10",
|
||||
"@ffmpeg/ffmpeg": "^0.12.15",
|
||||
"@ffmpeg/util": "^0.12.2",
|
||||
"@kawarp/core": "^1.1.1",
|
||||
|
|
@ -2578,6 +2579,15 @@
|
|||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@ffmpeg/core": {
|
||||
"version": "0.12.10",
|
||||
"resolved": "https://registry.npmjs.org/@ffmpeg/core/-/core-0.12.10.tgz",
|
||||
"integrity": "sha512-dzNplnn2Nxle2c2i2rrDhqcB19q9cglCkWnoMTDN9Q9l3PvdjZWd1HfSPjCNWc/p8Q3CT+Es9fWOR0UhAeYQZA==",
|
||||
"license": "GPL-2.0-or-later",
|
||||
"engines": {
|
||||
"node": ">=16.x"
|
||||
}
|
||||
},
|
||||
"node_modules/@ffmpeg/ffmpeg": {
|
||||
"version": "0.12.15",
|
||||
"resolved": "https://registry.npmjs.org/@ffmpeg/ffmpeg/-/ffmpeg-0.12.15.tgz",
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@
|
|||
"source-map": "^0.7.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ffmpeg/core": "^0.12.10",
|
||||
"@ffmpeg/ffmpeg": "^0.12.15",
|
||||
"@ffmpeg/util": "^0.12.2",
|
||||
"@kawarp/core": "^1.1.1",
|
||||
|
|
|
|||
Loading…
Reference in a new issue