Refactor to worker version
This commit is contained in:
parent
99adf5138c
commit
9e5d0c5cc6
@ -1,6 +1,11 @@
|
|||||||
{
|
{
|
||||||
"extends": "airbnb-base",
|
"extends": "airbnb-base",
|
||||||
"rules": {
|
"rules": {
|
||||||
"no-underscore-dangle": 0
|
"no-underscore-dangle": 0,
|
||||||
|
"global-require": 0
|
||||||
|
},
|
||||||
|
"env": {
|
||||||
|
"browser": true,
|
||||||
|
"node": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,11 +25,13 @@ $ npm install @ffmpeg/ffmpeg
|
|||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const ffmpeg = require('@ffmpeg/ffmpeg');
|
const { createWorker } = require('@ffmpeg/ffmpeg');
|
||||||
|
|
||||||
|
const worker = createWorker();
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
await ffmpeg.load();
|
await worker.load();
|
||||||
const data = ffmpeg.transcode('./test.avi', 'mp4');
|
const { data } = await worker.transcode('./test.avi', 'mp4');
|
||||||
fs.wrieFileSync('./test.mp4', data);
|
fs.wrieFileSync('./test.mp4', data);
|
||||||
})();
|
})();
|
||||||
```
|
```
|
||||||
|
36
examples/browser/transcode.html
Normal file
36
examples/browser/transcode.html
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<script src="/dist/ffmpeg.dev.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<input type="file" id="uploader">
|
||||||
|
<video controls>
|
||||||
|
<source id="video-src" type="video/mp4">
|
||||||
|
</video>
|
||||||
|
<script>
|
||||||
|
const { createWorker } = FFmpeg;
|
||||||
|
const worker = createWorker({
|
||||||
|
corePath: '../../node_modules/@ffmpeg/core/ffmpeg-core.js',
|
||||||
|
});
|
||||||
|
|
||||||
|
const bufferToBase64 = (buf) => {
|
||||||
|
const binstr = Array.prototype.map.call(buf, function (ch) {
|
||||||
|
return String.fromCharCode(ch);
|
||||||
|
}).join('');
|
||||||
|
return btoa(binstr);
|
||||||
|
}
|
||||||
|
|
||||||
|
const transcode = async ({ target: { files } }) => {
|
||||||
|
await worker.load();
|
||||||
|
console.log('Start transcoding');
|
||||||
|
const { data } = await worker.transcode('/tests/assets/test.avi', 'mp4');
|
||||||
|
console.log(data);
|
||||||
|
console.log('Complete transcoding');
|
||||||
|
const b64 = bufferToBase64(data);
|
||||||
|
document.getElementById('video-src').setAttribute('src', `data:video/mp4;base64,${b64}`);
|
||||||
|
}
|
||||||
|
const elm = document.getElementById('uploader');
|
||||||
|
elm.addEventListener('change', transcode);
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
12
examples/node/transcode.js
Executable file
12
examples/node/transcode.js
Executable file
@ -0,0 +1,12 @@
|
|||||||
|
const { createWorker } = require('../../src');
|
||||||
|
|
||||||
|
const { argv } = process;
|
||||||
|
const [,, inputPath, outputPath] = argv;
|
||||||
|
|
||||||
|
const worker = createWorker();
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
await worker.load();
|
||||||
|
const { data } = await worker.transcode(inputPath, outputPath.split('.').pop());
|
||||||
|
console.log(data.length);
|
||||||
|
})();
|
@ -1,10 +0,0 @@
|
|||||||
const fs = require('fs');
|
|
||||||
const ffmpeg = require('../src');
|
|
||||||
const { argv } = process;
|
|
||||||
const [,, inputPath, outputPath] = argv;
|
|
||||||
|
|
||||||
(async () => {
|
|
||||||
await ffmpeg.load();
|
|
||||||
const data = ffmpeg.transcode(inputPath, outputPath.split('.').pop());
|
|
||||||
fs.writeFileSync(outputPath, data);
|
|
||||||
})();
|
|
36
package-lock.json
generated
36
package-lock.json
generated
@ -3056,8 +3056,7 @@
|
|||||||
"ansi-regex": {
|
"ansi-regex": {
|
||||||
"version": "2.1.1",
|
"version": "2.1.1",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true
|
||||||
"optional": true
|
|
||||||
},
|
},
|
||||||
"aproba": {
|
"aproba": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
@ -3098,8 +3097,7 @@
|
|||||||
"code-point-at": {
|
"code-point-at": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true
|
||||||
"optional": true
|
|
||||||
},
|
},
|
||||||
"concat-map": {
|
"concat-map": {
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
@ -3109,8 +3107,7 @@
|
|||||||
"console-control-strings": {
|
"console-control-strings": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true
|
||||||
"optional": true
|
|
||||||
},
|
},
|
||||||
"core-util-is": {
|
"core-util-is": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
@ -3227,8 +3224,7 @@
|
|||||||
"inherits": {
|
"inherits": {
|
||||||
"version": "2.0.3",
|
"version": "2.0.3",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true
|
||||||
"optional": true
|
|
||||||
},
|
},
|
||||||
"ini": {
|
"ini": {
|
||||||
"version": "1.3.5",
|
"version": "1.3.5",
|
||||||
@ -3240,7 +3236,6 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"optional": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"number-is-nan": "^1.0.0"
|
"number-is-nan": "^1.0.0"
|
||||||
}
|
}
|
||||||
@ -3366,8 +3361,7 @@
|
|||||||
"number-is-nan": {
|
"number-is-nan": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true
|
||||||
"optional": true
|
|
||||||
},
|
},
|
||||||
"object-assign": {
|
"object-assign": {
|
||||||
"version": "4.1.1",
|
"version": "4.1.1",
|
||||||
@ -3500,7 +3494,6 @@
|
|||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"optional": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"code-point-at": "^1.0.0",
|
"code-point-at": "^1.0.0",
|
||||||
"is-fullwidth-code-point": "^1.0.0",
|
"is-fullwidth-code-point": "^1.0.0",
|
||||||
@ -3520,7 +3513,6 @@
|
|||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"optional": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"ansi-regex": "^2.0.0"
|
"ansi-regex": "^2.0.0"
|
||||||
}
|
}
|
||||||
@ -4199,6 +4191,11 @@
|
|||||||
"has-symbols": "^1.0.0"
|
"has-symbols": "^1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"is-url": {
|
||||||
|
"version": "1.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz",
|
||||||
|
"integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww=="
|
||||||
|
},
|
||||||
"is-windows": {
|
"is-windows": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz",
|
||||||
@ -4848,6 +4845,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node-fetch": {
|
||||||
|
"version": "2.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz",
|
||||||
|
"integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA=="
|
||||||
|
},
|
||||||
"node-libs-browser": {
|
"node-libs-browser": {
|
||||||
"version": "2.2.1",
|
"version": "2.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.2.1.tgz",
|
||||||
@ -5530,6 +5532,11 @@
|
|||||||
"regenerate": "^1.4.0"
|
"regenerate": "^1.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"regenerator-runtime": {
|
||||||
|
"version": "0.13.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz",
|
||||||
|
"integrity": "sha512-naKIZz2GQ8JWh///G7L3X6LaQUAMp2lvb1rvwwsURe/VXwD6VMfr+/1NuNw3ag8v2kY1aQ/go5SNn79O9JU7yw=="
|
||||||
|
},
|
||||||
"regenerator-transform": {
|
"regenerator-transform": {
|
||||||
"version": "0.14.1",
|
"version": "0.14.1",
|
||||||
"resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.1.tgz",
|
"resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.1.tgz",
|
||||||
@ -5680,8 +5687,7 @@
|
|||||||
"resolve-url": {
|
"resolve-url": {
|
||||||
"version": "0.2.1",
|
"version": "0.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz",
|
||||||
"integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=",
|
"integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"restore-cursor": {
|
"restore-cursor": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
|
@ -32,7 +32,11 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://github.com/jeromewu/ffmpeg.js#readme",
|
"homepage": "https://github.com/jeromewu/ffmpeg.js#readme",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ffmpeg/core": "^0.1.0"
|
"@ffmpeg/core": "^0.1.0",
|
||||||
|
"is-url": "^1.2.4",
|
||||||
|
"node-fetch": "^2.6.0",
|
||||||
|
"regenerator-runtime": "^0.13.3",
|
||||||
|
"resolve-url": "^0.2.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.6.4",
|
"@babel/core": "^7.6.4",
|
||||||
|
@ -32,8 +32,8 @@ module.exports = [
|
|||||||
library: 'FFmpeg',
|
library: 'FFmpeg',
|
||||||
libraryTarget: 'umd',
|
libraryTarget: 'umd',
|
||||||
}),
|
}),
|
||||||
//genConfig({
|
genConfig({
|
||||||
// entry: path.resolve(__dirname, '..', 'src', 'worker-script', 'browser', 'index.js'),
|
entry: path.resolve(__dirname, '..', 'src', 'worker-script', 'browser', 'index.js'),
|
||||||
// filename: 'worker.dev.js',
|
filename: 'worker.dev.js',
|
||||||
//}),
|
}),
|
||||||
];
|
];
|
||||||
|
@ -23,8 +23,8 @@ module.exports = [
|
|||||||
library: 'FFmpeg',
|
library: 'FFmpeg',
|
||||||
libraryTarget: 'umd',
|
libraryTarget: 'umd',
|
||||||
}),
|
}),
|
||||||
//genConfig({
|
genConfig({
|
||||||
// entry: path.resolve(__dirname, '..', 'src', 'worker-script', 'browser', 'index.js'),
|
entry: path.resolve(__dirname, '..', 'src', 'worker-script', 'browser', 'index.js'),
|
||||||
// filename: 'worker.min.js',
|
filename: 'worker.min.js',
|
||||||
//}),
|
}),
|
||||||
];
|
];
|
||||||
|
3
src/constants/defaultOptions.js
Normal file
3
src/constants/defaultOptions.js
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
module.exports = {
|
||||||
|
logger: () => {},
|
||||||
|
};
|
21
src/createJob.js
Normal file
21
src/createJob.js
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
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,
|
||||||
|
};
|
||||||
|
};
|
109
src/createWorker.js
Normal file
109
src/createWorker.js
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
const createJob = require('./createJob');
|
||||||
|
const { log } = require('./utils/log');
|
||||||
|
const getId = require('./utils/getId');
|
||||||
|
const {
|
||||||
|
defaultOptions,
|
||||||
|
spawnWorker,
|
||||||
|
terminateWorker,
|
||||||
|
onMessage,
|
||||||
|
loadMedia,
|
||||||
|
send,
|
||||||
|
} = require('./worker/node');
|
||||||
|
|
||||||
|
let workerCounter = 0;
|
||||||
|
|
||||||
|
module.exports = (_options = {}) => {
|
||||||
|
const id = getId('Worker', workerCounter);
|
||||||
|
const {
|
||||||
|
logger,
|
||||||
|
...options
|
||||||
|
} = {
|
||||||
|
...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) => {
|
||||||
|
log(`[${id}]: Start ${jobId}, action=${action}`);
|
||||||
|
setResolve(action, resolve);
|
||||||
|
setReject(action, reject);
|
||||||
|
send(worker, {
|
||||||
|
workerId: id,
|
||||||
|
jobId,
|
||||||
|
action,
|
||||||
|
payload,
|
||||||
|
});
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const load = (jobId) => (
|
||||||
|
startJob(createJob({
|
||||||
|
id: jobId, action: 'load', payload: { options },
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
|
||||||
|
const transcode = async (media, outputExt, opts, jobId) => (
|
||||||
|
startJob(createJob({
|
||||||
|
id: jobId,
|
||||||
|
action: 'transcode',
|
||||||
|
payload: {
|
||||||
|
media: await loadMedia(media),
|
||||||
|
outputExt,
|
||||||
|
options: opts,
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
|
||||||
|
const terminate = async (jobId) => {
|
||||||
|
if (worker !== null) {
|
||||||
|
await startJob(createJob({
|
||||||
|
id: jobId,
|
||||||
|
action: 'terminate',
|
||||||
|
}));
|
||||||
|
terminateWorker(worker);
|
||||||
|
worker = null;
|
||||||
|
}
|
||||||
|
return Promise.resolve();
|
||||||
|
};
|
||||||
|
|
||||||
|
onMessage(worker, ({
|
||||||
|
workerId, jobId, status, action, data,
|
||||||
|
}) => {
|
||||||
|
if (status === 'resolve') {
|
||||||
|
log(`[${workerId}]: Complete ${jobId}`);
|
||||||
|
let d = data;
|
||||||
|
if (action === 'transcode') {
|
||||||
|
d = Array.from({ ...data, length: Object.keys(data).length });
|
||||||
|
}
|
||||||
|
resolves[action]({ jobId, data: d });
|
||||||
|
} else if (status === 'reject') {
|
||||||
|
rejects[action](data);
|
||||||
|
throw Error(data);
|
||||||
|
} else if (status === 'progress') {
|
||||||
|
logger(data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
worker,
|
||||||
|
setResolve,
|
||||||
|
setReject,
|
||||||
|
load,
|
||||||
|
transcode,
|
||||||
|
terminate,
|
||||||
|
};
|
||||||
|
};
|
@ -1,7 +1,6 @@
|
|||||||
const load = require('./load');
|
require('regenerator-runtime/runtime');
|
||||||
const transcode = require('./transcode');
|
const createWorker = require('./createWorker');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
load,
|
createWorker,
|
||||||
transcode,
|
|
||||||
};
|
};
|
||||||
|
12
src/load.js
12
src/load.js
@ -1,12 +0,0 @@
|
|||||||
const FFmpegCore = require('@ffmpeg/core');
|
|
||||||
const { setModule } = require('./util/module');
|
|
||||||
|
|
||||||
module.exports = () => (
|
|
||||||
new Promise((resolve) => {
|
|
||||||
FFmpegCore()
|
|
||||||
.then((Module) => {
|
|
||||||
setModule(Module);
|
|
||||||
resolve();
|
|
||||||
});
|
|
||||||
})
|
|
||||||
);
|
|
@ -1,17 +0,0 @@
|
|||||||
const fs = require('fs');
|
|
||||||
const { getModule } = require('./util/module');
|
|
||||||
const getFFmpeg = require('./util/getFFmpeg');
|
|
||||||
const strList2ptr = require('./util/strList2ptr');
|
|
||||||
const defaultArgs = require('./constants/defaultArgs');
|
|
||||||
|
|
||||||
module.exports = (inputPath, outputExt, options = '') => {
|
|
||||||
const Module = getModule();
|
|
||||||
const data = new Uint8Array(fs.readFileSync(inputPath));
|
|
||||||
const iPath = `file.${inputPath.split('.').pop()}`;
|
|
||||||
const oPath = `file.${outputExt}`;
|
|
||||||
const ffmpeg = getFFmpeg();
|
|
||||||
const args = [...defaultArgs, ...`${options} -i ${iPath} ${oPath}`.trim().split(' ')];
|
|
||||||
Module.FS.writeFile(iPath, data);
|
|
||||||
ffmpeg(args.length, strList2ptr(args));
|
|
||||||
return Buffer.from(Module.FS.readFile(oPath));
|
|
||||||
};
|
|
@ -1,6 +0,0 @@
|
|||||||
const { getModule } = require('./module');
|
|
||||||
|
|
||||||
module.exports = () => {
|
|
||||||
const Module = getModule();
|
|
||||||
return Module.cwrap('ffmpeg', 'number', ['number', 'number']);
|
|
||||||
};
|
|
@ -1,7 +0,0 @@
|
|||||||
let Module = null;
|
|
||||||
|
|
||||||
exports.setModule = (m) => {
|
|
||||||
Module = m;
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.getModule = () => Module;
|
|
@ -1,11 +0,0 @@
|
|||||||
const { getModule } = require('./module');
|
|
||||||
|
|
||||||
module.exports = (s) => {
|
|
||||||
const Module = getModule();
|
|
||||||
const ptr = Module._malloc((s.length + 1) * Uint8Array.BYTES_PER_ELEMENT);
|
|
||||||
for (let i = 0; i < s.length; i += 1) {
|
|
||||||
Module.setValue(ptr + i, s.charCodeAt(i), 'i8');
|
|
||||||
}
|
|
||||||
Module.setValue(ptr + s.length, 0, 'i8');
|
|
||||||
return ptr;
|
|
||||||
};
|
|
@ -1,14 +0,0 @@
|
|||||||
const { getModule } = require('./module');
|
|
||||||
const str2ptr = require('./str2ptr');
|
|
||||||
|
|
||||||
module.exports = (strList) => {
|
|
||||||
const Module = getModule();
|
|
||||||
const listPtr = Module._malloc(strList.length * Uint32Array.BYTES_PER_ELEMENT);
|
|
||||||
|
|
||||||
strList.forEach((s, idx) => {
|
|
||||||
const strPtr = str2ptr(s);
|
|
||||||
Module.setValue(listPtr + (4 * idx), strPtr, 'i32');
|
|
||||||
});
|
|
||||||
|
|
||||||
return listPtr;
|
|
||||||
};
|
|
3
src/utils/getId.js
Normal file
3
src/utils/getId.js
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
module.exports = (prefix, cnt) => (
|
||||||
|
`${prefix}-${cnt}-${Math.random().toString(16).slice(3, 8)}`
|
||||||
|
);
|
9
src/utils/log.js
Normal file
9
src/utils/log.js
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
let logging = false;
|
||||||
|
|
||||||
|
exports.logging = logging;
|
||||||
|
|
||||||
|
exports.setLogging = (_logging) => {
|
||||||
|
logging = _logging;
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.log = (...args) => (logging ? console.log.apply(this, args) : null);
|
5
src/worker-script/browser/.eslintrc
Normal file
5
src/worker-script/browser/.eslintrc
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"env": {
|
||||||
|
"worker": true
|
||||||
|
}
|
||||||
|
}
|
6
src/worker-script/browser/getCore.js
Normal file
6
src/worker-script/browser/getCore.js
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
module.exports = (corePath) => {
|
||||||
|
if (typeof global.Module === 'undefined') {
|
||||||
|
global.importScripts(corePath);
|
||||||
|
}
|
||||||
|
return global.Module;
|
||||||
|
};
|
10
src/worker-script/browser/index.js
Normal file
10
src/worker-script/browser/index.js
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
const worker = require('../');
|
||||||
|
const getCore = require('./getCore');
|
||||||
|
|
||||||
|
global.addEventListener('message', ({ data }) => {
|
||||||
|
worker.dispatchHandlers(data, (obj) => postMessage(obj));
|
||||||
|
});
|
||||||
|
|
||||||
|
worker.setAdapter({
|
||||||
|
getCore,
|
||||||
|
});
|
83
src/worker-script/index.js
Normal file
83
src/worker-script/index.js
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
require('regenerator-runtime/runtime');
|
||||||
|
const defaultArgs = require('./constants/defaultArgs');
|
||||||
|
|
||||||
|
let Module = null;
|
||||||
|
let adapter = null;
|
||||||
|
let ffmpeg = null;
|
||||||
|
|
||||||
|
const str2ptr = (s) => {
|
||||||
|
const ptr = Module._malloc((s.length + 1) * Uint8Array.BYTES_PER_ELEMENT);
|
||||||
|
for (let i = 0; i < s.length; i += 1) {
|
||||||
|
Module.setValue(ptr + i, s.charCodeAt(i), 'i8');
|
||||||
|
}
|
||||||
|
Module.setValue(ptr + s.length, 0, 'i8');
|
||||||
|
return ptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
const strList2ptr = (strList) => {
|
||||||
|
const listPtr = Module._malloc(strList.length * Uint32Array.BYTES_PER_ELEMENT);
|
||||||
|
|
||||||
|
strList.forEach((s, idx) => {
|
||||||
|
const strPtr = str2ptr(s);
|
||||||
|
Module.setValue(listPtr + (4 * idx), strPtr, 'i32');
|
||||||
|
});
|
||||||
|
|
||||||
|
return listPtr;
|
||||||
|
};
|
||||||
|
|
||||||
|
const load = ({ payload: { options: { corePath } } }, res) => {
|
||||||
|
if (Module == null) {
|
||||||
|
const Core = adapter.getCore(corePath);
|
||||||
|
Core()
|
||||||
|
.then((_Module) => {
|
||||||
|
Module = _Module;
|
||||||
|
ffmpeg = Module.cwrap('ffmpeg', 'number', ['number', 'number']);
|
||||||
|
res.resolve(true);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
res.resolve(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const transcode = ({
|
||||||
|
payload: {
|
||||||
|
media,
|
||||||
|
outputExt,
|
||||||
|
options = '',
|
||||||
|
},
|
||||||
|
}, res) => {
|
||||||
|
const data = Uint8Array.from({ ...media, length: Object.keys(media).length });
|
||||||
|
const iPath = 'media';
|
||||||
|
const oPath = `media.${outputExt}`;
|
||||||
|
const args = [...defaultArgs, ...`${options} -i ${iPath} ${oPath}`.trim().split(' ')];
|
||||||
|
Module.FS.writeFile(iPath, data);
|
||||||
|
ffmpeg(args.length, strList2ptr(args));
|
||||||
|
res.resolve(Module.FS.readFile(oPath));
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.dispatchHandlers = (packet, send) => {
|
||||||
|
const res = (status, data) => {
|
||||||
|
send({
|
||||||
|
...packet,
|
||||||
|
status,
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
res.resolve = res.bind(this, 'resolve');
|
||||||
|
res.reject = res.bind(this, 'reject');
|
||||||
|
res.progress = res.bind(this, 'progress');
|
||||||
|
|
||||||
|
try {
|
||||||
|
({
|
||||||
|
load,
|
||||||
|
transcode,
|
||||||
|
})[packet.action](packet, res);
|
||||||
|
} catch (err) {
|
||||||
|
/** Prepare exception to travel through postMessage */
|
||||||
|
res.reject(err.toString());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.setAdapter = (_adapter) => {
|
||||||
|
adapter = _adapter;
|
||||||
|
};
|
8
src/worker-script/node/getCore.js
Normal file
8
src/worker-script/node/getCore.js
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
let FFmpegCore = null;
|
||||||
|
|
||||||
|
module.exports = () => {
|
||||||
|
if (FFmpegCore === null) {
|
||||||
|
FFmpegCore = require('@ffmpeg/core');
|
||||||
|
}
|
||||||
|
return FFmpegCore;
|
||||||
|
};
|
10
src/worker-script/node/index.js
Normal file
10
src/worker-script/node/index.js
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
const worker = require('../');
|
||||||
|
const getCore = require('./getCore');
|
||||||
|
|
||||||
|
process.on('message', (packet) => {
|
||||||
|
worker.dispatchHandlers(packet, (obj) => process.send(obj));
|
||||||
|
});
|
||||||
|
|
||||||
|
worker.setAdapter({
|
||||||
|
getCore,
|
||||||
|
});
|
14
src/worker/browser/defaultOptions.js
Normal file
14
src/worker/browser/defaultOptions.js
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
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`,
|
||||||
|
};
|
24
src/worker/browser/index.js
Normal file
24
src/worker/browser/index.js
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
/**
|
||||||
|
*
|
||||||
|
* 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 terminateWorker = require('./terminateWorker');
|
||||||
|
const onMessage = require('./onMessage');
|
||||||
|
const send = require('./send');
|
||||||
|
const loadMedia = require('./loadMedia');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
defaultOptions,
|
||||||
|
spawnWorker,
|
||||||
|
terminateWorker,
|
||||||
|
onMessage,
|
||||||
|
send,
|
||||||
|
loadMedia,
|
||||||
|
};
|
46
src/worker/browser/loadMedia.js
Normal file
46
src/worker/browser/loadMedia.js
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
const resolveURL = require('resolve-url');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* readFromBlobOrFile
|
||||||
|
*
|
||||||
|
* @name readFromBlobOrFile
|
||||||
|
* @function
|
||||||
|
* @access private
|
||||||
|
*/
|
||||||
|
const readFromBlobOrFile = (blob) => (
|
||||||
|
new Promise((resolve, reject) => {
|
||||||
|
const fileReader = new FileReader();
|
||||||
|
fileReader.onload = () => {
|
||||||
|
resolve(fileReader.result);
|
||||||
|
};
|
||||||
|
fileReader.onerror = ({ target: { error: { code } } }) => {
|
||||||
|
reject(Error(`File could not be read! Code=${code}`));
|
||||||
|
};
|
||||||
|
fileReader.readAsArrayBuffer(blob);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const loadMedia = async (image) => {
|
||||||
|
let data = image;
|
||||||
|
if (typeof image === 'undefined') {
|
||||||
|
return 'undefined';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof image === 'string') {
|
||||||
|
// Base64 Media
|
||||||
|
if (/data:image\/([a-zA-Z]*);base64,([^"]*)/.test(image)) {
|
||||||
|
data = atob(image.split(',')[1])
|
||||||
|
.split('')
|
||||||
|
.map((c) => c.charCodeAt(0));
|
||||||
|
} else {
|
||||||
|
const res = await fetch(resolveURL(image));
|
||||||
|
data = res.arrayBuffer();
|
||||||
|
}
|
||||||
|
} else if (image instanceof File || image instanceof Blob) {
|
||||||
|
data = await readFromBlobOrFile(image);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Uint8Array(data);
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = loadMedia;
|
5
src/worker/browser/onMessage.js
Normal file
5
src/worker/browser/onMessage.js
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
module.exports = (worker, handler) => {
|
||||||
|
worker.onmessage = ({ data }) => { // eslint-disable-line
|
||||||
|
handler(data);
|
||||||
|
};
|
||||||
|
};
|
10
src/worker/browser/send.js
Normal file
10
src/worker/browser/send.js
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
/**
|
||||||
|
* send
|
||||||
|
*
|
||||||
|
* @name send
|
||||||
|
* @function send packet to worker and create a job
|
||||||
|
* @access public
|
||||||
|
*/
|
||||||
|
module.exports = async (worker, packet) => {
|
||||||
|
worker.postMessage(packet);
|
||||||
|
};
|
10
src/worker/browser/spawnWorker.js
Normal file
10
src/worker/browser/spawnWorker.js
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
/**
|
||||||
|
* spawnWorker
|
||||||
|
*
|
||||||
|
* @name spawnWorker
|
||||||
|
* @function create a new Worker in browser
|
||||||
|
* @access public
|
||||||
|
*/
|
||||||
|
module.exports = ({ workerPath }) => (
|
||||||
|
new Worker(workerPath)
|
||||||
|
);
|
10
src/worker/browser/terminateWorker.js
Normal file
10
src/worker/browser/terminateWorker.js
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
/**
|
||||||
|
* terminateWorker
|
||||||
|
*
|
||||||
|
* @name terminateWorker
|
||||||
|
* @function terminate worker
|
||||||
|
* @access public
|
||||||
|
*/
|
||||||
|
module.exports = (worker) => {
|
||||||
|
worker.terminate();
|
||||||
|
};
|
10
src/worker/node/defaultOptions.js
Normal file
10
src/worker/node/defaultOptions.js
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
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'),
|
||||||
|
};
|
24
src/worker/node/index.js
Normal file
24
src/worker/node/index.js
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
/**
|
||||||
|
*
|
||||||
|
* Tesseract Worker impl. for node (using child_process)
|
||||||
|
*
|
||||||
|
* @fileoverview Tesseract Worker impl. for node
|
||||||
|
* @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 terminateWorker = require('./terminateWorker');
|
||||||
|
const onMessage = require('./onMessage');
|
||||||
|
const send = require('./send');
|
||||||
|
const loadMedia = require('./loadMedia');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
defaultOptions,
|
||||||
|
spawnWorker,
|
||||||
|
terminateWorker,
|
||||||
|
onMessage,
|
||||||
|
send,
|
||||||
|
loadMedia,
|
||||||
|
};
|
28
src/worker/node/loadMedia.js
Normal file
28
src/worker/node/loadMedia.js
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
const util = require('util');
|
||||||
|
const fs = require('fs');
|
||||||
|
const fetch = require('node-fetch');
|
||||||
|
const isURL = require('is-url');
|
||||||
|
|
||||||
|
const readFile = util.promisify(fs.readFile);
|
||||||
|
|
||||||
|
module.exports = async (media) => {
|
||||||
|
let data = media;
|
||||||
|
if (typeof media === 'undefined') {
|
||||||
|
return media;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof media === 'string') {
|
||||||
|
if (isURL(media) || media.startsWith('chrome-extension://') || media.startsWith('file://')) {
|
||||||
|
const res = await fetch(media);
|
||||||
|
data = res.arrayBuffer();
|
||||||
|
} else if (/data:media\/([a-zA-Z]*);base64,([^"]*)/.test(media)) {
|
||||||
|
data = Buffer.from(media.split(',')[1], 'base64');
|
||||||
|
} else {
|
||||||
|
data = await readFile(media);
|
||||||
|
}
|
||||||
|
} else if (Buffer.isBuffer(media)) {
|
||||||
|
data = media;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Uint8Array(data);
|
||||||
|
};
|
3
src/worker/node/onMessage.js
Normal file
3
src/worker/node/onMessage.js
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
module.exports = (worker, handler) => {
|
||||||
|
worker.on('message', handler);
|
||||||
|
};
|
10
src/worker/node/send.js
Normal file
10
src/worker/node/send.js
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
/**
|
||||||
|
* send
|
||||||
|
*
|
||||||
|
* @name send
|
||||||
|
* @function send packet to worker and create a job
|
||||||
|
* @access public
|
||||||
|
*/
|
||||||
|
module.exports = (worker, packet) => {
|
||||||
|
worker.send(packet);
|
||||||
|
};
|
12
src/worker/node/spawnWorker.js
Normal file
12
src/worker/node/spawnWorker.js
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
const { fork } = require('child_process');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* spawnWorker
|
||||||
|
*
|
||||||
|
* @name spawnWorker
|
||||||
|
* @function fork a new process in node
|
||||||
|
* @access public
|
||||||
|
*/
|
||||||
|
module.exports = ({ workerPath }) => (
|
||||||
|
fork(workerPath)
|
||||||
|
);
|
10
src/worker/node/terminateWorker.js
Normal file
10
src/worker/node/terminateWorker.js
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
/**
|
||||||
|
* terminateWorker
|
||||||
|
*
|
||||||
|
* @name terminateWorker
|
||||||
|
* @function kill worker
|
||||||
|
* @access public
|
||||||
|
*/
|
||||||
|
module.exports = (worker) => {
|
||||||
|
worker.kill();
|
||||||
|
};
|
Loading…
Reference in New Issue
Block a user