diff --git a/package.json b/package.json index d5108b4..f4bd2a2 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "build": "rollup -c rollup.config.mjs", "dev": "cross-env NODE_ENV=development nodemon --delay 2.5 -e js,cjs,mjs --exec node dist/app.mjs", "dev:watch": "cross-env NODE_ENV=development concurrently -n \"Watch,Dev\" -c \"green,blue\" \"npm run watch\" \"sleep 1 && npm run dev\" ", + "dev:bun": "bun run src/dev.ts --watch", "test": "tsx test/**/*.ts", "clean": "rm -rf dist", "pub": "npm run build && envision pack -p -u", @@ -36,13 +37,14 @@ }, "dependencies": { "@kevisual/code-center-module": "0.0.18", - "@kevisual/use-config": "^1.0.10", "@kevisual/mark": "0.0.7", "@kevisual/router": "0.0.10", + "@kevisual/use-config": "^1.0.10", "cookie": "^1.0.2", "dayjs": "^1.11.13", "formidable": "^3.5.2", - "lodash-es": "^4.17.21" + "lodash-es": "^4.17.21", + "node-record-lpcm16": "^1.0.1" }, "devDependencies": { "@kevisual/types": "^0.0.6", @@ -57,6 +59,7 @@ "@types/formidable": "^3.4.5", "@types/lodash-es": "^4.17.12", "@types/node": "^22.14.0", + "@types/ws": "^8.18.1", "commander": "^13.1.0", "concurrently": "^9.1.2", "cross-env": "^7.0.3", @@ -75,8 +78,10 @@ "rollup-plugin-esbuild": "^6.2.1", "sequelize": "^6.37.7", "tape": "^5.9.0", + "tsup": "^8.4.0", "tsx": "^4.19.3", - "typescript": "^5.8.3" + "typescript": "^5.8.3", + "ws": "^8.18.1" }, "packageManager": "pnpm@10.7.1" } \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e48647c..b2fbc9f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -32,6 +32,9 @@ importers: lodash-es: specifier: ^4.17.21 version: 4.17.21 + node-record-lpcm16: + specifier: ^1.0.1 + version: 1.0.1 devDependencies: '@kevisual/types': specifier: ^0.0.6 @@ -66,6 +69,9 @@ importers: '@types/node': specifier: ^22.14.0 version: 22.14.0 + '@types/ws': + specifier: ^8.18.1 + version: 8.18.1 commander: specifier: ^13.1.0 version: 13.1.0 @@ -120,12 +126,18 @@ importers: tape: specifier: ^5.9.0 version: 5.9.0 + tsup: + specifier: ^8.4.0 + version: 8.4.0(postcss@8.5.3)(tsx@4.19.3)(typescript@5.8.3) tsx: specifier: ^4.19.3 version: 4.19.3 typescript: specifier: ^5.8.3 version: 5.8.3 + ws: + specifier: ^8.18.1 + version: 8.18.1 packages: @@ -593,6 +605,10 @@ packages: '@oxc-project/types@0.60.0': resolution: {integrity: sha512-prhfNnb3ATFHOCv7mzKFfwLij5RzoUz6Y1n525ZhCEqfq5wreCXL+DyVoq3ShukPo7q45ZjYIdjFUgjj+WKzng==} + '@pkgjs/parseargs@0.11.0': + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + '@pm2/agent@2.1.1': resolution: {integrity: sha512-0V9ckHWd/HSC8BgAbZSoq8KXUG81X97nSkAxmhKDhmF8vanyaoc1YXwc2KVkbWz82Rg4gjd2n9qiT3i7bdvGrQ==} @@ -861,6 +877,9 @@ packages: '@types/validator@13.12.2': resolution: {integrity: sha512-6SlHBzUW8Jhf3liqrGGXyTJSIFe4nqlJ5A5KaMZ2l/vbM3Wh3KSybots/wfWVzNLK4D1NZluDlSQIbIEPx6oyA==} + '@types/ws@8.18.1': + resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==} + '@vue/compiler-core@3.5.13': resolution: {integrity: sha512-oOdAkwqUfW1WqpwSYJce06wvt6HljgY3fGeM9NcVA1HaYOij3mZG9Rkysn0OHuyUAGMbEbARIpsG+LPVlBJ5/Q==} @@ -951,6 +970,9 @@ packages: resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} engines: {node: '>=12'} + any-promise@1.3.0: + resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} + anymatch@3.1.3: resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} engines: {node: '>= 8'} @@ -1059,6 +1081,16 @@ packages: buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + bundle-require@5.1.0: + resolution: {integrity: sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + peerDependencies: + esbuild: '>=0.18' + + cac@6.7.14: + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} + engines: {node: '>=8'} + call-bind-apply-helpers@1.0.2: resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} engines: {node: '>= 0.4'} @@ -1094,6 +1126,10 @@ packages: resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} engines: {node: '>= 8.10.0'} + chokidar@4.0.3: + resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} + engines: {node: '>= 14.16.0'} + cli-boxes@3.0.0: resolution: {integrity: sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==} engines: {node: '>=10'} @@ -1142,6 +1178,10 @@ packages: commander@2.15.1: resolution: {integrity: sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==} + commander@4.1.1: + resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} + engines: {node: '>= 6'} + commondir@1.0.1: resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==} @@ -1157,6 +1197,10 @@ packages: resolution: {integrity: sha512-Bi6v586cy1CoTFViVO4lGTtx780lfF96fUmS1lSX6wpZf6330NvHUu6fReVuDP1de8Mg0nkZb01c8tAQdz1o3w==} engines: {node: '>=18'} + consola@3.4.2: + resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} + engines: {node: ^14.18.0 || >=16.10.0} + cookie@0.7.2: resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} engines: {node: '>= 0.6'} @@ -1223,6 +1267,14 @@ packages: resolution: {integrity: sha512-rBMW+F2TXryBwB54Q0d8drNEI+TfoS9JpNTAoVpukbWEhjXQq4rySFYLaqXMFXwdv61Zb2OHtj5bviSoimqxRQ==} engines: {node: '>=18'} + debug@2.6.9: + resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + debug@3.2.7: resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} peerDependencies: @@ -1604,6 +1656,10 @@ packages: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} + glob@10.4.5: + resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} + hasBin: true + glob@11.0.1: resolution: {integrity: sha512-zrQDm8XPnYEKawJScsnM0QzobJxlT/kHOOlRTio8IH/GrmxRE5fjllkzdaHclIuNjUQTJYH2xHNIGfdpJkDJUw==} engines: {node: 20 || >=22} @@ -1869,6 +1925,9 @@ packages: isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + jackspeak@3.4.3: + resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + jackspeak@4.0.3: resolution: {integrity: sha512-oSwM7q8PTHQWuZAlp995iPpPJ4Vkl7qT0ZRD+9duL9j2oBy6KcTfyxc8mEuHJYC+z/kbps80aJLkaNzTOrf/kw==} engines: {node: 20 || >=22} @@ -1906,6 +1965,17 @@ packages: resolution: {integrity: sha512-+eHkKSyrFJcJ0N/H57QAH3OsDja0T3zPN8DEti2ZyBL/4CZq/iR/BcEgyNgV2bFUvbR9/nUYbA33cgtztiQRsA==} hasBin: true + lilconfig@3.1.3: + resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} + engines: {node: '>=14'} + + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + + load-tsconfig@0.2.5: + resolution: {integrity: sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + locate-character@3.0.0: resolution: {integrity: sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==} @@ -1918,9 +1988,15 @@ packages: lodash.isarguments@3.1.0: resolution: {integrity: sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==} + lodash.sortby@4.7.0: + resolution: {integrity: sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==} + lodash@4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + lru-cache@11.0.2: resolution: {integrity: sha512-123qHRfJBmo2jXDbo/a5YOQrJoHF/GNQTLzQ5+IdK5pWpceK17yRc6ozlWd25FxvGKQbIUs91fDFkXmDHTKcyA==} engines: {node: 20 || >=22} @@ -1970,6 +2046,10 @@ packages: minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + minimatch@9.0.5: + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + engines: {node: '>=16 || 14 >=14.17'} + minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} @@ -1995,12 +2075,18 @@ packages: moment@2.30.1: resolution: {integrity: sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==} + ms@2.0.0: + resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} + ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} mute-stream@0.0.8: resolution: {integrity: sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==} + mz@2.7.0: + resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} + nanoid@3.3.9: resolution: {integrity: sha512-SppoicMGpZvbF1l3z4x7No3OlIjP7QJvC9XR7AhZr1kL133KHnKPztkKDc+Ir4aJ/1VhTySrtKhrsycmrMQfvg==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} @@ -2050,6 +2136,9 @@ packages: resolution: {integrity: sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==} engines: {node: '>= 6.13.0'} + node-record-lpcm16@1.0.1: + resolution: {integrity: sha512-H75GMOP8ErnF67m21+qSgj4USnzv5RLfm7OkEItdIi+soNKoJZpMQPX6umM8Cn9nVPSgd/dBUtc1msst5MmABA==} + nodemon@3.1.9: resolution: {integrity: sha512-hdr1oIb2p6ZSxu3PB2JWWYS7ZQ0qvaZsc3hK8DR8f02kRzc8rjYmxAIvdz+aYC+8F2IjNaB7HMcSDg8nQpJxyg==} engines: {node: '>=10'} @@ -2156,6 +2245,10 @@ packages: path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + path-scurry@1.11.1: + resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} + engines: {node: '>=16 || 14 >=14.18'} + path-scurry@2.0.0: resolution: {integrity: sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==} engines: {node: 20 || >=22} @@ -2238,6 +2331,10 @@ packages: resolution: {integrity: sha512-i85pKRCt4qMjZ1+L7sy2Ag4t1atFcdbEt76+7iRJn1g2BvsnRMGu9p8pivl9fs63M2kF/A0OacFZhTub+m/qMg==} hasBin: true + pirates@4.0.7: + resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} + engines: {node: '>= 6'} + pm2-axon-rpc@0.7.1: resolution: {integrity: sha512-FbLvW60w+vEyvMjP/xom2UPhUN/2bVpdtLfKJeYM3gwzYhoTEEChCOICfFzxkxuoEleOlnpjie+n1nue91bDQw==} engines: {node: '>=5'} @@ -2265,6 +2362,24 @@ packages: resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} engines: {node: '>= 0.4'} + postcss-load-config@6.0.1: + resolution: {integrity: sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==} + engines: {node: '>= 18'} + peerDependencies: + jiti: '>=1.21.0' + postcss: '>=8.0.9' + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + jiti: + optional: true + postcss: + optional: true + tsx: + optional: true + yaml: + optional: true + postcss@8.5.3: resolution: {integrity: sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==} engines: {node: ^10 || ^12 || >=14} @@ -2319,6 +2434,10 @@ packages: pump@3.0.2: resolution: {integrity: sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==} + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} @@ -2333,6 +2452,10 @@ packages: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} + readdirp@4.1.2: + resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} + engines: {node: '>= 14.18.0'} + real-require@0.2.0: resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==} engines: {node: '>= 12.13.0'} @@ -2365,6 +2488,10 @@ packages: resolution: {integrity: sha512-efCx3b+0Z69/LGJmm9Yvi4cqEdxnoGnxYxGxBghkkTTFeXRtTCmmhO0AnAfHz59k957uTSuy8WaHqOs8wbYUWg==} engines: {node: '>=6'} + resolve-from@5.0.0: + resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} + engines: {node: '>=8'} + resolve-pkg-maps@1.0.0: resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} @@ -2605,6 +2732,10 @@ packages: resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} engines: {node: '>=0.10.0'} + source-map@0.8.0-beta.0: + resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==} + engines: {node: '>= 8'} + sourcemap-codec@1.4.8: resolution: {integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==} deprecated: Please use @jridgewell/sourcemap-codec instead @@ -2669,6 +2800,11 @@ packages: stubborn-fs@1.2.5: resolution: {integrity: sha512-H2N9c26eXjzL/S/K+i/RHHcFanE74dptvvjM8iwzwbVcWY/zjBbgRqF3K0DY4+OD+uTTASTBvDoxPDaPN02D7g==} + sucrase@3.35.0: + resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==} + engines: {node: '>=16 || 14 >=14.17'} + hasBin: true + supports-color@5.5.0: resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} engines: {node: '>=4'} @@ -2699,9 +2835,23 @@ packages: resolution: {integrity: sha512-czbGgxSVwRlbB3Ly/aqQrNwrDAzKHDW/kVXegp4hSFmR2c8qqm3hCgZbUy1+3QAQFGhPDG7J56UsV1uNilBFCA==} hasBin: true + thenify-all@1.6.0: + resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} + engines: {node: '>=0.8'} + + thenify@3.3.1: + resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + thread-stream@3.1.0: resolution: {integrity: sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==} + tinyexec@0.3.2: + resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} + + tinyglobby@0.2.12: + resolution: {integrity: sha512-qkf4trmKSIiMTs/E63cxH+ojC2unam7rJ0WrauAzpT3ECNTxGRMlaXxVbfxMUC/w0LaYk6jQ4y/nGR9uBO3tww==} + engines: {node: '>=12.0.0'} + to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} @@ -2720,16 +2870,41 @@ packages: tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + tr46@1.0.1: + resolution: {integrity: sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==} + tree-kill@1.2.2: resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} hasBin: true + ts-interface-checker@0.1.13: + resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + tslib@1.9.3: resolution: {integrity: sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==} tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + tsup@8.4.0: + resolution: {integrity: sha512-b+eZbPCjz10fRryaAA7C8xlIHnf8VnsaRqydheLIqwG/Mcpfk8Z5zp3HayX7GaTygkigHl5cBUs+IhcySiIexQ==} + engines: {node: '>=18'} + hasBin: true + peerDependencies: + '@microsoft/api-extractor': ^7.36.0 + '@swc/core': ^1 + postcss: ^8.4.12 + typescript: '>=4.5.0' + peerDependenciesMeta: + '@microsoft/api-extractor': + optional: true + '@swc/core': + optional: true + postcss: + optional: true + typescript: + optional: true + tsx@4.19.3: resolution: {integrity: sha512-4H8vUNGNjQ4V2EOoGw005+c+dGuPSnhpPBPHBtsZdGZBk/iJb4kguGlPWaZTZ3q5nMtFOEsY0nRDlh9PJyd6SQ==} engines: {node: '>=18.0.0'} @@ -2849,12 +3024,18 @@ packages: webidl-conversions@3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + webidl-conversions@4.0.2: + resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==} + whatwg-fetch@3.6.20: resolution: {integrity: sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==} whatwg-url@5.0.0: resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + whatwg-url@7.1.0: + resolution: {integrity: sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==} + when-exit@2.1.4: resolution: {integrity: sha512-4rnvd3A1t16PWzrBUcSDZqcAmsUIy4minDXT/CZ8F2mVDgd65i4Aalimgz1aQkRGU0iH5eT5+6Rx2TK8o443Pg==} @@ -2925,18 +3106,6 @@ packages: utf-8-validate: optional: true - ws@8.18.0: - resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==} - 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 - ws@8.18.1: resolution: {integrity: sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==} engines: {node: '>=10.0.0'} @@ -3237,7 +3406,7 @@ snapshots: dependencies: path-to-regexp: 8.2.0 selfsigned: 2.4.1 - ws: 8.18.0 + ws: 8.18.1 transitivePeerDependencies: - bufferutil - utf-8-validate @@ -3458,6 +3627,9 @@ snapshots: '@oxc-project/types@0.60.0': {} + '@pkgjs/parseargs@0.11.0': + optional: true + '@pm2/agent@2.1.1': dependencies: async: 3.2.6 @@ -3711,6 +3883,10 @@ snapshots: '@types/validator@13.12.2': {} + '@types/ws@8.18.1': + dependencies: + '@types/node': 22.14.0 + '@vue/compiler-core@3.5.13': dependencies: '@babel/parser': 7.26.10 @@ -3815,6 +3991,8 @@ snapshots: ansi-styles@6.2.1: {} + any-promise@1.3.0: {} + anymatch@3.1.3: dependencies: normalize-path: 3.0.0 @@ -3920,6 +4098,13 @@ snapshots: buffer-from@1.1.2: {} + bundle-require@5.1.0(esbuild@0.25.0): + dependencies: + esbuild: 0.25.0 + load-tsconfig: 0.2.5 + + cac@6.7.14: {} + call-bind-apply-helpers@1.0.2: dependencies: es-errors: 1.3.0 @@ -3965,6 +4150,10 @@ snapshots: optionalDependencies: fsevents: 2.3.3 + chokidar@4.0.3: + dependencies: + readdirp: 4.1.2 + cli-boxes@3.0.0: {} cli-tableau@2.0.1: @@ -4001,6 +4190,8 @@ snapshots: commander@2.15.1: {} + commander@4.1.1: {} + commondir@1.0.1: {} concat-map@0.0.1: {} @@ -4027,6 +4218,8 @@ snapshots: semver: 7.7.1 uint8array-extras: 1.4.0 + consola@3.4.2: {} + cookie@0.7.2: {} cookie@1.0.2: {} @@ -4088,6 +4281,10 @@ snapshots: dependencies: mimic-function: 5.0.1 + debug@2.6.9: + dependencies: + ms: 2.0.0 + debug@3.2.7: dependencies: ms: 2.1.3 @@ -4559,6 +4756,15 @@ snapshots: dependencies: is-glob: 4.0.3 + glob@10.4.5: + dependencies: + foreground-child: 3.3.0 + jackspeak: 3.4.3 + minimatch: 9.0.5 + minipass: 7.1.2 + package-json-from-dist: 1.0.1 + path-scurry: 1.11.1 + glob@11.0.1: dependencies: foreground-child: 3.3.0 @@ -4837,6 +5043,12 @@ snapshots: isexe@2.0.0: {} + jackspeak@3.4.3: + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + jackspeak@4.0.3: dependencies: '@isaacs/cliui': 8.0.2 @@ -4912,6 +5124,12 @@ snapshots: - ws - zod + lilconfig@3.1.3: {} + + lines-and-columns@1.2.4: {} + + load-tsconfig@0.2.5: {} + locate-character@3.0.0: {} lodash-es@4.17.21: {} @@ -4920,8 +5138,12 @@ snapshots: lodash.isarguments@3.1.0: {} + lodash.sortby@4.7.0: {} + lodash@4.17.21: {} + lru-cache@10.4.3: {} + lru-cache@11.0.2: {} lru-cache@6.0.0: @@ -4963,6 +5185,10 @@ snapshots: dependencies: brace-expansion: 1.1.11 + minimatch@9.0.5: + dependencies: + brace-expansion: 2.0.1 + minimist@1.2.8: {} minipass@7.1.2: {} @@ -4987,10 +5213,18 @@ snapshots: moment@2.30.1: {} + ms@2.0.0: {} + ms@2.1.3: {} mute-stream@0.0.8: {} + mz@2.7.0: + dependencies: + any-promise: 1.3.0 + object-assign: 4.1.1 + thenify-all: 1.6.0 + nanoid@3.3.9: {} nanoid@5.1.2: {} @@ -5023,6 +5257,12 @@ snapshots: node-forge@1.3.1: {} + node-record-lpcm16@1.0.1: + dependencies: + debug: 2.6.9 + transitivePeerDependencies: + - supports-color + nodemon@3.1.9: dependencies: chokidar: 3.6.0 @@ -5160,6 +5400,11 @@ snapshots: path-parse@1.0.7: {} + path-scurry@1.11.1: + dependencies: + lru-cache: 10.4.3 + minipass: 7.1.2 + path-scurry@2.0.0: dependencies: lru-cache: 11.0.2 @@ -5257,6 +5502,8 @@ snapshots: sonic-boom: 4.2.0 thread-stream: 3.1.0 + pirates@4.0.7: {} + pm2-axon-rpc@0.7.1: dependencies: debug: 4.4.0(supports-color@5.5.0) @@ -5332,6 +5579,13 @@ snapshots: possible-typed-array-names@1.1.0: {} + postcss-load-config@6.0.1(postcss@8.5.3)(tsx@4.19.3): + dependencies: + lilconfig: 3.1.3 + optionalDependencies: + postcss: 8.5.3 + tsx: 4.19.3 + postcss@8.5.3: dependencies: nanoid: 3.3.9 @@ -5387,6 +5641,8 @@ snapshots: end-of-stream: 1.4.4 once: 1.4.0 + punycode@2.3.1: {} + queue-microtask@1.2.3: {} quick-format-unescaped@4.0.4: {} @@ -5399,6 +5655,8 @@ snapshots: dependencies: picomatch: 2.3.1 + readdirp@4.1.2: {} + real-require@0.2.0: {} redis-errors@1.2.0: {} @@ -5439,6 +5697,8 @@ snapshots: transitivePeerDependencies: - supports-color + resolve-from@5.0.0: {} + resolve-pkg-maps@1.0.0: {} resolve@1.22.10: @@ -5730,6 +5990,10 @@ snapshots: source-map@0.6.1: {} + source-map@0.8.0-beta.0: + dependencies: + whatwg-url: 7.1.0 + sourcemap-codec@1.4.8: {} split2@4.2.0: {} @@ -5800,6 +6064,16 @@ snapshots: stubborn-fs@1.2.5: {} + sucrase@3.35.0: + dependencies: + '@jridgewell/gen-mapping': 0.3.8 + commander: 4.1.1 + glob: 10.4.5 + lines-and-columns: 1.2.4 + mz: 2.7.0 + pirates: 4.0.7 + ts-interface-checker: 0.1.13 + supports-color@5.5.0: dependencies: has-flag: 3.0.0 @@ -5859,10 +6133,25 @@ snapshots: resolve: 2.0.0-next.5 string.prototype.trim: 1.2.10 + thenify-all@1.6.0: + dependencies: + thenify: 3.3.1 + + thenify@3.3.1: + dependencies: + any-promise: 1.3.0 + thread-stream@3.1.0: dependencies: real-require: 0.2.0 + tinyexec@0.3.2: {} + + tinyglobby@0.2.12: + dependencies: + fdir: 6.4.3(picomatch@4.0.2) + picomatch: 4.0.2 + to-regex-range@5.0.1: dependencies: is-number: 7.0.0 @@ -5875,12 +6164,45 @@ snapshots: tr46@0.0.3: {} + tr46@1.0.1: + dependencies: + punycode: 2.3.1 + tree-kill@1.2.2: {} + ts-interface-checker@0.1.13: {} + tslib@1.9.3: {} tslib@2.8.1: {} + tsup@8.4.0(postcss@8.5.3)(tsx@4.19.3)(typescript@5.8.3): + dependencies: + bundle-require: 5.1.0(esbuild@0.25.0) + cac: 6.7.14 + chokidar: 4.0.3 + consola: 3.4.2 + debug: 4.4.0(supports-color@5.5.0) + esbuild: 0.25.0 + joycon: 3.1.1 + picocolors: 1.1.1 + postcss-load-config: 6.0.1(postcss@8.5.3)(tsx@4.19.3) + resolve-from: 5.0.0 + rollup: 4.39.0 + source-map: 0.8.0-beta.0 + sucrase: 3.35.0 + tinyexec: 0.3.2 + tinyglobby: 0.2.12 + tree-kill: 1.2.2 + optionalDependencies: + postcss: 8.5.3 + typescript: 5.8.3 + transitivePeerDependencies: + - jiti + - supports-color + - tsx + - yaml + tsx@4.19.3: dependencies: esbuild: 0.25.0 @@ -5995,6 +6317,8 @@ snapshots: webidl-conversions@3.0.1: {} + webidl-conversions@4.0.2: {} + whatwg-fetch@3.6.20: {} whatwg-url@5.0.0: @@ -6002,6 +6326,12 @@ snapshots: tr46: 0.0.3 webidl-conversions: 3.0.1 + whatwg-url@7.1.0: + dependencies: + lodash.sortby: 4.7.0 + tr46: 1.0.1 + webidl-conversions: 4.0.2 + when-exit@2.1.4: {} which-boxed-primitive@1.1.1: @@ -6080,8 +6410,6 @@ snapshots: ws@8.17.1: {} - ws@8.18.0: {} - ws@8.18.1: {} xtend@4.0.2: {} diff --git a/src/app.ts b/src/app.ts deleted file mode 100644 index cd3952c..0000000 --- a/src/app.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { App } from '@kevisual/router'; -import { useContextKey } from '@kevisual/use-config/context'; - -const init = () => { - return new App(); -}; - -export const app = useContextKey('app', init); diff --git a/src/asr/provider/funasr/test/get-text.ts b/src/asr/provider/funasr/test/get-text.ts new file mode 100644 index 0000000..4f67449 --- /dev/null +++ b/src/asr/provider/funasr/test/get-text.ts @@ -0,0 +1,42 @@ +import { VideoWS } from '../ws.ts'; +import net from 'net'; +import path from 'path'; +import fs from 'fs'; + +const videoTestPath = path.join(process.cwd(), 'videos/asr_example.wav'); +const ws = new VideoWS({ + // url: 'wss://192.168.31.220:10095', + url: 'wss://funasr.xiongxiao.me', + isFile: true, + onConnect: async () => { + console.log('onConnect'); + const data = fs.readFileSync(videoTestPath); + let sampleBuf = new Uint8Array(data); + + var chunk_size = 960; // for asr chunk_size [5, 10, 5] + let totalsend = 0; + let len = 0; + ws.start(); + while (sampleBuf.length >= chunk_size) { + const sendBuf = sampleBuf.slice(0, chunk_size); + totalsend = totalsend + sampleBuf.length; + sampleBuf = sampleBuf.slice(chunk_size, sampleBuf.length); + if (len === 100) { + // ws.stop(); + // ws.start(); + await new Promise((resolve) => setTimeout(resolve, 1000)); + } + ws.send(sendBuf); + len++; + } + ws.stop(); + console.log('len', len); + }, +}); + +const server = net.createServer((socket) => { + socket.on('data', (data) => { + console.log('data', data); + }); +}); +server.listen(10096); diff --git a/src/asr/provider/funasr/test/recorder.ts b/src/asr/provider/funasr/test/recorder.ts new file mode 100644 index 0000000..18182a6 --- /dev/null +++ b/src/asr/provider/funasr/test/recorder.ts @@ -0,0 +1,41 @@ +import { VideoWS } from '../ws.ts'; +import net from 'net'; +import { Recording } from '../../../../recorder/index.ts'; +import Stream from 'stream'; + +const recorder = new Recording(); +const writeStream = new Stream.Writable(); +const ws = new VideoWS({ + url: 'wss://192.168.31.220:10095', + isFile: false, + onConnect: async () => { + console.log('onConnect'); + let chunks: Buffer = Buffer.alloc(0); + var chunk_size = 960; // for asr chunk_size [5, 10, 5] + let totalsend = 0; + let len = 0; + recorder.stream().on('data', (chunk) => { + chunks = Buffer.concat([chunks, chunk]); + if (chunks.length > chunk_size) { + ws.send(chunks); + totalsend += chunks.length; + chunks = Buffer.alloc(0); + } + }); + ws.start(); + setTimeout(() => { + ws.stop(); + setTimeout(() => { + process.exit(0); + }, 1000); + console.log('len', len); + }, 20000); + }, +}); + +const server = net.createServer((socket) => { + socket.on('data', (data) => { + console.log('data', data); + }); +}); +server.listen(10096); diff --git a/src/asr/provider/funasr/ws.ts b/src/asr/provider/funasr/ws.ts new file mode 100644 index 0000000..a835ffd --- /dev/null +++ b/src/asr/provider/funasr/ws.ts @@ -0,0 +1,114 @@ +import WebSocket from 'ws'; + +type VideoWSOptions = { + url?: string; + ws?: WebSocket; + itn?: boolean; + mode?: string; + isFile?: boolean; + onConnect?: () => void; +}; +export const VideoWsMode = ['2pass', 'online', 'offline']; +type VideoWsMode = (typeof VideoWsMode)[number]; + +export type VideoWsResult = { + isFinal: boolean; + mode: VideoWsMode; + stamp_sents: { end: number; punc: string; start: number; text_seg: string; tsList: [][] }[]; + text: string; + timestamp: string; + wav_name: string; +}; + +export class VideoWS { + ws: WebSocket; + itn?: boolean; + mode?: VideoWsMode; + isFile?: boolean; + onConnect?: () => void; + constructor(options?: VideoWSOptions) { + this.ws = + options?.ws || + new WebSocket(options.url, { + rejectUnauthorized: false, + }); + this.itn = options?.itn || false; + this.mode = options?.mode || 'online'; + this.isFile = options?.isFile || false; + + this.onConnect = options?.onConnect || (() => {}); + this.ws.onopen = this.onOpen.bind(this); + this.ws.onmessage = this.onMessage.bind(this); + this.ws.onerror = this.onError.bind(this); + this.ws.onclose = this.onClose.bind(this); + } + + async onOpen() { + this.onConnect(); + } + async start() { + let isFileMode = this.isFile; + const chunk_size = new Array(5, 10, 5); + type OpenRequest = { + chunk_size: number[]; + wav_name: string; + is_speaking: boolean; + chunk_interval: number; + itn: boolean; + mode: VideoWsMode; + wav_format?: string; + audio_fs?: number; + hotwords?: string; + }; + const request: OpenRequest = { + chunk_size: chunk_size, + wav_name: 'h5', // + is_speaking: true, + chunk_interval: 10, + itn: this.itn, + mode: this.mode || 'online', + }; + console.log('request', request); + if (isFileMode) { + const file_ext = 'wav'; + const file_sample_rate = 16000; + request.wav_format = file_ext; + if (file_ext == 'wav') { + request.wav_format = 'PCM'; + request.audio_fs = file_sample_rate; + } + } + this.ws.send(JSON.stringify(request)); + } + async stop() { + var chunk_size = new Array(5, 10, 5); + var request = { + chunk_size: chunk_size, + wav_name: 'h5', + is_speaking: false, + chunk_interval: 10, + mode: this.mode, + }; + this.ws.send(JSON.stringify(request)); + } + async send(data: any) { + if (this.ws && this.ws.readyState === WebSocket.OPEN) { + this.ws.send(data); + } + } + async onMessage(event: MessageEvent) { + const data = event.data; + try { + const result = JSON.parse(data.toString()); + console.log('result', result); + } catch (error) { + console.log('error', error); + } + } + async onError(event: Event) { + console.log('onError', event); + } + async onClose(event: CloseEvent) { + console.log('onClose', event); + } +} diff --git a/src/dev.ts b/src/dev.ts index c3e9636..18adf92 100644 --- a/src/dev.ts +++ b/src/dev.ts @@ -1,48 +1,20 @@ -import { app } from './index.ts'; -import { useConfig } from '@kevisual/use-config/env'; +import { Recording } from './recorder/index.ts'; +import fs from 'fs'; -app - .route({ - path: 'auth', - id: 'auth', - }) - .define(async (ctx) => { - ctx.query.token = '123'; - ctx.state.tokenUser = { - id: '123', - username: 'admin', - }; - }) - .addTo(app); +const file = fs.createWriteStream('test.wav', { encoding: 'binary' }); -app - .route({ - path: 'auth-admin', - id: 'auth-admin', - }) - .define(async (ctx) => { - ctx.body = '123'; - ctx.state.tokenUser = { - id: '123', - username: 'admin', - }; - }) - .addTo(app); -app - .route({ - path: 'demo', - key: 'demo', - }) - .define(async (ctx) => { - ctx.body = '123'; - }) - .addTo(app); - -const config = useConfig(); -const port = config.PORT || 4000; - -console.log('run demo: http://localhost:' + port + '/api/router?path=demo&key=demo'); - -app.listen(port, () => { - console.log(`server is running at http://localhost:${port}`); +const record = new Recording({ + sampleRate: 16000, + channels: 1, + audioType: 'wav', + threshold: 0, + recorder: 'rec', + silence: '1.0', + endOnSilence: true, }); +record.stream().pipe(file); + +setTimeout(() => { + record.stop(); + process.exit(0); +}, 5000); diff --git a/src/index.ts b/src/index.ts index 68f5dd6..58f89e9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,3 +1 @@ -import { app } from './app.ts'; - -export { app }; +export const test = 'test'; \ No newline at end of file diff --git a/src/logger/index.ts b/src/logger/index.ts index 1f6fe32..6495e0f 100644 --- a/src/logger/index.ts +++ b/src/logger/index.ts @@ -18,10 +18,10 @@ export const logger = pino({ req: pino.stdSerializers.req, res: pino.stdSerializers.res, }, - base: { - app: 'ai-chat', - env: process.env.NODE_ENV || 'development', - }, + // base: { + // app: 'ai-videos', + // env: process.env.NODE_ENV || 'development', + // }, }); export const logError = (message: string, data?: any) => logger.error({ data }, message); diff --git a/src/main.ts b/src/main.ts index 888ed08..2afa48a 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,9 +1,3 @@ -// 单应用实例启动 -import { useConfig } from '@kevisual/use-config/env'; -import { app } from './index.ts'; - -const config = useConfig(); -const port = config.PORT || 4000; -app.listen(port, () => { - console.log(`server is running at http://localhost:${port}`); -}); +export const main = () => { + console.log('main'); +}; diff --git a/src/modules/redis.ts b/src/modules/redis.ts deleted file mode 100644 index 399f9af..0000000 --- a/src/modules/redis.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { Redis } from 'ioredis'; - -// 配置 Redis 连接 -export const redis = new Redis({ - host: 'localhost', // Redis 服务器的主机名或 IP 地址 - port: 6379, // Redis 服务器的端口号 - // password: 'your_password', // Redis 的密码 (如果有) - db: 0, // 要使用的 Redis 数据库索引 (0-15) - keyPrefix: '', // key 前缀 - retryStrategy(times) { - // 连接重试策略 - return Math.min(times * 50, 2000); // 每次重试时延迟增加 - }, - maxRetriesPerRequest: null, // 允许请求重试的次数 (如果需要无限次重试) -}); - -// 监听连接事件 -redis.on('connect', () => { - console.log('Redis 连接成功'); -}); - -redis.on('error', (err) => { - console.error('Redis 连接错误', err); -}); - -// 初始化 Redis 客户端 -export const redisPublisher = new Redis(); // 用于发布消息 -export const redisSubscriber = new Redis(); // 用于订阅消息 diff --git a/src/modules/sequelize.ts b/src/modules/sequelize.ts deleted file mode 100644 index fe29a7a..0000000 --- a/src/modules/sequelize.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { Sequelize } from 'sequelize'; -import { useConfig } from '@kevisual/use-config/env'; - -const config = useConfig(); - -export type PostgresConfig = { - postgres: { - username: string; - password: string; - host: string; - port: number; - database: string; - }; -}; -if (!config.POSTGRES_PASSWORD || !config.POSTGRES_USER) { - console.error('postgres config is required password and user'); - process.exit(1); -} -const postgresConfig = { - username: config.POSTGRES_USER, - password: config.POSTGRES_PASSWORD, - host: config.POSTGRES_HOST || 'localhost', - port: parseInt(config.POSTGRES_PORT || '5432'), - database: config.POSTGRES_DB || 'postgres', -}; -// connect to db -export const sequelize = new Sequelize({ - dialect: 'postgres', - ...postgresConfig, - // logging: false, -}); diff --git a/src/modules/user.ts b/src/modules/user.ts deleted file mode 100644 index c628337..0000000 --- a/src/modules/user.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { sequelize, User, UserInit, Org, OrgInit } from '@kevisual/code-center-module'; - -export { sequelize, User, UserInit, Org, OrgInit }; - -export const init = () => { - UserInit(); - OrgInit(); -}; -init(); diff --git a/src/recorder/index.ts b/src/recorder/index.ts new file mode 100644 index 0000000..d204b9b --- /dev/null +++ b/src/recorder/index.ts @@ -0,0 +1,144 @@ +import assert from 'assert'; +import { logDebug, logInfo } from '../logger/index.ts'; +import { ChildProcessWithoutNullStreams, spawn } from 'child_process'; +import recorders from './recorders/index.ts'; +import Stream from 'stream'; + +export type RecordingOptions = { + /* 采样率,默认为16000 */ + sampleRate?: number; + /* 声道数,默认为1 */ + channels?: number; + /* 是否压缩音频,默认为false */ + compress?: boolean; + /* 录音开始的音量阈值,默认为0.5 */ + threshold?: number; + /* 开始录音的音量阈值 */ + thresholdStart?: number; + /* 结束录音的音量阈值 */ + thresholdEnd?: number; + /* 录音结束的静默时间,默认为'1.0'秒 */ + silence?: string; + /* 使用的录音器,默认为'sox' */ + recorder?: string; + /* 是否在静默时结束录音,默认为false */ + endOnSilence?: boolean; + /* 音频类型,默认为'wav' */ + audioType?: string; +}; +/** + * node-record-lpcm16 + * https://github.com/gillesdemey/node-record-lpcm16 + */ +export class Recording { + options: RecordingOptions; + cmd: string; + args: string[]; + cmdOptions: any; + process: ChildProcessWithoutNullStreams; + _stream: Stream.Readable; + + constructor(options?: RecordingOptions) { + const defaults = { + sampleRate: 16000, + channels: 1, + compress: false, + threshold: 0.5, + thresholdStart: null, + thresholdEnd: null, + silence: '1.0', + recorder: 'sox', + endOnSilence: false, + audioType: 'wav', + }; + + this.options = Object.assign(defaults, options); + + const recorder = recorders[this.options.recorder]; + if (!recorder) { + throw new Error(`No such recorder found: ${this.options.recorder}`); + } + const { cmd, args, spawnOptions = {} } = recorder(this.options); + + this.cmd = cmd; + this.args = args; + this.cmdOptions = Object.assign({ encoding: 'binary', stdio: 'pipe' }, spawnOptions); + + logDebug(`Started recording`); + logDebug('options', this.options); + logDebug(` ${this.cmd} ${this.args.join(' ')}`); + + return this.start(); + } + + start() { + const { cmd, args, cmdOptions } = this; + + const cp = spawn(cmd, args, cmdOptions); + const rec = cp.stdout; + const err = cp.stderr; + + this.process = cp; // expose child process + this._stream = rec; // expose output stream + + cp.on('close', (code) => { + if (code === 0) return; + rec.emit( + 'error', + `${this.cmd} has exited with error code ${code}. + +Enable debugging with the environment variable DEBUG=record.`, + ); + }); + + err.on('data', (chunk) => { + logDebug(`STDERR: ${chunk}`); + }); + + rec.on('data', (chunk) => { + logDebug(`Recording ${chunk.length} bytes`); + }); + + rec.on('end', () => { + logDebug('Recording ended'); + }); + + return this; + } + + stop() { + assert(this.process, 'Recording not yet started'); + + this.process.kill(); + } + + pause() { + assert(this.process, 'Recording not yet started'); + + this.process.kill('SIGSTOP'); + this._stream.pause(); + logDebug('Paused recording'); + } + + resume() { + assert(this.process, 'Recording not yet started'); + + this.process.kill('SIGCONT'); + this._stream.resume(); + logDebug('Resumed recording'); + } + + isPaused() { + assert(this.process, 'Recording not yet started'); + + return this._stream.isPaused(); + } + + stream() { + assert(this._stream, 'Recording not yet started'); + + return this._stream; + } +} + +export const record = (...args) => new Recording(...args); diff --git a/src/recorder/recorders/arecord.ts b/src/recorder/recorders/arecord.ts new file mode 100644 index 0000000..099ec8a --- /dev/null +++ b/src/recorder/recorders/arecord.ts @@ -0,0 +1,23 @@ +// On some systems (RasPi), arecord is the prefered recording binary +export default (options: any) => { + const cmd = 'arecord'; + + const args = [ + '-q', // show no progress + '-r', + options.sampleRate, // sample rate + '-c', + options.channels, // channels + '-t', + options.audioType, // audio type + '-f', + 'S16_LE', // Sample format + '-', // pipe + ]; + + if (options.device) { + args.unshift('-D', options.device); + } + + return { cmd, args }; +}; diff --git a/src/recorder/recorders/index.ts b/src/recorder/recorders/index.ts new file mode 100644 index 0000000..8081a2b --- /dev/null +++ b/src/recorder/recorders/index.ts @@ -0,0 +1,11 @@ +import path from 'path'; + +import sox from './sox.ts'; +import rec from './rec.ts'; +import arecord from './arecord.ts'; + +export default { + sox, + rec, + arecord, +}; diff --git a/src/recorder/recorders/rec.ts b/src/recorder/recorders/rec.ts new file mode 100644 index 0000000..48926c8 --- /dev/null +++ b/src/recorder/recorders/rec.ts @@ -0,0 +1,32 @@ +export default (options: any) => { + const cmd = 'rec'; + + let args = [ + '-q', // show no progress + '-r', + options.sampleRate, // sample rate + '-c', + options.channels, // channels + '-e', + 'signed-integer', // sample encoding + '-b', + '16', // precision (bits) + '-t', + options.audioType, // audio type + '-', // pipe + ]; + + if (options.endOnSilence) { + args = args.concat([ + 'silence', + '1', + '0.1', + options.thresholdStart || options.threshold + '%', + '1', + options.silence, + options.thresholdEnd || options.threshold + '%', + ]); + } + + return { cmd, args }; +}; diff --git a/src/recorder/recorders/sox.ts b/src/recorder/recorders/sox.ts new file mode 100644 index 0000000..9fb2eca --- /dev/null +++ b/src/recorder/recorders/sox.ts @@ -0,0 +1,39 @@ +export default (options: any) => { + const cmd = 'sox'; + + let args = [ + '--default-device', + '--no-show-progress', // show no progress + '--rate', + options.sampleRate, // sample rate + '--channels', + options.channels, // channels + '--encoding', + 'signed-integer', // sample encoding + '--bits', + '16', // precision (bits) + '--type', + options.audioType, // audio type + '-', // pipe + ]; + + if (options.endOnSilence) { + args = args.concat([ + 'silence', + '1', + '0.1', + options.thresholdStart || options.threshold + '%', + '1', + options.silence, + options.thresholdEnd || options.threshold + '%', + ]); + } + + const spawnOptions: any = {}; + + if (options.device) { + spawnOptions.env = { ...process.env, AUDIODEV: options.device }; + } + + return { cmd, args, spawnOptions }; +}; diff --git a/src/routes/app-demo/index.ts b/src/routes/app-demo/index.ts deleted file mode 100644 index 366a77c..0000000 --- a/src/routes/app-demo/index.ts +++ /dev/null @@ -1,119 +0,0 @@ -import { Op } from 'sequelize'; -import { AppDemoModel } from './models/index.ts'; -import { app } from '@/app.ts'; - -app - .route({ - path: 'app-demo', - key: 'list', - middleware: ['auth'], - }) - .define(async (ctx) => { - const tokenUser = ctx.state.tokenUser; - const { page = 1, pageSize = 20, search, sort = 'DESC' } = ctx.query; - const searchWhere = search - ? { - [Op.or]: [{ title: { [Op.like]: `%${search}%` } }, { summary: { [Op.like]: `%${search}%` } }], - } - : {}; - - const { rows: appDemo, count } = await AppDemoModel.findAndCountAll({ - where: { - uid: tokenUser.uid, - ...searchWhere, - }, - offset: (page - 1) * pageSize, - limit: pageSize, - order: [['updatedAt', sort]], - }); - - ctx.body = { - list: appDemo, - pagination: { - page, - current: page, - pageSize, - total: count, - }, - }; - }) - .addTo(app); - -app - .route({ - path: 'app-demo', - key: 'update', - middleware: ['auth'], - }) - .define(async (ctx) => { - const tokenUser = ctx.state.tokenUser; - const { id, data, updatedAt: _clear, createdAt: _clear2, ...rest } = ctx.query.data; - let appDemo: AppDemoModel; - let isNew = false; - if (id) { - const appDemo = await AppDemoModel.findByPk(id); - if (appDemo.uid !== tokenUser.uid) { - ctx.throw(403, 'No permission'); - } - } else { - appDemo = await AppDemoModel.create({ - data: data, - ...rest, - uid: tokenUser.uid, - }); - isNew = true; - } - if (!appDemo) { - ctx.throw(404, 'AppDemo not found'); - } - if (!isNew) { - appDemo = await appDemo.update({ - data: { ...appDemo.data, ...data }, - ...rest, - }); - } - - ctx.body = appDemo; - }) - .addTo(app); - -app - .route({ - path: 'app-demo', - key: 'delete', - middleware: ['auth'], - }) - .define(async (ctx) => { - const tokenUser = ctx.state.tokenUser; - const { id, force = false } = ctx.query.data || {}; - if (!id) { - ctx.throw(400, 'id is required'); - } - const appDemo = await AppDemoModel.findByPk(id); - if (appDemo.uid !== tokenUser.uid) { - ctx.throw(403, 'No permission'); - } - await appDemo.destroy({ force }); - ctx.body = appDemo; - }) - .addTo(app); - -app - .route({ - path: 'app-demo', - key: 'get', - middleware: ['auth'], - }) - .define(async (ctx) => { - const tokenUser = ctx.state.tokenUser; - const { id } = ctx.query.data || {}; - if (!id) { - ctx.throw(400, 'id is required'); - } - const appDemo = await AppDemoModel.findByPk(id); - if (appDemo.uid !== tokenUser.uid) { - ctx.throw(403, 'No permission'); - } - ctx.body = appDemo; - }) - .addTo(app); diff --git a/src/routes/app-demo/models/index.ts b/src/routes/app-demo/models/index.ts deleted file mode 100644 index 098bfc3..0000000 --- a/src/routes/app-demo/models/index.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { sequelize } from '@/modules/sequelize.ts'; -import { DataTypes, Model } from 'sequelize'; - -export interface AppDemoData { - [key: string]: any; -} - -export type AppDemo = Partial>; - -export class AppDemoModel extends Model { - declare id: string; - declare title: string; - declare description: string; - declare summary: string; - - declare data: AppDemoData; - declare tags: string[]; - declare version: string; - - declare uid: string; - - declare createdAt: Date; - declare updatedAt: Date; -} - -AppDemoModel.init( - { - id: { - type: DataTypes.UUID, - primaryKey: true, - defaultValue: DataTypes.UUIDV4, - }, - title: { - type: DataTypes.TEXT, - defaultValue: '', - }, - description: { - type: DataTypes.TEXT, - defaultValue: '', - }, - summary: { - type: DataTypes.TEXT, - defaultValue: '', - }, - tags: { - type: DataTypes.JSONB, - defaultValue: [], - }, - version: { - type: DataTypes.INTEGER, - defaultValue: 0, - }, - data: { - type: DataTypes.JSONB, - defaultValue: {}, - }, - uid: { - type: DataTypes.UUID, - allowNull: false, - }, - }, - { - sequelize, - tableName: 'kv_app_demo', - paranoid: true, - }, -); - -AppDemoModel.sync({ alter: true, logging: false }).catch((e) => { - console.error('AppDemoModel sync', e); -}); diff --git a/test.wav b/test.wav new file mode 100644 index 0000000..ee7742a Binary files /dev/null and b/test.wav differ diff --git a/tsup.config.mjs b/tsup.config.mjs new file mode 100644 index 0000000..3c8d3f1 --- /dev/null +++ b/tsup.config.mjs @@ -0,0 +1,20 @@ +import { defineConfig } from 'tsup'; +// import glob from 'fast-glob'; +// const services = glob.sync('src/services/*.ts'); + +const entrys = ['src/index.ts']; + +export default defineConfig({ + entry: entrys, + outExtension: ({ format }) => ({ + js: format === 'esm' ? '.mjs' : '.js', + }), + splitting: false, + sourcemap: false, + clean: true, + format: 'esm', + external: ['dotenv'], + dts: true, + outDir: 'dist', + tsconfig: 'tsconfig.json', +}); diff --git a/videos/asr_example.pcm b/videos/asr_example.pcm new file mode 100644 index 0000000..f58af1c Binary files /dev/null and b/videos/asr_example.pcm differ diff --git a/videos/asr_example.wav b/videos/asr_example.wav new file mode 100644 index 0000000..bf13bb1 Binary files /dev/null and b/videos/asr_example.wav differ diff --git a/videos/test.mp4 b/videos/test.mp4 new file mode 100644 index 0000000..0f6001f Binary files /dev/null and b/videos/test.mp4 differ