diff --git a/.eslintrc b/.eslintrc
index f979295..75b146d 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -2,6 +2,11 @@
"extends": "airbnb-base",
"rules": {
"no-underscore-dangle": 0,
- "linebreak-style": 0,
+ "linebreak-style": 0
+ "global-require": 0
+ },
+ "env": {
+ "browser": true,
+ "node": true
}
}
diff --git a/README.md b/README.md
index 31f9f3b..200ce13 100644
--- a/README.md
+++ b/README.md
@@ -28,11 +28,13 @@ $ npm install @ffmpeg/ffmpeg
```javascript
const fs = require('fs');
-const ffmpeg = require('@ffmpeg/ffmpeg');
+const { createWorker } = require('@ffmpeg/ffmpeg');
+
+const worker = createWorker();
(async () => {
- await ffmpeg.load();
- const data = ffmpeg.transcode('./test.avi', 'mp4');
+ await worker.load();
+ const { data } = await worker.transcode('./test.avi', 'mp4');
fs.wrieFileSync('./test.mp4', data);
})();
```
diff --git a/examples/browser/transcode.html b/examples/browser/transcode.html
new file mode 100644
index 0000000..2e05c7a
--- /dev/null
+++ b/examples/browser/transcode.html
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+ Upload a video to transcode to mp4 (x264) and play!
+
+
+
+
+
+
diff --git a/examples/node/transcode.js b/examples/node/transcode.js
new file mode 100755
index 0000000..3afac2f
--- /dev/null
+++ b/examples/node/transcode.js
@@ -0,0 +1,18 @@
+const fs = require('fs');
+const { createWorker } = require('../../src');
+
+const { argv } = process;
+const [,, inputPath, outputPath] = argv;
+
+const worker = createWorker({
+ logger: ({ message }) => console.log(message),
+});
+
+(async () => {
+ await worker.load();
+ console.log('Start transcoding');
+ const { data } = await worker.transcode(inputPath, outputPath.split('.').pop());
+ console.log('Complete transcoding');
+ fs.writeFileSync(outputPath, Buffer.from(data));
+ process.exit(0);
+})();
diff --git a/examples/transcode.js b/examples/transcode.js
deleted file mode 100755
index 724a182..0000000
--- a/examples/transcode.js
+++ /dev/null
@@ -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);
-})();
diff --git a/package-lock.json b/package-lock.json
index c82a8ec..c66adc5 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -811,9 +811,9 @@
}
},
"@ffmpeg/core": {
- "version": "0.1.0",
- "resolved": "https://registry.npmjs.org/@ffmpeg/core/-/core-0.1.0.tgz",
- "integrity": "sha512-a4/HokRytKFJDW+RVvuf22rUzjNgXZKhlF6FIVBcLACIDcCEQZ0uCUC+5DZb+xprCvdMyXm0wy90uoYLRTq+LA=="
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/@ffmpeg/core/-/core-0.3.0.tgz",
+ "integrity": "sha512-bm95T4C2/glYHuzTW7r49Ia+CCSYCbmOe0VOhvCorR3eBzPV/4ESuA4LxdouIraOHueDZf6rcPik94Jv1+t0iQ=="
},
"@webassemblyjs/ast": {
"version": "1.8.5",
@@ -3056,8 +3056,7 @@
"ansi-regex": {
"version": "2.1.1",
"bundled": true,
- "dev": true,
- "optional": true
+ "dev": true
},
"aproba": {
"version": "1.2.0",
@@ -3078,14 +3077,12 @@
"balanced-match": {
"version": "1.0.0",
"bundled": true,
- "dev": true,
- "optional": true
+ "dev": true
},
"brace-expansion": {
"version": "1.1.11",
"bundled": true,
"dev": true,
- "optional": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@@ -3100,20 +3097,17 @@
"code-point-at": {
"version": "1.1.0",
"bundled": true,
- "dev": true,
- "optional": true
+ "dev": true
},
"concat-map": {
"version": "0.0.1",
"bundled": true,
- "dev": true,
- "optional": true
+ "dev": true
},
"console-control-strings": {
"version": "1.1.0",
"bundled": true,
- "dev": true,
- "optional": true
+ "dev": true
},
"core-util-is": {
"version": "1.0.2",
@@ -3230,8 +3224,7 @@
"inherits": {
"version": "2.0.3",
"bundled": true,
- "dev": true,
- "optional": true
+ "dev": true
},
"ini": {
"version": "1.3.5",
@@ -3243,7 +3236,6 @@
"version": "1.0.0",
"bundled": true,
"dev": true,
- "optional": true,
"requires": {
"number-is-nan": "^1.0.0"
}
@@ -3258,7 +3250,6 @@
"version": "3.0.4",
"bundled": true,
"dev": true,
- "optional": true,
"requires": {
"brace-expansion": "^1.1.7"
}
@@ -3266,14 +3257,12 @@
"minimist": {
"version": "0.0.8",
"bundled": true,
- "dev": true,
- "optional": true
+ "dev": true
},
"minipass": {
"version": "2.3.5",
"bundled": true,
"dev": true,
- "optional": true,
"requires": {
"safe-buffer": "^5.1.2",
"yallist": "^3.0.0"
@@ -3292,7 +3281,6 @@
"version": "0.5.1",
"bundled": true,
"dev": true,
- "optional": true,
"requires": {
"minimist": "0.0.8"
}
@@ -3373,8 +3361,7 @@
"number-is-nan": {
"version": "1.0.1",
"bundled": true,
- "dev": true,
- "optional": true
+ "dev": true
},
"object-assign": {
"version": "4.1.1",
@@ -3386,7 +3373,6 @@
"version": "1.4.0",
"bundled": true,
"dev": true,
- "optional": true,
"requires": {
"wrappy": "1"
}
@@ -3508,7 +3494,6 @@
"version": "1.0.2",
"bundled": true,
"dev": true,
- "optional": true,
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
@@ -3528,7 +3513,6 @@
"version": "3.0.1",
"bundled": true,
"dev": true,
- "optional": true,
"requires": {
"ansi-regex": "^2.0.0"
}
@@ -4207,6 +4191,11 @@
"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": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz",
@@ -4856,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": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.2.1.tgz",
@@ -5538,6 +5532,11 @@
"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": {
"version": "0.14.1",
"resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.1.tgz",
@@ -5688,8 +5687,7 @@
"resolve-url": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz",
- "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=",
- "dev": true
+ "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo="
},
"restore-cursor": {
"version": "2.0.0",
diff --git a/package.json b/package.json
index 258dc98..86c3a61 100644
--- a/package.json
+++ b/package.json
@@ -32,7 +32,11 @@
},
"homepage": "https://github.com/ffmpegjs/ffmpeg.js#readme",
"dependencies": {
- "@ffmpeg/core": "^0.1.0"
+ "@ffmpeg/core": "^0.3.0",
+ "is-url": "^1.2.4",
+ "node-fetch": "^2.6.0",
+ "regenerator-runtime": "^0.13.3",
+ "resolve-url": "^0.2.1"
},
"devDependencies": {
"@babel/core": "^7.6.4",
diff --git a/scripts/webpack.config.dev.js b/scripts/webpack.config.dev.js
index f40b444..25299a7 100644
--- a/scripts/webpack.config.dev.js
+++ b/scripts/webpack.config.dev.js
@@ -32,8 +32,8 @@ module.exports = [
library: 'FFmpeg',
libraryTarget: 'umd',
}),
- //genConfig({
- // entry: path.resolve(__dirname, '..', 'src', 'worker-script', 'browser', 'index.js'),
- // filename: 'worker.dev.js',
- //}),
+ genConfig({
+ entry: path.resolve(__dirname, '..', 'src', 'worker-script', 'browser', 'index.js'),
+ filename: 'worker.dev.js',
+ }),
];
diff --git a/scripts/webpack.config.prod.js b/scripts/webpack.config.prod.js
index 2948874..74ef83d 100644
--- a/scripts/webpack.config.prod.js
+++ b/scripts/webpack.config.prod.js
@@ -23,8 +23,8 @@ module.exports = [
library: 'FFmpeg',
libraryTarget: 'umd',
}),
- //genConfig({
- // entry: path.resolve(__dirname, '..', 'src', 'worker-script', 'browser', 'index.js'),
- // filename: 'worker.min.js',
- //}),
+ genConfig({
+ entry: path.resolve(__dirname, '..', 'src', 'worker-script', 'browser', 'index.js'),
+ filename: 'worker.min.js',
+ }),
];
diff --git a/src/constants/defaultOptions.js b/src/constants/defaultOptions.js
new file mode 100644
index 0000000..0a82163
--- /dev/null
+++ b/src/constants/defaultOptions.js
@@ -0,0 +1,3 @@
+module.exports = {
+ logger: () => {},
+};
diff --git a/src/createJob.js b/src/createJob.js
new file mode 100644
index 0000000..f90364d
--- /dev/null
+++ b/src/createJob.js
@@ -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,
+ };
+};
diff --git a/src/createWorker.js b/src/createWorker.js
new file mode 100644
index 0000000..a8469e1
--- /dev/null
+++ b/src/createWorker.js
@@ -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 = Uint8Array.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,
+ };
+};
diff --git a/src/index.js b/src/index.js
index d1c275c..ab96c68 100755
--- a/src/index.js
+++ b/src/index.js
@@ -1,7 +1,9 @@
-const load = require('./load');
-const transcode = require('./transcode');
+require('regenerator-runtime/runtime');
+const { logging, setLogging } = require('./utils/log');
+const createWorker = require('./createWorker');
module.exports = {
- load,
- transcode,
+ logging,
+ setLogging,
+ createWorker,
};
diff --git a/src/load.js b/src/load.js
deleted file mode 100755
index e1fe32c..0000000
--- a/src/load.js
+++ /dev/null
@@ -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();
- });
- })
-);
diff --git a/src/transcode.js b/src/transcode.js
deleted file mode 100755
index 5197b8e..0000000
--- a/src/transcode.js
+++ /dev/null
@@ -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));
-};
diff --git a/src/util/getFFmpeg.js b/src/util/getFFmpeg.js
deleted file mode 100755
index 18c6692..0000000
--- a/src/util/getFFmpeg.js
+++ /dev/null
@@ -1,6 +0,0 @@
-const { getModule } = require('./module');
-
-module.exports = () => {
- const Module = getModule();
- return Module.cwrap('ffmpeg', 'number', ['number', 'number']);
-};
diff --git a/src/util/module.js b/src/util/module.js
deleted file mode 100755
index 3e7adf3..0000000
--- a/src/util/module.js
+++ /dev/null
@@ -1,7 +0,0 @@
-let Module = null;
-
-exports.setModule = (m) => {
- Module = m;
-};
-
-exports.getModule = () => Module;
diff --git a/src/util/str2ptr.js b/src/util/str2ptr.js
deleted file mode 100755
index c3c8e88..0000000
--- a/src/util/str2ptr.js
+++ /dev/null
@@ -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;
-};
diff --git a/src/util/strList2ptr.js b/src/util/strList2ptr.js
deleted file mode 100755
index d5bd680..0000000
--- a/src/util/strList2ptr.js
+++ /dev/null
@@ -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;
-};
diff --git a/src/utils/getId.js b/src/utils/getId.js
new file mode 100644
index 0000000..8c2b119
--- /dev/null
+++ b/src/utils/getId.js
@@ -0,0 +1,3 @@
+module.exports = (prefix, cnt) => (
+ `${prefix}-${cnt}-${Math.random().toString(16).slice(3, 8)}`
+);
diff --git a/src/utils/log.js b/src/utils/log.js
new file mode 100644
index 0000000..4faac26
--- /dev/null
+++ b/src/utils/log.js
@@ -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);
diff --git a/src/worker-script/browser/.eslintrc b/src/worker-script/browser/.eslintrc
new file mode 100644
index 0000000..e88da2e
--- /dev/null
+++ b/src/worker-script/browser/.eslintrc
@@ -0,0 +1,5 @@
+{
+ "env": {
+ "worker": true
+ }
+}
diff --git a/src/worker-script/browser/getCore.js b/src/worker-script/browser/getCore.js
new file mode 100644
index 0000000..521d008
--- /dev/null
+++ b/src/worker-script/browser/getCore.js
@@ -0,0 +1,6 @@
+module.exports = (corePath) => {
+ if (typeof global.Module === 'undefined') {
+ global.importScripts(corePath);
+ }
+ return global.Module;
+};
diff --git a/src/worker-script/browser/index.js b/src/worker-script/browser/index.js
new file mode 100644
index 0000000..0ce1497
--- /dev/null
+++ b/src/worker-script/browser/index.js
@@ -0,0 +1,10 @@
+const worker = require('../');
+const getCore = require('./getCore');
+
+global.addEventListener('message', ({ data }) => {
+ worker.dispatchHandlers(data, (obj) => postMessage(obj));
+});
+
+worker.setAdapter({
+ getCore,
+});
diff --git a/src/constants/defaultArgs.js b/src/worker-script/constants/defaultArgs.js
similarity index 81%
rename from src/constants/defaultArgs.js
rename to src/worker-script/constants/defaultArgs.js
index 16aacaa..55aae4f 100755
--- a/src/constants/defaultArgs.js
+++ b/src/worker-script/constants/defaultArgs.js
@@ -1,6 +1,4 @@
module.exports = [
'./ffmpeg', // args[0] is always binary path
'-nostdin', // Disable interaction mode
- '-loglevel',
- 'quiet',
];
diff --git a/src/worker-script/index.js b/src/worker-script/index.js
new file mode 100644
index 0000000..071287f
--- /dev/null
+++ b/src/worker-script/index.js
@@ -0,0 +1,92 @@
+require('regenerator-runtime/runtime');
+const defaultArgs = require('./constants/defaultArgs');
+
+let action = 'unknown';
+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 = ({ workerId, payload: { options: { corePath } } }, res) => {
+ if (Module == null) {
+ const Core = adapter.getCore(corePath);
+ Core()
+ .then((_Module) => {
+ Module = _Module;
+ Module.setLogger((message, type) => {
+ res.progress({ workerId, action, type, message });
+ });
+ 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 file:${iPath} ${oPath}`.trim().split(' ')];
+ Module.FS.writeFile(iPath, data);
+ ffmpeg(args.length, strList2ptr(args));
+ const out = Module.FS.readFile(oPath);
+ Module.FS.unlink(iPath);
+ Module.FS.unlink(oPath);
+ res.resolve(out);
+};
+
+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');
+
+ action = packet.action
+ try {
+ ({
+ load,
+ transcode,
+ })[packet.action](packet, res);
+ } catch (err) {
+ /** Prepare exception to travel through postMessage */
+ res.reject(err.toString());
+ }
+ action = 'unknown';
+};
+
+exports.setAdapter = (_adapter) => {
+ adapter = _adapter;
+};
diff --git a/src/worker-script/node/getCore.js b/src/worker-script/node/getCore.js
new file mode 100644
index 0000000..af4bb5f
--- /dev/null
+++ b/src/worker-script/node/getCore.js
@@ -0,0 +1,8 @@
+let FFmpegCore = null;
+
+module.exports = () => {
+ if (FFmpegCore === null) {
+ FFmpegCore = require('@ffmpeg/core');
+ }
+ return FFmpegCore;
+};
diff --git a/src/worker-script/node/index.js b/src/worker-script/node/index.js
new file mode 100644
index 0000000..f59ff58
--- /dev/null
+++ b/src/worker-script/node/index.js
@@ -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,
+});
diff --git a/src/worker/browser/defaultOptions.js b/src/worker/browser/defaultOptions.js
new file mode 100644
index 0000000..55146c0
--- /dev/null
+++ b/src/worker/browser/defaultOptions.js
@@ -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`,
+};
diff --git a/src/worker/browser/index.js b/src/worker/browser/index.js
new file mode 100644
index 0000000..ab562a0
--- /dev/null
+++ b/src/worker/browser/index.js
@@ -0,0 +1,24 @@
+/**
+ *
+ * Tesseract Worker adapter for browser
+ *
+ * @fileoverview Tesseract Worker adapter for browser
+ * @author Kevin Kwok
+ * @author Guillermo Webster
+ * @author Jerome Wu
+ */
+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,
+};
diff --git a/src/worker/browser/loadMedia.js b/src/worker/browser/loadMedia.js
new file mode 100644
index 0000000..818bbcc
--- /dev/null
+++ b/src/worker/browser/loadMedia.js
@@ -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;
diff --git a/src/worker/browser/onMessage.js b/src/worker/browser/onMessage.js
new file mode 100644
index 0000000..ec74783
--- /dev/null
+++ b/src/worker/browser/onMessage.js
@@ -0,0 +1,5 @@
+module.exports = (worker, handler) => {
+ worker.onmessage = ({ data }) => { // eslint-disable-line
+ handler(data);
+ };
+};
diff --git a/src/worker/browser/send.js b/src/worker/browser/send.js
new file mode 100644
index 0000000..88f8aaf
--- /dev/null
+++ b/src/worker/browser/send.js
@@ -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);
+};
diff --git a/src/worker/browser/spawnWorker.js b/src/worker/browser/spawnWorker.js
new file mode 100644
index 0000000..5604aa4
--- /dev/null
+++ b/src/worker/browser/spawnWorker.js
@@ -0,0 +1,10 @@
+/**
+ * spawnWorker
+ *
+ * @name spawnWorker
+ * @function create a new Worker in browser
+ * @access public
+ */
+module.exports = ({ workerPath }) => (
+ new Worker(workerPath)
+);
diff --git a/src/worker/browser/terminateWorker.js b/src/worker/browser/terminateWorker.js
new file mode 100644
index 0000000..753a3fd
--- /dev/null
+++ b/src/worker/browser/terminateWorker.js
@@ -0,0 +1,10 @@
+/**
+ * terminateWorker
+ *
+ * @name terminateWorker
+ * @function terminate worker
+ * @access public
+ */
+module.exports = (worker) => {
+ worker.terminate();
+};
diff --git a/src/worker/node/defaultOptions.js b/src/worker/node/defaultOptions.js
new file mode 100644
index 0000000..053c1e3
--- /dev/null
+++ b/src/worker/node/defaultOptions.js
@@ -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'),
+};
diff --git a/src/worker/node/index.js b/src/worker/node/index.js
new file mode 100644
index 0000000..1911335
--- /dev/null
+++ b/src/worker/node/index.js
@@ -0,0 +1,24 @@
+/**
+ *
+ * Tesseract Worker impl. for node (using child_process)
+ *
+ * @fileoverview Tesseract Worker impl. for node
+ * @author Kevin Kwok
+ * @author Guillermo Webster
+ * @author Jerome Wu
+ */
+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,
+};
diff --git a/src/worker/node/loadMedia.js b/src/worker/node/loadMedia.js
new file mode 100644
index 0000000..f30fc09
--- /dev/null
+++ b/src/worker/node/loadMedia.js
@@ -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);
+};
diff --git a/src/worker/node/onMessage.js b/src/worker/node/onMessage.js
new file mode 100644
index 0000000..7100e71
--- /dev/null
+++ b/src/worker/node/onMessage.js
@@ -0,0 +1,3 @@
+module.exports = (worker, handler) => {
+ worker.on('message', handler);
+};
diff --git a/src/worker/node/send.js b/src/worker/node/send.js
new file mode 100644
index 0000000..783c6e1
--- /dev/null
+++ b/src/worker/node/send.js
@@ -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);
+};
diff --git a/src/worker/node/spawnWorker.js b/src/worker/node/spawnWorker.js
new file mode 100644
index 0000000..e3c5d89
--- /dev/null
+++ b/src/worker/node/spawnWorker.js
@@ -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)
+);
diff --git a/src/worker/node/terminateWorker.js b/src/worker/node/terminateWorker.js
new file mode 100644
index 0000000..0e8b67e
--- /dev/null
+++ b/src/worker/node/terminateWorker.js
@@ -0,0 +1,10 @@
+/**
+ * terminateWorker
+ *
+ * @name terminateWorker
+ * @function kill worker
+ * @access public
+ */
+module.exports = (worker) => {
+ worker.kill();
+};
diff --git a/tests/assets/flame.avi b/tests/assets/flame.avi
new file mode 100644
index 0000000..e8e96e3
Binary files /dev/null and b/tests/assets/flame.avi differ
diff --git a/tests/assets/test.avi b/tests/assets/test.avi
deleted file mode 100755
index cc5ef6a..0000000
Binary files a/tests/assets/test.avi and /dev/null differ