├── .babelrc.js
├── .gitignore
├── .prettierignore
├── README.MD
├── commitlint.config.js
├── dist
├── es
│ ├── FFMPEGWebWorker.js
│ ├── FFMPEGWebWorkerClient.js
│ └── index.js
└── index.js
├── docs
├── build
│ └── bundle.f3381c3c.js
└── index.html
├── examples
└── theme.css
├── package.json
├── rollup.config.js
├── src
├── FFMPEGWebWorker.js
├── FFMPEGWebWorkerClient.js
└── index.js
├── styleguide.config.js
├── testSetup.js
├── typings
└── ffmpeg-webworker.d.ts
└── yarn.lock
/.babelrc.js:
--------------------------------------------------------------------------------
1 | // https://babeljs.io/docs/en/configuration
2 | const presets = ["@babel/preset-react"];
3 | if (process.env["BABEL_ENV"] === "es") {
4 | presets.unshift(["@babel/preset-env", { modules: false }]);
5 | } else {
6 | presets.unshift("@babel/preset-env");
7 | }
8 |
9 | const plugins = [
10 | "@babel/plugin-proposal-export-default-from",
11 | "@babel/plugin-proposal-logical-assignment-operators",
12 | ["@babel/plugin-proposal-optional-chaining", { loose: false }],
13 | ["@babel/plugin-proposal-pipeline-operator", { proposal: "minimal" }],
14 | ["@babel/plugin-proposal-nullish-coalescing-operator", { loose: false }],
15 | "@babel/plugin-proposal-class-properties",
16 | "@babel/plugin-proposal-do-expressions",
17 | "add-module-exports"
18 | ];
19 |
20 | module.exports = {
21 | presets,
22 | plugins,
23 | env: {
24 | test: {
25 | presets,
26 | plugins: [
27 | ...plugins,
28 | "@babel/plugin-transform-runtime",
29 | "dynamic-import-node"
30 | ]
31 | }
32 | }
33 | };
34 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | lerna-debug.log*
8 |
9 | # Diagnostic reports (https://nodejs.org/api/report.html)
10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
11 |
12 | # Runtime data
13 | pids
14 | *.pid
15 | *.seed
16 | *.pid.lock
17 |
18 | # Directory for instrumented libs generated by jscoverage/JSCover
19 | lib-cov
20 |
21 | # Coverage directory used by tools like istanbul
22 | coverage
23 | *.lcov
24 |
25 | # nyc test coverage
26 | .nyc_output
27 |
28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
29 | .grunt
30 |
31 | # Bower dependency directory (https://bower.io/)
32 | bower_components
33 |
34 | # node-waf configuration
35 | .lock-wscript
36 |
37 | # Compiled binary addons (https://nodejs.org/api/addons.html)
38 | build/Release
39 |
40 | # Dependency directories
41 | node_modules/
42 | jspm_packages/
43 |
44 | # TypeScript v1 declaration files
45 | # typings/
46 |
47 | # TypeScript cache
48 | *.tsbuildinfo
49 |
50 | # Optional npm cache directory
51 | .npm
52 |
53 | # Optional eslint cache
54 | .eslintcache
55 |
56 | # Optional REPL history
57 | .node_repl_history
58 |
59 | # Output of 'npm pack'
60 | *.tgz
61 |
62 | # Yarn Integrity file
63 | .yarn-integrity
64 |
65 | # dotenv environment variables file
66 | .env
67 | .env.test
68 |
69 | # parcel-bundler cache (https://parceljs.org/)
70 | .cache
71 |
72 | # next.js build output
73 | .next
74 |
75 | # nuxt.js build output
76 | .nuxt
77 |
78 | # vuepress build output
79 | .vuepress/dist
80 |
81 | # Serverless directories
82 | .serverless/
83 |
84 | # FuseBox cache
85 | .fusebox/
86 |
87 | # DynamoDB Local files
88 | .dynamodb/
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | dist/
2 | docs/
3 | node_modules/
--------------------------------------------------------------------------------
/README.MD:
--------------------------------------------------------------------------------
1 | # ffmpeg-webworker
2 |
3 | A WebWorker implementation that eases the use of ffmpeg library in the browser.
4 |
5 | This builds upon an existing work geniusly done by the folks at
6 | [Ffmpeg.js](https://github.com/muaz-khan/Ffmpeg.js/) and
7 | [videoconverter.js](https://github.com/bgrins/videoconverter.js)
8 |
9 | ## Demo
10 |
11 | [See it here](https://limistah.github.io/ffmpeg-webworker)
12 |
13 | ## Installation
14 |
15 | ```bash
16 | npm install --save ffmpeg-webworker
17 | ```
18 |
19 | or:
20 |
21 | ```bash
22 | yarn add ffmpeg-webworker
23 | ```
24 |
25 | ## Usage
26 |
27 | ```js
28 | import workerClient from "ffmpeg-webworker";
29 | import React from "react";
30 |
31 | class FFMPEG extends React.Component {
32 | constructor(props) {
33 | super(props);
34 | this.state = {
35 | stdOutputText: "",
36 | ffmpegReady: false
37 | };
38 |
39 | this.updateStdOutputText = this.updateStdOutputText.bind(this);
40 | this.handleInputChange = this.handleInputChange.bind(this);
41 | this.handleListCodecs = this.handleListCodecs.bind(this);
42 | }
43 |
44 | componentWillMount() {
45 | workerClient.on("onReady", () => this.setState({ ffmpegReady: true }));
46 | workerClient.on("onStdout", msg => this.updateStdOutputText(msg));
47 | workerClient.on("onFileReceived", msg => this.updateStdOutputText(msg));
48 | workerClient.on("onDone", data => {
49 | this.updateStdOutputText("Command Completed, check the console");
50 | console.log(data);
51 | });
52 | }
53 |
54 | updateStdOutputText(text) {
55 | this.setState({
56 | stdOutputText: `${this.state.stdOutputText} \n\n ${text}`
57 | });
58 | }
59 |
60 | handleInputChange(e) {
61 | this.setState({ stdOutputText: "" });
62 |
63 | const file = e.currentTarget.files[0];
64 | // Set the file for processing
65 | workerClient.inputFile = file;
66 | // Run a valid ffmpeg command
67 | workerClient.runCommand("-ss 00:00:05 -c copy -t 12 sliced-output.mp4");
68 | }
69 |
70 | Input({ onChange }) {
71 | return (
72 | onChange(e)} />
73 | );
74 | }
75 |
76 | StdOutput({ text, ffmpegReady }) {
77 | return (
78 |
79 | {ffmpegReady ? "FFMPEG is ready" : "Loading FFMPEG"}
80 | {text}
81 |
82 | );
83 | }
84 |
85 | handleListCodecs(e) {
86 | e.preventDefault();
87 | this.setState({ stdOutputText: "" });
88 | // Run a valid ffmpeg command
89 | workerClient.runCommand("-codecs");
90 | }
91 |
92 | render() {
93 | return (
94 |
95 | {}
96 |
97 |
101 |
102 | );
103 | }
104 | }
105 |
106 | ;
107 | ```
108 |
109 | ## Docs
110 |
111 | The default export from the library is a standard NodeJS event emitter client
112 | would listen to and dispatch events based on interactions with an already loaded
113 | ffmpeg webworker Javascript library inside of a webworker.
114 |
115 | It supports the following properties:
116 |
117 | ### workerClient.on : void
118 |
119 | Receives an event from the ffmpeg webworker. Below are the supported commands:
120 |
121 | - _onReady_: The webworker has loaded ffmpeg successfully.
122 | - _onStdout_: Listens to standard ffmpeg-js message outputs.
123 | - _onStart_: Command has been received and started.
124 | - _onDone_: Command has been completed. Receives the data from ffmpeg as the
125 | first parameter.
126 | - _onFileReceived_: Input file has been set on the client.
127 |
128 | ### workerClient.inputFile : File
129 |
130 | The file that ffmpeg-webworker would be working with on issue of command.
131 |
132 | ### workerClient.worker : WebWorker
133 |
134 | An instance of the web worker that has ffmpeg-js loaded in it.
135 |
136 | ### workerClient.readFileAsBufferArray (file: File) : ArrayBuffer
137 |
138 | Converts the passed file to a file buffer array.
139 |
140 | ### workerClient.inputFileExists : boolean
141 |
142 | Detects if the inputFile has been passed in correctly.
143 |
144 | ### workerClient.convertInputFileToArrayBuffer (file: File) : ArrayBuffer
145 |
146 | Converts already set input file in the library to Buffer Array for processing
147 |
148 | ### workerClient.runCommand (command: String, totalMemory = 33554432: Number) : void
149 |
150 | Accepts a valid ffmpeg command to be run against the input file
151 |
152 | ### workerClient.log (message: String [, String []]) : void
153 |
154 | Logs messages to standard console.
155 |
156 | This is the default exported property of the module. It ease the interaction
157 | with the already initialized and loaded ffmpeg webworker Javascript library.
158 |
159 | Other exported modules are:
160 |
161 | ### FFMPEGWebworker
162 |
163 | The official web worker implementation that makes easier the loading of
164 | ffmpeg-js and running command in a worker.
165 |
166 | ### FFMPEGWebworkerClient
167 |
168 | Official listener for FFMPEGWebworker.
169 |
170 | ```js static
171 | import { FFMPEGWebworkerClient } from "ffmpeg-webworker";
172 |
173 | const webworkerClient = new FFMPEGWebworkerClient();
174 |
175 | // The above is same as
176 | // import webworkerClient from "ffmpeg-webworker";
177 |
178 | webworkerClient.on("ready", () => {
179 | console.log("FFMPEG has been loaded, and can receive commands");
180 | });
181 | ```
182 |
183 | ## Release Notes
184 |
185 | 1.0.0
186 |
187 | - Supports all ffmpeg codecs
188 |
189 | Initial Release:
190 |
191 | - Setup for the library
192 | - Only supports lib H246
193 |
194 | ## Contributions
195 |
196 | Github issues can be used to log bug reports, suggestions and improvements
197 |
198 | You can fork, then send a pull request.
199 |
200 | Thank you...
201 |
202 | ## License
203 |
204 | MIT
205 |
206 | ## Credits
207 |
208 | This library has been made possible with the awesome work by folks at
209 | [Ffmpeg.js](https://github.com/muaz-khan/Ffmpeg.js/)
210 |
--------------------------------------------------------------------------------
/commitlint.config.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable jsx-a11y/label-has-associated-control */
2 | // eslint-disable-next-line quotes
3 | module.exports = { extends: ["@commitlint/config-angular"] };
4 |
--------------------------------------------------------------------------------
/dist/es/FFMPEGWebWorker.js:
--------------------------------------------------------------------------------
1 | var workerFile = function workerFile() {
2 | var workerPath = "https://cdn.rawgit.com/bgrins/videoconverter.js/master/build/ffmpeg-all-codecs.js";
3 | importScripts(workerPath);
4 | var now = Date.now;
5 |
6 | function print(text) {
7 | postMessage({
8 | type: "stdout",
9 | data: text
10 | });
11 | }
12 |
13 | onmessage = function onmessage(event) {
14 | var message = event.data;
15 |
16 | if (message.type === "command") {
17 | var Module = {
18 | print: print,
19 | printErr: print,
20 | files: message.files || [],
21 | arguments: message.arguments || [],
22 | TOTAL_MEMORY: message.totalMemory || 33554432
23 | };
24 | postMessage({
25 | type: "start",
26 | data: Module.arguments.join(" ")
27 | });
28 | postMessage({
29 | type: "stdout",
30 | data: "Received command: " + Module.arguments.join(" ") + (Module.TOTAL_MEMORY ? ". Processing with " + Module.TOTAL_MEMORY + " bits." : "")
31 | });
32 | var time = now();
33 | var result = ffmpeg_run(Module);
34 | var totalTime = now() - time;
35 | postMessage({
36 | type: "stdout",
37 | data: "Finished processing (took " + totalTime + "ms)"
38 | });
39 | postMessage({
40 | type: "done",
41 | data: result,
42 | time: totalTime
43 | });
44 | }
45 | };
46 |
47 | postMessage({
48 | type: "ready"
49 | });
50 | };
51 |
52 | export default workerFile;
--------------------------------------------------------------------------------
/dist/es/FFMPEGWebWorkerClient.js:
--------------------------------------------------------------------------------
1 | function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }
2 |
3 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
4 |
5 | function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
6 |
7 | function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
8 |
9 | function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); }
10 |
11 | function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }
12 |
13 | function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; }
14 |
15 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); }
16 |
17 | function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }
18 |
19 | function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
20 |
21 | import WorkerFile from "webworker-file";
22 | import workerFile from "./FFMPEGWebWorker";
23 | import { EventEmitter } from "events";
24 |
25 | var FFMPEGWebworkerClient =
26 | /*#__PURE__*/
27 | function (_EventEmitter) {
28 | _inherits(FFMPEGWebworkerClient, _EventEmitter);
29 |
30 | /**
31 | * @type {Worker}
32 | */
33 |
34 | /**
35 | * @type {Blob}
36 | */
37 |
38 | /**
39 | * @type {Boolean}
40 | */
41 | function FFMPEGWebworkerClient() {
42 | var _this;
43 |
44 | _classCallCheck(this, FFMPEGWebworkerClient);
45 |
46 | _this = _possibleConstructorReturn(this, _getPrototypeOf(FFMPEGWebworkerClient).call(this));
47 |
48 | _defineProperty(_assertThisInitialized(_this), "_worker", {});
49 |
50 | _defineProperty(_assertThisInitialized(_this), "_inputFile", {});
51 |
52 | _defineProperty(_assertThisInitialized(_this), "workerIsReady", false);
53 |
54 | _defineProperty(_assertThisInitialized(_this), "readFileAsBufferArray", function (file) {
55 | return new Promise(function (resolve, reject) {
56 | var fileReader = new FileReader();
57 |
58 | fileReader.onload = function () {
59 | resolve(this.result);
60 | };
61 |
62 | fileReader.onerror = function () {
63 | reject(this.error);
64 | };
65 |
66 | fileReader.readAsArrayBuffer(file);
67 | });
68 | });
69 |
70 | _defineProperty(_assertThisInitialized(_this), "runCommand", function (command) {
71 | var totalMemory = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 33554432;
72 |
73 | if (typeof command !== "string" || !command.length) {
74 | throw new Error("command should be string and not empty");
75 | }
76 |
77 | if (_this.inputFile && _this.inputFile.type) {
78 | _this.convertInputFileToArrayBuffer().then(function (arrayBuffer) {
79 | while (!_this.workerIsReady) {}
80 |
81 | var filename = "video-".concat(Date.now(), ".webm");
82 | var inputCommand = "-i ".concat(filename, " ").concat(command);
83 |
84 | _this.worker.postMessage({
85 | type: "command",
86 | arguments: inputCommand.split(" "),
87 | files: [{
88 | data: new Uint8Array(arrayBuffer),
89 | name: filename
90 | }],
91 | totalMemory: totalMemory
92 | });
93 | });
94 | } else {
95 | _this.worker.postMessage({
96 | type: "command",
97 | arguments: command.split(" "),
98 | totalMemory: totalMemory
99 | });
100 | }
101 | });
102 |
103 | _defineProperty(_assertThisInitialized(_this), "log", function (message) {
104 | return Array.isArray(message) ? console.log.call(null, message) : console.log(message);
105 | });
106 |
107 | _defineProperty(_assertThisInitialized(_this), "isVideo", function (file) {
108 | var fileType = file.type;
109 | return file instanceof Blob && (fileType.includes("video") || fileType.includes("audio"));
110 | });
111 |
112 | _this.initWebWorker();
113 |
114 | return _this;
115 | }
116 |
117 | _createClass(FFMPEGWebworkerClient, [{
118 | key: "initWebWorker",
119 | value: function initWebWorker() {
120 | var _this2 = this;
121 |
122 | this.worker = new WorkerFile(workerFile);
123 | this.log;
124 |
125 | var log = this.worker.onmessage = function (event) {
126 | var message = event.data;
127 |
128 | if (event && event.type) {
129 | if (message.type == "ready") {
130 | _this2.emit("onReady", "ffmpeg-asm.js file has been loaded.");
131 |
132 | _this2.workerIsReady = true;
133 | } else if (message.type == "stdout") {
134 | _this2.emit("onStdout", message.data);
135 | } else if (message.type == "start") {
136 | _this2.emit("onFileReceived", "File Received");
137 |
138 | log("file received ffmpeg command.");
139 | } else if (message.type == "done") {
140 | _this2.emit("onDone", message.data);
141 | }
142 | }
143 | };
144 | }
145 | }, {
146 | key: "inputFileExists",
147 | value: function inputFileExists() {
148 | var inputFile = this.inputFile;
149 | return !!(inputFile && inputFile instanceof Blob && inputFile.size && inputFile.type);
150 | }
151 | /**
152 | * use worker to encode audio
153 | * @param {Blob} inputFile
154 | * @return {Promise}
155 | */
156 |
157 | }, {
158 | key: "convertInputFileToArrayBuffer",
159 | value: function convertInputFileToArrayBuffer() {
160 | if (!this.inputFileExists()) {
161 | throw new Error("Input File has not been set");
162 | }
163 |
164 | return this.readFileAsBufferArray(this.inputFile);
165 | }
166 | /**
167 | * @param {String} command
168 | */
169 |
170 | }, {
171 | key: "worker",
172 | set: function set(worker) {
173 | this._worker = worker;
174 | },
175 | get: function get() {
176 | return this._worker;
177 | }
178 | }, {
179 | key: "inputFile",
180 | set: function set(inputFile) {
181 | if (!this.isVideo(inputFile)) {
182 | throw new Error("Input file is expected to be an audio or a video");
183 | }
184 |
185 | this._inputFile = inputFile;
186 | },
187 | get: function get() {
188 | return this._inputFile;
189 | }
190 | /**
191 | * use worker to encode audio
192 | * @param {Blob} file
193 | * @return {Promise}
194 | */
195 |
196 | }]);
197 |
198 | return FFMPEGWebworkerClient;
199 | }(EventEmitter);
200 |
201 | export { FFMPEGWebworkerClient as default };
--------------------------------------------------------------------------------
/dist/es/index.js:
--------------------------------------------------------------------------------
1 | import WebworkerClient from "./FFMPEGWebWorkerClient";
2 | import Webworker from "./FFMPEGWebWorker";
3 | export var FFMPEGWebworker = Webworker;
4 | export var FFMPEGWebworkerClient = WebworkerClient;
5 | var workerClient = {
6 | on: function on() {},
7 | emit: function emit() {}
8 | };
9 |
10 | var _window = global || window;
11 |
12 | if (_window && _window.Blob) {
13 | workerClient = new WebworkerClient();
14 | }
15 |
16 | export default workerClient;
--------------------------------------------------------------------------------
/dist/index.js:
--------------------------------------------------------------------------------
1 | !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e=e||self).ffmpegWebworker={})}(this,function(e){"use strict";var t="undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{};function r(e,t){for(var n=0;ni){s.warned=!0;var a=new Error("Possible EventEmitter memory leak detected. "+s.length+" "+t+" listeners added. Use emitter.setMaxListeners() to increase limit");a.name="MaxListenersExceededWarning",a.emitter=e,a.type=t,a.count=s.length,function(e){"function"==typeof console.warn?console.warn(e):console.log(e)}(a)}}else s=o[t]=n,++e._eventsCount;return e}function m(e,t,n){var r=!1;function i(){e.removeListener(t,i),r||(r=!0,n.apply(e,arguments))}return i.listener=n,i}function w(e){var t=this._events;if(t){var n=t[e];if("function"==typeof n)return 1;if(n)return n.length}return 0}function g(e,t){for(var n=new Array(t);t--;)n[t]=e[t];return n}h.prototype=Object.create(null),(d.EventEmitter=d).usingDomains=!1,d.prototype.domain=void 0,d.prototype._events=void 0,d.prototype._maxListeners=void 0,d.defaultMaxListeners=10,d.init=function(){this.domain=null,d.usingDomains&&l.active&&l.Domain,this._events&&this._events!==Object.getPrototypeOf(this)._events||(this._events=new h,this._eventsCount=0),this._maxListeners=this._maxListeners||void 0},d.prototype.setMaxListeners=function(e){if("number"!=typeof e||e<0||isNaN(e))throw new TypeError('"n" argument must be a positive number');return this._maxListeners=e,this},d.prototype.getMaxListeners=function(){return y(this)},d.prototype.emit=function(e,t,n,r){var i,o,s,a,u,f,l,c="error"===e;if(f=this._events)c=c&&null==f.error;else if(!c)return!1;if(l=this.domain,c){if(i=t,l)return(i=i||new Error('Uncaught, unspecified "error" event')).domainEmitter=this,i.domain=l,i.domainThrown=!1,l.emit("error",i),!1;if(i instanceof Error)throw i;var p=new Error('Uncaught, unspecified "error" event. ('+i+")");throw p.context=i,p}if(!(o=f[e]))return!1;var h="function"==typeof o;switch(s=arguments.length){case 1:!function(e,t,n){if(t)e.call(n);else for(var r=e.length,i=g(e,r),o=0;o
2 |
3 |
4 |
5 |
6 |
7 | ffmpeg-webworker
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/examples/theme.css:
--------------------------------------------------------------------------------
1 | .container {
2 | display: flex;
3 | flex-direction: column;
4 | font-family: sans-serif;
5 | }
6 |
7 | .container > p {
8 | font-size: 1rem;
9 | }
10 |
11 | .container > em {
12 | font-size: 0.8rem;
13 | }
14 |
15 | .dropzone {
16 | flex: 1;
17 | display: flex;
18 | flex-direction: column;
19 | align-items: center;
20 | padding: 20px;
21 | border-width: 2px;
22 | border-radius: 2px;
23 | border-color: #eeeeee;
24 | border-style: dashed;
25 | background-color: #fafafa;
26 | color: #bdbdbd;
27 | outline: none;
28 | transition: border 0.24s ease-in-out;
29 | }
30 |
31 | .dropzone:focus {
32 | border-color: #2196f3;
33 | }
34 |
35 | .dropzone.disabled {
36 | opacity: 0.6;
37 | }
38 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ffmpeg-webworker",
3 | "version": "1.3.0",
4 | "description": "",
5 | "main": "dist/index.js",
6 | "files": [
7 | "dist/"
8 | ],
9 | "module": "dist/es/index.js",
10 | "scripts": {
11 | "clean": "rimraf ./dist",
12 | "test": "cross-env NODE_ENV=test jest --coverage && npm run typescript",
13 | "build": "npm run clean && npm run build:umd && npm run build:es",
14 | "build:umd": "cross-env NODE_ENV=es rollup -c",
15 | "build:es": "cross-env BABEL_ENV=es babel ./src --out-dir ./dist/es --ignore '**/*.spec.js'",
16 | "start": "styleguidist server",
17 | "styleguide": "styleguidist build",
18 | "test:watch": "cross-env NODE_ENV=test jest --watch",
19 | "commitmsg": "commitlint -e",
20 | "precommit": "pretty-quick --staged",
21 | "prepublish": "npm run build && npm run size",
22 | "logo": "cd logo && sketchtool export artboards logo.sketch",
23 | "imagemin": "imagemin --out-dir=logo --plugin=pngquant --plugin=svgo",
24 | "size": "size-limit",
25 | "size:why": "size-limit --why",
26 | "typescript": "tsc --project ./typings/tests"
27 | },
28 | "size-limit": [
29 | {
30 | "path": "dist/index.js",
31 | "limit": "7 KB"
32 | },
33 | {
34 | "path": "dist/es/index.js",
35 | "limit": "7 KB"
36 | }
37 | ],
38 | "jest": {
39 | "clearMocks": true,
40 | "setupFilesAfterEnv": [
41 | "/testSetup.js"
42 | ],
43 | "coveragePathIgnorePatterns": [
44 | "/dist/",
45 | "/node_modules/",
46 | "/testSetup.js"
47 | ],
48 | "testPathIgnorePatterns": [
49 | "/node_modules/",
50 | "/dist/"
51 | ]
52 | },
53 | "keywords": [
54 | "ffmpeg",
55 | "webworker",
56 | "ffmpegjs",
57 | "audio",
58 | "video",
59 | "transcode",
60 | "convert"
61 | ],
62 | "author": "",
63 | "license": "MIT",
64 | "peerDependencies": {
65 | "react": ">= 16.8"
66 | },
67 | "dependencies": {
68 | "attr-accept": "^1.1.3",
69 | "file-selector": "^0.1.11",
70 | "prop-types": "^15.7.2",
71 | "react-styleguidist": "^9.2.0",
72 | "rollup-plugin-node-globals": "^1.4.0",
73 | "webpack-blocks": "^2.0.1",
74 | "webworker-file": "^0.2.0"
75 | },
76 | "devDependencies": {
77 | "@babel/cli": "^7.5.5",
78 | "@babel/core": "^7.5.5",
79 | "@babel/plugin-proposal-class-properties": "^7.5.5",
80 | "@babel/plugin-proposal-do-expressions": "^7.5.0",
81 | "@babel/plugin-proposal-export-default-from": "^7.5.2",
82 | "@babel/plugin-proposal-logical-assignment-operators": "^7.2.0",
83 | "@babel/plugin-proposal-nullish-coalescing-operator": "^7.4.4",
84 | "@babel/plugin-proposal-optional-chaining": "^7.2.0",
85 | "@babel/plugin-proposal-pipeline-operator": "^7.5.0",
86 | "@babel/plugin-transform-runtime": "^7.5.5",
87 | "@babel/preset-env": "^7.5.5",
88 | "@babel/preset-react": "^7.0.0",
89 | "@babel/register": "^7.0.0",
90 | "@commitlint/cli": "^7.0.0",
91 | "@commitlint/config-angular": "^7.0.1",
92 | "@commitlint/prompt": "^7.0.0",
93 | "@commitlint/prompt-cli": "^7.0.0",
94 | "@types/react": "^16.8.3",
95 | "@types/react-dom": "^16.8.1",
96 | "babel-plugin-add-module-exports": "^1.0.0",
97 | "babel-plugin-dynamic-import-node": "^2.2.0",
98 | "commitizen": "^2.10.1",
99 | "cross-env": "^5.2.0",
100 | "husky": "^0.14.3",
101 | "imagemin-cli": "^3.0.0",
102 | "imagemin-pngquant": "^6.0.0",
103 | "lint-staged": "^7.2.2",
104 | "markdownlint-cli": "^0.13.0",
105 | "prettier": "*",
106 | "pretty-quick": "^1.11.1",
107 | "react": "^16.8.2",
108 | "react-dom": "^16.8.2",
109 | "rimraf": "^2.5.2",
110 | "rollup": "^1.17.0",
111 | "rollup-plugin-babel": "^4.3.3",
112 | "rollup-plugin-commonjs": "^10.0.1",
113 | "rollup-plugin-node-builtins": "^2.1.2",
114 | "rollup-plugin-node-resolve": "^5.2.0",
115 | "rollup-plugin-uglify": "^6.0.2",
116 | "sinon": "^3.2.1",
117 | "size-limit": "^0.19.2",
118 | "style-loader": "^0.18.2",
119 | "styled-components": "^4.1.2",
120 | "tslint": "^5.9.1",
121 | "typescript": "^3.2.4",
122 | "webpack": "^4.29.5"
123 | },
124 | "repository": {
125 | "type": "git",
126 | "url": "https://github.com/limistah/ffmpeg-webworker.git"
127 | },
128 | "typings": "typings/ffmpeg-webworker.d.ts",
129 | "config": {
130 | "commitizen": {
131 | "path": "@commitlint/prompt"
132 | }
133 | },
134 | "husky": {
135 | "hooks": {
136 | "prepare-commit-msg": "exec < /dev/tty && git cz --hook"
137 | }
138 | },
139 | "engines": {
140 | "node": ">= 8"
141 | }
142 | }
143 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import babel from "rollup-plugin-babel";
2 | import resolve from "rollup-plugin-node-resolve";
3 | import commonjs from "rollup-plugin-commonjs";
4 | import { uglify } from "rollup-plugin-uglify";
5 | import builtins from "rollup-plugin-node-builtins";
6 | import globals from "rollup-plugin-node-globals";
7 |
8 | const umdGlobals = {
9 | Blob: "Blob",
10 | URL: "URL",
11 | Worker: "Worker"
12 | };
13 |
14 | export default {
15 | input: "./src/index.js",
16 | output: {
17 | file: "dist/index.js",
18 | format: "umd",
19 | name: "ffmpegWebworker",
20 | globals: umdGlobals,
21 | sourcemap: "inline",
22 | exports: "named"
23 | },
24 | external: Object.keys(umdGlobals),
25 | plugins: [
26 | babel({
27 | exclude: "node_modules/**"
28 | }),
29 | resolve({ preferBuiltins: true }),
30 | globals(),
31 | builtins(),
32 | commonjs(),
33 | uglify()
34 | ]
35 | };
36 |
--------------------------------------------------------------------------------
/src/FFMPEGWebWorker.js:
--------------------------------------------------------------------------------
1 | const workerFile = () => {
2 | const workerPath =
3 | "https://raw.githubusercontent.com/bgrins/videoconverter.js/master/build/ffmpeg-all-codecs.js";
4 |
5 | importScripts(workerPath);
6 | const now = Date.now;
7 | function print(text) {
8 | postMessage({ type: "stdout", data: text });
9 | }
10 | onmessage = function(event) {
11 | const message = event.data;
12 | if (message.type === "command") {
13 | const Module = {
14 | print: print,
15 | printErr: print,
16 | files: message.files || [],
17 | arguments: message.arguments || [],
18 | TOTAL_MEMORY: message.totalMemory || 33554432
19 | };
20 | postMessage({ type: "start", data: Module.arguments.join(" ") });
21 | postMessage({
22 | type: "stdout",
23 | data:
24 | "Received command: " +
25 | Module.arguments.join(" ") +
26 | (Module.TOTAL_MEMORY
27 | ? ". Processing with " + Module.TOTAL_MEMORY + " bits."
28 | : "")
29 | });
30 | const time = now();
31 | const result = ffmpeg_run(Module);
32 | const totalTime = now() - time;
33 | postMessage({
34 | type: "stdout",
35 | data: "Finished processing (took " + totalTime + "ms)"
36 | });
37 | postMessage({ type: "done", data: result, time: totalTime });
38 | }
39 | };
40 | postMessage({ type: "ready" });
41 | };
42 |
43 | export default workerFile;
44 |
--------------------------------------------------------------------------------
/src/FFMPEGWebWorkerClient.js:
--------------------------------------------------------------------------------
1 | import WorkerFile from "webworker-file";
2 | import workerFile from "./FFMPEGWebWorker";
3 | import { EventEmitter } from "events";
4 |
5 | export default class FFMPEGWebworkerClient extends EventEmitter {
6 | /**
7 | * @type {Worker}
8 | */
9 | _worker = {};
10 | /**
11 | * @type {Blob}
12 | */
13 | _inputFile = {};
14 |
15 | /**
16 | * @type {Boolean}
17 | */
18 | workerIsReady = false;
19 | constructor() {
20 | super();
21 | this.initWebWorker();
22 | }
23 |
24 | initWebWorker() {
25 | this.worker = new WorkerFile(workerFile);
26 | this.log;
27 | const log = (this.worker.onmessage = event => {
28 | let message = event.data;
29 | if (event && event.type) {
30 | if (message.type == "ready") {
31 | this.emit("onReady", "ffmpeg-asm.js file has been loaded.");
32 | this.workerIsReady = true;
33 | } else if (message.type == "stdout") {
34 | this.emit("onStdout", message.data);
35 | } else if (message.type == "start") {
36 | this.emit("onFileReceived", "File Received");
37 | log("file received ffmpeg command.");
38 | } else if (message.type == "done") {
39 | this.emit("onDone", message.data);
40 | }
41 | }
42 | });
43 | }
44 |
45 | set worker(worker) {
46 | this._worker = worker;
47 | }
48 | get worker() {
49 | return this._worker;
50 | }
51 |
52 | set inputFile(inputFile) {
53 | if (!this.isVideo(inputFile)) {
54 | throw new Error("Input file is expected to be an audio or a video");
55 | }
56 | this._inputFile = inputFile;
57 | }
58 | get inputFile() {
59 | return this._inputFile;
60 | }
61 |
62 | /**
63 | * use worker to encode audio
64 | * @param {Blob} file
65 | * @return {Promise}
66 | */
67 | readFileAsBufferArray = file => {
68 | return new Promise((resolve, reject) => {
69 | let fileReader = new FileReader();
70 | fileReader.onload = function() {
71 | resolve(this.result);
72 | };
73 | fileReader.onerror = function() {
74 | reject(this.error);
75 | };
76 | fileReader.readAsArrayBuffer(file);
77 | });
78 | };
79 |
80 | inputFileExists() {
81 | const inputFile = this.inputFile;
82 | return !!(
83 | inputFile &&
84 | inputFile instanceof Blob &&
85 | inputFile.size &&
86 | inputFile.type
87 | );
88 | }
89 |
90 | /**
91 | * use worker to encode audio
92 | * @param {Blob} inputFile
93 | * @return {Promise}
94 | */
95 | convertInputFileToArrayBuffer() {
96 | if (!this.inputFileExists()) {
97 | throw new Error("Input File has not been set");
98 | }
99 | return this.readFileAsBufferArray(this.inputFile);
100 | }
101 |
102 | /**
103 | * @param {String} command
104 | */
105 | runCommand = (command, totalMemory = 33554432) => {
106 | if (typeof command !== "string" || !command.length) {
107 | throw new Error("command should be string and not empty");
108 | }
109 | if (this.inputFile && this.inputFile.type) {
110 | this.convertInputFileToArrayBuffer().then(arrayBuffer => {
111 | while (!this.workerIsReady) {}
112 | const filename = `video-${Date.now()}.webm`;
113 | const inputCommand = `-i ${filename} ${command}`;
114 | this.worker.postMessage({
115 | type: "command",
116 | arguments: inputCommand.split(" "),
117 | files: [
118 | {
119 | data: new Uint8Array(arrayBuffer),
120 | name: filename
121 | }
122 | ],
123 | totalMemory
124 | });
125 | });
126 | } else {
127 | this.worker.postMessage({
128 | type: "command",
129 | arguments: command.split(" "),
130 | totalMemory
131 | });
132 | }
133 | };
134 |
135 | /**
136 | * @param {String | Array} message
137 | * @return {void}
138 | */
139 | log = message =>
140 | Array.isArray(message)
141 | ? console.log.call(null, message)
142 | : console.log(message);
143 |
144 | /**
145 | * @param {Blob} file
146 | * @return {Boolean}
147 | */
148 | isVideo = file => {
149 | const fileType = file.type;
150 | return (
151 | file instanceof Blob &&
152 | (fileType.includes("video") || fileType.includes("audio"))
153 | );
154 | };
155 | }
156 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import WebworkerClient from "./FFMPEGWebWorkerClient";
2 | import Webworker from "./FFMPEGWebWorker";
3 |
4 | export const FFMPEGWebworker = Webworker;
5 | export const FFMPEGWebworkerClient = WebworkerClient;
6 | let workerClient = { on() {}, emit() {} };
7 | const _window = global || window;
8 | if (_window && _window.Blob) {
9 | workerClient = new WebworkerClient();
10 | }
11 | export default workerClient;
12 |
--------------------------------------------------------------------------------
/styleguide.config.js:
--------------------------------------------------------------------------------
1 | const path = require("path");
2 | const { createConfig, babel, css } = require("webpack-blocks");
3 |
4 | module.exports = {
5 | title: "ffmpeg-webworker",
6 | styleguideDir: path.join(__dirname, "docs"),
7 | webpackConfig: createConfig([babel(), css()]),
8 | exampleMode: "expand",
9 | usageMode: "expand",
10 | showSidebar: false,
11 | serverPort: 8080,
12 | moduleAliases: {
13 | "ffmpeg-webworker": path.resolve(__dirname, "./src")
14 | },
15 | require: [path.join(__dirname, "examples/theme.css")],
16 | sections: [
17 | {
18 | name: "",
19 | content: "README.MD"
20 | }
21 | ]
22 | };
23 |
--------------------------------------------------------------------------------
/testSetup.js:
--------------------------------------------------------------------------------
1 | const jest = require('jest');
2 | // https://www.npmjs.com/package/jest-dom
3 | require('jest-dom/extend-expect');
4 |
5 | // TODO: Ignore warnings about act(), it refers to having async side effects updating the state;
6 | // This happens because our async getFilesFromEvent() fn
7 | // See https://github.com/kentcdodds/react-testing-library/issues/281,
8 | // https://github.com/facebook/react/issues/14769
9 | jest.spyOn(console, 'error').mockImplementation(() => jest.fn());
10 |
--------------------------------------------------------------------------------
/typings/ffmpeg-webworker.d.ts:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/limistah/ffmpeg-webworker/ba47aa0efc8bc2e822652e0833f853e1a731d5fc/typings/ffmpeg-webworker.d.ts
--------------------------------------------------------------------------------