From 8b0d27499c664272271ccc3fd45cbd4561768ea7 Mon Sep 17 00:00:00 2001 From: Jacob <64662184+aarthificial@users.noreply.github.com> Date: Fri, 23 Aug 2024 17:29:58 +0200 Subject: [PATCH] feat(ffmpeg): skip encoding when extracting frames (#1095) --- commitlint.config.js | 1 + package-lock.json | 914 ++++++++++-------- packages/core/src/app/Exporter.ts | 2 + packages/core/src/app/Renderer.ts | 1 + packages/core/src/app/Stage.ts | 7 +- .../ffmpeg/client/FFmpegExporterClient.ts | 62 +- packages/ffmpeg/package.json | 5 +- packages/ffmpeg/server/FFmpegBridge.ts | 23 +- .../ffmpeg/server/FFmpegExporterServer.ts | 26 +- packages/ffmpeg/server/ImageStream.ts | 62 +- packages/ffmpeg/server/index.ts | 2 +- packages/template/vite.config.ts | 4 + packages/ui/src/components/console/Log.tsx | 18 +- 13 files changed, 687 insertions(+), 440 deletions(-) diff --git a/commitlint.config.js b/commitlint.config.js index 2885d4d76..39e925413 100644 --- a/commitlint.config.js +++ b/commitlint.config.js @@ -12,6 +12,7 @@ module.exports = { 'docs', 'e2e', 'examples', + 'ffmpeg', 'legacy', 'player', 'ui', diff --git a/package-lock.json b/package-lock.json index e1ec17ac5..177bf9ea5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3248,6 +3248,56 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "node_modules/@derhuerst/http-basic": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/@derhuerst/http-basic/-/http-basic-8.2.0.tgz", + "integrity": "sha512-v1cqPUpFjU8DInW4YkC9caGKy8kUkqz0z10yCHawkxgpaJPId0F5xKi8fUY5rqC58F9Muz9T136jNReZQH9xIw==", + "dependencies": { + "caseless": "^0.12.0", + "concat-stream": "^1.6.2", + "http-response-object": "^3.0.1", + "parse-cache-control": "^1.0.1" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@derhuerst/http-basic/node_modules/concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "engines": [ + "node >= 0.8" + ], + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "node_modules/@derhuerst/http-basic/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/@derhuerst/http-basic/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/@docsearch/css": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-3.3.3.tgz", @@ -4333,243 +4383,6 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, - "node_modules/@ffmpeg-installer/darwin-arm64": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@ffmpeg-installer/darwin-arm64/-/darwin-arm64-4.1.5.tgz", - "integrity": "sha512-hYqTiP63mXz7wSQfuqfFwfLOfwwFChUedeCVKkBtl/cliaTM7/ePI9bVzfZ2c+dWu3TqCwLDRWNSJ5pqZl8otA==", - "cpu": [ - "arm64" - ], - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@ffmpeg-installer/darwin-x64": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@ffmpeg-installer/darwin-x64/-/darwin-x64-4.1.0.tgz", - "integrity": "sha512-Z4EyG3cIFjdhlY8wI9aLUXuH8nVt7E9SlMVZtWvSPnm2sm37/yC2CwjUzyCQbJbySnef1tQwGG2Sx+uWhd9IAw==", - "cpu": [ - "x64" - ], - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@ffmpeg-installer/ffmpeg": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@ffmpeg-installer/ffmpeg/-/ffmpeg-1.1.0.tgz", - "integrity": "sha512-Uq4rmwkdGxIa9A6Bd/VqqYbT7zqh1GrT5/rFwCwKM70b42W5gIjWeVETq6SdcL0zXqDtY081Ws/iJWhr1+xvQg==", - "optionalDependencies": { - "@ffmpeg-installer/darwin-arm64": "4.1.5", - "@ffmpeg-installer/darwin-x64": "4.1.0", - "@ffmpeg-installer/linux-arm": "4.1.3", - "@ffmpeg-installer/linux-arm64": "4.1.4", - "@ffmpeg-installer/linux-ia32": "4.1.0", - "@ffmpeg-installer/linux-x64": "4.1.0", - "@ffmpeg-installer/win32-ia32": "4.1.0", - "@ffmpeg-installer/win32-x64": "4.1.0" - } - }, - "node_modules/@ffmpeg-installer/linux-arm": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/@ffmpeg-installer/linux-arm/-/linux-arm-4.1.3.tgz", - "integrity": "sha512-NDf5V6l8AfzZ8WzUGZ5mV8O/xMzRag2ETR6+TlGIsMHp81agx51cqpPItXPib/nAZYmo55Bl2L6/WOMI3A5YRg==", - "cpu": [ - "arm" - ], - "hasInstallScript": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@ffmpeg-installer/linux-arm64": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/@ffmpeg-installer/linux-arm64/-/linux-arm64-4.1.4.tgz", - "integrity": "sha512-dljEqAOD0oIM6O6DxBW9US/FkvqvQwgJ2lGHOwHDDwu/pX8+V0YsDL1xqHbj1DMX/+nP9rxw7G7gcUvGspSoKg==", - "cpu": [ - "arm64" - ], - "hasInstallScript": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@ffmpeg-installer/linux-ia32": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@ffmpeg-installer/linux-ia32/-/linux-ia32-4.1.0.tgz", - "integrity": "sha512-0LWyFQnPf+Ij9GQGD034hS6A90URNu9HCtQ5cTqo5MxOEc7Rd8gLXrJvn++UmxhU0J5RyRE9KRYstdCVUjkNOQ==", - "cpu": [ - "ia32" - ], - "hasInstallScript": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@ffmpeg-installer/linux-x64": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@ffmpeg-installer/linux-x64/-/linux-x64-4.1.0.tgz", - "integrity": "sha512-Y5BWhGLU/WpQjOArNIgXD3z5mxxdV8c41C+U15nsE5yF8tVcdCGet5zPs5Zy3Ta6bU7haGpIzryutqCGQA/W8A==", - "cpu": [ - "x64" - ], - "hasInstallScript": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@ffmpeg-installer/win32-ia32": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@ffmpeg-installer/win32-ia32/-/win32-ia32-4.1.0.tgz", - "integrity": "sha512-FV2D7RlaZv/lrtdhaQ4oETwoFUsUjlUiasiZLDxhEUPdNDWcH1OU9K1xTvqz+OXLdsmYelUDuBS/zkMOTtlUAw==", - "cpu": [ - "ia32" - ], - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@ffmpeg-installer/win32-x64": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@ffmpeg-installer/win32-x64/-/win32-x64-4.1.0.tgz", - "integrity": "sha512-Drt5u2vzDnIONf4ZEkKtFlbvwj6rI3kxw1Ck9fpudmtgaZIHD4ucsWB2lCZBXRxJgXR+2IMSti+4rtM4C4rXgg==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@ffprobe-installer/darwin-arm64": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@ffprobe-installer/darwin-arm64/-/darwin-arm64-5.0.1.tgz", - "integrity": "sha512-vwNCNjokH8hfkbl6m95zICHwkSzhEvDC3GVBcUp5HX8+4wsX10SP3B+bGur7XUzTIZ4cQpgJmEIAx6TUwRepMg==", - "cpu": [ - "arm64" - ], - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@ffprobe-installer/darwin-x64": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@ffprobe-installer/darwin-x64/-/darwin-x64-5.1.0.tgz", - "integrity": "sha512-J+YGscZMpQclFg31O4cfVRGmDpkVsQ2fZujoUdMAAYcP0NtqpC49Hs3SWJpBdsGB4VeqOt5TTm1vSZQzs1NkhA==", - "cpu": [ - "x64" - ], - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@ffprobe-installer/ffprobe": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@ffprobe-installer/ffprobe/-/ffprobe-2.1.2.tgz", - "integrity": "sha512-ZNvwk4f2magF42Zji2Ese16SMj9BS7Fui4kRjg6gTYTxY3gWZNpg85n4MIfQyI9nimHg4x/gT6FVkp/bBDuBwg==", - "engines": { - "node": ">=14.21.2" - }, - "optionalDependencies": { - "@ffprobe-installer/darwin-arm64": "5.0.1", - "@ffprobe-installer/darwin-x64": "5.1.0", - "@ffprobe-installer/linux-arm": "5.2.0", - "@ffprobe-installer/linux-arm64": "5.2.0", - "@ffprobe-installer/linux-ia32": "5.2.0", - "@ffprobe-installer/linux-x64": "5.2.0", - "@ffprobe-installer/win32-ia32": "5.1.0", - "@ffprobe-installer/win32-x64": "5.1.0" - } - }, - "node_modules/@ffprobe-installer/linux-arm": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@ffprobe-installer/linux-arm/-/linux-arm-5.2.0.tgz", - "integrity": "sha512-PF5HqEhCY7WTWHtLDYbA/+rLS+rhslWvyBlAG1Fk8VzVlnRdl93o6hy7DE2kJgxWQbFaR3ZktPQGEzfkrmQHvQ==", - "cpu": [ - "arm" - ], - "hasInstallScript": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@ffprobe-installer/linux-arm64": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@ffprobe-installer/linux-arm64/-/linux-arm64-5.2.0.tgz", - "integrity": "sha512-X1VvWtlLs6ScP73biVLuHD5ohKJKsMTa0vafCESOen4mOoNeLAYbxOVxDWAdFz9cpZgRiloFj5QD6nDj8E28yQ==", - "cpu": [ - "arm64" - ], - "hasInstallScript": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@ffprobe-installer/linux-ia32": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@ffprobe-installer/linux-ia32/-/linux-ia32-5.2.0.tgz", - "integrity": "sha512-TFVK5sasXyXhbIG7LtPRDmtkrkOsInwKcL43iEvEw+D9vCS2rc//mn9/0Q+BR0UoJEiMK4+ApYr/3LLVUBPOCQ==", - "cpu": [ - "ia32" - ], - "hasInstallScript": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@ffprobe-installer/linux-x64": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@ffprobe-installer/linux-x64/-/linux-x64-5.2.0.tgz", - "integrity": "sha512-D3UeqTLYPNs7pBWPLUYGehPdRVqU8eACox4OZy3pZUZatxye2YKlvBwEfaLdL1v2Z4FOAlLUhms0kY8m8kqSRA==", - "cpu": [ - "x64" - ], - "hasInstallScript": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@ffprobe-installer/win32-ia32": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@ffprobe-installer/win32-ia32/-/win32-ia32-5.1.0.tgz", - "integrity": "sha512-5O3vOoNRxmut0/Nu9vSazTdSHasrr+zPT2B3Hm7kjmO3QVFcIfVImS6ReQnZeSy8JPJOqXts5kX5x/3KOX54XQ==", - "cpu": [ - "ia32" - ], - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@ffprobe-installer/win32-x64": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@ffprobe-installer/win32-x64/-/win32-x64-5.1.0.tgz", - "integrity": "sha512-jMGYeAgkrdn4e2vvYt/qakgHRE3CPju4bn5TmdPfoAm1BlX1mY9cyMd8gf5vSzI8gH8Zq5WQAyAkmekX/8TSTg==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "win32" - ] - }, "node_modules/@hapi/hoek": { "version": "9.3.0", "license": "BSD-3-Clause" @@ -8950,8 +8763,7 @@ "node_modules/@yarnpkg/lockfile": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz", - "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==", - "dev": true + "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==" }, "node_modules/@yarnpkg/parsers": { "version": "3.0.0-rc.46", @@ -9075,7 +8887,6 @@ }, "node_modules/agent-base": { "version": "6.0.2", - "devOptional": true, "license": "MIT", "dependencies": { "debug": "4" @@ -9333,7 +9144,8 @@ "node_modules/async": { "version": "3.2.5", "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", - "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==" + "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==", + "dev": true }, "node_modules/asynckit": { "version": "0.4.0", @@ -10237,6 +10049,11 @@ } ] }, + "node_modules/caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==" + }, "node_modules/ccount": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/ccount/-/ccount-1.1.0.tgz", @@ -12727,7 +12544,6 @@ "version": "2.2.1", "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", - "dev": true, "engines": { "node": ">=6" } @@ -13390,6 +13206,22 @@ "node": ">=0.4.0" } }, + "node_modules/ffmpeg-ffprobe-static": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/ffmpeg-ffprobe-static/-/ffmpeg-ffprobe-static-6.1.1.tgz", + "integrity": "sha512-sg58bZu2xNo8XYSHtqd4LrYxDB1ISCAMshhLyBGq5Qp/hyKi+RrDjnNpwBJ72L9iyN7QZNnKiu5nnAUeDyG1vA==", + "hasInstallScript": true, + "dependencies": { + "@derhuerst/http-basic": "8.2.0", + "env-paths": "^2.2.0", + "https-proxy-agent": "^5.0.0", + "patch-package": "^6.2.2", + "progress": "^2.0.3" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/figures": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", @@ -13548,6 +13380,14 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/find-yarn-workspace-root": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/find-yarn-workspace-root/-/find-yarn-workspace-root-2.0.0.tgz", + "integrity": "sha512-1IMnbjt4KzsQfnhnzNd8wUEgXZ44IzZaZmnLYx7D5FZlaHt2gW20Cri8Q+E/t5tIj4+epTBub+2Zxu/vNILzqQ==", + "dependencies": { + "micromatch": "^4.0.2" + } + }, "node_modules/flat": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", @@ -13575,17 +13415,22 @@ "license": "ISC" }, "node_modules/fluent-ffmpeg": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/fluent-ffmpeg/-/fluent-ffmpeg-2.1.2.tgz", - "integrity": "sha512-IZTB4kq5GK0DPp7sGQ0q/BWurGHffRtQQwVkiqDgeO6wYJLLV5ZhgNOQ65loZxxuPMKZKZcICCUnaGtlxBiR0Q==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/fluent-ffmpeg/-/fluent-ffmpeg-2.1.3.tgz", + "integrity": "sha512-Be3narBNt2s6bsaqP6Jzq91heDgOEaDCJAXcE3qcma/EJBSy5FB4cvO31XBInuAuKBx8Kptf8dkhjK0IOru39Q==", "dependencies": { - "async": ">=0.2.9", + "async": "^0.2.9", "which": "^1.1.1" }, "engines": { - "node": ">=0.8.0" + "node": ">=18" } }, + "node_modules/fluent-ffmpeg/node_modules/async": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/async/-/async-0.2.10.tgz", + "integrity": "sha512-eAkdoKxU6/LkKDBzLpT+t6Ff5EtfSF4wx1WfJiPEEV7WNLnDaRXk0oVysiEPm262roaachGexwUv94WhSgN5TQ==" + }, "node_modules/fluent-ffmpeg/node_modules/which": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", @@ -14910,9 +14755,21 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/http-response-object": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/http-response-object/-/http-response-object-3.0.2.tgz", + "integrity": "sha512-bqX0XTF6fnXSQcEJ2Iuyr75yVakyjIDCqroJQ/aHfSdlM743Cwqoi2nDYMzLGWUcuTWGWy8AAvOKXTfiv6q9RA==", + "dependencies": { + "@types/node": "^10.0.3" + } + }, + "node_modules/http-response-object/node_modules/@types/node": { + "version": "10.17.60", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.60.tgz", + "integrity": "sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==" + }, "node_modules/https-proxy-agent": { "version": "5.0.1", - "devOptional": true, "license": "MIT", "dependencies": { "agent-base": "6", @@ -16610,6 +16467,14 @@ "node": ">=0.10.0" } }, + "node_modules/klaw-sync": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/klaw-sync/-/klaw-sync-6.0.0.tgz", + "integrity": "sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ==", + "dependencies": { + "graceful-fs": "^4.1.11" + } + }, "node_modules/kleur": { "version": "3.0.3", "license": "MIT", @@ -18657,6 +18522,11 @@ "version": "2.6.2", "license": "MIT" }, + "node_modules/nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==" + }, "node_modules/no-case": { "version": "3.0.4", "license": "MIT", @@ -19333,7 +19203,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -19567,6 +19436,11 @@ "node": ">=6" } }, + "node_modules/parse-cache-control": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parse-cache-control/-/parse-cache-control-1.0.1.tgz", + "integrity": "sha512-60zvsJReQPX5/QP0Kzfd/VrpjScIQ7SHBW6bFCYfEP+fp0Eppr1SHhIO5nd1PjZtvclzSzES9D/p5nFJurwfWg==" + }, "node_modules/parse-conflict-json": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/parse-conflict-json/-/parse-conflict-json-3.0.1.tgz", @@ -19714,6 +19588,155 @@ "tslib": "^2.0.3" } }, + "node_modules/patch-package": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/patch-package/-/patch-package-6.5.1.tgz", + "integrity": "sha512-I/4Zsalfhc6bphmJTlrLoOcAF87jcxko4q0qsv4bGcurbr8IskEOtdnt9iCmsQVGL1B+iUhSQqweyTLJfCF9rA==", + "dependencies": { + "@yarnpkg/lockfile": "^1.1.0", + "chalk": "^4.1.2", + "cross-spawn": "^6.0.5", + "find-yarn-workspace-root": "^2.0.0", + "fs-extra": "^9.0.0", + "is-ci": "^2.0.0", + "klaw-sync": "^6.0.0", + "minimist": "^1.2.6", + "open": "^7.4.2", + "rimraf": "^2.6.3", + "semver": "^5.6.0", + "slash": "^2.0.0", + "tmp": "^0.0.33", + "yaml": "^1.10.2" + }, + "bin": { + "patch-package": "index.js" + }, + "engines": { + "node": ">=10", + "npm": ">5" + } + }, + "node_modules/patch-package/node_modules/cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dependencies": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + }, + "engines": { + "node": ">=4.8" + } + }, + "node_modules/patch-package/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/patch-package/node_modules/open": { + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", + "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==", + "dependencies": { + "is-docker": "^2.0.0", + "is-wsl": "^2.1.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/patch-package/node_modules/path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", + "engines": { + "node": ">=4" + } + }, + "node_modules/patch-package/node_modules/rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/patch-package/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/patch-package/node_modules/shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", + "dependencies": { + "shebang-regex": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/patch-package/node_modules/shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/patch-package/node_modules/slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "engines": { + "node": ">=6" + } + }, + "node_modules/patch-package/node_modules/tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dependencies": { + "os-tmpdir": "~1.0.2" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/patch-package/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, "node_modules/path-browserify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", @@ -20742,6 +20765,14 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/promise": { "version": "7.3.1", "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", @@ -23733,8 +23764,7 @@ "node_modules/typedarray": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", - "dev": true + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==" }, "node_modules/typedarray-to-buffer": { "version": "3.1.5", @@ -25591,11 +25621,10 @@ "version": "3.17.0", "license": "MIT", "dependencies": { - "@ffmpeg-installer/ffmpeg": "^1.1.0", - "@ffprobe-installer/ffprobe": "^2.0.0", "@motion-canvas/core": "^3.17.0", "@motion-canvas/vite-plugin": "^3.17.0", - "fluent-ffmpeg": "^2.1.2" + "ffmpeg-ffprobe-static": "^6.1.1-rc.5", + "fluent-ffmpeg": "^2.1.3" }, "devDependencies": { "@types/fluent-ffmpeg": "^2.1.21" @@ -27924,6 +27953,52 @@ } } }, + "@derhuerst/http-basic": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/@derhuerst/http-basic/-/http-basic-8.2.0.tgz", + "integrity": "sha512-v1cqPUpFjU8DInW4YkC9caGKy8kUkqz0z10yCHawkxgpaJPId0F5xKi8fUY5rqC58F9Muz9T136jNReZQH9xIw==", + "requires": { + "caseless": "^0.12.0", + "concat-stream": "^1.6.2", + "http-response-object": "^3.0.1", + "parse-cache-control": "^1.0.1" + }, + "dependencies": { + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, "@docsearch/css": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-3.3.3.tgz", @@ -28619,132 +28694,6 @@ "integrity": "sha512-ut5V+D+fOoWPgGGNj83GGjnntO39xDy6DWxO0wb7Jp3DcMX0TfIqdzHF85VTQkerdyGmuuMD9AKAo5KiNlf/AQ==", "devOptional": true }, - "@ffmpeg-installer/darwin-arm64": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@ffmpeg-installer/darwin-arm64/-/darwin-arm64-4.1.5.tgz", - "integrity": "sha512-hYqTiP63mXz7wSQfuqfFwfLOfwwFChUedeCVKkBtl/cliaTM7/ePI9bVzfZ2c+dWu3TqCwLDRWNSJ5pqZl8otA==", - "optional": true - }, - "@ffmpeg-installer/darwin-x64": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@ffmpeg-installer/darwin-x64/-/darwin-x64-4.1.0.tgz", - "integrity": "sha512-Z4EyG3cIFjdhlY8wI9aLUXuH8nVt7E9SlMVZtWvSPnm2sm37/yC2CwjUzyCQbJbySnef1tQwGG2Sx+uWhd9IAw==", - "optional": true - }, - "@ffmpeg-installer/ffmpeg": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@ffmpeg-installer/ffmpeg/-/ffmpeg-1.1.0.tgz", - "integrity": "sha512-Uq4rmwkdGxIa9A6Bd/VqqYbT7zqh1GrT5/rFwCwKM70b42W5gIjWeVETq6SdcL0zXqDtY081Ws/iJWhr1+xvQg==", - "requires": { - "@ffmpeg-installer/darwin-arm64": "4.1.5", - "@ffmpeg-installer/darwin-x64": "4.1.0", - "@ffmpeg-installer/linux-arm": "4.1.3", - "@ffmpeg-installer/linux-arm64": "4.1.4", - "@ffmpeg-installer/linux-ia32": "4.1.0", - "@ffmpeg-installer/linux-x64": "4.1.0", - "@ffmpeg-installer/win32-ia32": "4.1.0", - "@ffmpeg-installer/win32-x64": "4.1.0" - } - }, - "@ffmpeg-installer/linux-arm": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/@ffmpeg-installer/linux-arm/-/linux-arm-4.1.3.tgz", - "integrity": "sha512-NDf5V6l8AfzZ8WzUGZ5mV8O/xMzRag2ETR6+TlGIsMHp81agx51cqpPItXPib/nAZYmo55Bl2L6/WOMI3A5YRg==", - "optional": true - }, - "@ffmpeg-installer/linux-arm64": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/@ffmpeg-installer/linux-arm64/-/linux-arm64-4.1.4.tgz", - "integrity": "sha512-dljEqAOD0oIM6O6DxBW9US/FkvqvQwgJ2lGHOwHDDwu/pX8+V0YsDL1xqHbj1DMX/+nP9rxw7G7gcUvGspSoKg==", - "optional": true - }, - "@ffmpeg-installer/linux-ia32": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@ffmpeg-installer/linux-ia32/-/linux-ia32-4.1.0.tgz", - "integrity": "sha512-0LWyFQnPf+Ij9GQGD034hS6A90URNu9HCtQ5cTqo5MxOEc7Rd8gLXrJvn++UmxhU0J5RyRE9KRYstdCVUjkNOQ==", - "optional": true - }, - "@ffmpeg-installer/linux-x64": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@ffmpeg-installer/linux-x64/-/linux-x64-4.1.0.tgz", - "integrity": "sha512-Y5BWhGLU/WpQjOArNIgXD3z5mxxdV8c41C+U15nsE5yF8tVcdCGet5zPs5Zy3Ta6bU7haGpIzryutqCGQA/W8A==", - "optional": true - }, - "@ffmpeg-installer/win32-ia32": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@ffmpeg-installer/win32-ia32/-/win32-ia32-4.1.0.tgz", - "integrity": "sha512-FV2D7RlaZv/lrtdhaQ4oETwoFUsUjlUiasiZLDxhEUPdNDWcH1OU9K1xTvqz+OXLdsmYelUDuBS/zkMOTtlUAw==", - "optional": true - }, - "@ffmpeg-installer/win32-x64": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@ffmpeg-installer/win32-x64/-/win32-x64-4.1.0.tgz", - "integrity": "sha512-Drt5u2vzDnIONf4ZEkKtFlbvwj6rI3kxw1Ck9fpudmtgaZIHD4ucsWB2lCZBXRxJgXR+2IMSti+4rtM4C4rXgg==", - "optional": true - }, - "@ffprobe-installer/darwin-arm64": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@ffprobe-installer/darwin-arm64/-/darwin-arm64-5.0.1.tgz", - "integrity": "sha512-vwNCNjokH8hfkbl6m95zICHwkSzhEvDC3GVBcUp5HX8+4wsX10SP3B+bGur7XUzTIZ4cQpgJmEIAx6TUwRepMg==", - "optional": true - }, - "@ffprobe-installer/darwin-x64": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@ffprobe-installer/darwin-x64/-/darwin-x64-5.1.0.tgz", - "integrity": "sha512-J+YGscZMpQclFg31O4cfVRGmDpkVsQ2fZujoUdMAAYcP0NtqpC49Hs3SWJpBdsGB4VeqOt5TTm1vSZQzs1NkhA==", - "optional": true - }, - "@ffprobe-installer/ffprobe": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@ffprobe-installer/ffprobe/-/ffprobe-2.1.2.tgz", - "integrity": "sha512-ZNvwk4f2magF42Zji2Ese16SMj9BS7Fui4kRjg6gTYTxY3gWZNpg85n4MIfQyI9nimHg4x/gT6FVkp/bBDuBwg==", - "requires": { - "@ffprobe-installer/darwin-arm64": "5.0.1", - "@ffprobe-installer/darwin-x64": "5.1.0", - "@ffprobe-installer/linux-arm": "5.2.0", - "@ffprobe-installer/linux-arm64": "5.2.0", - "@ffprobe-installer/linux-ia32": "5.2.0", - "@ffprobe-installer/linux-x64": "5.2.0", - "@ffprobe-installer/win32-ia32": "5.1.0", - "@ffprobe-installer/win32-x64": "5.1.0" - } - }, - "@ffprobe-installer/linux-arm": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@ffprobe-installer/linux-arm/-/linux-arm-5.2.0.tgz", - "integrity": "sha512-PF5HqEhCY7WTWHtLDYbA/+rLS+rhslWvyBlAG1Fk8VzVlnRdl93o6hy7DE2kJgxWQbFaR3ZktPQGEzfkrmQHvQ==", - "optional": true - }, - "@ffprobe-installer/linux-arm64": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@ffprobe-installer/linux-arm64/-/linux-arm64-5.2.0.tgz", - "integrity": "sha512-X1VvWtlLs6ScP73biVLuHD5ohKJKsMTa0vafCESOen4mOoNeLAYbxOVxDWAdFz9cpZgRiloFj5QD6nDj8E28yQ==", - "optional": true - }, - "@ffprobe-installer/linux-ia32": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@ffprobe-installer/linux-ia32/-/linux-ia32-5.2.0.tgz", - "integrity": "sha512-TFVK5sasXyXhbIG7LtPRDmtkrkOsInwKcL43iEvEw+D9vCS2rc//mn9/0Q+BR0UoJEiMK4+ApYr/3LLVUBPOCQ==", - "optional": true - }, - "@ffprobe-installer/linux-x64": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@ffprobe-installer/linux-x64/-/linux-x64-5.2.0.tgz", - "integrity": "sha512-D3UeqTLYPNs7pBWPLUYGehPdRVqU8eACox4OZy3pZUZatxye2YKlvBwEfaLdL1v2Z4FOAlLUhms0kY8m8kqSRA==", - "optional": true - }, - "@ffprobe-installer/win32-ia32": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@ffprobe-installer/win32-ia32/-/win32-ia32-5.1.0.tgz", - "integrity": "sha512-5O3vOoNRxmut0/Nu9vSazTdSHasrr+zPT2B3Hm7kjmO3QVFcIfVImS6ReQnZeSy8JPJOqXts5kX5x/3KOX54XQ==", - "optional": true - }, - "@ffprobe-installer/win32-x64": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@ffprobe-installer/win32-x64/-/win32-x64-5.1.0.tgz", - "integrity": "sha512-jMGYeAgkrdn4e2vvYt/qakgHRE3CPju4bn5TmdPfoAm1BlX1mY9cyMd8gf5vSzI8gH8Zq5WQAyAkmekX/8TSTg==", - "optional": true - }, "@hapi/hoek": { "version": "9.3.0" }, @@ -29848,12 +29797,11 @@ "@motion-canvas/ffmpeg": { "version": "file:packages/ffmpeg", "requires": { - "@ffmpeg-installer/ffmpeg": "^1.1.0", - "@ffprobe-installer/ffprobe": "^2.0.0", "@motion-canvas/core": "^3.17.0", "@motion-canvas/vite-plugin": "^3.17.0", "@types/fluent-ffmpeg": "^2.1.21", - "fluent-ffmpeg": "^2.1.2" + "ffmpeg-ffprobe-static": "^6.1.1-rc.5", + "fluent-ffmpeg": "^2.1.3" } }, "@motion-canvas/internal": { @@ -32167,8 +32115,7 @@ "@yarnpkg/lockfile": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz", - "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==", - "dev": true + "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==" }, "@yarnpkg/parsers": { "version": "3.0.0-rc.46", @@ -32259,7 +32206,6 @@ }, "agent-base": { "version": "6.0.2", - "devOptional": true, "requires": { "debug": "4" } @@ -32425,7 +32371,8 @@ "async": { "version": "3.2.5", "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", - "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==" + "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==", + "dev": true }, "asynckit": { "version": "0.4.0", @@ -32998,6 +32945,11 @@ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001563.tgz", "integrity": "sha512-na2WUmOxnwIZtwnFI2CZ/3er0wdNzU7hN+cPYz/z2ajHThnkWjNBOpEPP4n+4r2WPM847JaMotaJE3bnfzjyKw==" }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==" + }, "ccount": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/ccount/-/ccount-1.1.0.tgz", @@ -34606,8 +34558,7 @@ "env-paths": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", - "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", - "dev": true + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==" }, "envinfo": { "version": "7.13.0", @@ -35084,6 +35035,18 @@ "xml-js": "^1.6.11" } }, + "ffmpeg-ffprobe-static": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/ffmpeg-ffprobe-static/-/ffmpeg-ffprobe-static-6.1.1.tgz", + "integrity": "sha512-sg58bZu2xNo8XYSHtqd4LrYxDB1ISCAMshhLyBGq5Qp/hyKi+RrDjnNpwBJ72L9iyN7QZNnKiu5nnAUeDyG1vA==", + "requires": { + "@derhuerst/http-basic": "8.2.0", + "env-paths": "^2.2.0", + "https-proxy-agent": "^5.0.0", + "patch-package": "^6.2.2", + "progress": "^2.0.3" + } + }, "figures": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", @@ -35195,6 +35158,14 @@ "path-exists": "^4.0.0" } }, + "find-yarn-workspace-root": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/find-yarn-workspace-root/-/find-yarn-workspace-root-2.0.0.tgz", + "integrity": "sha512-1IMnbjt4KzsQfnhnzNd8wUEgXZ44IzZaZmnLYx7D5FZlaHt2gW20Cri8Q+E/t5tIj4+epTBub+2Zxu/vNILzqQ==", + "requires": { + "micromatch": "^4.0.2" + } + }, "flat": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", @@ -35214,14 +35185,19 @@ "devOptional": true }, "fluent-ffmpeg": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/fluent-ffmpeg/-/fluent-ffmpeg-2.1.2.tgz", - "integrity": "sha512-IZTB4kq5GK0DPp7sGQ0q/BWurGHffRtQQwVkiqDgeO6wYJLLV5ZhgNOQ65loZxxuPMKZKZcICCUnaGtlxBiR0Q==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/fluent-ffmpeg/-/fluent-ffmpeg-2.1.3.tgz", + "integrity": "sha512-Be3narBNt2s6bsaqP6Jzq91heDgOEaDCJAXcE3qcma/EJBSy5FB4cvO31XBInuAuKBx8Kptf8dkhjK0IOru39Q==", "requires": { - "async": ">=0.2.9", + "async": "^0.2.9", "which": "^1.1.1" }, "dependencies": { + "async": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/async/-/async-0.2.10.tgz", + "integrity": "sha512-eAkdoKxU6/LkKDBzLpT+t6Ff5EtfSF4wx1WfJiPEEV7WNLnDaRXk0oVysiEPm262roaachGexwUv94WhSgN5TQ==" + }, "which": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", @@ -36139,9 +36115,23 @@ } } }, + "http-response-object": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/http-response-object/-/http-response-object-3.0.2.tgz", + "integrity": "sha512-bqX0XTF6fnXSQcEJ2Iuyr75yVakyjIDCqroJQ/aHfSdlM743Cwqoi2nDYMzLGWUcuTWGWy8AAvOKXTfiv6q9RA==", + "requires": { + "@types/node": "^10.0.3" + }, + "dependencies": { + "@types/node": { + "version": "10.17.60", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.60.tgz", + "integrity": "sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==" + } + } + }, "https-proxy-agent": { "version": "5.0.1", - "devOptional": true, "requires": { "agent-base": "6", "debug": "4" @@ -37279,6 +37269,14 @@ "kind-of": { "version": "6.0.3" }, + "klaw-sync": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/klaw-sync/-/klaw-sync-6.0.0.tgz", + "integrity": "sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ==", + "requires": { + "graceful-fs": "^4.1.11" + } + }, "kleur": { "version": "3.0.3" }, @@ -38688,6 +38686,11 @@ "neo-async": { "version": "2.6.2" }, + "nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==" + }, "no-case": { "version": "3.0.4", "requires": { @@ -39176,8 +39179,7 @@ "os-tmpdir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", - "dev": true + "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==" }, "p-cancelable": { "version": "1.1.0" @@ -39327,6 +39329,11 @@ "callsites": "^3.0.0" } }, + "parse-cache-control": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parse-cache-control/-/parse-cache-control-1.0.1.tgz", + "integrity": "sha512-60zvsJReQPX5/QP0Kzfd/VrpjScIQ7SHBW6bFCYfEP+fp0Eppr1SHhIO5nd1PjZtvclzSzES9D/p5nFJurwfWg==" + }, "parse-conflict-json": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/parse-conflict-json/-/parse-conflict-json-3.0.1.tgz", @@ -39440,6 +39447,113 @@ "tslib": "^2.0.3" } }, + "patch-package": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/patch-package/-/patch-package-6.5.1.tgz", + "integrity": "sha512-I/4Zsalfhc6bphmJTlrLoOcAF87jcxko4q0qsv4bGcurbr8IskEOtdnt9iCmsQVGL1B+iUhSQqweyTLJfCF9rA==", + "requires": { + "@yarnpkg/lockfile": "^1.1.0", + "chalk": "^4.1.2", + "cross-spawn": "^6.0.5", + "find-yarn-workspace-root": "^2.0.0", + "fs-extra": "^9.0.0", + "is-ci": "^2.0.0", + "klaw-sync": "^6.0.0", + "minimist": "^1.2.6", + "open": "^7.4.2", + "rimraf": "^2.6.3", + "semver": "^5.6.0", + "slash": "^2.0.0", + "tmp": "^0.0.33", + "yaml": "^1.10.2" + }, + "dependencies": { + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "requires": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + } + }, + "open": { + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", + "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==", + "requires": { + "is-docker": "^2.0.0", + "is-wsl": "^2.1.1" + } + }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==" + }, + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "requires": { + "glob": "^7.1.3" + } + }, + "semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==" + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==" + }, + "slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==" + }, + "tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "requires": { + "os-tmpdir": "~1.0.2" + } + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "requires": { + "isexe": "^2.0.0" + } + } + } + }, "path-browserify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", @@ -39990,6 +40104,11 @@ "integrity": "sha512-69agxLtnI8xBs9gUGqEnK26UfiexpHy+KUpBQWabiytQjnn5wFY8rklAi7GRfABIuPNnQ/ik48+LGLkYYJcy4A==", "dev": true }, + "progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==" + }, "promise": { "version": "7.3.1", "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", @@ -42122,8 +42241,7 @@ "typedarray": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", - "dev": true + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==" }, "typedarray-to-buffer": { "version": "3.1.5", diff --git a/packages/core/src/app/Exporter.ts b/packages/core/src/app/Exporter.ts index d6678e77a..3c141df2c 100644 --- a/packages/core/src/app/Exporter.ts +++ b/packages/core/src/app/Exporter.ts @@ -77,6 +77,7 @@ export interface Exporter { * @param sceneFrame - The frame number within the scene. * @param sceneName - The name of the scene with which the frame is associated. * @param signal - An abort signal triggered if the user aborts the rendering. + * @param context - A 2D rendering context for the canvas. */ handleFrame( canvas: HTMLCanvasElement, @@ -84,6 +85,7 @@ export interface Exporter { sceneFrame: number, sceneName: string, signal: AbortSignal, + context: CanvasRenderingContext2D, ): Promise; /** diff --git a/packages/core/src/app/Renderer.ts b/packages/core/src/app/Renderer.ts index a3844195b..1e774dd5d 100644 --- a/packages/core/src/app/Renderer.ts +++ b/packages/core/src/app/Renderer.ts @@ -287,6 +287,7 @@ export class Renderer { sceneFrame, this.playback.currentScene.name, signal, + this.stage.context, ); } } diff --git a/packages/core/src/app/Stage.ts b/packages/core/src/app/Stage.ts index dad5c8c94..fa07b2621 100644 --- a/packages/core/src/app/Stage.ts +++ b/packages/core/src/app/Stage.ts @@ -26,7 +26,7 @@ export class Stage { private readonly currentBuffer: HTMLCanvasElement; private readonly previousBuffer: HTMLCanvasElement; - private context: CanvasRenderingContext2D; + public context: CanvasRenderingContext2D; private currentContext: CanvasRenderingContext2D; private previousContext: CanvasRenderingContext2D; @@ -40,7 +40,10 @@ export class Stage { this.previousBuffer = document.createElement('canvas'); const colorSpace = this.colorSpace; - this.context = getContext({colorSpace}, this.finalBuffer); + this.context = getContext( + {colorSpace, willReadFrequently: true}, + this.finalBuffer, + ); this.currentContext = getContext({colorSpace}, this.currentBuffer); this.previousContext = getContext({colorSpace}, this.previousBuffer); } diff --git a/packages/ffmpeg/client/FFmpegExporterClient.ts b/packages/ffmpeg/client/FFmpegExporterClient.ts index ebb15040e..9378c10ad 100644 --- a/packages/ffmpeg/client/FFmpegExporterClient.ts +++ b/packages/ffmpeg/client/FFmpegExporterClient.ts @@ -28,6 +28,11 @@ type FFmpegExporterOptions = ValueOf< ReturnType >; +type InvokeStrategy = 'ws' | 'octet-stream'; + +const EXPORT_FRAME_LIMIT = 256; +const EXPORT_RETRY_DELAY = 1000; + /** * FFmpeg video exporter. * @@ -74,6 +79,9 @@ export class FFmpegExporterClient implements Exporter { } } + private concurrentFrames = 0; + private error: unknown = false; + public constructor( private readonly project: Project, private readonly settings: RendererSettings, @@ -90,13 +98,43 @@ export class FFmpegExporterClient implements Exporter { }); } - public async handleFrame(canvas: HTMLCanvasElement): Promise { - await this.invoke('handleFrame', { - data: canvas.toDataURL('image/png'), - }); + public async handleFrame( + canvas: HTMLCanvasElement, + _frame: number, + _sceneFrame: number, + _sceneName: string, + _signal: AbortSignal, + context: CanvasRenderingContext2D, + ): Promise { + while (this.concurrentFrames >= EXPORT_FRAME_LIMIT) { + await new Promise(resolve => setTimeout(resolve, EXPORT_RETRY_DELAY)); + } + + if (this.error) { + throw this.error; + } + + const data = context.getImageData(0, 0, canvas.width, canvas.height).data; + this.concurrentFrames++; + this.invoke('handleFrame', data, 'octet-stream') + .then(() => { + this.concurrentFrames--; + }) + .catch(error => { + this.error = error; + this.concurrentFrames--; + }); } public async stop(result: RendererResult): Promise { + while (this.concurrentFrames >= EXPORT_FRAME_LIMIT) { + await new Promise(resolve => setTimeout(resolve, EXPORT_RETRY_DELAY)); + } + + if (this.error) { + throw this.error; + } + await this.invoke('end', result); } @@ -106,10 +144,12 @@ export class FFmpegExporterClient implements Exporter { * @param method - The method name to execute on the server. * @param data - The data that will be passed as an argument to the method. * Should be serializable. + * @param strategy - How the data should be sent to the server. */ private invoke( method: string, data: TData, + strategy: InvokeStrategy = 'ws', ): Promise { if (import.meta.hot) { return new Promise((resolve, reject) => { @@ -130,7 +170,19 @@ export class FFmpegExporterClient implements Exporter { } }; FFmpegExporterClient.response.subscribe(handle); - import.meta.hot!.send('motion-canvas/ffmpeg', {method, data}); + switch (strategy) { + case 'ws': + import.meta.hot!.send('motion-canvas/ffmpeg', {method, data}); + break; + case 'octet-stream': + fetch(`/ffmpeg/${method}`, { + method: 'POST', + body: data as ArrayBuffer, + // eslint-disable-next-line @typescript-eslint/naming-convention + headers: {'Content-Type': 'application/octet-stream'}, + }).catch(reject); + break; + } }); } else { throw new Error('FFmpegExporter can only be used locally.'); diff --git a/packages/ffmpeg/package.json b/packages/ffmpeg/package.json index c496c891d..4279c50d6 100644 --- a/packages/ffmpeg/package.json +++ b/packages/ffmpeg/package.json @@ -25,10 +25,9 @@ "@types/fluent-ffmpeg": "^2.1.21" }, "dependencies": { - "@ffmpeg-installer/ffmpeg": "^1.1.0", - "@ffprobe-installer/ffprobe": "^2.0.0", "@motion-canvas/core": "^3.17.0", "@motion-canvas/vite-plugin": "^3.17.0", - "fluent-ffmpeg": "^2.1.2" + "ffmpeg-ffprobe-static": "^6.1.1-rc.5", + "fluent-ffmpeg": "^2.1.3" } } diff --git a/packages/ffmpeg/server/FFmpegBridge.ts b/packages/ffmpeg/server/FFmpegBridge.ts index 8014f3464..e726d8f58 100644 --- a/packages/ffmpeg/server/FFmpegBridge.ts +++ b/packages/ffmpeg/server/FFmpegBridge.ts @@ -1,5 +1,6 @@ import {PluginConfig} from '@motion-canvas/vite-plugin'; -import type {WebSocketServer} from 'vite'; +import {ServerResponse} from 'node:http'; +import {Connect, ViteDevServer} from 'vite'; import { FFmpegExporterServer, FFmpegExporterSettings, @@ -21,12 +22,24 @@ export class FFmpegBridge { private process: FFmpegExporterServer | null = null; public constructor( - private readonly ws: WebSocketServer, + private readonly server: ViteDevServer, private readonly config: PluginConfig, ) { - ws.on('motion-canvas/ffmpeg', this.handleMessage); + server.ws.on('motion-canvas/ffmpeg', this.handleMessage); + server.middlewares.use('/ffmpeg', this.handleRequest); } + private handleRequest = async ( + req: Connect.IncomingMessage, + res: ServerResponse, + ) => { + res.end(); + await this.handleMessage({ + method: req.url!.slice(1), + data: req, + }); + }; + private handleMessage = async ({method, data}: BrowserRequest) => { if (method === 'start') { try { @@ -63,7 +76,7 @@ export class FFmpegBridge { }; private respondSuccess(method: string, data: any = {}) { - this.ws.send('motion-canvas/ffmpeg-ack', { + this.server.ws.send('motion-canvas/ffmpeg-ack', { status: 'success', method, data, @@ -71,7 +84,7 @@ export class FFmpegBridge { } private respondError(method: string, message = 'Unknown error.') { - this.ws.send('motion-canvas/ffmpeg-ack', { + this.server.ws.send('motion-canvas/ffmpeg-ack', { status: 'error', method, message, diff --git a/packages/ffmpeg/server/FFmpegExporterServer.ts b/packages/ffmpeg/server/FFmpegExporterServer.ts index 0f255b3a2..37e09771f 100644 --- a/packages/ffmpeg/server/FFmpegExporterServer.ts +++ b/packages/ffmpeg/server/FFmpegExporterServer.ts @@ -1,14 +1,14 @@ -import {path as ffmpegPath} from '@ffmpeg-installer/ffmpeg'; -import {path as ffprobePath} from '@ffprobe-installer/ffprobe'; import type {RendererResult, RendererSettings} from '@motion-canvas/core'; import type {PluginConfig} from '@motion-canvas/vite-plugin'; +import {ffmpegPath, ffprobePath} from 'ffmpeg-ffprobe-static'; import ffmpeg from 'fluent-ffmpeg'; import * as fs from 'fs'; import * as path from 'path'; +import {Readable} from 'stream'; import {ImageStream} from './ImageStream'; -ffmpeg.setFfmpegPath(ffmpegPath); -ffmpeg.setFfprobePath(ffprobePath); +ffmpeg.setFfmpegPath(ffmpegPath!); +ffmpeg.setFfprobePath(ffprobePath!); export interface FFmpegExporterSettings extends RendererSettings { audio?: string; @@ -29,13 +29,18 @@ export class FFmpegExporterServer { settings: FFmpegExporterSettings, private readonly config: PluginConfig, ) { - this.stream = new ImageStream(); + const size = { + x: Math.round(settings.size.x * settings.resolutionScale), + y: Math.round(settings.size.y * settings.resolutionScale), + }; + this.stream = new ImageStream(size); this.command = ffmpeg(); // Input image sequence this.command .input(this.stream) - .inputFormat('image2pipe') + .inputFormat('rawvideo') + .inputOptions(['-pix_fmt rgba', '-s:v', `${size.x}x${size.y}`]) .inputFps(settings.fps); // Input audio file @@ -47,10 +52,6 @@ export class FFmpegExporterServer { } // Output settings - const size = { - x: Math.round(settings.size.x * settings.resolutionScale), - y: Math.round(settings.size.y * settings.resolutionScale), - }; this.command .output(path.join(this.config.output, `${settings.name}.mp4`)) .outputOptions(['-pix_fmt yuv420p', '-shortest']) @@ -72,9 +73,8 @@ export class FFmpegExporterServer { this.command.run(); } - public async handleFrame({data}: {data: string}) { - const base64Data = data.slice(data.indexOf(',') + 1); - this.stream.pushImage(Buffer.from(base64Data, 'base64')); + public async handleFrame(req: Readable) { + await this.stream.pushImage(req); } public async end(result: RendererResult) { diff --git a/packages/ffmpeg/server/ImageStream.ts b/packages/ffmpeg/server/ImageStream.ts index a92e233d0..c877ffea9 100644 --- a/packages/ffmpeg/server/ImageStream.ts +++ b/packages/ffmpeg/server/ImageStream.ts @@ -1,20 +1,66 @@ import {Readable} from 'stream'; +type QueueItem = + | { + type: 'frame'; + array: Uint8Array; + finished: boolean; + } + | { + type: 'end'; + }; + export class ImageStream extends Readable { - private image: Buffer | null = null; - private hasData = false; + private queue: QueueItem[] = []; + + public constructor(private size: {x: number; y: number}) { + super(); + } + + public async pushImage(readable: Readable | null) { + if (readable) { + const length = this.size.x * this.size.y * 4; + const item: QueueItem = { + type: 'frame', + array: new Uint8Array(length), + finished: false, + }; + this.queue.push(item); + + let pointer = 0; + readable.on('data', (chunk: Uint8Array) => { + item.array.set(chunk, pointer); + pointer += chunk.length; + }); + + await new Promise((resolve, reject) => { + readable.on('end', resolve).on('error', reject); + }); + + item.finished = true; + } else { + this.queue.push({type: 'end'}); + } - public pushImage(image: Buffer | null) { - this.image = image; - this.hasData = true; this._read(); } // eslint-disable-next-line @typescript-eslint/naming-convention public override _read() { - if (this.hasData) { - this.hasData = false; - this.push(this.image); + while (this.queue.length > 0) { + const item = this.queue[0]; + if (item.type === 'end') { + this.queue = []; + this.push(null); + return; + } + + if (!item.finished) { + return; + } + + this.queue.shift(); + this.push(item.array); } } } diff --git a/packages/ffmpeg/server/index.ts b/packages/ffmpeg/server/index.ts index e7f7975ed..c174db5b7 100644 --- a/packages/ffmpeg/server/index.ts +++ b/packages/ffmpeg/server/index.ts @@ -12,7 +12,7 @@ export default (): Plugin => { }, }, configureServer(server) { - new FFmpegBridge(server.ws, config); + new FFmpegBridge(server, config); }, }; }; diff --git a/packages/template/vite.config.ts b/packages/template/vite.config.ts index cbf6a299c..82ae5f61d 100644 --- a/packages/template/vite.config.ts +++ b/packages/template/vite.config.ts @@ -15,6 +15,10 @@ export default defineConfig({ find: '@motion-canvas/2d/editor', replacement: '@motion-canvas/2d/src/editor', }, + { + find: '@motion-canvas/ffmpeg/lib/client', + replacement: '@motion-canvas/ffmpeg/client', + }, { find: /@motion-canvas\/2d(\/lib)?/, replacement: '@motion-canvas/2d/src/lib', diff --git a/packages/ui/src/components/console/Log.tsx b/packages/ui/src/components/console/Log.tsx index 9296c7253..2a63ff4f4 100644 --- a/packages/ui/src/components/console/Log.tsx +++ b/packages/ui/src/components/console/Log.tsx @@ -21,11 +21,19 @@ export function Log({payload}: LogProps) { const [open, setOpen] = useState(payload.level === LogLevel.Error); const [entries, setEntries] = useState(null); const duration = useFormattedNumber(payload.durationMs, 2); - const object = useMemo( - () => - payload.object ? JSON.stringify(payload.object, undefined, 2) : null, - [payload], - ); + const object = useMemo(() => { + if (!payload.object) { + return null; + } + + if (typeof payload.object === 'object' && 'byteLength' in payload.object) { + return `${payload.object.prototype?.name ?? 'ArrayLike'}[${ + payload.object.byteLength + }]`; + } + + return JSON.stringify(payload.object, undefined, 2); + }, [payload]); const userEntry = useMemo(() => { return entries?.find(entry => !entry.isExternal) ?? null; }, [entries]);