├── .gitignore ├── .gitmodules ├── LICENSE ├── README.md ├── package-lock.json ├── package.json ├── src ├── constants.ts ├── download.ts ├── index.ts ├── shell.ts ├── test.ts ├── tsToArray.ts └── whisper.ts └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | whisper/ 2 | .DS_Store 3 | .vscode/ 4 | 5 | # Logs 6 | logs 7 | *.log 8 | npm-debug.log* 9 | yarn-debug.log* 10 | yarn-error.log* 11 | lerna-debug.log* 12 | 13 | # Diagnostic reports (https://nodejs.org/api/report.html) 14 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 15 | 16 | # Runtime data 17 | pids 18 | *.pid 19 | *.seed 20 | *.pid.lock 21 | 22 | # Directory for instrumented libs generated by jscoverage/JSCover 23 | lib-cov 24 | 25 | # Coverage directory used by tools like istanbul 26 | coverage 27 | *.lcov 28 | 29 | # nyc test coverage 30 | .nyc_output 31 | 32 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 33 | .grunt 34 | 35 | # Bower dependency directory (https://bower.io/) 36 | bower_components 37 | 38 | # node-waf configuration 39 | .lock-wscript 40 | 41 | # Compiled binary addons (https://nodejs.org/api/addons.html) 42 | build/Release 43 | 44 | # Dependency directories 45 | node_modules/ 46 | jspm_packages/ 47 | 48 | # TypeScript v1 declaration files 49 | typings/ 50 | 51 | # TypeScript cache 52 | *.tsbuildinfo 53 | 54 | # Optional npm cache directory 55 | .npm 56 | 57 | # Optional eslint cache 58 | .eslintcache 59 | 60 | # Microbundle cache 61 | .rpt2_cache/ 62 | .rts2_cache_cjs/ 63 | .rts2_cache_es/ 64 | .rts2_cache_umd/ 65 | 66 | # Optional REPL history 67 | .node_repl_history 68 | 69 | # Output of 'npm pack' 70 | *.tgz 71 | 72 | # Yarn Integrity file 73 | .yarn-integrity 74 | 75 | # dotenv environment variables file 76 | .env 77 | .env.test 78 | 79 | # parcel-bundler cache (https://parceljs.org/) 80 | .cache 81 | 82 | # Next.js build output 83 | .next 84 | 85 | # Nuxt.js build / generate output 86 | .nuxt 87 | dist 88 | 89 | # Gatsby files 90 | .cache/ 91 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 92 | # https://nextjs.org/blog/next-9-1#public-directory-support 93 | # public 94 | 95 | # vuepress build output 96 | .vuepress/dist 97 | 98 | # Serverless directories 99 | .serverless/ 100 | 101 | # FuseBox cache 102 | .fusebox/ 103 | 104 | # DynamoDB Local files 105 | .dynamodb/ 106 | 107 | # TernJS port file 108 | .tern-port 109 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lib/whisper.cpp"] 2 | path = lib/whisper.cpp 3 | url = https://github.com/ggerganov/whisper.cpp 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Ari 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # whisper-node 2 | 3 | [![npm downloads](https://img.shields.io/npm/dm/whisper-node)](https://npmjs.org/package/whisper-node) 4 | [![npm downloads](https://img.shields.io/npm/l/whisper-node)](https://npmjs.org/package/whisper-node) 5 | 6 | Node.js bindings for OpenAI's Whisper. Transcription done local. 7 | 8 | ## Features 9 | 10 | - Output transcripts to **JSON** (also .txt .srt .vtt) 11 | - **Optimized for CPU** (Including Apple Silicon ARM) 12 | - Timestamp precision to single word 13 | 14 | ## Installation 15 | 16 | 1. Add dependency to project 17 | 18 | ```text 19 | npm install whisper-node 20 | ``` 21 | 22 | 2. Download whisper model of choice [OPTIONAL] 23 | 24 | ```text 25 | npx whisper-node download 26 | ``` 27 | 28 | [Requirement for Windows: Install the ```make``` command from here.](https://gnuwin32.sourceforge.net/packages/make.htm) 29 | 30 | ## Usage 31 | 32 | ```javascript 33 | import whisper from 'whisper-node'; 34 | 35 | const transcript = await whisper("example/sample.wav"); 36 | 37 | console.log(transcript); // output: [ {start,end,speech} ] 38 | ``` 39 | 40 | ### Output (JSON) 41 | 42 | ```javascript 43 | [ 44 | { 45 | "start": "00:00:14.310", // time stamp begin 46 | "end": "00:00:16.480", // time stamp end 47 | "speech": "howdy" // transcription 48 | } 49 | ] 50 | ``` 51 | 52 | ### Full Options List 53 | 54 | ```javascript 55 | import whisper from 'whisper-node'; 56 | 57 | const filePath = "example/sample.wav"; // required 58 | 59 | const options = { 60 | modelName: "base.en", // default 61 | // modelPath: "/custom/path/to/model.bin", // use model in a custom directory (cannot use along with 'modelName') 62 | whisperOptions: { 63 | language: 'auto' // default (use 'auto' for auto detect) 64 | gen_file_txt: false, // outputs .txt file 65 | gen_file_subtitle: false, // outputs .srt file 66 | gen_file_vtt: false, // outputs .vtt file 67 | word_timestamps: true // timestamp for every word 68 | // timestamp_size: 0 // cannot use along with word_timestamps:true 69 | } 70 | } 71 | 72 | const transcript = await whisper(filePath, options); 73 | ``` 74 | 75 | ### Input File Format 76 | 77 | Files must be .wav and 16Hz 78 | 79 | Example .mp3 file converted with an [FFmpeg](https://ffmpeg.org) command: ```ffmpeg -i input.mp3 -ar 16000 output.wav``` 80 | 81 | ## Made with 82 | 83 | - [Whisper OpenAI (using C++ port by: ggerganov)](https://github.com/ggerganov/whisper.cpp) 84 | - [ShellJS](https://www.npmjs.com/package/shelljs) 85 | 86 | ## Roadmap 87 | 88 | - [x] Support projects not using Typescript 89 | - [x] Allow custom directory for storing models 90 | - [ ] Config files as alternative to model download cli 91 | - [ ] Remove *path*, *shelljs* and *prompt-sync* package for browser, react-native expo, and webassembly compatibility 92 | - [ ] [fluent-ffmpeg](https://www.npmjs.com/package/fluent-ffmpeg) to automatically convert to 16Hz .wav files as well as support separating audio from video 93 | - [ ] [Pyanote diarization](https://huggingface.co/pyannote/speaker-diarization) for speaker names 94 | - [ ] [Implement WhisperX as optional alternative model](https://github.com/m-bain/whisperX) for diarization and higher precision timestamps (as alternative to C++ version) 95 | - [ ] Add option for viewing detected langauge as described in [Issue 16](https://github.com/ariym/whisper-node/issues/16) 96 | - [ ] Include typescript typescript types in ```d.ts``` file 97 | - [x] Add support for language option 98 | - [ ] Add support for transcribing audio streams as already implemented in whisper.cpp 99 | 100 | ## Modifying whisper-node 101 | 102 | ```npm run dev``` - runs nodemon and tsc on '/src/test.ts' 103 | 104 | ```npm run build``` - runs tsc, outputs to '/dist' and gives sh permission to 'dist/download.js' 105 | 106 | ## Acknowledgements 107 | 108 | - [Georgi Gerganov](https://ggerganov.com/) 109 | - [Ari](https://aricv.com) 110 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "whisper-node", 3 | "version": "0.2.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "whisper-node", 9 | "version": "0.2.0", 10 | "hasInstallScript": true, 11 | "license": "MIT", 12 | "dependencies": { 13 | "readline-sync": "^1.4.10", 14 | "shelljs": "^0.8.5" 15 | }, 16 | "devDependencies": { 17 | "@types/node": "^18.11.13", 18 | "nodemon": "^2.0.20", 19 | "ts-node": "^10.9.1", 20 | "typescript": "^4.9.3" 21 | } 22 | }, 23 | "node_modules/@cspotcode/source-map-support": { 24 | "version": "0.8.1", 25 | "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", 26 | "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", 27 | "dev": true, 28 | "dependencies": { 29 | "@jridgewell/trace-mapping": "0.3.9" 30 | }, 31 | "engines": { 32 | "node": ">=12" 33 | } 34 | }, 35 | "node_modules/@jridgewell/resolve-uri": { 36 | "version": "3.1.0", 37 | "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", 38 | "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", 39 | "dev": true, 40 | "engines": { 41 | "node": ">=6.0.0" 42 | } 43 | }, 44 | "node_modules/@jridgewell/sourcemap-codec": { 45 | "version": "1.4.14", 46 | "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", 47 | "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", 48 | "dev": true 49 | }, 50 | "node_modules/@jridgewell/trace-mapping": { 51 | "version": "0.3.9", 52 | "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", 53 | "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", 54 | "dev": true, 55 | "dependencies": { 56 | "@jridgewell/resolve-uri": "^3.0.3", 57 | "@jridgewell/sourcemap-codec": "^1.4.10" 58 | } 59 | }, 60 | "node_modules/@tsconfig/node10": { 61 | "version": "1.0.9", 62 | "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", 63 | "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", 64 | "dev": true 65 | }, 66 | "node_modules/@tsconfig/node12": { 67 | "version": "1.0.11", 68 | "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", 69 | "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", 70 | "dev": true 71 | }, 72 | "node_modules/@tsconfig/node14": { 73 | "version": "1.0.3", 74 | "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", 75 | "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", 76 | "dev": true 77 | }, 78 | "node_modules/@tsconfig/node16": { 79 | "version": "1.0.3", 80 | "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz", 81 | "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==", 82 | "dev": true 83 | }, 84 | "node_modules/@types/node": { 85 | "version": "18.11.17", 86 | "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.17.tgz", 87 | "integrity": "sha512-HJSUJmni4BeDHhfzn6nF0sVmd1SMezP7/4F0Lq+aXzmp2xm9O7WXrUtHW/CHlYVtZUbByEvWidHqRtcJXGF2Ng==", 88 | "dev": true 89 | }, 90 | "node_modules/abbrev": { 91 | "version": "1.1.1", 92 | "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", 93 | "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", 94 | "dev": true 95 | }, 96 | "node_modules/acorn": { 97 | "version": "8.8.1", 98 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz", 99 | "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==", 100 | "dev": true, 101 | "bin": { 102 | "acorn": "bin/acorn" 103 | }, 104 | "engines": { 105 | "node": ">=0.4.0" 106 | } 107 | }, 108 | "node_modules/acorn-walk": { 109 | "version": "8.2.0", 110 | "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", 111 | "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", 112 | "dev": true, 113 | "engines": { 114 | "node": ">=0.4.0" 115 | } 116 | }, 117 | "node_modules/anymatch": { 118 | "version": "3.1.3", 119 | "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", 120 | "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", 121 | "dev": true, 122 | "dependencies": { 123 | "normalize-path": "^3.0.0", 124 | "picomatch": "^2.0.4" 125 | }, 126 | "engines": { 127 | "node": ">= 8" 128 | } 129 | }, 130 | "node_modules/arg": { 131 | "version": "4.1.3", 132 | "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", 133 | "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", 134 | "dev": true 135 | }, 136 | "node_modules/balanced-match": { 137 | "version": "1.0.2", 138 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 139 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" 140 | }, 141 | "node_modules/binary-extensions": { 142 | "version": "2.2.0", 143 | "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", 144 | "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", 145 | "dev": true, 146 | "engines": { 147 | "node": ">=8" 148 | } 149 | }, 150 | "node_modules/brace-expansion": { 151 | "version": "1.1.11", 152 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 153 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 154 | "dependencies": { 155 | "balanced-match": "^1.0.0", 156 | "concat-map": "0.0.1" 157 | } 158 | }, 159 | "node_modules/braces": { 160 | "version": "3.0.2", 161 | "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", 162 | "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", 163 | "dev": true, 164 | "dependencies": { 165 | "fill-range": "^7.0.1" 166 | }, 167 | "engines": { 168 | "node": ">=8" 169 | } 170 | }, 171 | "node_modules/chokidar": { 172 | "version": "3.5.3", 173 | "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", 174 | "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", 175 | "dev": true, 176 | "funding": [ 177 | { 178 | "type": "individual", 179 | "url": "https://paulmillr.com/funding/" 180 | } 181 | ], 182 | "dependencies": { 183 | "anymatch": "~3.1.2", 184 | "braces": "~3.0.2", 185 | "glob-parent": "~5.1.2", 186 | "is-binary-path": "~2.1.0", 187 | "is-glob": "~4.0.1", 188 | "normalize-path": "~3.0.0", 189 | "readdirp": "~3.6.0" 190 | }, 191 | "engines": { 192 | "node": ">= 8.10.0" 193 | }, 194 | "optionalDependencies": { 195 | "fsevents": "~2.3.2" 196 | } 197 | }, 198 | "node_modules/concat-map": { 199 | "version": "0.0.1", 200 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 201 | "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" 202 | }, 203 | "node_modules/create-require": { 204 | "version": "1.1.1", 205 | "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", 206 | "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", 207 | "dev": true 208 | }, 209 | "node_modules/debug": { 210 | "version": "3.2.7", 211 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", 212 | "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", 213 | "dev": true, 214 | "dependencies": { 215 | "ms": "^2.1.1" 216 | } 217 | }, 218 | "node_modules/diff": { 219 | "version": "4.0.2", 220 | "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", 221 | "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", 222 | "dev": true, 223 | "engines": { 224 | "node": ">=0.3.1" 225 | } 226 | }, 227 | "node_modules/fill-range": { 228 | "version": "7.0.1", 229 | "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", 230 | "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", 231 | "dev": true, 232 | "dependencies": { 233 | "to-regex-range": "^5.0.1" 234 | }, 235 | "engines": { 236 | "node": ">=8" 237 | } 238 | }, 239 | "node_modules/fs.realpath": { 240 | "version": "1.0.0", 241 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 242 | "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" 243 | }, 244 | "node_modules/fsevents": { 245 | "version": "2.3.2", 246 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", 247 | "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", 248 | "dev": true, 249 | "hasInstallScript": true, 250 | "optional": true, 251 | "os": [ 252 | "darwin" 253 | ], 254 | "engines": { 255 | "node": "^8.16.0 || ^10.6.0 || >=11.0.0" 256 | } 257 | }, 258 | "node_modules/function-bind": { 259 | "version": "1.1.1", 260 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", 261 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" 262 | }, 263 | "node_modules/glob": { 264 | "version": "7.2.3", 265 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", 266 | "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", 267 | "dependencies": { 268 | "fs.realpath": "^1.0.0", 269 | "inflight": "^1.0.4", 270 | "inherits": "2", 271 | "minimatch": "^3.1.1", 272 | "once": "^1.3.0", 273 | "path-is-absolute": "^1.0.0" 274 | }, 275 | "engines": { 276 | "node": "*" 277 | }, 278 | "funding": { 279 | "url": "https://github.com/sponsors/isaacs" 280 | } 281 | }, 282 | "node_modules/glob-parent": { 283 | "version": "5.1.2", 284 | "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", 285 | "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", 286 | "dev": true, 287 | "dependencies": { 288 | "is-glob": "^4.0.1" 289 | }, 290 | "engines": { 291 | "node": ">= 6" 292 | } 293 | }, 294 | "node_modules/has": { 295 | "version": "1.0.3", 296 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", 297 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", 298 | "dependencies": { 299 | "function-bind": "^1.1.1" 300 | }, 301 | "engines": { 302 | "node": ">= 0.4.0" 303 | } 304 | }, 305 | "node_modules/has-flag": { 306 | "version": "3.0.0", 307 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", 308 | "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", 309 | "dev": true, 310 | "engines": { 311 | "node": ">=4" 312 | } 313 | }, 314 | "node_modules/ignore-by-default": { 315 | "version": "1.0.1", 316 | "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", 317 | "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", 318 | "dev": true 319 | }, 320 | "node_modules/inflight": { 321 | "version": "1.0.6", 322 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 323 | "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", 324 | "dependencies": { 325 | "once": "^1.3.0", 326 | "wrappy": "1" 327 | } 328 | }, 329 | "node_modules/inherits": { 330 | "version": "2.0.4", 331 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 332 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 333 | }, 334 | "node_modules/interpret": { 335 | "version": "1.4.0", 336 | "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", 337 | "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", 338 | "engines": { 339 | "node": ">= 0.10" 340 | } 341 | }, 342 | "node_modules/is-binary-path": { 343 | "version": "2.1.0", 344 | "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", 345 | "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", 346 | "dev": true, 347 | "dependencies": { 348 | "binary-extensions": "^2.0.0" 349 | }, 350 | "engines": { 351 | "node": ">=8" 352 | } 353 | }, 354 | "node_modules/is-core-module": { 355 | "version": "2.11.0", 356 | "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", 357 | "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", 358 | "dependencies": { 359 | "has": "^1.0.3" 360 | }, 361 | "funding": { 362 | "url": "https://github.com/sponsors/ljharb" 363 | } 364 | }, 365 | "node_modules/is-extglob": { 366 | "version": "2.1.1", 367 | "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", 368 | "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", 369 | "dev": true, 370 | "engines": { 371 | "node": ">=0.10.0" 372 | } 373 | }, 374 | "node_modules/is-glob": { 375 | "version": "4.0.3", 376 | "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", 377 | "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", 378 | "dev": true, 379 | "dependencies": { 380 | "is-extglob": "^2.1.1" 381 | }, 382 | "engines": { 383 | "node": ">=0.10.0" 384 | } 385 | }, 386 | "node_modules/is-number": { 387 | "version": "7.0.0", 388 | "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", 389 | "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", 390 | "dev": true, 391 | "engines": { 392 | "node": ">=0.12.0" 393 | } 394 | }, 395 | "node_modules/make-error": { 396 | "version": "1.3.6", 397 | "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", 398 | "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", 399 | "dev": true 400 | }, 401 | "node_modules/minimatch": { 402 | "version": "3.1.2", 403 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", 404 | "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", 405 | "dependencies": { 406 | "brace-expansion": "^1.1.7" 407 | }, 408 | "engines": { 409 | "node": "*" 410 | } 411 | }, 412 | "node_modules/ms": { 413 | "version": "2.1.3", 414 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 415 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", 416 | "dev": true 417 | }, 418 | "node_modules/nodemon": { 419 | "version": "2.0.20", 420 | "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.20.tgz", 421 | "integrity": "sha512-Km2mWHKKY5GzRg6i1j5OxOHQtuvVsgskLfigG25yTtbyfRGn/GNvIbRyOf1PSCKJ2aT/58TiuUsuOU5UToVViw==", 422 | "dev": true, 423 | "dependencies": { 424 | "chokidar": "^3.5.2", 425 | "debug": "^3.2.7", 426 | "ignore-by-default": "^1.0.1", 427 | "minimatch": "^3.1.2", 428 | "pstree.remy": "^1.1.8", 429 | "semver": "^5.7.1", 430 | "simple-update-notifier": "^1.0.7", 431 | "supports-color": "^5.5.0", 432 | "touch": "^3.1.0", 433 | "undefsafe": "^2.0.5" 434 | }, 435 | "bin": { 436 | "nodemon": "bin/nodemon.js" 437 | }, 438 | "engines": { 439 | "node": ">=8.10.0" 440 | }, 441 | "funding": { 442 | "type": "opencollective", 443 | "url": "https://opencollective.com/nodemon" 444 | } 445 | }, 446 | "node_modules/nopt": { 447 | "version": "1.0.10", 448 | "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", 449 | "integrity": "sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==", 450 | "dev": true, 451 | "dependencies": { 452 | "abbrev": "1" 453 | }, 454 | "bin": { 455 | "nopt": "bin/nopt.js" 456 | }, 457 | "engines": { 458 | "node": "*" 459 | } 460 | }, 461 | "node_modules/normalize-path": { 462 | "version": "3.0.0", 463 | "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", 464 | "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", 465 | "dev": true, 466 | "engines": { 467 | "node": ">=0.10.0" 468 | } 469 | }, 470 | "node_modules/once": { 471 | "version": "1.4.0", 472 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 473 | "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", 474 | "dependencies": { 475 | "wrappy": "1" 476 | } 477 | }, 478 | "node_modules/path-is-absolute": { 479 | "version": "1.0.1", 480 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 481 | "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", 482 | "engines": { 483 | "node": ">=0.10.0" 484 | } 485 | }, 486 | "node_modules/path-parse": { 487 | "version": "1.0.7", 488 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", 489 | "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" 490 | }, 491 | "node_modules/picomatch": { 492 | "version": "2.3.1", 493 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", 494 | "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", 495 | "dev": true, 496 | "engines": { 497 | "node": ">=8.6" 498 | }, 499 | "funding": { 500 | "url": "https://github.com/sponsors/jonschlinkert" 501 | } 502 | }, 503 | "node_modules/pstree.remy": { 504 | "version": "1.1.8", 505 | "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", 506 | "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", 507 | "dev": true 508 | }, 509 | "node_modules/readdirp": { 510 | "version": "3.6.0", 511 | "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", 512 | "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", 513 | "dev": true, 514 | "dependencies": { 515 | "picomatch": "^2.2.1" 516 | }, 517 | "engines": { 518 | "node": ">=8.10.0" 519 | } 520 | }, 521 | "node_modules/readline-sync": { 522 | "version": "1.4.10", 523 | "resolved": "https://registry.npmjs.org/readline-sync/-/readline-sync-1.4.10.tgz", 524 | "integrity": "sha512-gNva8/6UAe8QYepIQH/jQ2qn91Qj0B9sYjMBBs3QOB8F2CXcKgLxQaJRP76sWVRQt+QU+8fAkCbCvjjMFu7Ycw==", 525 | "engines": { 526 | "node": ">= 0.8.0" 527 | } 528 | }, 529 | "node_modules/rechoir": { 530 | "version": "0.6.2", 531 | "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", 532 | "integrity": "sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==", 533 | "dependencies": { 534 | "resolve": "^1.1.6" 535 | }, 536 | "engines": { 537 | "node": ">= 0.10" 538 | } 539 | }, 540 | "node_modules/resolve": { 541 | "version": "1.22.1", 542 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", 543 | "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", 544 | "dependencies": { 545 | "is-core-module": "^2.9.0", 546 | "path-parse": "^1.0.7", 547 | "supports-preserve-symlinks-flag": "^1.0.0" 548 | }, 549 | "bin": { 550 | "resolve": "bin/resolve" 551 | }, 552 | "funding": { 553 | "url": "https://github.com/sponsors/ljharb" 554 | } 555 | }, 556 | "node_modules/semver": { 557 | "version": "5.7.1", 558 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", 559 | "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", 560 | "dev": true, 561 | "bin": { 562 | "semver": "bin/semver" 563 | } 564 | }, 565 | "node_modules/shelljs": { 566 | "version": "0.8.5", 567 | "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.5.tgz", 568 | "integrity": "sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==", 569 | "dependencies": { 570 | "glob": "^7.0.0", 571 | "interpret": "^1.0.0", 572 | "rechoir": "^0.6.2" 573 | }, 574 | "bin": { 575 | "shjs": "bin/shjs" 576 | }, 577 | "engines": { 578 | "node": ">=4" 579 | } 580 | }, 581 | "node_modules/simple-update-notifier": { 582 | "version": "1.1.0", 583 | "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-1.1.0.tgz", 584 | "integrity": "sha512-VpsrsJSUcJEseSbMHkrsrAVSdvVS5I96Qo1QAQ4FxQ9wXFcB+pjj7FB7/us9+GcgfW4ziHtYMc1J0PLczb55mg==", 585 | "dev": true, 586 | "dependencies": { 587 | "semver": "~7.0.0" 588 | }, 589 | "engines": { 590 | "node": ">=8.10.0" 591 | } 592 | }, 593 | "node_modules/simple-update-notifier/node_modules/semver": { 594 | "version": "7.0.0", 595 | "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", 596 | "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", 597 | "dev": true, 598 | "bin": { 599 | "semver": "bin/semver.js" 600 | } 601 | }, 602 | "node_modules/supports-color": { 603 | "version": "5.5.0", 604 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", 605 | "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", 606 | "dev": true, 607 | "dependencies": { 608 | "has-flag": "^3.0.0" 609 | }, 610 | "engines": { 611 | "node": ">=4" 612 | } 613 | }, 614 | "node_modules/supports-preserve-symlinks-flag": { 615 | "version": "1.0.0", 616 | "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", 617 | "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", 618 | "engines": { 619 | "node": ">= 0.4" 620 | }, 621 | "funding": { 622 | "url": "https://github.com/sponsors/ljharb" 623 | } 624 | }, 625 | "node_modules/to-regex-range": { 626 | "version": "5.0.1", 627 | "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", 628 | "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", 629 | "dev": true, 630 | "dependencies": { 631 | "is-number": "^7.0.0" 632 | }, 633 | "engines": { 634 | "node": ">=8.0" 635 | } 636 | }, 637 | "node_modules/touch": { 638 | "version": "3.1.0", 639 | "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", 640 | "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==", 641 | "dev": true, 642 | "dependencies": { 643 | "nopt": "~1.0.10" 644 | }, 645 | "bin": { 646 | "nodetouch": "bin/nodetouch.js" 647 | } 648 | }, 649 | "node_modules/ts-node": { 650 | "version": "10.9.1", 651 | "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", 652 | "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", 653 | "dev": true, 654 | "dependencies": { 655 | "@cspotcode/source-map-support": "^0.8.0", 656 | "@tsconfig/node10": "^1.0.7", 657 | "@tsconfig/node12": "^1.0.7", 658 | "@tsconfig/node14": "^1.0.0", 659 | "@tsconfig/node16": "^1.0.2", 660 | "acorn": "^8.4.1", 661 | "acorn-walk": "^8.1.1", 662 | "arg": "^4.1.0", 663 | "create-require": "^1.1.0", 664 | "diff": "^4.0.1", 665 | "make-error": "^1.1.1", 666 | "v8-compile-cache-lib": "^3.0.1", 667 | "yn": "3.1.1" 668 | }, 669 | "bin": { 670 | "ts-node": "dist/bin.js", 671 | "ts-node-cwd": "dist/bin-cwd.js", 672 | "ts-node-esm": "dist/bin-esm.js", 673 | "ts-node-script": "dist/bin-script.js", 674 | "ts-node-transpile-only": "dist/bin-transpile.js", 675 | "ts-script": "dist/bin-script-deprecated.js" 676 | }, 677 | "peerDependencies": { 678 | "@swc/core": ">=1.2.50", 679 | "@swc/wasm": ">=1.2.50", 680 | "@types/node": "*", 681 | "typescript": ">=2.7" 682 | }, 683 | "peerDependenciesMeta": { 684 | "@swc/core": { 685 | "optional": true 686 | }, 687 | "@swc/wasm": { 688 | "optional": true 689 | } 690 | } 691 | }, 692 | "node_modules/typescript": { 693 | "version": "4.9.4", 694 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.4.tgz", 695 | "integrity": "sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==", 696 | "dev": true, 697 | "bin": { 698 | "tsc": "bin/tsc", 699 | "tsserver": "bin/tsserver" 700 | }, 701 | "engines": { 702 | "node": ">=4.2.0" 703 | } 704 | }, 705 | "node_modules/undefsafe": { 706 | "version": "2.0.5", 707 | "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", 708 | "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", 709 | "dev": true 710 | }, 711 | "node_modules/v8-compile-cache-lib": { 712 | "version": "3.0.1", 713 | "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", 714 | "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", 715 | "dev": true 716 | }, 717 | "node_modules/wrappy": { 718 | "version": "1.0.2", 719 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 720 | "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" 721 | }, 722 | "node_modules/yn": { 723 | "version": "3.1.1", 724 | "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", 725 | "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", 726 | "dev": true, 727 | "engines": { 728 | "node": ">=6" 729 | } 730 | } 731 | } 732 | } 733 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "whisper-node", 3 | "version": "1.1.2", 4 | "description": "Local audio transcription on CPU. Node.js bindings for OpenAI's Whisper.", 5 | "homepage": "https://npmjs.com/whisper-node", 6 | "author": "ariym", 7 | "main": "dist/index.js", 8 | "bin": { 9 | "download": "dist/download.js" 10 | }, 11 | "scripts": { 12 | "start": "echo Starting with: npm run dev... && npm run dev", 13 | "dev": "NODE_ENV='development' nodemon --watch './**/*.ts' --exec 'ts-node' src/test.ts", 14 | "build": "npx tsc && chmod +x dist/download.js", 15 | "test": "node dist/test.js" 16 | }, 17 | "devDependencies": { 18 | "@types/node": "^18.11.13", 19 | "nodemon": "^2.0.20", 20 | "ts-node": "^10.9.1", 21 | "typescript": "^4.9.3" 22 | }, 23 | "dependencies": { 24 | "readline-sync": "^1.4.10", 25 | "shelljs": "^0.8.5" 26 | }, 27 | "repository": { 28 | "type": "git", 29 | "url": "https://github.com/ariym/whisper-node.git" 30 | }, 31 | "license": "MIT", 32 | "keywords": [ 33 | "OpenAI", 34 | "Whisper", 35 | "CPP", 36 | "C++", 37 | "Bindings", 38 | "Transcribe", 39 | "Transcriber", 40 | "Transcript", 41 | "Transcription", 42 | "Audio", 43 | "Speech", 44 | "Speech-to-Text", 45 | "STT", 46 | "TTS", 47 | "SRT", 48 | "Diarization", 49 | "local" 50 | ] 51 | } 52 | -------------------------------------------------------------------------------- /src/constants.ts: -------------------------------------------------------------------------------- 1 | export const DEFAULT_MODEL = "base.en"; 2 | export const NODE_MODULES_MODELS_PATH = "node_modules/whisper-node/lib/whisper.cpp/models"; -------------------------------------------------------------------------------- /src/download.ts: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env node 2 | 3 | // Javascript layer for using the whisper.cpp built-in model downloader scripts 4 | // 5 | // npx whisper-node download 6 | 7 | import shell from 'shelljs'; 8 | 9 | import readlineSync from 'readline-sync'; 10 | 11 | import {DEFAULT_MODEL, NODE_MODULES_MODELS_PATH} from './constants' 12 | 13 | const MODELS_LIST = [ 14 | "tiny", 15 | "tiny.en", 16 | "base", 17 | "base.en", 18 | "small", 19 | "small.en", 20 | "medium", 21 | "medium.en", 22 | "large-v1", 23 | "large" 24 | ]; 25 | 26 | 27 | const askModel = async () => { 28 | const answer = await readlineSync.question(`\n[whisper-node] Enter model name (e.g. 'base.en') or 'cancel' to exit\n(ENTER for base.en): `) 29 | 30 | if (answer === "cancel") { 31 | console.log("[whisper-node] Exiting model downloader. Run again with: 'npx whisper-node download'"); 32 | process.exit(0); 33 | } 34 | // user presses enter 35 | else if (answer === "") { 36 | console.log("[whisper-node] Going with", DEFAULT_MODEL); 37 | return DEFAULT_MODEL; 38 | } 39 | else if (!MODELS_LIST.includes(answer)) { 40 | console.log("\n[whisper-node] FAIL: Name not found. Check your spelling OR quit wizard and use custom model."); 41 | 42 | // re-ask question 43 | return await askModel(); 44 | } 45 | 46 | return answer; 47 | } 48 | 49 | 50 | 51 | export default async function downloadModel() { 52 | try { 53 | // shell.exec("echo $PWD"); 54 | shell.cd(NODE_MODULES_MODELS_PATH); 55 | 56 | console.log(` 57 | | Model | Disk | RAM | 58 | |-----------|--------|---------| 59 | | tiny | 75 MB | ~390 MB | 60 | | tiny.en | 75 MB | ~390 MB | 61 | | base | 142 MB | ~500 MB | 62 | | base.en | 142 MB | ~500 MB | 63 | | small | 466 MB | ~1.0 GB | 64 | | small.en | 466 MB | ~1.0 GB | 65 | | medium | 1.5 GB | ~2.6 GB | 66 | | medium.en | 1.5 GB | ~2.6 GB | 67 | | large-v1 | 2.9 GB | ~4.7 GB | 68 | | large | 2.9 GB | ~4.7 GB | 69 | `); 70 | 71 | // ensure running in correct path 72 | if (!shell.which("./download-ggml-model.sh")) { 73 | throw "whisper-node downloader is not being run from the correct path! cd to project root and run again." 74 | } 75 | 76 | const modelName = await askModel(); 77 | 78 | // default is .sh 79 | let scriptPath = "./download-ggml-model.sh" 80 | // windows .cmd version 81 | if(process.platform === 'win32') scriptPath = "download-ggml-model.cmd"; 82 | 83 | shell.exec(`${scriptPath} ${modelName}`); 84 | 85 | // TODO: add check in case download-ggml-model doesn't return a successful download. 86 | // to prevent continuing to compile; that makes it harder for user to see which script failed. 87 | 88 | console.log("[whisper-node] Attempting to compile model..."); 89 | 90 | // move up directory, run make in whisper.cpp 91 | shell.cd("../") 92 | // this has to run in whichever directory the model is located in?? 93 | shell.exec("make"); 94 | 95 | process.exit(0); 96 | } catch (error) { 97 | console.log("ERROR Caught in downloadModel") 98 | console.log(error); 99 | return error; 100 | } 101 | } 102 | 103 | // runs after being called in package.json 104 | downloadModel(); 105 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import path from 'path' 2 | import shell, {IShellOptions} from './shell'; 3 | import { createCppCommand, IFlagTypes } from './whisper'; 4 | import transcriptToArray, { ITranscriptLine } from './tsToArray'; 5 | 6 | 7 | interface IOptions { 8 | modelName?: string, // name of model stored in node_modules/whisper-node/lib/whisper.cpp/models 9 | modelPath?: string, // custom path for model 10 | whisperOptions?: IFlagTypes 11 | shellOptions?: IShellOptions 12 | } 13 | 14 | // returns array[]: {start, end, speech} 15 | export const whisper = async (filePath: string, options?: IOptions): Promise => { 16 | 17 | try { 18 | console.log("[whisper-node] Transcribing:", filePath, "\n"); 19 | 20 | // todo: combine steps 1 & 2 into sepparate function called whisperCpp (createCppCommand + shell) 21 | 22 | // 1. create command string for whisper.cpp 23 | const command = createCppCommand({ 24 | filePath: path.normalize(`"${filePath}"`), 25 | modelName: options?.modelName, 26 | modelPath: options?.modelPath ? `"${options?.modelPath}"` : undefined, 27 | options: options?.whisperOptions 28 | }) 29 | 30 | // 2. run command in whisper.cpp directory 31 | // todo: add return for continually updated progress value 32 | const transcript = await shell(command, options?.shellOptions); 33 | 34 | // 3. parse whisper response string into array 35 | const transcriptArray = transcriptToArray(transcript); 36 | 37 | return transcriptArray; 38 | 39 | } catch (error) { 40 | console.log("[whisper-node] Problem:", error); 41 | } 42 | }; 43 | 44 | export default whisper; 45 | -------------------------------------------------------------------------------- /src/shell.ts: -------------------------------------------------------------------------------- 1 | import path from "path"; 2 | import shell from 'shelljs'; 3 | 4 | // docs: https://github.com/ggerganov/whisper.cpp 5 | const WHISPER_CPP_PATH = path.join(__dirname, '..', 'lib/whisper.cpp'); 6 | const WHISPER_CPP_MAIN_PATH = "./main"; 7 | 8 | export interface IShellOptions { 9 | silent: boolean, // true: won't print to console 10 | async: boolean 11 | } 12 | 13 | // default passed to shelljs exec 14 | const defaultShellOptions = { 15 | silent: true, // true: won't print to console 16 | async: false 17 | } 18 | 19 | 20 | // return shelljs process 21 | export default async function whisperShell 22 | (command: string, options: IShellOptions = defaultShellOptions): Promise { 23 | 24 | return new Promise(async (resolve, reject) => { 25 | try { 26 | // docs: https://github.com/shelljs/shelljs#execcommand--options--callback 27 | shell.exec( 28 | command, 29 | options, 30 | (code: number, stdout: string, stderr: string) => { 31 | 32 | if (code === 0) resolve(stdout); 33 | else reject(stderr); 34 | 35 | } 36 | ) 37 | } catch (error) { 38 | reject(error) 39 | } 40 | }); 41 | 42 | } 43 | 44 | try { 45 | 46 | // shell.cd(__dirname + WHISPER_CPP_PATH); 47 | shell.cd(WHISPER_CPP_PATH); 48 | 49 | // ensure command exists in local path 50 | if (!shell.which(WHISPER_CPP_MAIN_PATH)) { 51 | shell.echo("[whisper-node] Problem. whisper.cpp not initialized. Current shelljs directory: ", __dirname); 52 | shell.echo("[whisper-node] Attempting to run 'make' command in /whisper directory..."); 53 | 54 | // todo: move this 55 | shell.exec("make", defaultShellOptions); 56 | 57 | if (!shell.which(WHISPER_CPP_MAIN_PATH)) { 58 | console.log("[whisper-node] Problem. 'make' command failed. Please run 'make' command in /whisper directory. Current shelljs directory: ", __dirname); 59 | process.exit(1); 60 | } 61 | else console.log("[whisper-node] 'make' command successful. Current shelljs directory: ", __dirname); 62 | 63 | } 64 | } catch (error) { 65 | console.log("error caught in try catch block") 66 | throw error; 67 | } -------------------------------------------------------------------------------- /src/test.ts: -------------------------------------------------------------------------------- 1 | import {whisper} from './index'; 2 | (async function run() { 3 | try { 4 | 5 | 6 | const transcript = await whisper( 7 | // "/Users/Shared/twospeak_clip.wav", 8 | "/Users/Shared/mando.wav", 9 | { 10 | // modelPath: "/Users/Shared/custom-models/ggml-base.en.bin", 11 | modelName: "base", 12 | whisperOptions: { 13 | language: 'auto', 14 | word_timestamps: false, 15 | timestamp_size: 1 16 | } 17 | } 18 | ); 19 | 20 | console.log("transcript", transcript); 21 | 22 | // prefer when word_timestamps=true 23 | // console.table(transcript) 24 | 25 | console.log(transcript.length, "rows."); 26 | 27 | 28 | } catch (error) { 29 | console.log("ERROR", error); 30 | } 31 | })() -------------------------------------------------------------------------------- /src/tsToArray.ts: -------------------------------------------------------------------------------- 1 | export type ITranscriptLine = { 2 | start: string; 3 | end: string; 4 | speech: string; 5 | } 6 | 7 | export default function parseTranscript(vtt: string): ITranscriptLine[] { 8 | // 1. separate lines by matching the format like "[00:03:04.000 --> 00:03:13.000] XXXXXX" 9 | const lines: string[] = vtt.match(/\[[0-9:.]+\s-->\s[0-9:.]+\].*/g); 10 | 11 | // 2. remove the first line, which is empty 12 | lines.shift(); 13 | 14 | // 3. convert each line into an object 15 | return lines.map(line => { 16 | // 3a. split ts from speech 17 | let [timestamp, speech] = line.split('] '); // two spaces (3 spaces doesn't work with punctuation like period . ) 18 | 19 | // 3b. remove the open bracket of timestamp 20 | timestamp = timestamp.substring(1); 21 | 22 | // 3c. split timestamp into begin and end 23 | const [start, end] = timestamp.split(' --> '); 24 | 25 | // 3d. remove \n from speech with regex 26 | speech = speech.replace(/\n/g, ''); 27 | 28 | // 3e. remove beginning space 29 | speech = speech.replace(' ', ''); 30 | 31 | return { start, end, speech }; 32 | }); 33 | } -------------------------------------------------------------------------------- /src/whisper.ts: -------------------------------------------------------------------------------- 1 | // todo: remove all imports from file 2 | import { existsSync } from 'fs'; 3 | import { DEFAULT_MODEL } from './constants'; 4 | 5 | // return as syntax for whisper.cpp command 6 | export const createCppCommand = ({ filePath, modelName = null, modelPath = null, options = null }: CppCommandTypes) => 7 | 8 | `./main ${getFlags(options)} -m ${modelPathOrName(modelName, modelPath)} -f ${filePath}`; 9 | 10 | 11 | const modelPathOrName = (mn: string, mp: string) => { 12 | if (mn && mp) throw "Submit a modelName OR a modelPath. NOT BOTH!" 13 | else if (!mn && !mp) { 14 | console.log("[whisper-node] No 'modelName' or 'modelPath' provided. Trying default model:", DEFAULT_MODEL,"\n"); 15 | 16 | // second modelname check to verify is installed in directory 17 | const modelPath = `./models/${MODELS_LIST[DEFAULT_MODEL]}` 18 | 19 | if (!existsSync(modelPath)) { 20 | // throw `'${mn}' not downloaded! Run 'npx whisper-node download'`; 21 | throw `'${DEFAULT_MODEL}' not downloaded! Run 'npx whisper-node download'\n`; 22 | } 23 | 24 | return modelPath; 25 | } 26 | // modelpath 27 | else if (mp) return mp; 28 | // modelname 29 | else if (MODELS_LIST[mn]) { 30 | // second modelname check to verify is installed in directory 31 | const modelPath = `./models/${MODELS_LIST[mn]}` 32 | 33 | if (!existsSync(modelPath)) { 34 | throw `'${mn}' not found! Run 'npx whisper-node download'`; 35 | } 36 | 37 | return modelPath; 38 | } 39 | else if (mn) throw `modelName "${mn}" not found in list of models. Check your spelling OR use a custom modelPath.` 40 | else throw `modelName OR modelPath required! You submitted modelName: '${mn}', modelPath: '${mp}'` 41 | } 42 | 43 | 44 | // option flags list: https://github.com/ggerganov/whisper.cpp/blob/master/README.md?plain=1#L91 45 | // TODO: Replace with for loop that rejects all unrecognized keys 46 | const getFlags = (flags: IFlagTypes): string => { 47 | let s = ""; 48 | 49 | // output files 50 | if (flags.gen_file_txt) s += " -otxt"; 51 | if (flags.gen_file_subtitle) s += " -osrt"; 52 | if (flags.gen_file_vtt) s += " -ovtt"; 53 | // timestamps 54 | if (flags.timestamp_size && flags.word_timestamps) throw "Invalid option pair. Use 'timestamp_size' OR 'word_timestamps'. NOT BOTH!" 55 | if(flags.word_timestamps) s += " -ml 1"; // shorthand for timestamp_size:1 56 | if(flags.timestamp_size) s += " -ml " + String(flags.timestamp_size); 57 | // input language 58 | if(flags.language) s += " -l " + flags.language; 59 | 60 | return s; 61 | } 62 | 63 | 64 | // model list: https://github.com/ggerganov/whisper.cpp/#more-audio-samples 65 | export const MODELS_LIST = { 66 | "tiny": "ggml-tiny.bin", 67 | "tiny.en": "ggml-tiny.en.bin", 68 | "base": "ggml-base.bin", 69 | "base.en": "ggml-base.en.bin", 70 | "small": "ggml-small.bin", 71 | "small.en": "ggml-small.en.bin", 72 | "medium": "ggml-medium.bin", 73 | "medium.en": "ggml-medium.en.bin", 74 | "large-v1": "ggml-large-v1.bin", 75 | "large": "ggml-large.bin" 76 | } 77 | 78 | 79 | type CppCommandTypes = { 80 | filePath: string, 81 | modelName?: string, 82 | modelPath?: string, 83 | options?: IFlagTypes 84 | } 85 | 86 | 87 | export type IFlagTypes = { 88 | "gen_file_txt"?: boolean, 89 | "gen_file_subtitle"?: boolean, 90 | "gen_file_vtt"?: boolean, 91 | "timestamp_size"?: number, 92 | "word_timestamps"?: boolean, 93 | "language"?: string 94 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "esModuleInterop": true, 5 | "target": "es6", 6 | "moduleResolution": "node", 7 | "sourceMap": true, 8 | "outDir": "dist" 9 | }, 10 | "lib": ["es2015"] 11 | } --------------------------------------------------------------------------------