From e17812a99971410305999ba1809aa0e760fc50f1 Mon Sep 17 00:00:00 2001 From: Jerome Wu Date: Tue, 4 Oct 2022 12:17:04 +0800 Subject: [PATCH] Add tests --- apps/browser/transcode.html | 4 +- package-lock.json | 918 +++++++++++++++++- package.json | 21 +- packages/ffmpeg/package.json | 8 +- packages/ffmpeg/src/classes.ts | 287 ++++-- packages/ffmpeg/src/const.ts | 9 +- packages/ffmpeg/src/errors.ts | 1 + packages/ffmpeg/src/types.ts | 58 +- packages/ffmpeg/src/utils.ts | 58 +- packages/ffmpeg/src/worker.ts | 88 +- packages/ffmpeg/webpack.dev.config.js | 27 - packages/types/types/index.d.ts | 16 +- tests/.eslintrc.json | 9 +- tests/ffmpeg-core-mt.test.html | 2 +- tests/ffmpeg-core-st.test.html | 2 +- tests/ffmpeg-core.test.js | 66 +- tests/ffmpeg-mt.test.html | 25 + tests/ffmpeg-st.test.html | 25 + tests/ffmpeg.test.js | 171 ++++ .../{constants.js => test-helper-browser.js} | 12 + tests/test-helper-mt.js | 6 +- tests/test-helper-st.js | 6 +- 22 files changed, 1593 insertions(+), 226 deletions(-) delete mode 100644 packages/ffmpeg/webpack.dev.config.js create mode 100644 tests/ffmpeg-mt.test.html create mode 100644 tests/ffmpeg-st.test.html create mode 100644 tests/ffmpeg.test.js rename tests/{constants.js => test-helper-browser.js} (95%) diff --git a/apps/browser/transcode.html b/apps/browser/transcode.html index a1dec53..81a24a1 100644 --- a/apps/browser/transcode.html +++ b/apps/browser/transcode.html @@ -21,8 +21,8 @@ ffmpeg.on(FFmpeg.DOWNLOAD, ({ url, total, received, done }) => { console.log(`downloading ${url}, progress: ${received / total * 100} %, done: ${done}`); }); - ffmpeg.on(FFmpeg.PROGRESS, (p) => { - message.innerHTML = `${p * 100} %`; + ffmpeg.on(FFmpeg.PROGRESS, ({ progress }) => { + message.innerHTML = `${progress * 100} %`; }); ffmpeg.on(FFmpeg.LOG, ({ message }) => { console.log(message); diff --git a/package-lock.json b/package-lock.json index 9ebb080..60c4732 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,10 +12,12 @@ ], "devDependencies": { "chai": "^4.3.6", + "http-server": "^14.1.1", "lerna": "^5.4.3", "mocha": "^10.0.0", "mocha-headless-chrome": "^4.0.0", - "npm-run-all": "^4.1.5" + "npm-run-all": "^4.1.5", + "start-server-and-test": "^1.14.0" } }, "apps/browser": { @@ -157,6 +159,21 @@ "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==", "dev": true }, + "node_modules/@hapi/hoek": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", + "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==", + "dev": true + }, + "node_modules/@hapi/topo": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", + "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", + "dev": true, + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.10.4", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.10.4.tgz", @@ -2225,6 +2242,27 @@ "url": "https://opencollective.com/parcel" } }, + "node_modules/@sideway/address": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.4.tgz", + "integrity": "sha512-7vwq+rOHVWjyXxVlR76Agnvhy8I9rpzjosTESvmhNeXOXdZZB15Fl+TI9x1SiHZH5Jv2wTGduSxFDIaq0m3DUw==", + "dev": true, + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, + "node_modules/@sideway/formula": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.0.tgz", + "integrity": "sha512-vHe7wZ4NOXVfkoRb8T5otiENVlT7a3IAiw7H5M2+GO+9CDgcVUUsX1zalAztCmwyOr2RUTGJdgB+ZvSVqmdHmg==", + "dev": true + }, + "node_modules/@sideway/pinpoint": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", + "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==", + "dev": true + }, "node_modules/@tootallnate/once": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", @@ -3075,6 +3113,15 @@ "node": "*" } }, + "node_modules/async": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", + "dev": true, + "dependencies": { + "lodash": "^4.17.14" + } + }, "node_modules/at-least-node": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", @@ -3084,6 +3131,15 @@ "node": ">= 4.0.0" } }, + "node_modules/axios": { + "version": "0.21.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", + "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", + "dev": true, + "dependencies": { + "follow-redirects": "^1.14.0" + } + }, "node_modules/balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", @@ -3110,6 +3166,18 @@ } ] }, + "node_modules/basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "dev": true, + "dependencies": { + "safe-buffer": "5.1.2" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/before-after-hook": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.2.tgz", @@ -3185,6 +3253,12 @@ "node": ">= 6" } }, + "node_modules/bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "dev": true + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -3484,6 +3558,15 @@ "node": "*" } }, + "node_modules/check-more-types": { + "version": "2.24.0", + "resolved": "https://registry.npmjs.org/check-more-types/-/check-more-types-2.24.0.tgz", + "integrity": "sha512-Pj779qHxV2tuapviy1bSZNEL1maXr13bPYpsvSDB68HlYcYuhlDrmGd63i0JHMCLKzc7rUSNIrpdJlhVlNwrxA==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/chokidar": { "version": "3.5.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", @@ -4008,6 +4091,15 @@ "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", "dev": true }, + "node_modules/corser": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/corser/-/corser-2.0.1.tgz", + "integrity": "sha512-utCYNzRSQIZNPIcGZdQc92UVJYAhtGAteCFg0yRaFm8f0P+CPtyGyHXJcGXnffjCybUCEx3FQ2G7U3/o9eIkVQ==", + "dev": true, + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/cosmiconfig": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.1.tgz", @@ -4830,6 +4922,33 @@ "node": ">=0.10.0" } }, + "node_modules/event-stream": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz", + "integrity": "sha512-QHpkERcGsR0T7Qm3HNJSyXKEEj8AHNxkY3PK8TS2KJvQ7NiSHe3DDpwVKKtoYprL/AreyzFBeIkBIWChAqn60g==", + "dev": true, + "dependencies": { + "duplexer": "~0.1.1", + "from": "~0", + "map-stream": "~0.1.0", + "pause-stream": "0.0.11", + "split": "0.3", + "stream-combiner": "~0.0.4", + "through": "~2.3.1" + } + }, + "node_modules/event-stream/node_modules/split": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/split/-/split-0.3.3.tgz", + "integrity": "sha512-wD2AeVmxXRBoX44wAycgjVpMhvbwdI2aZjCkvfNcH1YqHQvJVa1duWc73OyVGJUc05fhFaTZeQ/PYsrmyH0JVA==", + "dev": true, + "dependencies": { + "through": "2" + }, + "engines": { + "node": "*" + } + }, "node_modules/eventemitter3": { "version": "4.0.7", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", @@ -5066,6 +5185,32 @@ "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", "dev": true }, + "node_modules/follow-redirects": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", + "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/from": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz", + "integrity": "sha512-twe20eF1OxVxp/ML/kq2p1uc6KvFK/+vs8WjEbeKmV2He22MKm7YF2ANIt+EOqhJ5L3K/SuuPhk0hWQDjOM23g==", + "dev": true + }, "node_modules/fs-constants": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", @@ -5601,12 +5746,38 @@ "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", "dev": true }, + "node_modules/html-encoding-sniffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", + "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==", + "dev": true, + "dependencies": { + "whatwg-encoding": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/http-cache-semantics": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==", "dev": true }, + "node_modules/http-proxy": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "dev": true, + "dependencies": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/http-proxy-agent": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", @@ -5621,6 +5792,103 @@ "node": ">= 6" } }, + "node_modules/http-server": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/http-server/-/http-server-14.1.1.tgz", + "integrity": "sha512-+cbxadF40UXd9T01zUHgA+rlo2Bg1Srer4+B4NwIHdaGxAGGv59nYRnGGDJ9LBk7alpS0US+J+bLLdQOOkJq4A==", + "dev": true, + "dependencies": { + "basic-auth": "^2.0.1", + "chalk": "^4.1.2", + "corser": "^2.0.1", + "he": "^1.2.0", + "html-encoding-sniffer": "^3.0.0", + "http-proxy": "^1.18.1", + "mime": "^1.6.0", + "minimist": "^1.2.6", + "opener": "^1.5.1", + "portfinder": "^1.0.28", + "secure-compare": "3.0.1", + "union": "~0.5.0", + "url-join": "^4.0.1" + }, + "bin": { + "http-server": "bin/http-server" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/http-server/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/http-server/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/http-server/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/http-server/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/http-server/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/http-server/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/https-proxy-agent": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", @@ -6342,6 +6610,19 @@ "node": ">=0.10.0" } }, + "node_modules/joi": { + "version": "17.6.2", + "resolved": "https://registry.npmjs.org/joi/-/joi-17.6.2.tgz", + "integrity": "sha512-+gqqdh1xc1wb+Lor0J9toqgeReyDOCqOdG8QSdRcEvwrcRiFQZneUCGKjFjuyBWUb3uaFOgY56yMaZ5FIc+H4w==", + "dev": true, + "dependencies": { + "@hapi/hoek": "^9.0.0", + "@hapi/topo": "^5.0.0", + "@sideway/address": "^4.1.3", + "@sideway/formula": "^3.0.0", + "@sideway/pinpoint": "^2.0.0" + } + }, "node_modules/js-sdsl": { "version": "4.1.4", "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.1.4.tgz", @@ -6481,6 +6762,15 @@ "node": ">=0.10.0" } }, + "node_modules/lazy-ass": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/lazy-ass/-/lazy-ass-1.6.0.tgz", + "integrity": "sha512-cc8oEVoctTvsFZ/Oje/kGnHbpWHYBe8IAJe4C0QNc3t8uM/0Y8+erSz/7Y1ALuXTEZTMvxXwO6YbX1ey3ujiZw==", + "dev": true, + "engines": { + "node": "> 0.8" + } + }, "node_modules/lerna": { "version": "5.5.1", "resolved": "https://registry.npmjs.org/lerna/-/lerna-5.5.1.tgz", @@ -6920,6 +7210,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/map-stream": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz", + "integrity": "sha512-CkYQrPYZfWnu/DAmVCpTSX/xHpKZ80eKh2lAkyA6AJTef6bW+6JpbQZN5rofum7da+SyN1bi5ctTm+lTfcCW3g==", + "dev": true + }, "node_modules/marked": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/marked/-/marked-4.1.0.tgz", @@ -7122,6 +7418,18 @@ "node": ">=8.6" } }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", @@ -8510,6 +8818,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/opener": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", + "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==", + "dev": true, + "bin": { + "opener": "bin/opener-bin.js" + } + }, "node_modules/optionator": { "version": "0.9.1", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", @@ -8952,6 +9269,15 @@ "node": "*" } }, + "node_modules/pause-stream": { + "version": "0.0.11", + "resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", + "integrity": "sha512-e3FBlXLmN/D1S+zHzanP4E/4Z60oFAa3O051qt1pxa7DEJWKAyil6upYVXCWadEnuoqa4Pkc9oUx9zsxYeRv8A==", + "dev": true, + "dependencies": { + "through": "~2.3" + } + }, "node_modules/pend": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", @@ -9010,6 +9336,41 @@ "node": ">=8" } }, + "node_modules/portfinder": { + "version": "1.0.32", + "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.32.tgz", + "integrity": "sha512-on2ZJVVDXRADWE6jnQaX0ioEylzgBpQk8r55NE4wjXW1ZxO+BgDlY6DXwj20i0V8eB4SenDQ00WEaxfiIQPcxg==", + "dev": true, + "dependencies": { + "async": "^2.6.4", + "debug": "^3.2.7", + "mkdirp": "^0.5.6" + }, + "engines": { + "node": ">= 0.12.0" + } + }, + "node_modules/portfinder/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/portfinder/node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -9107,6 +9468,21 @@ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", "dev": true }, + "node_modules/ps-tree": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/ps-tree/-/ps-tree-1.2.0.tgz", + "integrity": "sha512-0VnamPPYHl4uaU/nSFeZZpR21QAWRz+sRv4iW9+v/GS/J5U5iZB5BNN6J0RMoOvdx2gWM2+ZFMIm58q24e4UYA==", + "dev": true, + "dependencies": { + "event-stream": "=3.3.4" + }, + "bin": { + "ps-tree": "bin/ps-tree.js" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/pump": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", @@ -9136,6 +9512,21 @@ "teleport": ">=0.2.0" } }, + "node_modules/qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dev": true, + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -9415,6 +9806,12 @@ "node": ">=0.10.0" } }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "dev": true + }, "node_modules/resolve": { "version": "1.22.1", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", @@ -9600,6 +9997,12 @@ "url": "https://opencollective.com/webpack" } }, + "node_modules/secure-compare": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/secure-compare/-/secure-compare-3.0.1.tgz", + "integrity": "sha512-AckIIV90rPDcBcglUwXPF3kg0P0qmPsPXAj6BBEENQE1p5yA1xfmDJzfi1Tappj37Pv2mVbKpL3Z1T+Nn7k1Qw==", + "dev": true + }, "node_modules/semver": { "version": "7.3.7", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", @@ -9862,6 +10265,55 @@ "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, + "node_modules/start-server-and-test": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/start-server-and-test/-/start-server-and-test-1.14.0.tgz", + "integrity": "sha512-on5ELuxO2K0t8EmNj9MtVlFqwBMxfWOhu4U7uZD1xccVpFlOQKR93CSe0u98iQzfNxRyaNTb/CdadbNllplTsw==", + "dev": true, + "dependencies": { + "bluebird": "3.7.2", + "check-more-types": "2.24.0", + "debug": "4.3.2", + "execa": "5.1.1", + "lazy-ass": "1.6.0", + "ps-tree": "1.2.0", + "wait-on": "6.0.0" + }, + "bin": { + "server-test": "src/bin/start.js", + "start-server-and-test": "src/bin/start.js", + "start-test": "src/bin/start.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/start-server-and-test/node_modules/debug": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/stream-combiner": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz", + "integrity": "sha512-rT00SPnTVyRsaSz5zgSPma/aHSOic5U1prhYdRy5HS2kTZviFpmDgzilbtsJsxiroqACmayynDN/9VzIbX5DOw==", + "dev": true, + "dependencies": { + "duplexer": "~0.1.1" + } + }, "node_modules/string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", @@ -10631,6 +11083,18 @@ "through": "^2.3.8" } }, + "node_modules/union": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/union/-/union-0.5.0.tgz", + "integrity": "sha512-N6uOhuW6zO95P3Mel2I2zMsbsanvvtgn6jVqJv4vbVcz/JN0OkL9suomjQGmWtxJQXOCqUJvquc1sMeNz/IwlA==", + "dev": true, + "dependencies": { + "qs": "^6.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/unique-filename": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-2.0.1.tgz", @@ -10716,6 +11180,12 @@ "punycode": "^2.1.0" } }, + "node_modules/url-join": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz", + "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==", + "dev": true + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -10777,6 +11247,25 @@ "integrity": "sha512-gu73tuZfJgu+mvCSy4UZwd2JXykjK9zAZsfmDeut5dx/1a7FeTk0XwJsSuqQn+cuMCGVbIBfl+s53X4T19DnzQ==", "dev": true }, + "node_modules/wait-on": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/wait-on/-/wait-on-6.0.0.tgz", + "integrity": "sha512-tnUJr9p5r+bEYXPUdRseolmz5XqJTTj98JgOsfBn7Oz2dxfE2g3zw1jE+Mo8lopM3j3et/Mq1yW7kKX6qw7RVw==", + "dev": true, + "dependencies": { + "axios": "^0.21.1", + "joi": "^17.4.0", + "lodash": "^4.17.21", + "minimist": "^1.2.5", + "rxjs": "^7.1.0" + }, + "bin": { + "wait-on": "bin/wait-on" + }, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/walk-up-path": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/walk-up-path/-/walk-up-path-1.0.0.tgz", @@ -10963,6 +11452,30 @@ "node": ">=4.0" } }, + "node_modules/whatwg-encoding": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", + "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", + "dev": true, + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-encoding/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/whatwg-url": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", @@ -11436,6 +11949,7 @@ "@typescript-eslint/eslint-plugin": "^5.37.0", "@typescript-eslint/parser": "^5.37.0", "eslint": "^8.23.1", + "http-server": "^14.1.1", "npm-run-all": "^4.1.5", "rimraf": "^3.0.2", "ts-loader": "^9.4.1", @@ -11606,6 +12120,7 @@ "@typescript-eslint/parser": "^5.37.0", "eslint": "^8.23.1", "events": "^3.3.0", + "http-server": "^14.1.1", "npm-run-all": "^4.1.5", "rimraf": "^3.0.2", "ts-loader": "^9.4.1", @@ -11644,6 +12159,21 @@ "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==", "dev": true }, + "@hapi/hoek": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", + "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==", + "dev": true + }, + "@hapi/topo": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", + "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", + "dev": true, + "requires": { + "@hapi/hoek": "^9.0.0" + } + }, "@humanwhocodes/config-array": { "version": "0.10.4", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.10.4.tgz", @@ -13280,6 +13810,27 @@ "node-gyp-build": "^4.3.0" } }, + "@sideway/address": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.4.tgz", + "integrity": "sha512-7vwq+rOHVWjyXxVlR76Agnvhy8I9rpzjosTESvmhNeXOXdZZB15Fl+TI9x1SiHZH5Jv2wTGduSxFDIaq0m3DUw==", + "dev": true, + "requires": { + "@hapi/hoek": "^9.0.0" + } + }, + "@sideway/formula": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.0.tgz", + "integrity": "sha512-vHe7wZ4NOXVfkoRb8T5otiENVlT7a3IAiw7H5M2+GO+9CDgcVUUsX1zalAztCmwyOr2RUTGJdgB+ZvSVqmdHmg==", + "dev": true + }, + "@sideway/pinpoint": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", + "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==", + "dev": true + }, "@tootallnate/once": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", @@ -13948,12 +14499,30 @@ "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", "dev": true }, + "async": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", + "dev": true, + "requires": { + "lodash": "^4.17.14" + } + }, "at-least-node": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", "dev": true }, + "axios": { + "version": "0.21.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", + "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", + "dev": true, + "requires": { + "follow-redirects": "^1.14.0" + } + }, "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", @@ -13966,6 +14535,15 @@ "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", "dev": true }, + "basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "dev": true, + "requires": { + "safe-buffer": "5.1.2" + } + }, "before-after-hook": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.2.tgz", @@ -14030,6 +14608,12 @@ } } }, + "bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "dev": true + }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -14245,6 +14829,12 @@ "integrity": "sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==", "dev": true }, + "check-more-types": { + "version": "2.24.0", + "resolved": "https://registry.npmjs.org/check-more-types/-/check-more-types-2.24.0.tgz", + "integrity": "sha512-Pj779qHxV2tuapviy1bSZNEL1maXr13bPYpsvSDB68HlYcYuhlDrmGd63i0JHMCLKzc7rUSNIrpdJlhVlNwrxA==", + "dev": true + }, "chokidar": { "version": "3.5.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", @@ -14660,6 +15250,12 @@ "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", "dev": true }, + "corser": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/corser/-/corser-2.0.1.tgz", + "integrity": "sha512-utCYNzRSQIZNPIcGZdQc92UVJYAhtGAteCFg0yRaFm8f0P+CPtyGyHXJcGXnffjCybUCEx3FQ2G7U3/o9eIkVQ==", + "dev": true + }, "cosmiconfig": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.1.tgz", @@ -15279,6 +15875,32 @@ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true }, + "event-stream": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz", + "integrity": "sha512-QHpkERcGsR0T7Qm3HNJSyXKEEj8AHNxkY3PK8TS2KJvQ7NiSHe3DDpwVKKtoYprL/AreyzFBeIkBIWChAqn60g==", + "dev": true, + "requires": { + "duplexer": "~0.1.1", + "from": "~0", + "map-stream": "~0.1.0", + "pause-stream": "0.0.11", + "split": "0.3", + "stream-combiner": "~0.0.4", + "through": "~2.3.1" + }, + "dependencies": { + "split": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/split/-/split-0.3.3.tgz", + "integrity": "sha512-wD2AeVmxXRBoX44wAycgjVpMhvbwdI2aZjCkvfNcH1YqHQvJVa1duWc73OyVGJUc05fhFaTZeQ/PYsrmyH0JVA==", + "dev": true, + "requires": { + "through": "2" + } + } + } + }, "eventemitter3": { "version": "4.0.7", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", @@ -15463,6 +16085,18 @@ "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", "dev": true }, + "follow-redirects": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", + "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", + "dev": true + }, + "from": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz", + "integrity": "sha512-twe20eF1OxVxp/ML/kq2p1uc6KvFK/+vs8WjEbeKmV2He22MKm7YF2ANIt+EOqhJ5L3K/SuuPhk0hWQDjOM23g==", + "dev": true + }, "fs-constants": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", @@ -15860,12 +16494,32 @@ "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", "dev": true }, + "html-encoding-sniffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", + "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==", + "dev": true, + "requires": { + "whatwg-encoding": "^2.0.0" + } + }, "http-cache-semantics": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==", "dev": true }, + "http-proxy": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "dev": true, + "requires": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + } + }, "http-proxy-agent": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", @@ -15877,6 +16531,78 @@ "debug": "4" } }, + "http-server": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/http-server/-/http-server-14.1.1.tgz", + "integrity": "sha512-+cbxadF40UXd9T01zUHgA+rlo2Bg1Srer4+B4NwIHdaGxAGGv59nYRnGGDJ9LBk7alpS0US+J+bLLdQOOkJq4A==", + "dev": true, + "requires": { + "basic-auth": "^2.0.1", + "chalk": "^4.1.2", + "corser": "^2.0.1", + "he": "^1.2.0", + "html-encoding-sniffer": "^3.0.0", + "http-proxy": "^1.18.1", + "mime": "^1.6.0", + "minimist": "^1.2.6", + "opener": "^1.5.1", + "portfinder": "^1.0.28", + "secure-compare": "3.0.1", + "union": "~0.5.0", + "url-join": "^4.0.1" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, "https-proxy-agent": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", @@ -16401,6 +17127,19 @@ "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", "dev": true }, + "joi": { + "version": "17.6.2", + "resolved": "https://registry.npmjs.org/joi/-/joi-17.6.2.tgz", + "integrity": "sha512-+gqqdh1xc1wb+Lor0J9toqgeReyDOCqOdG8QSdRcEvwrcRiFQZneUCGKjFjuyBWUb3uaFOgY56yMaZ5FIc+H4w==", + "dev": true, + "requires": { + "@hapi/hoek": "^9.0.0", + "@hapi/topo": "^5.0.0", + "@sideway/address": "^4.1.3", + "@sideway/formula": "^3.0.0", + "@sideway/pinpoint": "^2.0.0" + } + }, "js-sdsl": { "version": "4.1.4", "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.1.4.tgz", @@ -16514,6 +17253,12 @@ "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", "dev": true }, + "lazy-ass": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/lazy-ass/-/lazy-ass-1.6.0.tgz", + "integrity": "sha512-cc8oEVoctTvsFZ/Oje/kGnHbpWHYBe8IAJe4C0QNc3t8uM/0Y8+erSz/7Y1ALuXTEZTMvxXwO6YbX1ey3ujiZw==", + "dev": true + }, "lerna": { "version": "5.5.1", "resolved": "https://registry.npmjs.org/lerna/-/lerna-5.5.1.tgz", @@ -16866,6 +17611,12 @@ "integrity": "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==", "dev": true }, + "map-stream": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz", + "integrity": "sha512-CkYQrPYZfWnu/DAmVCpTSX/xHpKZ80eKh2lAkyA6AJTef6bW+6JpbQZN5rofum7da+SyN1bi5ctTm+lTfcCW3g==", + "dev": true + }, "marked": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/marked/-/marked-4.1.0.tgz", @@ -17020,6 +17771,12 @@ "picomatch": "^2.3.1" } }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true + }, "mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", @@ -18064,6 +18821,12 @@ "is-wsl": "^2.2.0" } }, + "opener": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", + "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==", + "dev": true + }, "optionator": { "version": "0.9.1", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", @@ -18389,6 +19152,15 @@ "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", "dev": true }, + "pause-stream": { + "version": "0.0.11", + "resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", + "integrity": "sha512-e3FBlXLmN/D1S+zHzanP4E/4Z60oFAa3O051qt1pxa7DEJWKAyil6upYVXCWadEnuoqa4Pkc9oUx9zsxYeRv8A==", + "dev": true, + "requires": { + "through": "~2.3" + } + }, "pend": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", @@ -18429,6 +19201,37 @@ "find-up": "^4.0.0" } }, + "portfinder": { + "version": "1.0.32", + "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.32.tgz", + "integrity": "sha512-on2ZJVVDXRADWE6jnQaX0ioEylzgBpQk8r55NE4wjXW1ZxO+BgDlY6DXwj20i0V8eB4SenDQ00WEaxfiIQPcxg==", + "dev": true, + "requires": { + "async": "^2.6.4", + "debug": "^3.2.7", + "mkdirp": "^0.5.6" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "requires": { + "minimist": "^1.2.6" + } + } + } + }, "prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -18508,6 +19311,15 @@ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", "dev": true }, + "ps-tree": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/ps-tree/-/ps-tree-1.2.0.tgz", + "integrity": "sha512-0VnamPPYHl4uaU/nSFeZZpR21QAWRz+sRv4iW9+v/GS/J5U5iZB5BNN6J0RMoOvdx2gWM2+ZFMIm58q24e4UYA==", + "dev": true, + "requires": { + "event-stream": "=3.3.4" + } + }, "pump": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", @@ -18530,6 +19342,15 @@ "integrity": "sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==", "dev": true }, + "qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dev": true, + "requires": { + "side-channel": "^1.0.4" + } + }, "queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -18737,6 +19558,12 @@ "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", "dev": true }, + "requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "dev": true + }, "resolve": { "version": "1.22.1", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", @@ -18868,6 +19695,12 @@ "ajv-keywords": "^3.5.2" } }, + "secure-compare": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/secure-compare/-/secure-compare-3.0.1.tgz", + "integrity": "sha512-AckIIV90rPDcBcglUwXPF3kg0P0qmPsPXAj6BBEENQE1p5yA1xfmDJzfi1Tappj37Pv2mVbKpL3Z1T+Nn7k1Qw==", + "dev": true + }, "semver": { "version": "7.3.7", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", @@ -19081,6 +19914,41 @@ "minipass": "^3.1.1" } }, + "start-server-and-test": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/start-server-and-test/-/start-server-and-test-1.14.0.tgz", + "integrity": "sha512-on5ELuxO2K0t8EmNj9MtVlFqwBMxfWOhu4U7uZD1xccVpFlOQKR93CSe0u98iQzfNxRyaNTb/CdadbNllplTsw==", + "dev": true, + "requires": { + "bluebird": "3.7.2", + "check-more-types": "2.24.0", + "debug": "4.3.2", + "execa": "5.1.1", + "lazy-ass": "1.6.0", + "ps-tree": "1.2.0", + "wait-on": "6.0.0" + }, + "dependencies": { + "debug": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + } + } + }, + "stream-combiner": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz", + "integrity": "sha512-rT00SPnTVyRsaSz5zgSPma/aHSOic5U1prhYdRy5HS2kTZviFpmDgzilbtsJsxiroqACmayynDN/9VzIbX5DOw==", + "dev": true, + "requires": { + "duplexer": "~0.1.1" + } + }, "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", @@ -19647,6 +20515,15 @@ "through": "^2.3.8" } }, + "union": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/union/-/union-0.5.0.tgz", + "integrity": "sha512-N6uOhuW6zO95P3Mel2I2zMsbsanvvtgn6jVqJv4vbVcz/JN0OkL9suomjQGmWtxJQXOCqUJvquc1sMeNz/IwlA==", + "dev": true, + "requires": { + "qs": "^6.4.0" + } + }, "unique-filename": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-2.0.1.tgz", @@ -19703,6 +20580,12 @@ "punycode": "^2.1.0" } }, + "url-join": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz", + "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==", + "dev": true + }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -19758,6 +20641,19 @@ "integrity": "sha512-gu73tuZfJgu+mvCSy4UZwd2JXykjK9zAZsfmDeut5dx/1a7FeTk0XwJsSuqQn+cuMCGVbIBfl+s53X4T19DnzQ==", "dev": true }, + "wait-on": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/wait-on/-/wait-on-6.0.0.tgz", + "integrity": "sha512-tnUJr9p5r+bEYXPUdRseolmz5XqJTTj98JgOsfBn7Oz2dxfE2g3zw1jE+Mo8lopM3j3et/Mq1yW7kKX6qw7RVw==", + "dev": true, + "requires": { + "axios": "^0.21.1", + "joi": "^17.4.0", + "lodash": "^4.17.21", + "minimist": "^1.2.5", + "rxjs": "^7.1.0" + } + }, "walk-up-path": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/walk-up-path/-/walk-up-path-1.0.0.tgz", @@ -19888,6 +20784,26 @@ "dev": true, "peer": true }, + "whatwg-encoding": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", + "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", + "dev": true, + "requires": { + "iconv-lite": "0.6.3" + }, + "dependencies": { + "iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + } + } + } + }, "whatwg-url": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", diff --git a/package.json b/package.json index 105c720..b4f6b14 100644 --- a/package.json +++ b/package.json @@ -5,13 +5,18 @@ "lint": "npm-run-all lint:*", "lint:packages": "lerna run lint", "lint:root": "eslint tests", - "test": "npm-run-all test:*:*", - "test:browser": "mocha-headless-chrome -a allow-file-access-from-files -a enable-features=SharedArrayBuffer", - "test:browser:mt": "npm run test:browser -- -f tests/*-mt.test.html", - "test:browser:st": "npm run test:browser -- -f tests/*-st.test.html", + "pretest": "lerna run build", + "test": "server-test test:browser:server http://localhost:3000 test:all", + "test:all": "npm-run-all test:*:*:*", + "test:browser": "mocha-headless-chrome -t 60000 -a allow-file-access-from-files -a enable-features=SharedArrayBuffer", + "test:browser:core:mt": "npm run test:browser -- -f tests/ffmpeg-core-mt.test.html", + "test:browser:core:st": "npm run test:browser -- -f tests/ffmpeg-core-st.test.html", + "test:browser:ffmpeg:mt": "npm run test:browser -- -f tests/ffmpeg-mt.test.html", + "test:browser:ffmpeg:st": "npm run test:browser -- -f tests/ffmpeg-st.test.html", + "test:browser:server": "http-server -c-1 --cors -p 3000 .", "test:node": "mocha --exit --bail", - "test:node:mt": "npm run test:node -- --require tests/test-helper-mt.js tests/*.test.js", - "test:node:st": "npm run test:node -- --require tests/test-helper-st.js tests/*.test.js" + "test:node:core:mt": "npm run test:node -- --require tests/test-helper-mt.js tests/ffmpeg-core.test.js", + "test:node:core:st": "npm run test:node -- --require tests/test-helper-st.js tests/ffmpeg-core.test.js" }, "workspaces": [ "packages/*", @@ -19,9 +24,11 @@ ], "devDependencies": { "chai": "^4.3.6", + "http-server": "^14.1.1", "lerna": "^5.4.3", "mocha": "^10.0.0", "mocha-headless-chrome": "^4.0.0", - "npm-run-all": "^4.1.5" + "npm-run-all": "^4.1.5", + "start-server-and-test": "^1.14.0" } } diff --git a/packages/ffmpeg/package.json b/packages/ffmpeg/package.json index 5d71c8a..cf80c8f 100644 --- a/packages/ffmpeg/package.json +++ b/packages/ffmpeg/package.json @@ -1,7 +1,7 @@ { "name": "@ffmpeg/ffmpeg", "version": "0.11.5", - "description": "FFmpeg WebAssembly version", + "description": "FFmpeg WebAssembly version for browser", "main": "./dist/umd/ffmpeg.js", "types": "./dist/umd/ffmpeg.d.ts", "exports": { @@ -12,14 +12,15 @@ } }, "scripts": { - "dev": "webpack --watch -c webpack.dev.config.js", + "dev": "webpack -w --mode development", "lint": "eslint src", "clean": "rimraf dist", "build:umd": "webpack", "build:d": "tsc -p tsconfig.d.json", "build:esm": "tsc -p tsconfig.esm.json", "build": "npm-run-all clean build:*", - "docs": "typedoc --entryPointStrategy expand ./src" + "docs": "typedoc --entryPointStrategy expand ./src", + "docs:serve": "http-server docs" }, "files": [ "dist", @@ -52,6 +53,7 @@ "@typescript-eslint/eslint-plugin": "^5.37.0", "@typescript-eslint/parser": "^5.37.0", "eslint": "^8.23.1", + "http-server": "^14.1.1", "npm-run-all": "^4.1.5", "rimraf": "^3.0.2", "ts-loader": "^9.4.1", diff --git a/packages/ffmpeg/src/classes.ts b/packages/ffmpeg/src/classes.ts index 2f55c98..f18ccb3 100644 --- a/packages/ffmpeg/src/classes.ts +++ b/packages/ffmpeg/src/classes.ts @@ -4,15 +4,73 @@ import { CallbackData, Callbacks, DownloadProgressEvent, + FFFSPaths, FFMessageEventCallback, FFMessageLoadConfig, - IsDone, + OK, IsFirst, LogEvent, Message, Progress, + FileData, } from "./types"; import { getMessageID } from "./utils"; +import { ERROR_TERMINATED, ERROR_NOT_LOADED } from "./errors"; + +export declare interface FFmpeg { + /** + * Listen to download progress events from `ffmpeg.load()`. + * + * @example + * ```ts + * ffmpeg.on(FFmpeg.DOWNLOAD, ({ url, total, received, delta, done }) => { + * // ... + * }) + * ``` + * + * @category Event + */ + on( + event: typeof FFmpeg.DOWNLOAD, + listener: (data: DownloadProgressEvent) => void + ): this; + /** + * Listen to log events from `ffmpeg.exec()`. + * + * @example + * ```ts + * ffmpeg.on(FFmpeg.LOG, ({ message }) => { + * // ... + * }) + * ``` + * + * @remarks + * log includes output to stdout and stderr. + * + * @category Event + */ + on(event: typeof FFmpeg.LOG, listener: (log: LogEvent) => void): this; + /** + * Listen to progress events from `ffmpeg.exec()`. + * + * @example + * ```ts + * ffmpeg.on(FFmpeg.PROGRESS, ({ progress }) => { + * // ... + * }) + * ``` + * + * @remarks + * The progress events are accurate only when the length of + * input and output video/audio file are the same. + * + * @category Event + */ + on( + event: typeof FFmpeg.PROGRESS, + listener: (progress: Progress) => void + ): this; +} /** * Provides APIs to interact with ffmpeg web worker. @@ -27,80 +85,56 @@ export class FFmpeg extends EventEmitter { /** @event */ static readonly LOG = "log" as const; /** @event */ static readonly PROGRESS = "progress" as const; + #worker: Worker | null = null; /** - * Listen to download progress events from `ffmpeg.load()`. + * #resolves and #rejects tracks Promise resolves and rejects to + * be called when we receive message from web worker. * - * @category Event */ - on( - event: typeof FFmpeg.DOWNLOAD, - listener: (data: DownloadProgressEvent) => void - ): this; - /** - * Listen to log events from `ffmpeg.exec()`. - * - * @remarks - * log includes output to stdout and stderr. - * - * @category Event - */ - on(event: typeof FFmpeg.LOG, listener: (log: LogEvent) => void): this; - /** - * Listen to progress events from `ffmpeg.exec()`. - * - * @remarks - * The progress events are accurate only when the length of - * input and output video/audio file are the same. - * - * @category Event - */ - on( - event: typeof FFmpeg.PROGRESS, - listener: (progress: Progress) => void - ): this; - on(event: string, listener: any): this { - return this; - } - - #worker: Worker; #resolves: Callbacks = {}; #rejects: Callbacks = {}; constructor() { super(); - this.#worker = new Worker(new URL("./worker.ts", import.meta.url)); - this.#registerHandlers(); } - /** register worker message event handlers. + /** + * register worker message event handlers. */ #registerHandlers = () => { - this.#worker.onmessage = ({ - data: { id, type, data }, - }: FFMessageEventCallback) => { - switch (type) { - case FFMessageType.LOAD: - case FFMessageType.EXEC: - case FFMessageType.WRITE_FILE: - case FFMessageType.READ_FILE: - this.#resolves[id](data); - break; - case FFMessageType.DOWNLOAD: - this.emit(FFmpeg.DOWNLOAD, data as DownloadProgressEvent); - break; - case FFMessageType.LOG: - this.emit(FFmpeg.LOG, data as LogEvent); - break; - case FFMessageType.PROGRESS: - this.emit(FFmpeg.PROGRESS, data as Progress); - break; - case FFMessageType.ERROR: - this.#rejects[id](data); - break; - } - delete this.#resolves[id]; - delete this.#rejects[id]; - }; + if (this.#worker) { + this.#worker.onmessage = ({ + data: { id, type, data }, + }: FFMessageEventCallback) => { + switch (type) { + case FFMessageType.LOAD: + case FFMessageType.EXEC: + case FFMessageType.WRITE_FILE: + case FFMessageType.READ_FILE: + case FFMessageType.DELETE_FILE: + case FFMessageType.RENAME: + case FFMessageType.CREATE_DIR: + case FFMessageType.LIST_DIR: + case FFMessageType.DELETE_DIR: + this.#resolves[id](data); + break; + case FFMessageType.DOWNLOAD: + this.emit(FFmpeg.DOWNLOAD, data as DownloadProgressEvent); + break; + case FFMessageType.LOG: + this.emit(FFmpeg.LOG, data as LogEvent); + break; + case FFMessageType.PROGRESS: + this.emit(FFmpeg.PROGRESS, data as Progress); + break; + case FFMessageType.ERROR: + this.#rejects[id](data); + break; + } + delete this.#resolves[id]; + delete this.#rejects[id]; + }; + } }; /** @@ -109,13 +143,18 @@ export class FFmpeg extends EventEmitter { #send = ( { type, data }: Message, trans: Transferable[] = [] - ): Promise => - new Promise((resolve, reject) => { + ): Promise => { + if (!this.#worker) { + return Promise.reject(ERROR_NOT_LOADED); + } + + return new Promise((resolve, reject) => { const id = getMessageID(); - this.#worker.postMessage({ id, type, data }, trans); + this.#worker && this.#worker.postMessage({ id, type, data }, trans); this.#resolves[id] = resolve; this.#rejects[id] = reject; }); + }; /** * Loads ffmpeg-core inside web worker. It is required to call this method first @@ -124,11 +163,16 @@ export class FFmpeg extends EventEmitter { * @category FFmpeg * @returns `true` if ffmpeg core is loaded for the first time. */ - public load = (config: FFMessageLoadConfig): Promise => - this.#send({ + public load = (config: FFMessageLoadConfig = {}): Promise => { + if (!this.#worker) { + this.#worker = new Worker(new URL("./worker.ts", import.meta.url)); + this.#registerHandlers(); + } + return this.#send({ type: FFMessageType.LOAD, data: config, }) as Promise; + }; /** * Execute ffmpeg command. @@ -166,7 +210,28 @@ export class FFmpeg extends EventEmitter { }) as Promise; /** - * Write data to ffmpeg.wasm in memory file system. + * Terminate all ongoing API calls and terminate web worker. + * `FFmpeg.load()` must be called again before calling any other APIs. + * + * @category FFmpeg + */ + public terminate = (): void => { + const ids = Object.keys(this.#rejects); + // rejects all incomplete Promises. + for (const id of ids) { + this.#rejects[id](ERROR_TERMINATED); + delete this.#rejects[id]; + delete this.#resolves[id]; + } + + if (this.#worker) { + this.#worker.terminate(); + this.#worker = null; + } + }; + + /** + * Write data to ffmpeg.wasm. * * @example * ```ts @@ -178,25 +243,22 @@ export class FFmpeg extends EventEmitter { * * @category File System */ - public writeFile = ( - path: string, - bin: Uint8Array | string - ): Promise => { + public writeFile = (path: string, data: FileData): Promise => { const trans: Transferable[] = []; - if (bin instanceof Uint8Array) { - trans.push(bin.buffer); + if (data instanceof Uint8Array) { + trans.push(data.buffer); } return this.#send( { type: FFMessageType.WRITE_FILE, - data: { path, bin }, + data: { path, data }, }, trans - ) as Promise; + ) as Promise; }; /** - * Read data from ffmpeg.wasm in memory file system. + * Read data from ffmpeg.wasm. * * @example * ```ts @@ -207,9 +269,74 @@ export class FFmpeg extends EventEmitter { * * @category File System */ - public readFile = (path: string): Promise => + public readFile = ( + path: string, + /** + * File content encoding, supports two encodings: + * - utf8: read file as text file, return data in string type. + * - binary: read file as binary file, return data in Uint8Array type. + * + * @defaultValue binary + */ + encoding = "binary" + ): Promise => this.#send({ type: FFMessageType.READ_FILE, + data: { path, encoding }, + }) as Promise; + + /** + * Delete a file. + * + * @category File System + */ + public deleteFile = (path: string): Promise => + this.#send({ + type: FFMessageType.DELETE_FILE, data: { path }, - }) as Promise; + }) as Promise; + + /** + * Rename a file or directory. + * + * @category File System + */ + public rename = (oldPath: string, newPath: string): Promise => + this.#send({ + type: FFMessageType.RENAME, + data: { oldPath, newPath }, + }) as Promise; + + /** + * Create a directory. + * + * @category File System + */ + public createDir = (path: string): Promise => + this.#send({ + type: FFMessageType.CREATE_DIR, + data: { path }, + }) as Promise; + + /** + * List directory contents. + * + * @category File System + */ + public listDir = (path: string): Promise => + this.#send({ + type: FFMessageType.LIST_DIR, + data: { path }, + }) as Promise; + + /** + * Delete an empty directory. + * + * @category File System + */ + public deleteDir = (path: string): Promise => + this.#send({ + type: FFMessageType.DELETE_DIR, + data: { path }, + }) as Promise; } diff --git a/packages/ffmpeg/src/const.ts b/packages/ffmpeg/src/const.ts index 4e46dfd..f01f071 100644 --- a/packages/ffmpeg/src/const.ts +++ b/packages/ffmpeg/src/const.ts @@ -3,13 +3,18 @@ export const MIME_TYPE_JAVASCRIPT = "text/javascript"; export const MIME_TYPE_WASM = "application/wasm"; export const CORE_VERSION = "0.12.0"; -export const CORE_URL = `https://unpkg.com/@ffmpeg/core@${CORE_VERSION}/dist/umd/ffmpeg-core.js`; +export const CORE_URL = `https://unpkg.com/@ffmpeg/core@${CORE_VERSION}/dist/ffmpeg-core.js`; export enum FFMessageType { LOAD = "load", - WRITE_FILE = "WRITE_FILE", EXEC = "EXEC", + WRITE_FILE = "WRITE_FILE", READ_FILE = "READ_FILE", + DELETE_FILE = "DELETE_FILE", + RENAME = "RENAME", + CREATE_DIR = "CREATE_DIR", + LIST_DIR = "LIST_DIR", + DELETE_DIR = "DELETE_DIR", ERROR = "ERROR", DOWNLOAD = "DOWNLOAD", diff --git a/packages/ffmpeg/src/errors.ts b/packages/ffmpeg/src/errors.ts index 9d98e51..afac3e2 100644 --- a/packages/ffmpeg/src/errors.ts +++ b/packages/ffmpeg/src/errors.ts @@ -11,3 +11,4 @@ export const ERROR_NOT_LOADED = new Error( export const ERROR_INCOMPLETED_DOWNLOAD = new Error( "failed to complete download" ); +export const ERROR_TERMINATED = new Error("called FFmpeg.terminate()"); diff --git a/packages/ffmpeg/src/types.ts b/packages/ffmpeg/src/types.ts index 87476e9..66f6927 100644 --- a/packages/ffmpeg/src/types.ts +++ b/packages/ffmpeg/src/types.ts @@ -1,4 +1,5 @@ export type FFFSPath = string; +export type FFFSPaths = FFFSPath[]; /** * ffmpeg-core loading configuration. @@ -48,25 +49,56 @@ export interface FFMessageLoadConfig { thread?: boolean; } -export interface FFMessageWriteFileData { - path: FFFSPath; - bin: Uint8Array | string; -} - export interface FFMessageExecData { args: string[]; timeout?: number; } +export interface FFMessageWriteFileData { + path: FFFSPath; + data: FileData; +} + export interface FFMessageReadFileData { path: FFFSPath; + encoding: string; +} + +export interface FFMessageDeleteFileData { + path: FFFSPath; +} + +export interface FFMessageRenameData { + oldPath: FFFSPath; + newPath: FFFSPath; +} + +export interface FFMessageCreateDirData { + path: FFFSPath; +} + +export interface FFMessageListDirData { + path: FFFSPath; +} + +/** + * @remarks + * Only deletes empty directory. + */ +export interface FFMessageDeleteDirData { + path: FFFSPath; } export type FFMessageData = | FFMessageLoadConfig - | FFMessageWriteFileData | FFMessageExecData - | FFMessageReadFileData; + | FFMessageWriteFileData + | FFMessageReadFileData + | FFMessageDeleteFileData + | FFMessageRenameData + | FFMessageCreateDirData + | FFMessageListDirData + | FFMessageDeleteDirData; export interface Message { type: string; @@ -94,12 +126,15 @@ export interface LogEvent { message: string; } +export interface Progress { + progress: number; +} + export type ExitCode = number; export type ErrorMessage = string; -export type FileData = Uint8Array; -export type Progress = number; +export type FileData = Uint8Array | string; export type IsFirst = boolean; -export type IsDone = boolean; +export type OK = boolean; export type CallbackData = | FileData @@ -109,8 +144,9 @@ export type CallbackData = | LogEvent | Progress | IsFirst - | IsDone + | OK | Error + | FFFSPaths | undefined; export interface Callbacks { diff --git a/packages/ffmpeg/src/utils.ts b/packages/ffmpeg/src/utils.ts index 70016eb..d18a75e 100644 --- a/packages/ffmpeg/src/utils.ts +++ b/packages/ffmpeg/src/utils.ts @@ -16,36 +16,56 @@ export const getMessageID = (() => { /** * Download content of a URL with progress. + * + * Progress only works when Content-Length is provided by the server. + * */ export const downloadWithProgress = async ( url: string | URL, cb: ProgressCallback -): Promise => { +): Promise => { const resp = await fetch(url); - const reader = resp.body?.getReader(); - if (!reader) throw ERROR_RESPONSE_BODY_READER; + let buf; - const total = parseInt(resp.headers.get(HeaderContentLength) || "0"); - if (total === 0) throw ERROR_ZERO_CONTENT_LENGTH; + try { + const total = parseInt(resp.headers.get(HeaderContentLength) || "0"); + if (total === 0) throw ERROR_ZERO_CONTENT_LENGTH; - const data = new Uint8Array(total); - let received = 0; - for (;;) { - const { done, value } = await reader.read(); - const delta = value ? value.length : 0; + const reader = resp.body?.getReader(); + if (!reader) throw ERROR_RESPONSE_BODY_READER; - if (done) { - if (total !== received) throw ERROR_INCOMPLETED_DOWNLOAD; + const data = new Uint8Array(total); + let received = 0; + for (;;) { + const { done, value } = await reader.read(); + const delta = value ? value.length : 0; + + if (done) { + if (total !== received) throw ERROR_INCOMPLETED_DOWNLOAD; + cb({ url, total, received, delta, done }); + break; + } + + data.set(value, received); + received += delta; cb({ url, total, received, delta, done }); - break; } - data.set(value, received); - received += delta; - cb({ url, total, received, delta, done }); + buf = data.buffer; + } catch (e) { + console.log(`failed to send download progress event: `, e); + // Fetch arrayBuffer directly when it is not possible to get progress. + buf = await resp.arrayBuffer(); + cb({ + url, + total: buf.byteLength, + received: buf.byteLength, + delta: 0, + done: true, + }); } - return data; + return buf; }; /** @@ -58,5 +78,7 @@ export const toBlobURL = async ( cb: ProgressCallback ): Promise => URL.createObjectURL( - new Blob([(await downloadWithProgress(url, cb)).buffer], { type: mimeType }) + new Blob([await downloadWithProgress(url, cb)], { + type: mimeType, + }) ); diff --git a/packages/ffmpeg/src/worker.ts b/packages/ffmpeg/src/worker.ts index 2f82c1a..1f38e50 100644 --- a/packages/ffmpeg/src/worker.ts +++ b/packages/ffmpeg/src/worker.ts @@ -6,13 +6,20 @@ import type { FFmpegCoreModule, FFmpegCoreModuleFactory } from "@ffmpeg/types"; import type { FFMessageEvent, FFMessageLoadConfig, - FFMessageWriteFileData, FFMessageExecData, + FFMessageWriteFileData, FFMessageReadFileData, + FFMessageDeleteFileData, + FFMessageRenameData, + FFMessageCreateDirData, + FFMessageListDirData, + FFMessageDeleteDirData, CallbackData, IsFirst, - IsDone, + OK, ExitCode, + FFFSPaths, + FileData, } from "./types"; import { toBlobURL } from "./utils"; import { @@ -53,18 +60,15 @@ const load = async ({ self.postMessage({ type: FFMessageType.DOWNLOAD, data }) ); if (thread) { - try { - workerURL = await toBlobURL(workerURL, MIME_TYPE_JAVASCRIPT, (data) => - self.postMessage({ type: FFMessageType.DOWNLOAD, data }) - ); - // eslint-disable-next-line - } catch (e) {} + workerURL = await toBlobURL(workerURL, MIME_TYPE_JAVASCRIPT, (data) => + self.postMessage({ type: FFMessageType.DOWNLOAD, data }) + ); } } importScripts(coreURL); ffmpeg = await (self as WorkerGlobalScope).createFFmpegCore({ - // Fixed `Overload resolution failed.` when using multi-threaded ffmpeg-core. + // Fix `Overload resolution failed.` when using multi-threaded ffmpeg-core. mainScriptUrlOrBlob: coreURL, locateFile: (path: string, prefix: string): string => { if (path.endsWith(".wasm")) return wasmURL; @@ -75,17 +79,12 @@ const load = async ({ ffmpeg.setLogger((data) => self.postMessage({ type: FFMessageType.LOG, data }) ); - ffmpeg.setProgress((data: number) => - self.postMessage({ type: FFMessageType.PROGRESS, data }) + ffmpeg.setProgress((progress: number) => + self.postMessage({ type: FFMessageType.PROGRESS, data: { progress } }) ); return first; }; -const writeFile = ({ path, bin }: FFMessageWriteFileData): IsDone => { - ffmpeg.FS.writeFile(path, bin); - return true; -}; - const exec = ({ args, timeout = -1 }: FFMessageExecData): ExitCode => { ffmpeg.setTimeout(timeout); ffmpeg.exec(...args); @@ -94,8 +93,40 @@ const exec = ({ args, timeout = -1 }: FFMessageExecData): ExitCode => { return ret; }; -const readFile = ({ path }: FFMessageReadFileData): Uint8Array => - ffmpeg.FS.readFile(path); +const writeFile = ({ path, data }: FFMessageWriteFileData): OK => { + ffmpeg.FS.writeFile(path, data); + return true; +}; + +const readFile = ({ path, encoding }: FFMessageReadFileData): FileData => + ffmpeg.FS.readFile(path, { encoding }); + +// TODO: check if deletion works. +const deleteFile = ({ path }: FFMessageDeleteFileData): OK => { + ffmpeg.FS.unlink(path); + return true; +}; + +const rename = ({ oldPath, newPath }: FFMessageRenameData): OK => { + ffmpeg.FS.rename(oldPath, newPath); + return true; +}; + +// TODO: check if creation works. +const createDir = ({ path }: FFMessageCreateDirData): OK => { + ffmpeg.FS.mkdir(path); + return true; +}; + +const listDir = ({ path }: FFMessageListDirData): FFFSPaths => { + return ffmpeg.FS.readdir(path); +}; + +// TODO: check if deletion works. +const deleteDir = ({ path }: FFMessageDeleteDirData): OK => { + ffmpeg.FS.rmdir(path); + return true; +}; self.onmessage = async ({ data: { id, type, data: _data }, @@ -109,15 +140,30 @@ self.onmessage = async ({ case FFMessageType.LOAD: data = await load(_data as FFMessageLoadConfig); break; - case FFMessageType.WRITE_FILE: - data = writeFile(_data as FFMessageWriteFileData); - break; case FFMessageType.EXEC: data = exec(_data as FFMessageExecData); break; + case FFMessageType.WRITE_FILE: + data = writeFile(_data as FFMessageWriteFileData); + break; case FFMessageType.READ_FILE: data = readFile(_data as FFMessageReadFileData); break; + case FFMessageType.DELETE_FILE: + data = deleteFile(_data as FFMessageDeleteFileData); + break; + case FFMessageType.RENAME: + data = rename(_data as FFMessageRenameData); + break; + case FFMessageType.CREATE_DIR: + data = createDir(_data as FFMessageCreateDirData); + break; + case FFMessageType.LIST_DIR: + data = listDir(_data as FFMessageListDirData); + break; + case FFMessageType.DELETE_DIR: + data = deleteDir(_data as FFMessageDeleteDirData); + break; default: throw ERROR_UNKNOWN_MESSAGE_TYPE; } diff --git a/packages/ffmpeg/webpack.dev.config.js b/packages/ffmpeg/webpack.dev.config.js deleted file mode 100644 index 329de5a..0000000 --- a/packages/ffmpeg/webpack.dev.config.js +++ /dev/null @@ -1,27 +0,0 @@ -const path = require("path"); - -module.exports = { - mode: "development", - devtool: "source-map", - entry: "./src/index.ts", - module: { - rules: [ - { - test: /\.ts$/, - loader: "ts-loader", - options: { - transpileOnly: false, - }, - }, - ], - }, - resolve: { - extensions: [".ts"], - }, - output: { - path: path.resolve(__dirname, "dist/umd"), - filename: "ffmpeg.js", - library: "FFmpegWASM", - libraryTarget: "umd", - }, -}; diff --git a/packages/types/types/index.d.ts b/packages/types/types/index.d.ts index 2385dc8..c6a0a1a 100644 --- a/packages/types/types/index.d.ts +++ b/packages/types/types/index.d.ts @@ -4,12 +4,18 @@ export type Pointer = number; export type StringPointer = Pointer; export type StringArrayPointer = Pointer; +export interface ReadFileOptions { + encdoing: string; +} + export interface FS { - mkdir: (fileName: string) => void; - readFile: (fileName: string) => Uint8Array; - readdir: (pathName: string) => string[]; - unlink: (fileName: string) => void; - writeFile: (fileName: string, binaryData: Uint8Array | string) => void; + mkdir: (path: string) => void; + rmdir: (path: string) => void; + rename: (oldPath: string, newPath: string) => void; + writeFile: (path: string, data: Uint8Array | string) => void; + readFile: (path: string, opts: OptionReadFile) => Uint8Array | string; + readdir: (path: string) => string[]; + unlink: (path: string) => void; } export interface Log { diff --git a/tests/.eslintrc.json b/tests/.eslintrc.json index b548f41..398d3e1 100644 --- a/tests/.eslintrc.json +++ b/tests/.eslintrc.json @@ -1,10 +1,13 @@ { "extends": "eslint:recommended", "globals": { - "expect": true, - "createFFmpegCore": true, + "CORE_URL": true, + "FFMPEG_TYPE": true, + "FFmpegWASM": true, "VIDEO_1S_MP4": true, - "FFMPEG_TYPE": true + "b64ToUint8Array": true, + "createFFmpegCore": true, + "expect": true }, "env": { "node": true, diff --git a/tests/ffmpeg-core-mt.test.html b/tests/ffmpeg-core-mt.test.html index a53d7f0..7793122 100644 --- a/tests/ffmpeg-core-mt.test.html +++ b/tests/ffmpeg-core-mt.test.html @@ -10,7 +10,7 @@ - + diff --git a/tests/ffmpeg-core-st.test.html b/tests/ffmpeg-core-st.test.html index faecbe6..58bf10f 100644 --- a/tests/ffmpeg-core-st.test.html +++ b/tests/ffmpeg-core-st.test.html @@ -10,7 +10,7 @@ - + diff --git a/tests/ffmpeg-core.test.js b/tests/ffmpeg-core.test.js index a08a1bf..efb4708 100644 --- a/tests/ffmpeg-core.test.js +++ b/tests/ffmpeg-core.test.js @@ -1,31 +1,21 @@ -let ffmpeg; +let core; const genName = (name) => `[ffmpeg-core][${FFMPEG_TYPE}] ${name}`; -const b64ToUint8Array = (b64) => { - const bin = atob(b64); - const len = bin.length; - const bytes = new Uint8Array(len); - for (let i = 0; i < len; i++) { - bytes[i] = bin.charCodeAt(i); - } - return bytes; -}; - const reset = () => { - ffmpeg.reset(); - ffmpeg.setLogger(() => {}); - ffmpeg.setProgress(() => {}); + core.reset(); + core.setLogger(() => {}); + core.setProgress(() => {}); }; before(async () => { - ffmpeg = await createFFmpegCore(); - ffmpeg.FS.writeFile("video.mp4", b64ToUint8Array(VIDEO_1S_MP4)); + core = await createFFmpegCore(); + core.FS.writeFile("video.mp4", b64ToUint8Array(VIDEO_1S_MP4)); }); describe(genName("createFFmpeg()"), () => { it("should be OK", () => { - expect(ffmpeg).to.be.ok; + expect(core).to.be.ok; }); }); @@ -33,16 +23,16 @@ describe(genName("reset()"), () => { beforeEach(reset); it("should exist", () => { - expect("reset" in ffmpeg).to.be.true; + expect("reset" in core).to.be.true; }); it("should reset ret and timeout", () => { - ffmpeg.ret = 1024; - ffmpeg.timeout = 1024; + core.ret = 1024; + core.timeout = 1024; - ffmpeg.reset(); + core.reset(); - expect(ffmpeg.ret).to.equal(-1); - expect(ffmpeg.timeout).to.equal(-1); + expect(core.ret).to.equal(-1); + expect(core.timeout).to.equal(-1); }); }); @@ -50,18 +40,18 @@ describe(genName("exec()"), () => { beforeEach(reset); it("should exist", () => { - expect("exec" in ffmpeg).to.be.true; + expect("exec" in core).to.be.true; }); it("should output help", () => { - expect(ffmpeg.exec("-h")).to.equal(0); + expect(core.exec("-h")).to.equal(0); }); it("should transcode", () => { - expect(ffmpeg.exec("-i", "video.mp4", "video.avi")).to.equal(0); - const out = ffmpeg.FS.readFile("video.avi"); + expect(core.exec("-i", "video.mp4", "video.avi")).to.equal(0); + const out = core.FS.readFile("video.avi"); expect(out.length).to.not.equal(0); - ffmpeg.FS.unlink("video.avi"); + core.FS.unlink("video.avi"); }); }); @@ -69,12 +59,12 @@ describe(genName("setTimeout()"), () => { beforeEach(reset); it("should exist", () => { - expect("setTimeout" in ffmpeg).to.be.true; + expect("setTimeout" in core).to.be.true; }); it("should timeout", () => { - ffmpeg.setTimeout(1); // timeout after 1ms - expect(ffmpeg.exec("-i", "video.mp4", "video.avi")).to.equal(1); + core.setTimeout(1); // timeout after 1ms + expect(core.exec("-i", "video.mp4", "video.avi")).to.equal(1); }); }); @@ -82,13 +72,13 @@ describe(genName("setLogger()"), () => { beforeEach(reset); it("should exist", () => { - expect("setLogger" in ffmpeg).to.be.true; + expect("setLogger" in core).to.be.true; }); it("should handle logs", () => { const logs = []; - ffmpeg.setLogger(({ message }) => logs.push(message)); - ffmpeg.exec("-h"); + core.setLogger(({ message }) => logs.push(message)); + core.exec("-h"); expect(logs.length).to.not.equal(0); }); }); @@ -97,14 +87,14 @@ describe(genName("setProgress()"), () => { beforeEach(reset); it("should exist", () => { - expect("setProgress" in ffmpeg).to.be.true; + expect("setProgress" in core).to.be.true; }); it("should handle progress", () => { let progress = 0; - ffmpeg.setProgress((_progress) => (progress = _progress)); - expect(ffmpeg.exec("-i", "video.mp4", "video.avi")).to.equal(0); + core.setProgress((_progress) => (progress = _progress)); + expect(core.exec("-i", "video.mp4", "video.avi")).to.equal(0); expect(progress).to.equal(1); - ffmpeg.FS.unlink("video.avi"); + core.FS.unlink("video.avi"); }); }); diff --git a/tests/ffmpeg-mt.test.html b/tests/ffmpeg-mt.test.html new file mode 100644 index 0000000..0d9c473 --- /dev/null +++ b/tests/ffmpeg-mt.test.html @@ -0,0 +1,25 @@ + + + + + FFmpeg Unit Test + + + +
+ + + + + + + + + + diff --git a/tests/ffmpeg-st.test.html b/tests/ffmpeg-st.test.html new file mode 100644 index 0000000..bddf59c --- /dev/null +++ b/tests/ffmpeg-st.test.html @@ -0,0 +1,25 @@ + + + + + FFmpeg Unit Test + + + +
+ + + + + + + + + + diff --git a/tests/ffmpeg.test.js b/tests/ffmpeg.test.js new file mode 100644 index 0000000..01c9643 --- /dev/null +++ b/tests/ffmpeg.test.js @@ -0,0 +1,171 @@ +const { FFmpeg } = FFmpegWASM; + +const genName = (name) => `[ffmpeg][${FFMPEG_TYPE}] ${name}`; + +const createFFmpeg = async () => { + const ffmpeg = new FFmpeg(); + await ffmpeg.load({ + coreURL: CORE_URL, + thread: FFMPEG_TYPE === "mt", + }); + return ffmpeg; +}; + +describe(genName("new FFmpeg()"), () => { + it("should be OK", () => { + expect(new FFmpeg()).to.be.ok; + }); +}); + +describe(genName("FFmpeg.load()"), function () { + // it("should work without any args", async () => { + // const ffmpeg = new FFmpeg(); + // await ffmpeg.load(); + // expect(ffmpeg).to.be.ok; + // }); + + it("should work when blob is false", async () => { + const ffmpeg = new FFmpeg(); + await ffmpeg.load({ + coreURL: CORE_URL, + blob: false, + thread: FFMPEG_TYPE === "mt", + }); + expect(ffmpeg).to.be.ok; + ffmpeg.terminate(); + }); + + it("should receive download progress events", async () => { + const ffmpeg = new FFmpeg(); + let done = false; + ffmpeg.on(FFmpeg.DOWNLOAD, ({ done: _done }) => { + done = _done; + }); + await ffmpeg.load({ + coreURL: CORE_URL, + thread: FFMPEG_TYPE === "mt", + }); + expect(done).to.be.true; + ffmpeg.terminate(); + }); +}); + +describe( + genName( + "FFmpeg directory APIs (createDir(), listDir(), deleteDir(), rename())" + ), + function () { + let ffmpeg; + + before(async () => { + ffmpeg = await createFFmpeg(); + }); + + after(() => { + ffmpeg.terminate(); + }); + + it("should list root dir", async () => { + const files = await ffmpeg.listDir("/"); + expect(files).to.have.lengthOf(6); + }); + + it("should create a dir", async () => { + await ffmpeg.createDir("/dir1"); + const files = await ffmpeg.listDir("/"); + expect(files).to.include("dir1"); + }); + + it("should delete a dir", async () => { + await ffmpeg.createDir("/dir2"); + await ffmpeg.deleteDir("/dir2"); + const files = await ffmpeg.listDir("/"); + expect(files).to.not.include("dir2"); + }); + + it("should rename a dir", async () => { + await ffmpeg.createDir("/dir3"); + await ffmpeg.rename("/dir3", "/dir4"); + const files = await ffmpeg.listDir("/"); + expect(files).to.not.include("dir3"); + expect(files).to.include("dir4"); + }); + } +); + +describe( + genName( + "FFmpeg files APIs (readFile(), writeFile(), deleteFile(), rename())" + ), + function () { + let ffmpeg; + + before(async () => { + ffmpeg = await createFFmpeg(); + }); + + after(() => { + ffmpeg.terminate(); + }); + + it("should write/read a text file", async () => { + const text = "foo"; + await ffmpeg.writeFile("/file1", text); + const data = await ffmpeg.readFile("/file1", "utf8"); + const files = await ffmpeg.listDir("/"); + expect(files).to.include("file1"); + expect(data).to.equal(text); + }); + + it("should write a binary file", async () => { + const bin = [1, 2, 3]; + await ffmpeg.writeFile("/file2", Uint8Array.from(bin)); + const data = await ffmpeg.readFile("/file2"); + const files = await ffmpeg.listDir("/"); + expect(files).to.include("file2"); + expect(data).to.deep.equal(Uint8Array.from(bin)); + }); + } +); + +describe(genName("FFmpeg.exec()"), function () { + let ffmpeg; + + before(async () => { + ffmpeg = await createFFmpeg(); + await ffmpeg.writeFile("video.mp4", b64ToUint8Array(VIDEO_1S_MP4)); + }); + + after(() => { + ffmpeg.terminate(); + }); + + it("should output help with exit code 0", async () => { + let m; + const listener = ({ message }) => { + m = message; + }; + ffmpeg.on(FFmpeg.LOG, listener); + const ret = await ffmpeg.exec(["-h"]); + expect(ret).to.equal(0); + expect(m).to.be.a("string"); + ffmpeg.removeListener(FFmpeg.LOG, listener); + }); + + it("should transcode mp4 to avi", async () => { + let p; + const listener = ({ progress }) => { + p = progress; + }; + ffmpeg.on(FFmpeg.PROGRESS, listener); + const ret = await ffmpeg.exec(["-i", "video.mp4", "video.avi"]); + expect(ret).to.equal(0); + expect(p).to.equal(1); + ffmpeg.removeListener(FFmpeg.PROGRESS, listener); + }); + + it("should stop if timeout", async () => { + const ret = await ffmpeg.exec(["-i", "video.mp4", "video.avi"], 1); + expect(ret).to.equal(1); + }); +}); diff --git a/tests/constants.js b/tests/test-helper-browser.js similarity index 95% rename from tests/constants.js rename to tests/test-helper-browser.js index 46a3ce5..1f1aedd 100644 --- a/tests/constants.js +++ b/tests/test-helper-browser.js @@ -1,7 +1,19 @@ const VIDEO_1S_MP4 = "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"; + +const b64ToUint8Array = (b64) => { + const bin = atob(b64); + const len = bin.length; + const bytes = new Uint8Array(len); + for (let i = 0; i < len; i++) { + bytes[i] = bin.charCodeAt(i); + } + return bytes; +}; + if (typeof module !== "undefined") { module.exports = { VIDEO_1S_MP4, + b64ToUint8Array, }; } diff --git a/tests/test-helper-mt.js b/tests/test-helper-mt.js index ba4f1ba..bdefd9d 100644 --- a/tests/test-helper-mt.js +++ b/tests/test-helper-mt.js @@ -1,11 +1,11 @@ const chai = require("chai"); -const constants = require("./constants"); +const browser = require("./test-helper-browser"); global.expect = chai.expect; global.createFFmpegCore = require("../packages/core-mt"); global.atob = require("./util").atob; global.FFMPEG_TYPE = "mt"; -Object.keys(constants).forEach((key) => { - global[key] = constants[key]; +Object.keys(browser).forEach((key) => { + global[key] = browser[key]; }); diff --git a/tests/test-helper-st.js b/tests/test-helper-st.js index 26c98af..859a43c 100644 --- a/tests/test-helper-st.js +++ b/tests/test-helper-st.js @@ -1,11 +1,11 @@ const chai = require("chai"); -const constants = require("./constants"); +const browser = require("./test-helper-browser"); global.expect = chai.expect; global.createFFmpegCore = require("../packages/core"); global.atob = require("./util").atob; global.FFMPEG_TYPE = "st"; -Object.keys(constants).forEach((key) => { - global[key] = constants[key]; +Object.keys(browser).forEach((key) => { + global[key] = browser[key]; });