diff --git a/package-lock.json b/package-lock.json index 2ee39bc..4009cc2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,13 +9,44 @@ "version": "1.0.0", "license": "MIT", "dependencies": { + "chokidar": "^5.0.0", "cors": "^2.8.5", "express": "^4.21.2", "mp4-muxer": "^5.2.2", "multer": "^1.4.5-lts.1", + "node-fetch": "^3.3.2", + "puppeteer": "^25.10.0", "webm-muxer": "^5.1.4" } }, + "node_modules/@puppeteer/browsers": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-3.2.2.tgz", + "integrity": "sha512-q2BU4YfO9h/Wt7IcWPcggpOOqLk2Tbs1hDwolvKZrweRjy751OJBKMN9zO5bfD0pzU7X/tvKw/exQds4pM/LOg==", + "license": "Apache-2.0", + "dependencies": { + "modern-tar": "^0.8.4", + "yargs": "^18.0.0" + }, + "bin": { + "browsers": "lib/main-cli.js" + }, + "engines": { + "node": ">=22.12.0" + }, + "peerDependencies": { + "proxy-agent": ">=8.0.1", + "yauzl": "^2.10.0 || ^3.4.0" + }, + "peerDependenciesMeta": { + "proxy-agent": { + "optional": true + }, + "yauzl": { + "optional": true + } + } + }, "node_modules/@types/dom-webcodecs": { "version": "0.1.18", "resolved": "https://registry.npmjs.org/@types/dom-webcodecs/-/dom-webcodecs-0.1.18.tgz", @@ -41,6 +72,30 @@ "node": ">= 0.6" } }, + "node_modules/ansi-regex": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.3.0.tgz", + "integrity": "sha512-WpDfL7NO6j7tH88IDBNVdUJxDh9nmCteAVW9dsep846XdwF4naCBK+/tGLX3KJgcpgMRXCFlTM2hKGoK9FsdrQ==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/append-field": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", @@ -132,6 +187,68 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/chokidar": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", + "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", + "license": "MIT", + "dependencies": { + "readdirp": "^5.0.0" + }, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/chromium-bidi": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-17.0.2.tgz", + "integrity": "sha512-5v9GQFhTktFvotn/OFNJBmKLKRAb6n9r0bVCwf7sHgWc3/JryK0bj1nn93L3pHFrfgcsu6Be6EWsDi+1XHTGDg==", + "license": "Apache-2.0", + "dependencies": { + "mitt": "^3.0.1", + "zod": "^3.24.1" + }, + "engines": { + "node": ">=20.19.0 <22.0.0 || >=22.12.0" + }, + "peerDependencies": { + "devtools-protocol": "*" + } + }, + "node_modules/cliui": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-9.0.1.tgz", + "integrity": "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==", + "license": "ISC", + "dependencies": { + "string-width": "^7.2.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/cliui/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/concat-stream": { "version": "1.6.2", "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", @@ -206,6 +323,15 @@ "url": "https://opencollective.com/express" } }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, "node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -234,6 +360,12 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/devtools-protocol": { + "version": "0.0.1666840", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1666840.tgz", + "integrity": "sha512-gCcO42XCHKEs7Ag0S7aGYsnJ7hlgrO3qderYqeiY0Eqk+0GFfuvT13IA0hHreJTa2KCdDVyGMeOhdMNmrrTjVg==", + "license": "BSD-3-Clause" + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -254,6 +386,12 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", "license": "MIT" }, + "node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "license": "MIT" + }, "node_modules/encodeurl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", @@ -293,6 +431,15 @@ "node": ">= 0.4" } }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -354,6 +501,29 @@ "url": "https://opencollective.com/express" } }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, "node_modules/finalhandler": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", @@ -372,6 +542,18 @@ "node": ">= 0.8" } }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "license": "MIT", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -399,6 +581,27 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz", + "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/get-intrinsic": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", @@ -525,6 +728,18 @@ "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", "license": "MIT" }, + "node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -603,6 +818,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "license": "MIT" + }, "node_modules/mkdirp": { "version": "0.5.6", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", @@ -615,6 +836,15 @@ "mkdirp": "bin/cmd.js" } }, + "node_modules/modern-tar": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/modern-tar/-/modern-tar-0.8.4.tgz", + "integrity": "sha512-gN54ddmyzEg10orwZ2u4OOv+bjpMWdIl5jIkodK97bMq8QBSL5c0D7YX0lT1Ooz+99S7+PvFbnxzdjgHo1r41g==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/mp4-muxer": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/mp4-muxer/-/mp4-muxer-5.2.2.tgz", @@ -660,6 +890,44 @@ "node": ">= 0.6" } }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "deprecated": "Use your platform's native DOMException instead", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "license": "MIT", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -727,6 +995,44 @@ "node": ">= 0.10" } }, + "node_modules/puppeteer": { + "version": "25.10.0", + "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-25.10.0.tgz", + "integrity": "sha512-9ZfkiaZDQWpGPJp9XTS+Bkn/D78hPvYmtjPfIBeybn05oeY6Jj7aiSbYdfcSQD2UMvC0vE7Yi9PSDo179euRzw==", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@puppeteer/browsers": "3.2.2", + "chromium-bidi": "17.0.2", + "devtools-protocol": "0.0.1666840", + "lilconfig": "^3.1.3", + "puppeteer-core": "25.10.0", + "typed-query-selector": "^2.12.2" + }, + "bin": { + "puppeteer": "lib/puppeteer/node/cli.js" + }, + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/puppeteer-core": { + "version": "25.10.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-25.10.0.tgz", + "integrity": "sha512-Hy5eMQshOEMil4JUUx03h5pw1HYkYCso1RG/gcpPlFSd4cYPOcopxcXEAxpLPOkOPJb9LIJtwxuj66bSdvknFg==", + "license": "Apache-2.0", + "dependencies": { + "@puppeteer/browsers": "3.2.2", + "chromium-bidi": "17.0.2", + "devtools-protocol": "0.0.1666840", + "typed-query-selector": "^2.12.2", + "webdriver-bidi-protocol": "0.4.3", + "ws": "^8.21.3" + }, + "engines": { + "node": ">=22.12.0" + } + }, "node_modules/qs": { "version": "6.15.3", "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.3.tgz", @@ -788,6 +1094,19 @@ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "license": "MIT" }, + "node_modules/readdirp": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.1.1.tgz", + "integrity": "sha512-Kko+Y5XQ6fM+Ce3dq3m9YGxnacYZYl9cA1wZjaF3Vbry2L3i1qVg8+CAgNPsXRArPMUMCaOR7oa9Nqntc43JKA==", + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -969,6 +1288,37 @@ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "license": "MIT" }, + "node_modules/string-width": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.2.tgz", + "integrity": "sha512-GaPUh5gfdrYzqeVNZvUfT23vYYxXzKYidUcnMtJg/3rxRV63EFZy3k6xfKlmfeJD0176lnUV/Usr3XcwSvFzpg==", + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.5.0", + "strip-ansi": "^7.1.2" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", @@ -991,6 +1341,12 @@ "node": ">= 0.6" } }, + "node_modules/typed-query-selector": { + "version": "2.12.2", + "resolved": "https://registry.npmjs.org/typed-query-selector/-/typed-query-selector-2.12.2.tgz", + "integrity": "sha512-EOPFbyIub4ngnEdqi2yOcNeDLaX/0jcE1JoAXQDDMIthap7FoN795lc/SHfIq2d416VufXpM8z/lD+WRm2gfOQ==", + "license": "MIT" + }, "node_modules/typedarray": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", @@ -1030,6 +1386,21 @@ "node": ">= 0.8" } }, + "node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/webdriver-bidi-protocol": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/webdriver-bidi-protocol/-/webdriver-bidi-protocol-0.4.3.tgz", + "integrity": "sha512-uuN0goWfxP22B7J/uAgBpOYNPttC+XVseYE+rSY5+rQ+YBeVz/VORw8WbmLVcqW78zNg5A4qnjNXYUWR3il2ig==", + "license": "Apache-2.0" + }, "node_modules/webm-muxer": { "version": "5.1.4", "resolved": "https://registry.npmjs.org/webm-muxer/-/webm-muxer-5.1.4.tgz", @@ -1041,6 +1412,61 @@ "@types/wicg-file-system-access": "^2020.9.5" } }, + "node_modules/wrap-ansi": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ws": { + "version": "8.21.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.3.tgz", + "integrity": "sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", @@ -1049,6 +1475,50 @@ "engines": { "node": ">=0.4" } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "18.1.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-18.1.0.tgz", + "integrity": "sha512-2rAgRKu54VsHkqI0/tYkmluGXHD4KW7yZoycuqDQ15QOTnc2VVfy0nN/1eMhnQLO00A+dwtK20xuCnc1YGeUyg==", + "license": "MIT", + "dependencies": { + "cliui": "^9.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "string-width": "^8.2.1", + "y18n": "^5.0.5", + "yargs-parser": "^22.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=23" + } + }, + "node_modules/yargs-parser": { + "version": "22.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz", + "integrity": "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==", + "license": "ISC", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=23" + } + }, + "node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } } } } diff --git a/package.json b/package.json index 3070d9c..1954e1a 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,8 @@ "type": "module", "scripts": { "start": "node server.mjs", - "dev": "node --watch server.mjs" + "dev": "node --watch server.mjs", + "worker": "node worker.mjs" }, "keywords": [ "audio-visualizer", @@ -18,10 +19,13 @@ "author": "Jannik", "license": "MIT", "dependencies": { + "chokidar": "^5.0.0", "cors": "^2.8.5", "express": "^4.21.2", "mp4-muxer": "^5.2.2", "multer": "^1.4.5-lts.1", + "node-fetch": "^3.3.2", + "puppeteer": "^25.10.0", "webm-muxer": "^5.1.4" } } diff --git a/public/app.js b/public/app.js index 2f496e5..82d8664 100644 --- a/public/app.js +++ b/public/app.js @@ -2993,6 +2993,9 @@ const defaultKey = window.VIZ_PRESETS.trap_nation ? 'trap_nation' : Object.keys(window.VIZ_PRESETS)[0]; if (defaultKey) applyPreset(defaultKey); + // Expose for Headless Automation + window.VIZ_API = { applyPreset, updateConfigFromUI, state }; + setupEvents(); // updateExportEngineBadge(); fetchRenderHistory(); diff --git a/worker.mjs b/worker.mjs new file mode 100644 index 0000000..0829b54 --- /dev/null +++ b/worker.mjs @@ -0,0 +1,238 @@ +import fs from 'fs'; +import path from 'path'; +import chokidar from 'chokidar'; +import puppeteer from 'puppeteer'; +import fetch from 'node-fetch'; +import { pipeline } from 'stream/promises'; + +const WATCH_DIR = 'D:/Google Drive/Videos/AIWentNonsense/*/Viz'; +const VIZ_URL = 'http://localhost:3000'; +const NTFY_URL = 'https://ntfy.orfel.de/viz_errors'; + +const queue = []; +let isProcessing = false; + +// Initialize watcher +const watcher = chokidar.watch(WATCH_DIR, { + ignored: /(^|[\/\\])\../, // ignore dotfiles + persistent: true, + depth: 0 +}); + +console.log(`\n👁️ Watching for JSON presets in: ${WATCH_DIR}\n`); + +watcher.on('add', async (filePath) => { + // Ignoriere Dateien in done/ oder failed/ + if ( + filePath.toLowerCase().endsWith('.json') && + !filePath.includes('\\done\\') && !filePath.includes('/done/') && + !filePath.includes('\\failed\\') && !filePath.includes('/failed/') + ) { + console.log(`\n📥 Neues Preset gefunden: ${filePath}`); + queue.push(filePath); + processQueue(); + } +}); + +async function processQueue() { + if (isProcessing || queue.length === 0) return; + isProcessing = true; + + const filePath = queue.shift(); + const dirName = path.dirname(filePath); + + // ensure done/ failed/ folders exist + fs.mkdirSync(path.join(dirName, 'done'), { recursive: true }); + fs.mkdirSync(path.join(dirName, 'failed'), { recursive: true }); + + const fileName = path.basename(filePath); + + try { + await processPreset(filePath); + + // Move to done + fs.renameSync(filePath, path.join(dirName, 'done', fileName)); + console.log(`✅ Job abgeschlossen und verschoben nach done/${fileName}`); + + } catch (err) { + console.error(`\n❌ Fehler bei der Verarbeitung von ${fileName}:`, err.message); + + // Write error log + fs.writeFileSync(path.join(dirName, 'failed', fileName + '.error.log'), err.stack || err.message); + + // Move to failed + fs.renameSync(filePath, path.join(dirName, 'failed', fileName)); + + // Send ntfy notification + try { + await fetch(NTFY_URL, { + method: 'POST', + body: `Viz Auto-Render fehlgeschlagen für ${fileName}: ${err.message}`, + headers: { + 'Title': 'Viz Render Error', + 'Tags': 'warning,video' + } + }); + console.log('📬 Ntfy-Benachrichtigung gesendet.'); + } catch (ntfyErr) { + console.error('⚠️ Konnte Ntfy Benachrichtigung nicht senden:', ntfyErr.message); + } + } + + isProcessing = false; + + // Nächsten Job starten + setTimeout(processQueue, 1000); +} + +async function processPreset(presetPath) { + const jobRaw = fs.readFileSync(presetPath, 'utf8'); + let job; + try { + job = JSON.parse(jobRaw); + } catch(e) { + throw new Error('Ungültiges JSON-Format: ' + e.message); + } + + console.log(`\n🎬 Starte Render-Job für: ${job.title} (${job.id})`); + + const audioFileAbs = path.resolve(path.dirname(presetPath), job.audio); + if (!fs.existsSync(audioFileAbs)) { + throw new Error(`Audiodatei nicht gefunden: ${audioFileAbs}`); + } + + console.log(`🎵 Audio geladen: ${audioFileAbs}`); + + const browser = await puppeteer.launch({ + headless: 'new', + args: [ + '--autoplay-policy=no-user-gesture-required', + '--use-gl=egl', + '--ignore-gpu-blocklist', + '--enable-gpu-rasterization', + '--mute-audio', + '--window-size=1920,1080' + ] + }); + + try { + const page = await browser.newPage(); + await page.setViewport({ width: 1920, height: 1080 }); + + console.log('🌐 Öffne Viz Web-UI...'); + await page.goto(VIZ_URL, { waitUntil: 'networkidle2' }); + + // 1. Audio Upload auslösen + const audioInput = await page.$('#audioFileInput'); + await audioInput.uploadFile(audioFileAbs); + + // Warten bis audioLoaded === true im Window + await page.waitForFunction(() => window.VIZ_API && window.VIZ_API.state && window.VIZ_API.state.audioLoaded === true, { timeout: 30000 }); + console.log('🔊 Audio erfolgreich im Browser geladen.'); + + // 2. Preset setzen + if (job.preset) { + await page.evaluate((presetName) => { + if (window.VIZ_PRESETS && window.VIZ_PRESETS[presetName]) { + window.VIZ_API.applyPreset(presetName); + } else { + throw new Error('Preset nicht gefunden: ' + presetName); + } + }, job.preset); + console.log(`🎨 Preset "${job.preset}" angewendet.`); + } + + // 3. Titel, Artist, Aspect + await page.evaluate((title, artist, aspect, format) => { + const titleEl = document.getElementById('trackTitle'); + if (titleEl) titleEl.value = title || 'Visualizer'; + + const artistEl = document.getElementById('trackArtist'); + if (artistEl) artistEl.value = artist || ''; + + if (aspect) { + const btn = document.querySelector(`.aspect-btn[data-aspect="${aspect}"]`); + if (btn) btn.click(); + } + + if (format) { + const fmtEl = document.getElementById('exportFormat'); + if (fmtEl) fmtEl.value = format; + } + + window.VIZ_API.updateConfigFromUI(); + }, job.title, job.artist, job.aspect, job.format || 'prores'); + + // 4. Render-Knopf drücken + console.log('🚀 Starte Export-Prozess...'); + await page.click('#btnStartExport'); + + // 5. Überwache den History API Endpunkt bis der Job done ist + console.log('⏳ Warte auf den Abschluss des Renderings...'); + + let isDone = false; + let targetFormat = job.format === 'prores4444' ? 'prores' : (job.format || 'prores'); + if (targetFormat === 'prores4444') targetFormat = 'prores'; + + let lastStatus = ''; + + // Kurz warten bis die Session in der History auftaucht + await new Promise(r => setTimeout(r, 4000)); + + const historyRes = await fetch(`${VIZ_URL}/api/export/history`); + const history = await historyRes.json(); + + if (history.length === 0) throw new Error('Kein Export-Job in History gefunden'); + const sessionId = history[0].id; + console.log(`🔗 Session ID ermittelt: ${sessionId}`); + + while (!isDone) { + await new Promise(r => setTimeout(r, 2000)); + + const pollRes = await fetch(`${VIZ_URL}/api/export/history`); + if (!pollRes.ok) continue; // Server vllt kurz busy + + const hData = await pollRes.json(); + const sess = hData.find(s => s.id === sessionId); + + if (!sess) { + throw new Error('Session plötzlich verschwunden'); + } + + if (sess.error) { + throw new Error('Server Render Fehler: ' + sess.error); + } + + if (sess.currentStatus !== lastStatus) { + lastStatus = sess.currentStatus; + console.log(` [Status] ${lastStatus}`); + } + + if (sess.done && sess.formats[targetFormat] && sess.formats[targetFormat].ready) { + isDone = true; + + const serverPath = sess.formats[targetFormat].path; + const outExt = (targetFormat === 'prores' || targetFormat === 'qtrle') ? '.mov' : (targetFormat === 'webm_alpha' ? '.webm' : '.mp4'); + const finalTitle = job.title ? job.title.replace(/[^a-zA-Z0-9_-]/g, '_') : 'Output'; + const finalDest = path.join(path.dirname(presetPath), `${finalTitle}${outExt}`); + + console.log(`\n💾 Kopiere fertiges Video nach: ${finalDest}`); + + if (fs.existsSync(serverPath)) { + fs.copyFileSync(serverPath, finalDest); + } else { + // Fallback HTTP Download falls Pfade lokal nicht uebereinstimmen + console.log(` Lokale Datei ${serverPath} nicht gefunden. Lade über HTTP herunter...`); + const res = await fetch(`${VIZ_URL}/api/export/download/${sessionId}/${targetFormat}`); + if (!res.ok) throw new Error('HTTP Download fehlgeschlagen'); + await pipeline(res.body, fs.createWriteStream(finalDest)); + } + } else if (sess.done) { + // done ist true, aber das gewünschte Format ist nicht da? + throw new Error(`Render abgeschlossen, aber Format ${targetFormat} nicht verfügbar.`); + } + } + } finally { + await browser.close(); + } +}