add funasr demo

This commit is contained in:
abearxiong 2025-04-16 23:47:32 +08:00
parent bd789de7b6
commit 2377318446
25 changed files with 842 additions and 345 deletions

View File

@ -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"
}

358
pnpm-lock.yaml generated
View File

@ -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: {}

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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);
}
}

View File

@ -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);

View File

@ -1,3 +1 @@
import { app } from './app.ts';
export { app };
export const test = 'test';

View File

@ -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);

View File

@ -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');
};

View File

@ -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(); // 用于订阅消息

View File

@ -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,
});

View File

@ -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();

144
src/recorder/index.ts Normal file
View File

@ -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);

View File

@ -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 };
};

View File

@ -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,
};

View File

@ -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 };
};

View File

@ -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 };
};

View File

@ -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);

View File

@ -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<InstanceType<typeof AppDemoModel>>;
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);
});

BIN
test.wav Normal file

Binary file not shown.

20
tsup.config.mjs Normal file
View File

@ -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',
});

BIN
videos/asr_example.pcm Normal file

Binary file not shown.

BIN
videos/asr_example.wav Normal file

Binary file not shown.

BIN
videos/test.mp4 Normal file

Binary file not shown.