Major refactor to adapt new ffmpeg-core.js
This commit is contained in:
parent
cd5fe43905
commit
b36360f16f
6
package-lock.json
generated
6
package-lock.json
generated
@ -2034,9 +2034,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@ffmpeg/core": {
|
"@ffmpeg/core": {
|
||||||
"version": "0.6.0",
|
"version": "0.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/@ffmpeg/core/-/core-0.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/@ffmpeg/core/-/core-0.7.0.tgz",
|
||||||
"integrity": "sha512-5VNyabaCPZJEFmn92C5SIh2vFChL2l9OVGut1hmst8IPuChZahF6yniFjEdF2Ri8ZZFKXTAQrPh3WfpVaEPK9A=="
|
"integrity": "sha512-ulDKwckLF4NrNIn98sYe8KCzKM9GfIfc9kR2Mel4pA8URQ9GEr2tGDMST5xW63Usnr6ybZCZKgwiDkAHK7VWCQ=="
|
||||||
},
|
},
|
||||||
"@hapi/address": {
|
"@hapi/address": {
|
||||||
"version": "2.1.2",
|
"version": "2.1.2",
|
||||||
|
10
package.json
10
package.json
@ -14,13 +14,13 @@
|
|||||||
"wait": "rimraf dist && wait-on http://localhost:3000/dist/ffmpeg.dev.js",
|
"wait": "rimraf dist && wait-on http://localhost:3000/dist/ffmpeg.dev.js",
|
||||||
"test": "npm-run-all -p -r start test:all",
|
"test": "npm-run-all -p -r start test:all",
|
||||||
"test:all": "npm-run-all wait test:browser:ffmpeg test:node:all",
|
"test:all": "npm-run-all wait test:browser:ffmpeg test:node:all",
|
||||||
"test:node": "NODE_OPTIONS=--experimental-worker nyc mocha --exit --bail --require ./scripts/test-helper.js",
|
"test:node": "node --experimental-wasm-threads --experimental-wasm-bulk-memory node_modules/.bin/_mocha --exit --bail --require ./scripts/test-helper.js",
|
||||||
"test:node:all": "npm run test:node -- ./tests/*.test.js",
|
"test:node:all": "npm run test:node -- ./tests/*.test.js",
|
||||||
"test:browser": "mocha-headless-chrome -a incognito -a no-sandbox -a disable-setuid-sandbox -a disable-logging -t 300000",
|
"test:browser": "mocha-headless-chrome -a allow-file-access-from-files -a incognito -a no-sandbox -a disable-setuid-sandbox -a disable-logging -t 300000",
|
||||||
"test:browser:ffmpeg": "npm run test:browser -- -f ./tests/ffmpeg.test.html"
|
"test:browser:ffmpeg": "npm run test:browser -- -f ./tests/ffmpeg.test.html"
|
||||||
},
|
},
|
||||||
"browser": {
|
"browser": {
|
||||||
"./src/worker/node/index.js": "./src/worker/browser/index.js"
|
"./src/node/index.js": "./src/browser/index.js"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@ -37,11 +37,11 @@
|
|||||||
"url": "https://github.com/ffmpegjs/ffmpeg.js/issues"
|
"url": "https://github.com/ffmpegjs/ffmpeg.js/issues"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=10.5.0"
|
"node": ">=12.16.1"
|
||||||
},
|
},
|
||||||
"homepage": "https://github.com/ffmpegjs/ffmpeg.js#readme",
|
"homepage": "https://github.com/ffmpegjs/ffmpeg.js#readme",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ffmpeg/core": "^0.6.0",
|
"@ffmpeg/core": "^0.7.0",
|
||||||
"idb": "^4.0.5",
|
"idb": "^4.0.5",
|
||||||
"is-electron": "^2.2.0",
|
"is-electron": "^2.2.0",
|
||||||
"is-url": "^1.2.4",
|
"is-url": "^1.2.4",
|
||||||
|
@ -32,8 +32,4 @@ module.exports = [
|
|||||||
library: 'FFmpeg',
|
library: 'FFmpeg',
|
||||||
libraryTarget: 'umd',
|
libraryTarget: 'umd',
|
||||||
}),
|
}),
|
||||||
genConfig({
|
|
||||||
entry: path.resolve(__dirname, '..', 'src', 'worker-script', 'browser', 'index.js'),
|
|
||||||
filename: 'worker.dev.js',
|
|
||||||
}),
|
|
||||||
];
|
];
|
||||||
|
@ -23,8 +23,4 @@ module.exports = [
|
|||||||
library: 'FFmpeg',
|
library: 'FFmpeg',
|
||||||
libraryTarget: 'umd',
|
libraryTarget: 'umd',
|
||||||
}),
|
}),
|
||||||
genConfig({
|
|
||||||
entry: path.resolve(__dirname, '..', 'src', 'worker-script', 'browser', 'index.js'),
|
|
||||||
filename: 'worker.min.js',
|
|
||||||
}),
|
|
||||||
];
|
];
|
||||||
|
13
src/browser/defaultOptions.js
Normal file
13
src/browser/defaultOptions.js
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
const resolveURL = require('resolve-url');
|
||||||
|
const { dependencies } = require('../../package.json');
|
||||||
|
const defaultOptions = require('../constants/defaultOptions');
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Default options for browser worker
|
||||||
|
*/
|
||||||
|
module.exports = {
|
||||||
|
...defaultOptions,
|
||||||
|
corePath: (typeof process !== 'undefined' && process.env.FFMPEG_ENV === 'development')
|
||||||
|
? resolveURL('/node_modules/@ffmpeg/core/ffmpeg-core.js')
|
||||||
|
: `https://unpkg.com/@ffmpeg/core@v${dependencies['@ffmpeg/core'].substring(1)}/ffmpeg-core.js`,
|
||||||
|
};
|
23
src/browser/getModule.js
Normal file
23
src/browser/getModule.js
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
const { log } = require('../utils/log');
|
||||||
|
|
||||||
|
module.exports = ({ corePath }) => new Promise((resolve) => {
|
||||||
|
if (typeof window.Module === 'undefined') {
|
||||||
|
log('info', `download ffmpeg-core script (~25 MB) from ${corePath}`);
|
||||||
|
const script = document.createElement('script');
|
||||||
|
const eventHandler = () => {
|
||||||
|
script.removeEventListener('load', eventHandler);
|
||||||
|
log('info', 'initialize ffmpeg-core');
|
||||||
|
window.Module.onRuntimeInitialized = () => {
|
||||||
|
log('info', 'ffmpeg-core initialized');
|
||||||
|
resolve(window.Module);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
script.src = corePath;
|
||||||
|
script.type = 'text/javascript';
|
||||||
|
script.addEventListener('load', eventHandler);
|
||||||
|
document.getElementsByTagName('head')[0].appendChild(script);
|
||||||
|
} else {
|
||||||
|
log('info', 'ffmpeg-core is loaded already');
|
||||||
|
resolve(window.Module);
|
||||||
|
}
|
||||||
|
});
|
@ -1,11 +1,9 @@
|
|||||||
const defaultOptions = require('./defaultOptions');
|
const defaultOptions = require('./defaultOptions');
|
||||||
const spawnWorker = require('./spawnWorker');
|
const getModule = require('./getModule');
|
||||||
const onMessage = require('./onMessage');
|
|
||||||
const fetchFile = require('./fetchFile');
|
const fetchFile = require('./fetchFile');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
defaultOptions,
|
defaultOptions,
|
||||||
spawnWorker,
|
getModule,
|
||||||
onMessage,
|
|
||||||
fetchFile,
|
fetchFile,
|
||||||
};
|
};
|
@ -1,4 +1,5 @@
|
|||||||
module.exports = [
|
module.exports = [
|
||||||
'./ffmpeg', // args[0] is always binary path
|
'./ffmpeg', // args[0] is always binary path
|
||||||
'-nostdin', // Disable interaction mode
|
'-nostdin', // Disable interaction mode
|
||||||
|
'-hide_banner', // Not to output banner
|
||||||
];
|
];
|
@ -1,4 +1,5 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
|
log: false,
|
||||||
logger: () => {},
|
logger: () => {},
|
||||||
progress: () => {},
|
progress: () => {},
|
||||||
};
|
};
|
||||||
|
130
src/createFFmpeg.js
Normal file
130
src/createFFmpeg.js
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
const defaultArgs = require('./constants/defaultArgs');
|
||||||
|
const { setLogging, log } = require('./utils/log');
|
||||||
|
const resolvePaths = require('./utils/resolvePaths');
|
||||||
|
const parseProgress = require('./utils/parseProgress');
|
||||||
|
const stringList2pointer = require('./utils/stringList2pointer');
|
||||||
|
const {
|
||||||
|
defaultOptions,
|
||||||
|
getModule,
|
||||||
|
fetchFile,
|
||||||
|
} = require('./node');
|
||||||
|
|
||||||
|
const NO_LOAD = Error('FFmpeg.js is not ready, make sure you have completed load().');
|
||||||
|
const NO_MULTIPLE_RUN = Error('FFmpeg.js can only run one command at a time');
|
||||||
|
let Module = null;
|
||||||
|
let ffmpeg = null;
|
||||||
|
|
||||||
|
module.exports = (_options = {}) => {
|
||||||
|
let runResolve = null;
|
||||||
|
let running = false;
|
||||||
|
const {
|
||||||
|
log: logging,
|
||||||
|
logger,
|
||||||
|
progress,
|
||||||
|
...options
|
||||||
|
} = resolvePaths({
|
||||||
|
...defaultOptions,
|
||||||
|
..._options,
|
||||||
|
});
|
||||||
|
const detectCompletion = ({ message, type }) => {
|
||||||
|
if (type === 'ffmpeg-stdout'
|
||||||
|
&& message === 'FFMPEG_END'
|
||||||
|
&& runResolve !== null) {
|
||||||
|
runResolve();
|
||||||
|
runResolve = null;
|
||||||
|
running = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
setLogging(logging);
|
||||||
|
|
||||||
|
const load = async () => {
|
||||||
|
if (Module === null) {
|
||||||
|
log('info', 'load ffmpeg-core');
|
||||||
|
Module = await getModule(options);
|
||||||
|
Module.setLogger((_log) => {
|
||||||
|
detectCompletion(_log);
|
||||||
|
parseProgress(_log, progress);
|
||||||
|
logger(_log);
|
||||||
|
log(_log.type, _log.message);
|
||||||
|
});
|
||||||
|
if (ffmpeg === null) {
|
||||||
|
ffmpeg = Module.cwrap('proxy_main', 'number', ['number', 'number']);
|
||||||
|
}
|
||||||
|
log('info', 'ffmpeg-core loaded');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const FS = (method, args) => {
|
||||||
|
if (Module === null) {
|
||||||
|
throw NO_LOAD;
|
||||||
|
} else {
|
||||||
|
log('info', `FS.${method} ${args[0]}`);
|
||||||
|
return Module.FS[method](...args);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const write = async (path, data) => (
|
||||||
|
FS('writeFile', [path, await fetchFile(data)])
|
||||||
|
);
|
||||||
|
|
||||||
|
const writeText = (path, text) => (
|
||||||
|
FS('writeFile', [path, text])
|
||||||
|
);
|
||||||
|
|
||||||
|
const read = (path) => (
|
||||||
|
FS('readFile', [path])
|
||||||
|
);
|
||||||
|
|
||||||
|
const remove = (path) => (
|
||||||
|
FS('unlink', [path])
|
||||||
|
);
|
||||||
|
|
||||||
|
const ls = (path) => (
|
||||||
|
FS('readir', [path])
|
||||||
|
);
|
||||||
|
|
||||||
|
const run = (_args) => {
|
||||||
|
if (ffmpeg === null) {
|
||||||
|
throw NO_LOAD;
|
||||||
|
} else if (running) {
|
||||||
|
throw NO_MULTIPLE_RUN;
|
||||||
|
} else {
|
||||||
|
running = true;
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const args = [...defaultArgs, ..._args.trim().split(' ')].filter((s) => s.length !== 0);
|
||||||
|
log('info', `ffmpeg command: ${args.join(' ')}`);
|
||||||
|
runResolve = resolve;
|
||||||
|
ffmpeg(args.length, stringList2pointer(Module, args));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const transcode = (input, output, opts = '') => (
|
||||||
|
run(`-i ${input} ${opts} ${output}`)
|
||||||
|
);
|
||||||
|
|
||||||
|
const trim = (input, output, from, to, opts = '') => (
|
||||||
|
run(`-i ${input} -ss ${from} -to ${to} ${opts} ${output}`)
|
||||||
|
);
|
||||||
|
|
||||||
|
const concatDemuxer = (input, output, opts = '') => {
|
||||||
|
const text = input.reduce((acc, path) => `${acc}\nfile ${path}`, '');
|
||||||
|
writeText('concat_list.txt', text);
|
||||||
|
return run(`-f concat -safe 0 -i concat_list.txt ${opts} ${output}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
load,
|
||||||
|
FS,
|
||||||
|
write,
|
||||||
|
writeText,
|
||||||
|
read,
|
||||||
|
remove,
|
||||||
|
ls,
|
||||||
|
run,
|
||||||
|
transcode,
|
||||||
|
trim,
|
||||||
|
concatDemuxer,
|
||||||
|
};
|
||||||
|
};
|
@ -1,21 +0,0 @@
|
|||||||
const getId = require('./utils/getId');
|
|
||||||
|
|
||||||
let jobCounter = 0;
|
|
||||||
|
|
||||||
module.exports = ({
|
|
||||||
id: _id,
|
|
||||||
action,
|
|
||||||
payload = {},
|
|
||||||
}) => {
|
|
||||||
let id = _id;
|
|
||||||
if (typeof id === 'undefined') {
|
|
||||||
id = getId('Job', jobCounter);
|
|
||||||
jobCounter += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
id,
|
|
||||||
action,
|
|
||||||
payload,
|
|
||||||
};
|
|
||||||
};
|
|
@ -1,217 +0,0 @@
|
|||||||
const createJob = require('./createJob');
|
|
||||||
const { log } = require('./utils/log');
|
|
||||||
const getId = require('./utils/getId');
|
|
||||||
const parseProgress = require('./utils/parseProgress');
|
|
||||||
const resolvePaths = require('./utils/resolvePaths');
|
|
||||||
const getTransferables = require('./utils/getTransferables');
|
|
||||||
const {
|
|
||||||
defaultOptions,
|
|
||||||
spawnWorker,
|
|
||||||
onMessage,
|
|
||||||
fetchFile,
|
|
||||||
} = require('./worker/node');
|
|
||||||
|
|
||||||
let workerCounter = 0;
|
|
||||||
|
|
||||||
module.exports = (_options = {}) => {
|
|
||||||
const id = getId('Worker', workerCounter);
|
|
||||||
const {
|
|
||||||
logger,
|
|
||||||
progress,
|
|
||||||
...options
|
|
||||||
} = resolvePaths({
|
|
||||||
...defaultOptions,
|
|
||||||
..._options,
|
|
||||||
});
|
|
||||||
const resolves = {};
|
|
||||||
const rejects = {};
|
|
||||||
let worker = spawnWorker(options);
|
|
||||||
|
|
||||||
workerCounter += 1;
|
|
||||||
|
|
||||||
const setResolve = (action, res) => {
|
|
||||||
resolves[action] = res;
|
|
||||||
};
|
|
||||||
|
|
||||||
const setReject = (action, rej) => {
|
|
||||||
rejects[action] = rej;
|
|
||||||
};
|
|
||||||
|
|
||||||
const startJob = ({ id: jobId, action, payload }) => (
|
|
||||||
new Promise((resolve, reject) => {
|
|
||||||
const packet = {
|
|
||||||
workerId: id,
|
|
||||||
jobId,
|
|
||||||
action,
|
|
||||||
payload,
|
|
||||||
};
|
|
||||||
log(`[${id}]: Start ${jobId}, action=${action}`);
|
|
||||||
setResolve(action, resolve);
|
|
||||||
setReject(action, reject);
|
|
||||||
/*
|
|
||||||
* By using Transferable in postMessage, we are able
|
|
||||||
* to send large files to worker
|
|
||||||
* @ref: https://github.com/ffmpegjs/ffmpeg.js/issues/8#issuecomment-572230128
|
|
||||||
*/
|
|
||||||
worker.postMessage(packet, getTransferables(packet));
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
const load = (jobId) => (
|
|
||||||
startJob(createJob({
|
|
||||||
id: jobId, action: 'load', payload: { options },
|
|
||||||
}))
|
|
||||||
);
|
|
||||||
|
|
||||||
const write = async (path, data, jobId) => (
|
|
||||||
startJob(createJob({
|
|
||||||
id: jobId,
|
|
||||||
action: 'FS',
|
|
||||||
payload: {
|
|
||||||
method: 'writeFile',
|
|
||||||
args: [path, await fetchFile(data)],
|
|
||||||
},
|
|
||||||
}))
|
|
||||||
);
|
|
||||||
|
|
||||||
const writeText = (path, text, jobId) => (
|
|
||||||
startJob(createJob({
|
|
||||||
id: jobId,
|
|
||||||
action: 'FS',
|
|
||||||
payload: {
|
|
||||||
method: 'writeFile',
|
|
||||||
args: [path, text],
|
|
||||||
},
|
|
||||||
}))
|
|
||||||
);
|
|
||||||
|
|
||||||
const read = (path, jobId) => (
|
|
||||||
startJob(createJob({
|
|
||||||
id: jobId,
|
|
||||||
action: 'FS',
|
|
||||||
payload: {
|
|
||||||
method: 'readFile',
|
|
||||||
args: [path],
|
|
||||||
},
|
|
||||||
}))
|
|
||||||
);
|
|
||||||
|
|
||||||
const remove = (path, jobId) => (
|
|
||||||
startJob(createJob({
|
|
||||||
id: jobId,
|
|
||||||
action: 'FS',
|
|
||||||
payload: {
|
|
||||||
method: 'unlink',
|
|
||||||
args: [path],
|
|
||||||
},
|
|
||||||
}))
|
|
||||||
);
|
|
||||||
|
|
||||||
const run = (args, jobId) => (
|
|
||||||
startJob(createJob({
|
|
||||||
id: jobId,
|
|
||||||
action: 'run',
|
|
||||||
payload: {
|
|
||||||
args,
|
|
||||||
},
|
|
||||||
}))
|
|
||||||
);
|
|
||||||
|
|
||||||
const ls = (path, jobId) => (
|
|
||||||
startJob(createJob({
|
|
||||||
id: jobId,
|
|
||||||
action: 'FS',
|
|
||||||
payload: {
|
|
||||||
method: 'readdir',
|
|
||||||
args: [path],
|
|
||||||
},
|
|
||||||
}))
|
|
||||||
);
|
|
||||||
|
|
||||||
const transcode = (input, output, opts = '', jobId) => (
|
|
||||||
run(
|
|
||||||
`-i ${input} ${opts} ${output}`,
|
|
||||||
jobId,
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
const trim = (input, output, from, to, opts = '', jobId) => (
|
|
||||||
run(
|
|
||||||
`-i ${input} -ss ${from} -to ${to} ${opts} ${output}`,
|
|
||||||
jobId,
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
const concatDemuxer = async (input, output, opts = '', jobId) => {
|
|
||||||
const text = input.reduce((acc, path) => `${acc}\nfile ${path}`, '');
|
|
||||||
await writeText('concat_list.txt', text);
|
|
||||||
return run(`-f concat -safe 0 -i concat_list.txt ${opts} ${output}`, jobId);
|
|
||||||
};
|
|
||||||
|
|
||||||
const terminate = async () => {
|
|
||||||
if (worker !== null) {
|
|
||||||
/*
|
|
||||||
await startJob(createJob({
|
|
||||||
id: jobId,
|
|
||||||
action: 'terminate',
|
|
||||||
}));
|
|
||||||
*/
|
|
||||||
worker.terminate();
|
|
||||||
worker = null;
|
|
||||||
}
|
|
||||||
return Promise.resolve();
|
|
||||||
};
|
|
||||||
|
|
||||||
onMessage(worker, ({
|
|
||||||
workerId, jobId, action, status, payload,
|
|
||||||
}) => {
|
|
||||||
if (status === 'resolve') {
|
|
||||||
const { message, data } = payload;
|
|
||||||
log(`[${workerId}]: Complete ${jobId}`);
|
|
||||||
resolves[action]({
|
|
||||||
workerId,
|
|
||||||
jobId,
|
|
||||||
message,
|
|
||||||
data,
|
|
||||||
});
|
|
||||||
} else if (status === 'reject') {
|
|
||||||
rejects[action](payload);
|
|
||||||
throw Error(
|
|
||||||
`${payload}
|
|
||||||
|
|
||||||
To get more informaion for debugging, please add logger in createWorker():
|
|
||||||
|
|
||||||
const worker = createWorker({
|
|
||||||
logger: ({ message }) => console.log(message),
|
|
||||||
});
|
|
||||||
|
|
||||||
Even more details:
|
|
||||||
|
|
||||||
const { setLogging } = require('@ffmpeg/ffmpeg');
|
|
||||||
setLogging(true);
|
|
||||||
`,
|
|
||||||
);
|
|
||||||
} else if (status === 'progress') {
|
|
||||||
parseProgress(payload, progress);
|
|
||||||
logger(payload);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
id,
|
|
||||||
worker,
|
|
||||||
setResolve,
|
|
||||||
setReject,
|
|
||||||
load,
|
|
||||||
write,
|
|
||||||
writeText,
|
|
||||||
read,
|
|
||||||
remove,
|
|
||||||
ls,
|
|
||||||
run,
|
|
||||||
transcode,
|
|
||||||
trim,
|
|
||||||
concatDemuxer,
|
|
||||||
terminate,
|
|
||||||
};
|
|
||||||
};
|
|
7
src/index.js
Executable file → Normal file
7
src/index.js
Executable file → Normal file
@ -1,9 +1,6 @@
|
|||||||
require('regenerator-runtime/runtime');
|
require('regenerator-runtime/runtime');
|
||||||
const { logging, setLogging } = require('./utils/log');
|
const createFFmpeg = require('./createFFmpeg');
|
||||||
const createWorker = require('./createWorker');
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
logging,
|
createFFmpeg,
|
||||||
setLogging,
|
|
||||||
createWorker,
|
|
||||||
};
|
};
|
||||||
|
8
src/node/defaultOptions.js
Normal file
8
src/node/defaultOptions.js
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
const defaultOptions = require('../constants/defaultOptions');
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Default options for node environment
|
||||||
|
*/
|
||||||
|
module.exports = {
|
||||||
|
...defaultOptions,
|
||||||
|
};
|
6
src/node/getModule.js
Normal file
6
src/node/getModule.js
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
module.exports = () => new Promise((resolve) => {
|
||||||
|
const Module = require('@ffmpeg/core');
|
||||||
|
Module.onRuntimeInitialized = () => {
|
||||||
|
resolve(Module);
|
||||||
|
};
|
||||||
|
});
|
9
src/node/index.js
Normal file
9
src/node/index.js
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
const defaultOptions = require('./defaultOptions');
|
||||||
|
const getModule = require('./getModule');
|
||||||
|
const fetchFile = require('./fetchFile');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
defaultOptions,
|
||||||
|
getModule,
|
||||||
|
fetchFile,
|
||||||
|
};
|
@ -1,3 +0,0 @@
|
|||||||
module.exports = (prefix, cnt) => (
|
|
||||||
`${prefix}-${cnt}-${Math.random().toString(16).slice(3, 8)}`
|
|
||||||
);
|
|
@ -1,16 +0,0 @@
|
|||||||
module.exports = (packet) => {
|
|
||||||
const transferables = [];
|
|
||||||
const check = (b) => {
|
|
||||||
if (b instanceof Uint8Array) {
|
|
||||||
transferables.push(b.buffer);
|
|
||||||
} else if (b instanceof ArrayBuffer) {
|
|
||||||
transferables.push(b);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const { payload: { args, data } } = packet;
|
|
||||||
check(data);
|
|
||||||
if (Array.isArray(args)) {
|
|
||||||
args.forEach((arg) => check(arg));
|
|
||||||
}
|
|
||||||
return transferables;
|
|
||||||
};
|
|
@ -6,4 +6,4 @@ exports.setLogging = (_logging) => {
|
|||||||
logging = _logging;
|
logging = _logging;
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.log = (...args) => (logging ? console.log.apply(this, args) : null);
|
exports.log = (type, message) => (logging ? console.log(`[${type}] ${message}`) : null);
|
||||||
|
@ -3,7 +3,7 @@ const resolveURL = isBrowser ? require('resolve-url') : s => s; // eslint-disabl
|
|||||||
|
|
||||||
module.exports = (options) => {
|
module.exports = (options) => {
|
||||||
const opts = { ...options };
|
const opts = { ...options };
|
||||||
['corePath', 'workerPath'].forEach((key) => {
|
['corePath'].forEach((key) => {
|
||||||
if (typeof options[key] !== 'undefined') {
|
if (typeof options[key] !== 'undefined') {
|
||||||
opts[key] = resolveURL(opts[key]);
|
opts[key] = resolveURL(opts[key]);
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
const str2ptr = require('./str2ptr');
|
const string2pointer = require('./string2pointer');
|
||||||
|
|
||||||
module.exports = (Module, strList) => {
|
module.exports = (Module, strList) => {
|
||||||
const listPtr = Module._malloc(strList.length * Uint32Array.BYTES_PER_ELEMENT);
|
const listPtr = Module._malloc(strList.length * Uint32Array.BYTES_PER_ELEMENT);
|
||||||
|
|
||||||
strList.forEach((s, idx) => {
|
strList.forEach((s, idx) => {
|
||||||
const strPtr = str2ptr(Module, s);
|
const strPtr = string2pointer(Module, s);
|
||||||
Module.setValue(listPtr + (4 * idx), strPtr, 'i32');
|
Module.setValue(listPtr + (4 * idx), strPtr, 'i32');
|
||||||
});
|
});
|
||||||
|
|
@ -1,5 +0,0 @@
|
|||||||
{
|
|
||||||
"env": {
|
|
||||||
"worker": true
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,6 +0,0 @@
|
|||||||
module.exports = (corePath) => {
|
|
||||||
if (typeof global.Module === 'undefined') {
|
|
||||||
global.importScripts(corePath);
|
|
||||||
}
|
|
||||||
return global.Module;
|
|
||||||
};
|
|
@ -1,10 +0,0 @@
|
|||||||
const worker = require('..');
|
|
||||||
const getCore = require('./getCore');
|
|
||||||
|
|
||||||
global.addEventListener('message', ({ data }) => {
|
|
||||||
worker.dispatchHandlers(data, postMessage);
|
|
||||||
});
|
|
||||||
|
|
||||||
worker.setAdapter({
|
|
||||||
getCore,
|
|
||||||
});
|
|
@ -1,96 +0,0 @@
|
|||||||
require('regenerator-runtime/runtime');
|
|
||||||
const defaultArgs = require('./constants/defaultArgs');
|
|
||||||
const strList2ptr = require('./utils/strList2ptr');
|
|
||||||
const getTransferables = require('../utils/getTransferables');
|
|
||||||
|
|
||||||
const NO_LOAD_ERROR = 'FFmpegCore is not ready, make sure you have completed Worker.load().';
|
|
||||||
|
|
||||||
let action = 'unknown';
|
|
||||||
let Module = null;
|
|
||||||
let adapter = null;
|
|
||||||
let ffmpeg = null;
|
|
||||||
|
|
||||||
const load = ({ workerId, payload: { options: { corePath } } }, res) => {
|
|
||||||
if (Module === null) {
|
|
||||||
const Core = adapter.getCore(corePath);
|
|
||||||
Core()
|
|
||||||
.then(async (_Module) => {
|
|
||||||
Module = _Module;
|
|
||||||
Module.setLogger((message, type) => {
|
|
||||||
res.progress({
|
|
||||||
workerId, action, type, message,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
ffmpeg = Module.cwrap('ffmpeg', 'number', ['number', 'number']);
|
|
||||||
res.resolve({ message: 'Loaded ffmpeg-core' });
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
res.resolve({ message: 'Loaded ffmpeg-core' });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const FS = ({
|
|
||||||
payload: {
|
|
||||||
method,
|
|
||||||
args,
|
|
||||||
},
|
|
||||||
}, res) => {
|
|
||||||
if (Module === null) {
|
|
||||||
throw NO_LOAD_ERROR;
|
|
||||||
} else {
|
|
||||||
res.resolve({
|
|
||||||
message: `Complete ${method}`,
|
|
||||||
data: Module.FS[method](...args),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const run = ({
|
|
||||||
payload: {
|
|
||||||
args: _args,
|
|
||||||
},
|
|
||||||
}, res) => {
|
|
||||||
if (Module === null) {
|
|
||||||
throw NO_LOAD_ERROR;
|
|
||||||
} else {
|
|
||||||
const args = [...defaultArgs, ..._args.trim().split(' ')].filter((s) => s.length !== 0);
|
|
||||||
ffmpeg(args.length, strList2ptr(Module, args));
|
|
||||||
res.resolve({
|
|
||||||
message: `Complete ${args.join(' ')}`,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.dispatchHandlers = (packet, send) => {
|
|
||||||
const { workerId, jobId, action: act } = packet;
|
|
||||||
const res = (status, payload) => {
|
|
||||||
const pkt = {
|
|
||||||
workerId,
|
|
||||||
jobId,
|
|
||||||
action: act,
|
|
||||||
status,
|
|
||||||
payload,
|
|
||||||
};
|
|
||||||
send(pkt, getTransferables(pkt));
|
|
||||||
};
|
|
||||||
res.resolve = res.bind(this, 'resolve');
|
|
||||||
res.reject = res.bind(this, 'reject');
|
|
||||||
res.progress = res.bind(this, 'progress');
|
|
||||||
|
|
||||||
action = act;
|
|
||||||
try {
|
|
||||||
({
|
|
||||||
load,
|
|
||||||
FS,
|
|
||||||
run,
|
|
||||||
})[act](packet, res);
|
|
||||||
} catch (err) {
|
|
||||||
/** Prepare exception to travel through postMessage */
|
|
||||||
res.reject(err.toString());
|
|
||||||
}
|
|
||||||
action = 'unknown';
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.setAdapter = (_adapter) => {
|
|
||||||
adapter = _adapter;
|
|
||||||
};
|
|
@ -1,8 +0,0 @@
|
|||||||
let FFmpegCore = null;
|
|
||||||
|
|
||||||
module.exports = () => {
|
|
||||||
if (FFmpegCore === null) {
|
|
||||||
FFmpegCore = require('@ffmpeg/core');
|
|
||||||
}
|
|
||||||
return FFmpegCore;
|
|
||||||
};
|
|
@ -1,16 +0,0 @@
|
|||||||
const { parentPort } = require('worker_threads');
|
|
||||||
const worker = require('..');
|
|
||||||
const getCore = require('./getCore');
|
|
||||||
|
|
||||||
parentPort.on('message', (packet) => {
|
|
||||||
worker.dispatchHandlers(
|
|
||||||
packet,
|
|
||||||
(...args) => {
|
|
||||||
parentPort.postMessage(...args);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
worker.setAdapter({
|
|
||||||
getCore,
|
|
||||||
});
|
|
@ -1,15 +0,0 @@
|
|||||||
const resolveURL = require('resolve-url');
|
|
||||||
const { version, dependencies } = require('../../../package.json');
|
|
||||||
const defaultOptions = require('../../constants/defaultOptions');
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Default options for browser worker
|
|
||||||
*/
|
|
||||||
module.exports = {
|
|
||||||
...defaultOptions,
|
|
||||||
workerPath: (typeof process !== 'undefined' && process.env.FFMPEG_ENV === 'development')
|
|
||||||
? resolveURL(`/dist/worker.dev.js?nocache=${Math.random().toString(36).slice(3)}`)
|
|
||||||
: `https://unpkg.com/@ffmpeg/ffmpeg@v${version}/dist/worker.min.js`,
|
|
||||||
corePath: `https://unpkg.com/@ffmpeg/core@v${dependencies['@ffmpeg/core'].substring(1)}/ffmpeg-core.js`,
|
|
||||||
workerBlobURL: true,
|
|
||||||
};
|
|
@ -1,20 +0,0 @@
|
|||||||
/**
|
|
||||||
*
|
|
||||||
* Tesseract Worker adapter for browser
|
|
||||||
*
|
|
||||||
* @fileoverview Tesseract Worker adapter for browser
|
|
||||||
* @author Kevin Kwok <antimatter15@gmail.com>
|
|
||||||
* @author Guillermo Webster <gui@mit.edu>
|
|
||||||
* @author Jerome Wu <jeromewus@gmail.com>
|
|
||||||
*/
|
|
||||||
const defaultOptions = require('./defaultOptions');
|
|
||||||
const spawnWorker = require('./spawnWorker');
|
|
||||||
const onMessage = require('./onMessage');
|
|
||||||
const fetchFile = require('./fetchFile');
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
defaultOptions,
|
|
||||||
spawnWorker,
|
|
||||||
onMessage,
|
|
||||||
fetchFile,
|
|
||||||
};
|
|
@ -1,5 +0,0 @@
|
|||||||
module.exports = (worker, handler) => {
|
|
||||||
worker.onmessage = ({ data }) => { // eslint-disable-line
|
|
||||||
handler(data);
|
|
||||||
};
|
|
||||||
};
|
|
@ -1,20 +0,0 @@
|
|||||||
/**
|
|
||||||
* spawnWorker
|
|
||||||
*
|
|
||||||
* @name spawnWorker
|
|
||||||
* @function create a new Worker in browser
|
|
||||||
* @access public
|
|
||||||
*/
|
|
||||||
module.exports = ({ workerPath, workerBlobURL }) => {
|
|
||||||
let worker;
|
|
||||||
if (Blob && URL && workerBlobURL) {
|
|
||||||
/* Use Blob to load cross domain worker script */
|
|
||||||
const blob = new Blob([`importScripts("${workerPath}");`], {
|
|
||||||
type: 'text/javascript',
|
|
||||||
});
|
|
||||||
worker = new Worker(URL.createObjectURL(blob));
|
|
||||||
} else {
|
|
||||||
worker = new Worker(workerPath);
|
|
||||||
}
|
|
||||||
return worker;
|
|
||||||
};
|
|
@ -1,10 +0,0 @@
|
|||||||
const path = require('path');
|
|
||||||
const defaultOptions = require('../../constants/defaultOptions');
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Default options for node worker
|
|
||||||
*/
|
|
||||||
module.exports = {
|
|
||||||
...defaultOptions,
|
|
||||||
workerPath: path.join(__dirname, '..', '..', 'worker-script', 'node', 'index.js'),
|
|
||||||
};
|
|
@ -1,3 +0,0 @@
|
|||||||
module.exports = (worker, handler) => {
|
|
||||||
worker.on('message', handler);
|
|
||||||
};
|
|
@ -1,12 +0,0 @@
|
|||||||
const { Worker } = require('worker_threads');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* spawnWorker
|
|
||||||
*
|
|
||||||
* @name spawnWorker
|
|
||||||
* @function fork a new worker thread in node
|
|
||||||
* @access public
|
|
||||||
*/
|
|
||||||
module.exports = ({ workerPath }) => (
|
|
||||||
new Worker(workerPath)
|
|
||||||
);
|
|
BIN
tests/assets/StarWars3.wav
Normal file
BIN
tests/assets/StarWars3.wav
Normal file
Binary file not shown.
@ -1,11 +1,9 @@
|
|||||||
const TIMEOUT = 30000;
|
const TIMEOUT = 60000;
|
||||||
const BASE_URL = 'http://localhost:3000/tests/assets';
|
const BASE_URL = 'http://localhost:3000/tests/assets';
|
||||||
const IS_BROWSER = typeof window !== 'undefined' && typeof window.document !== 'undefined';
|
const IS_BROWSER = typeof window !== 'undefined' && typeof window.document !== 'undefined';
|
||||||
const OPTIONS = {
|
const OPTIONS = {
|
||||||
corePath: '../node_modules/@ffmpeg/core/ffmpeg-core.js',
|
corePath: '../node_modules/@ffmpeg/core/ffmpeg-core.js',
|
||||||
...(IS_BROWSER ? { workerPath: '../dist/worker.dev.js' } : {}),
|
|
||||||
};
|
};
|
||||||
const FLAME_MP4_LENGTH = 100374;
|
|
||||||
|
|
||||||
if (typeof module !== 'undefined') {
|
if (typeof module !== 'undefined') {
|
||||||
module.exports = {
|
module.exports = {
|
||||||
@ -13,6 +11,5 @@ if (typeof module !== 'undefined') {
|
|||||||
BASE_URL,
|
BASE_URL,
|
||||||
IS_BROWSER,
|
IS_BROWSER,
|
||||||
OPTIONS,
|
OPTIONS,
|
||||||
FLAME_MP4_LENGTH,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,20 +1,27 @@
|
|||||||
const { createWorker } = FFmpeg;
|
const { createFFmpeg } = FFmpeg;
|
||||||
const worker = createWorker(OPTIONS);
|
const ffmpeg = createFFmpeg(OPTIONS);
|
||||||
|
|
||||||
before(async function cb() {
|
before(async function cb() {
|
||||||
this.timeout(0);
|
this.timeout(0);
|
||||||
await worker.load();
|
await ffmpeg.load();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('transcode()', () => {
|
describe('transcode()', () => {
|
||||||
describe('should transcode different format', () => {
|
describe('should transcode different format', () => {
|
||||||
['flame.avi'].forEach((name) => (
|
[1, 2, 4].forEach((n) => {
|
||||||
it(`transcode ${name}`, async () => {
|
[
|
||||||
await worker.write(name, `${BASE_URL}/${name}`);
|
{ from: 'flame.avi', to: 'flame.mp4' },
|
||||||
await worker.transcode(name, 'output.mp4');
|
{ from: 'flame.avi', to: 'flame.webm' },
|
||||||
const { data } = await worker.read('output.mp4');
|
{ from: 'StarWars3.wav', to: 'StarWars3.mp3' },
|
||||||
expect(data.length).to.be(FLAME_MP4_LENGTH);
|
].forEach(({ from, to }) => (
|
||||||
|
it(`transcode ${from} to ${to} (${n} threads)`, async () => {
|
||||||
|
await ffmpeg.write(from, `${BASE_URL}/${from}`);
|
||||||
|
await ffmpeg.transcode(from, to, `-threads ${n}`);
|
||||||
|
const data = ffmpeg.read(to);
|
||||||
|
ffmpeg.remove(to);
|
||||||
|
expect(data.length).not.to.be(0);
|
||||||
}).timeout(TIMEOUT)
|
}).timeout(TIMEOUT)
|
||||||
));
|
));
|
||||||
});
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user