echogarden 0.12.2 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (334) hide show
  1. package/README.md +15 -14
  2. package/data/schemas/options.json +398 -111
  3. package/dist/alignment/DTWMfccSequenceAlignment.d.ts +1 -1
  4. package/dist/alignment/DTWMfccSequenceAlignment.js +8 -8
  5. package/dist/alignment/DTWSequenceAlignment.d.ts +1 -1
  6. package/dist/alignment/DTWSequenceAlignment.js +1 -1
  7. package/dist/alignment/DTWSequenceAlignmentWindowed.d.ts +1 -1
  8. package/dist/alignment/DTWSequenceAlignmentWindowed.js +2 -2
  9. package/dist/alignment/LevenshteinSequenceAlignment.d.ts +1 -1
  10. package/dist/alignment/LevenshteinSequenceAlignment.js +1 -1
  11. package/dist/alignment/SpeechAlignment.d.ts +9 -10
  12. package/dist/alignment/SpeechAlignment.js +136 -105
  13. package/dist/alignment/SpeechAlignment.js.map +1 -1
  14. package/dist/api/API.d.ts +13 -12
  15. package/dist/api/API.js +14 -13
  16. package/dist/api/API.js.map +1 -1
  17. package/dist/api/APIOptions.d.ts +5 -4
  18. package/dist/api/Alignment.d.ts +15 -9
  19. package/dist/api/Alignment.js +88 -74
  20. package/dist/api/Alignment.js.map +1 -1
  21. package/dist/api/Common.js +1 -1
  22. package/dist/api/Denoising.d.ts +6 -6
  23. package/dist/api/Denoising.js +23 -23
  24. package/dist/api/Denoising.js.map +1 -1
  25. package/dist/api/LanguageDetection.d.ts +19 -12
  26. package/dist/api/LanguageDetection.js +88 -38
  27. package/dist/api/LanguageDetection.js.map +1 -1
  28. package/dist/api/Recognition.d.ts +16 -6
  29. package/dist/api/Recognition.js +129 -55
  30. package/dist/api/Recognition.js.map +1 -1
  31. package/dist/api/SourceSeparation.d.ts +17 -0
  32. package/dist/api/SourceSeparation.js +61 -0
  33. package/dist/api/SourceSeparation.js.map +1 -0
  34. package/dist/api/Synthesis.d.ts +18 -18
  35. package/dist/api/Synthesis.js +191 -164
  36. package/dist/api/Synthesis.js.map +1 -1
  37. package/dist/api/Translation.d.ts +19 -8
  38. package/dist/api/Translation.js +132 -35
  39. package/dist/api/Translation.js.map +1 -1
  40. package/dist/api/Vad.d.ts +10 -5
  41. package/dist/api/Vad.js +76 -38
  42. package/dist/api/Vad.js.map +1 -1
  43. package/dist/audio/AudioBufferConversion.d.ts +1 -1
  44. package/dist/audio/AudioBufferConversion.js +4 -4
  45. package/dist/audio/AudioPlayer.d.ts +1 -1
  46. package/dist/audio/AudioPlayer.js +26 -26
  47. package/dist/audio/AudioPlayer.js.map +1 -1
  48. package/dist/audio/AudioRecorder.d.ts +1 -1
  49. package/dist/audio/AudioRecorder.js +5 -5
  50. package/dist/audio/AudioUtilities.d.ts +13 -9
  51. package/dist/audio/AudioUtilities.js +86 -24
  52. package/dist/audio/AudioUtilities.js.map +1 -1
  53. package/dist/cli/CLI.d.ts +3 -3
  54. package/dist/cli/CLI.js +271 -162
  55. package/dist/cli/CLI.js.map +1 -1
  56. package/dist/cli/CLIConfigFile.js +8 -8
  57. package/dist/cli/CLILauncher.js +6 -6
  58. package/dist/cli/CLIOptionsSchema.js +2 -2
  59. package/dist/cli/CLIParser.js +5 -5
  60. package/dist/cli/CLIStarter.js +4 -4
  61. package/dist/codecs/FFMpegTranscoder.d.ts +2 -2
  62. package/dist/codecs/FFMpegTranscoder.js +37 -37
  63. package/dist/codecs/FFMpegTranscoder.js.map +1 -1
  64. package/dist/codecs/TIMITCodec.js +5 -5
  65. package/dist/codecs/WaveCodec.d.ts +1 -1
  66. package/dist/codecs/WaveCodec.js +22 -22
  67. package/dist/denoising/RNNoise.d.ts +1 -1
  68. package/dist/denoising/RNNoise.js +9 -9
  69. package/dist/dsp/BiquadFilter.d.ts +3 -2
  70. package/dist/dsp/BiquadFilter.js +18 -11
  71. package/dist/dsp/BiquadFilter.js.map +1 -1
  72. package/dist/dsp/DecayingPeakEstimator.d.ts +16 -0
  73. package/dist/dsp/DecayingPeakEstimator.js +23 -0
  74. package/dist/dsp/DecayingPeakEstimator.js.map +1 -0
  75. package/dist/dsp/FFT.d.ts +8 -4
  76. package/dist/dsp/FFT.js +76 -30
  77. package/dist/dsp/FFT.js.map +1 -1
  78. package/dist/dsp/KWeightingFilter.d.ts +9 -0
  79. package/dist/dsp/KWeightingFilter.js +40 -0
  80. package/dist/dsp/KWeightingFilter.js.map +1 -0
  81. package/dist/dsp/LoudnessEstimator.d.ts +21 -0
  82. package/dist/dsp/LoudnessEstimator.js +47 -0
  83. package/dist/dsp/LoudnessEstimator.js.map +1 -0
  84. package/dist/dsp/MFCC.d.ts +2 -2
  85. package/dist/dsp/MFCC.js +15 -15
  86. package/dist/dsp/MelSpectogram.d.ts +1 -1
  87. package/dist/dsp/MelSpectogram.js +6 -6
  88. package/dist/dsp/Rubberband.d.ts +11 -11
  89. package/dist/dsp/Rubberband.js +27 -27
  90. package/dist/dsp/Sonic.d.ts +1 -1
  91. package/dist/dsp/Sonic.js +3 -3
  92. package/dist/dsp/SpeexResampler.d.ts +1 -1
  93. package/dist/dsp/SpeexResampler.js +2 -2
  94. package/dist/math/VectorMath.d.ts +12 -8
  95. package/dist/math/VectorMath.js +35 -32
  96. package/dist/math/VectorMath.js.map +1 -1
  97. package/dist/nlp/ChineseSegmentation.js +2 -2
  98. package/dist/nlp/CompromiseNLP.js +3 -3
  99. package/dist/nlp/EspeakPhonemizer.js +30 -30
  100. package/dist/nlp/IPA.js +20 -20
  101. package/dist/nlp/JapaneseSegmentation.js +6 -6
  102. package/dist/nlp/Lexicon.d.ts +1 -1
  103. package/dist/nlp/Lexicon.js +7 -7
  104. package/dist/nlp/Segmentation.d.ts +3 -0
  105. package/dist/nlp/Segmentation.js +21 -14
  106. package/dist/nlp/Segmentation.js.map +1 -1
  107. package/dist/nlp/TextNormalizer.js +16 -16
  108. package/dist/recognition/AmazonTranscribeSTT.d.ts +2 -2
  109. package/dist/recognition/AmazonTranscribeSTT.js +13 -14
  110. package/dist/recognition/AmazonTranscribeSTT.js.map +1 -1
  111. package/dist/recognition/AzureCognitiveServicesSTT.js +5 -6
  112. package/dist/recognition/AzureCognitiveServicesSTT.js.map +1 -1
  113. package/dist/recognition/GoogleCloudSTT.d.ts +3 -3
  114. package/dist/recognition/GoogleCloudSTT.js +18 -18
  115. package/dist/recognition/OpenAICloudSTT.d.ts +19 -0
  116. package/dist/recognition/OpenAICloudSTT.js +81 -0
  117. package/dist/recognition/OpenAICloudSTT.js.map +1 -0
  118. package/dist/recognition/SileroSTT.d.ts +2 -2
  119. package/dist/recognition/SileroSTT.js +25 -25
  120. package/dist/recognition/VoskSTT.d.ts +2 -2
  121. package/dist/recognition/VoskSTT.js +8 -8
  122. package/dist/recognition/WhisperCppSTT.d.ts +88 -0
  123. package/dist/recognition/WhisperCppSTT.js +332 -0
  124. package/dist/recognition/WhisperCppSTT.js.map +1 -0
  125. package/dist/recognition/WhisperSTT.d.ts +49 -25
  126. package/dist/recognition/WhisperSTT.js +626 -481
  127. package/dist/recognition/WhisperSTT.js.map +1 -1
  128. package/dist/server/Client.d.ts +1 -1
  129. package/dist/server/Client.js +22 -22
  130. package/dist/server/Server.js +9 -9
  131. package/dist/server/Server.js.map +1 -1
  132. package/dist/server/Worker.d.ts +22 -22
  133. package/dist/server/Worker.js +36 -36
  134. package/dist/server/Worker.js.map +1 -1
  135. package/dist/server/WorkerStarter.js +2 -2
  136. package/dist/source-separation/MDXNetSourceSeparation.d.ts +11 -0
  137. package/dist/source-separation/MDXNetSourceSeparation.js +161 -0
  138. package/dist/source-separation/MDXNetSourceSeparation.js.map +1 -0
  139. package/dist/speech-language-detection/SileroLanguageDetection.d.ts +1 -1
  140. package/dist/speech-language-detection/SileroLanguageDetection.js +7 -7
  141. package/dist/subtitles/Subtitles.d.ts +10 -0
  142. package/dist/subtitles/Subtitles.js +2 -2
  143. package/dist/subtitles/Subtitles.js.map +1 -1
  144. package/dist/synthesis/AwsPollyTTS.d.ts +1 -1
  145. package/dist/synthesis/AwsPollyTTS.js +12 -12
  146. package/dist/synthesis/AzureCognitiveServicesTTS.js +7 -7
  147. package/dist/synthesis/CoquiServerTTS.js +10 -10
  148. package/dist/synthesis/CoquiServerTTS.js.map +1 -1
  149. package/dist/synthesis/ElevenlabsTTS.d.ts +23 -0
  150. package/dist/synthesis/ElevenlabsTTS.js +103 -0
  151. package/dist/synthesis/ElevenlabsTTS.js.map +1 -0
  152. package/dist/synthesis/EspeakTTS.d.ts +6 -5
  153. package/dist/synthesis/EspeakTTS.js +81 -69
  154. package/dist/synthesis/EspeakTTS.js.map +1 -1
  155. package/dist/synthesis/FliteTTS.d.ts +3 -3
  156. package/dist/synthesis/FliteTTS.js +154 -154
  157. package/dist/synthesis/FliteTTS.js.map +1 -1
  158. package/dist/synthesis/GoogleCloudTTS.d.ts +3 -3
  159. package/dist/synthesis/GoogleCloudTTS.js +17 -17
  160. package/dist/synthesis/GoogleCloudTTS.js.map +1 -1
  161. package/dist/synthesis/GoogleTranslateTTS.d.ts +1 -1
  162. package/dist/synthesis/GoogleTranslateTTS.js +103 -103
  163. package/dist/synthesis/MicrosoftEdgeTTS.d.ts +2 -2
  164. package/dist/synthesis/MicrosoftEdgeTTS.js +74 -74
  165. package/dist/synthesis/OpenAICloudTTS.d.ts +13 -0
  166. package/dist/synthesis/OpenAICloudTTS.js +169 -0
  167. package/dist/synthesis/OpenAICloudTTS.js.map +1 -0
  168. package/dist/synthesis/SamTTS.js +3 -3
  169. package/dist/synthesis/SapiTTS.d.ts +3 -3
  170. package/dist/synthesis/SapiTTS.js +26 -26
  171. package/dist/synthesis/StreamlabsPollyTTS.d.ts +2 -2
  172. package/dist/synthesis/StreamlabsPollyTTS.js +27 -27
  173. package/dist/synthesis/SvoxPicoTTS.d.ts +2 -2
  174. package/dist/synthesis/SvoxPicoTTS.js +65 -65
  175. package/dist/synthesis/SvoxPicoTTS.js.map +1 -1
  176. package/dist/synthesis/VitsTTS.d.ts +3 -3
  177. package/dist/synthesis/VitsTTS.js +378 -378
  178. package/dist/synthesis/VitsTTS.js.map +1 -1
  179. package/dist/tests/Test.js +2 -2
  180. package/dist/utilities/Compression.d.ts +5 -0
  181. package/dist/utilities/Compression.js +29 -13
  182. package/dist/utilities/Compression.js.map +1 -1
  183. package/dist/utilities/FileDownloader.d.ts +1 -1
  184. package/dist/utilities/FileDownloader.js +16 -16
  185. package/dist/utilities/FileSystem.js +7 -7
  186. package/dist/utilities/Locale.d.ts +7 -7
  187. package/dist/utilities/Locale.js +15 -15
  188. package/dist/utilities/Logger.js +3 -3
  189. package/dist/utilities/ObjectUtilities.js +19 -19
  190. package/dist/utilities/OpenPromise.js +2 -2
  191. package/dist/utilities/OpenPromise.js.map +1 -1
  192. package/dist/utilities/PackageManager.js +31 -0
  193. package/dist/utilities/PackageManager.js.map +1 -1
  194. package/dist/utilities/PathUtilities.js +8 -8
  195. package/dist/utilities/RandomGenerator.js +2 -2
  196. package/dist/utilities/SmoothEstimator.d.ts +8 -0
  197. package/dist/utilities/SmoothEstimator.js +25 -0
  198. package/dist/utilities/SmoothEstimator.js.map +1 -0
  199. package/dist/utilities/TarballMaker.js +8 -8
  200. package/dist/utilities/Timeline.d.ts +3 -2
  201. package/dist/utilities/Timeline.js +11 -11
  202. package/dist/utilities/Timeline.js.map +1 -1
  203. package/dist/utilities/Timer.js +4 -4
  204. package/dist/utilities/Utilities.d.ts +4 -0
  205. package/dist/utilities/Utilities.js +38 -15
  206. package/dist/utilities/Utilities.js.map +1 -1
  207. package/dist/utilities/WasmMemoryManager.js +7 -7
  208. package/dist/utilities/WebReader.js +23 -23
  209. package/dist/utilities/WikipediaReader.js +2 -2
  210. package/dist/voice-activity-detection/AdaptiveGateVAD.d.ts +28 -0
  211. package/dist/voice-activity-detection/AdaptiveGateVAD.js +138 -0
  212. package/dist/voice-activity-detection/AdaptiveGateVAD.js.map +1 -0
  213. package/dist/voice-activity-detection/SileroVAD.d.ts +1 -1
  214. package/dist/voice-activity-detection/SileroVAD.js +5 -5
  215. package/dist/voice-activity-detection/SileroVAD.js.map +1 -1
  216. package/dist/voice-activity-detection/WebRtcVAD.d.ts +1 -1
  217. package/dist/voice-activity-detection/WebRtcVAD.js +4 -4
  218. package/docs/API.md +29 -11
  219. package/docs/CLI.md +31 -7
  220. package/docs/Contributing.md +38 -0
  221. package/docs/Development.md +93 -19
  222. package/docs/Engines.md +28 -16
  223. package/docs/Licenses.md +4 -1
  224. package/docs/Options.md +158 -78
  225. package/docs/Releases.md +262 -0
  226. package/docs/Server.md +7 -7
  227. package/docs/Tasklist.md +95 -76
  228. package/docs/Technical.md +4 -4
  229. package/package.json +13 -14
  230. package/src/alignment/DTWMfccSequenceAlignment.ts +9 -9
  231. package/src/alignment/DTWSequenceAlignment.ts +2 -2
  232. package/src/alignment/DTWSequenceAlignmentWindowed.ts +3 -3
  233. package/src/alignment/LevenshteinSequenceAlignment.ts +2 -2
  234. package/src/alignment/SpeechAlignment.ts +204 -119
  235. package/src/api/API.ts +14 -13
  236. package/src/api/APIOptions.ts +12 -11
  237. package/src/api/Alignment.ts +147 -90
  238. package/src/api/Common.ts +1 -1
  239. package/src/api/Denoising.ts +28 -28
  240. package/src/api/LanguageDetection.ts +135 -48
  241. package/src/api/Recognition.ts +198 -59
  242. package/src/api/SourceSeparation.ts +99 -0
  243. package/src/api/Synthesis.ts +217 -181
  244. package/src/api/Translation.ts +193 -40
  245. package/src/api/Vad.ts +110 -41
  246. package/src/audio/AudioBufferConversion.ts +4 -4
  247. package/src/audio/AudioPlayer.ts +27 -27
  248. package/src/audio/AudioRecorder.ts +5 -5
  249. package/src/audio/AudioUtilities.ts +107 -24
  250. package/src/cli/CLI.ts +313 -164
  251. package/src/cli/CLIConfigFile.ts +8 -8
  252. package/src/cli/CLILauncher.ts +6 -6
  253. package/src/cli/CLIOptionsSchema.ts +2 -2
  254. package/src/cli/CLIParser.ts +5 -5
  255. package/src/cli/CLIStarter.ts +4 -4
  256. package/src/codecs/FFMpegTranscoder.ts +38 -38
  257. package/src/codecs/TIMITCodec.ts +5 -5
  258. package/src/codecs/WaveCodec.ts +22 -22
  259. package/src/denoising/RNNoise.ts +9 -9
  260. package/src/dsp/BiquadFilter.ts +19 -11
  261. package/src/dsp/DecayingPeakEstimator.ts +35 -0
  262. package/src/dsp/FFT.ts +103 -35
  263. package/src/dsp/KWeightingFilter.ts +43 -0
  264. package/src/dsp/LoudnessEstimator.ts +74 -0
  265. package/src/dsp/MFCC.ts +15 -15
  266. package/src/dsp/MelSpectogram.ts +7 -7
  267. package/src/dsp/Rubberband.ts +38 -38
  268. package/src/dsp/Sonic.ts +4 -4
  269. package/src/dsp/SpeexResampler.ts +2 -2
  270. package/src/math/VectorMath.ts +42 -33
  271. package/src/nlp/ChineseSegmentation.ts +3 -3
  272. package/src/nlp/CompromiseNLP.ts +3 -3
  273. package/src/nlp/EspeakPhonemizer.ts +30 -30
  274. package/src/nlp/IPA.ts +20 -20
  275. package/src/nlp/JapaneseSegmentation.ts +6 -6
  276. package/src/nlp/Lexicon.ts +8 -8
  277. package/src/nlp/Segmentation.ts +23 -14
  278. package/src/nlp/TextNormalizer.ts +16 -16
  279. package/src/recognition/AmazonTranscribeSTT.ts +16 -17
  280. package/src/recognition/AzureCognitiveServicesSTT.ts +8 -6
  281. package/src/recognition/GoogleCloudSTT.ts +21 -21
  282. package/src/recognition/OpenAICloudSTT.ts +142 -0
  283. package/src/recognition/SileroSTT.ts +26 -26
  284. package/src/recognition/VoskSTT.ts +10 -10
  285. package/src/recognition/WhisperCppSTT.ts +555 -0
  286. package/src/recognition/WhisperSTT.ts +760 -507
  287. package/src/server/Client.ts +23 -23
  288. package/src/server/Server.ts +9 -9
  289. package/src/server/Worker.ts +53 -53
  290. package/src/server/WorkerStarter.ts +2 -2
  291. package/src/source-separation/MDXNetSourceSeparation.ts +228 -0
  292. package/src/speech-language-detection/SileroLanguageDetection.ts +8 -8
  293. package/src/subtitles/Subtitles.ts +3 -3
  294. package/src/synthesis/AwsPollyTTS.ts +14 -14
  295. package/src/synthesis/AzureCognitiveServicesTTS.ts +10 -10
  296. package/src/synthesis/CoquiServerTTS.ts +10 -10
  297. package/src/synthesis/ElevenlabsTTS.ts +137 -0
  298. package/src/synthesis/EspeakTTS.ts +90 -71
  299. package/src/synthesis/FliteTTS.ts +157 -157
  300. package/src/synthesis/GoogleCloudTTS.ts +19 -19
  301. package/src/synthesis/GoogleTranslateTTS.ts +104 -104
  302. package/src/synthesis/MicrosoftEdgeTTS.ts +80 -80
  303. package/src/synthesis/OpenAICloudTTS.ts +196 -0
  304. package/src/synthesis/SamTTS.ts +3 -3
  305. package/src/synthesis/SapiTTS.ts +29 -29
  306. package/src/synthesis/StreamlabsPollyTTS.ts +29 -29
  307. package/src/synthesis/SvoxPicoTTS.ts +67 -67
  308. package/src/synthesis/VitsTTS.ts +380 -380
  309. package/src/tests/Test.ts +4 -4
  310. package/src/utilities/Compression.ts +34 -13
  311. package/src/utilities/FileDownloader.ts +19 -19
  312. package/src/utilities/FileSystem.ts +7 -7
  313. package/src/utilities/Locale.ts +22 -22
  314. package/src/utilities/Logger.ts +4 -4
  315. package/src/utilities/ObjectUtilities.ts +19 -19
  316. package/src/utilities/OpenPromise.ts +2 -2
  317. package/src/utilities/PackageManager.ts +40 -0
  318. package/src/utilities/PathUtilities.ts +8 -8
  319. package/src/utilities/RandomGenerator.ts +3 -3
  320. package/src/utilities/SmoothEstimator.ts +35 -0
  321. package/src/utilities/TarballMaker.ts +9 -9
  322. package/src/utilities/Timeline.ts +15 -13
  323. package/src/utilities/Timer.ts +4 -4
  324. package/src/utilities/Utilities.ts +49 -15
  325. package/src/utilities/WasmMemoryManager.ts +7 -7
  326. package/src/utilities/WebReader.ts +23 -23
  327. package/src/utilities/WikipediaReader.ts +2 -2
  328. package/src/voice-activity-detection/AdaptiveGateVAD.ts +202 -0
  329. package/src/voice-activity-detection/SileroVAD.ts +5 -5
  330. package/src/voice-activity-detection/WebRtcVAD.ts +5 -5
  331. package/dist/synthesis/ElevenLabsTTS.d.ts +0 -8
  332. package/dist/synthesis/ElevenLabsTTS.js +0 -82
  333. package/dist/synthesis/ElevenLabsTTS.js.map +0 -1
  334. package/src/synthesis/ElevenLabsTTS.ts +0 -104
@@ -1,60 +1,69 @@
1
1
  import Onnx from 'onnxruntime-node';
2
2
  import { Logger } from '../utilities/Logger.js';
3
- import { computeMelSpectogramUsingFilterbanks } from "../dsp/MelSpectogram.js";
4
- import { clip, splitFloat32Array, writeToStderr, yieldToEventLoop } from '../utilities/Utilities.js';
3
+ import { computeMelSpectogramUsingFilterbanks } from '../dsp/MelSpectogram.js';
4
+ import { clip, getIntegerRange, getRepetitionScoreRelativeToFirstSubstring, splitFloat32Array, yieldToEventLoop } from '../utilities/Utilities.js';
5
5
  import { indexOfMax, logOfVector, logSumExp, meanOfVector, medianFilter, softmax, stdDeviationOfVector } from '../math/VectorMath.js';
6
- import { splitToWords } from '../nlp/Segmentation.js';
7
6
  import { alignDTWWindowed } from '../alignment/DTWSequenceAlignmentWindowed.js';
8
- import { deepClone, extendDeep } from '../utilities/ObjectUtilities.js';
7
+ import { extendDeep } from '../utilities/ObjectUtilities.js';
9
8
  import { getRawAudioDuration } from '../audio/AudioUtilities.js';
10
- import { readAndParseJsonFile, readFile } from '../utilities/FileSystem.js';
9
+ import { readFile } from '../utilities/FileSystem.js';
11
10
  import path from 'path';
12
11
  import { getShortLanguageCode, languageCodeToName } from '../utilities/Locale.js';
13
12
  import { loadPackage } from '../utilities/PackageManager.js';
14
13
  import chalk from 'chalk';
15
14
  import { XorShift32RNG } from '../utilities/RandomGenerator.js';
16
15
  import { detectSpeechLanguageByParts } from '../api/LanguageDetection.js';
17
- export async function recognize(sourceRawAudio, modelName, modelDir, tokenizerDir, task, sourceLanguage, options) {
16
+ export async function recognize(sourceRawAudio, modelName, modelDir, task, sourceLanguage, options) {
18
17
  if (sourceRawAudio.sampleRate != 16000) {
19
- throw new Error("Source audio must have a sampling rate of 16000");
18
+ throw new Error('Source audio must have a sampling rate of 16000');
20
19
  }
21
20
  sourceLanguage = getShortLanguageCode(sourceLanguage);
22
21
  if (!(sourceLanguage in languageIdLookup)) {
23
22
  throw new Error(`The language ${languageCodeToName(sourceLanguage)} is not supported by the Whisper engine.`);
24
23
  }
25
- if (!isMultiligualModel(modelName) && sourceLanguage != 'en') {
24
+ if (isEnglishOnlyModel(modelName) && sourceLanguage != 'en') {
26
25
  throw new Error(`The model '${modelName}' can only be used with English inputs. However, the given source language was ${languageCodeToName(sourceLanguage)}.`);
27
26
  }
28
- const whisper = new Whisper(modelName, modelDir, tokenizerDir);
29
- await whisper.initialize();
27
+ if (options.temperature && options.temperature < 0) {
28
+ throw new Error(`Temperature can't be negative`);
29
+ }
30
+ let seed = options.seed;
31
+ if (seed) {
32
+ seed = Math.max(Math.floor(seed), 1) | 0;
33
+ }
34
+ const whisper = new Whisper(modelName, modelDir, seed);
30
35
  const result = await whisper.recognize(sourceRawAudio, task, sourceLanguage, options);
31
36
  return result;
32
37
  }
33
- export async function align(sourceRawAudio, referenceText, modelName, modelDir, tokenizerDir, sourceLanguage) {
38
+ export async function align(sourceRawAudio, referenceText, modelName, modelDir, sourceLanguage) {
34
39
  if (sourceRawAudio.sampleRate != 16000) {
35
- throw new Error("Source audio must have a sampling rate of 16000");
40
+ throw new Error('Source audio must have a sampling rate of 16000');
36
41
  }
37
42
  sourceLanguage = getShortLanguageCode(sourceLanguage);
38
43
  if (!(sourceLanguage in languageIdLookup)) {
39
44
  throw new Error(`The language ${languageCodeToName(sourceLanguage)} is not supported by the Whisper engine.`);
40
45
  }
41
- if (!isMultiligualModel(modelName) && sourceLanguage != 'en') {
46
+ if (isEnglishOnlyModel(modelName) && sourceLanguage != 'en') {
42
47
  throw new Error(`The model '${modelName}' can only be used with English inputs. However, the given source language was ${languageCodeToName(sourceLanguage)}.`);
43
48
  }
44
- const whisper = new Whisper(modelName, modelDir, tokenizerDir);
45
- await whisper.initialize();
49
+ const whisper = new Whisper(modelName, modelDir);
46
50
  const timeline = await whisper.align(sourceRawAudio, referenceText, sourceLanguage);
47
51
  return timeline;
48
52
  }
49
- export async function detectLanguage(sourceRawAudio, modelName, modelDir, tokenizerDir) {
53
+ export async function detectLanguage(sourceRawAudio, modelName, modelDir, temperature) {
50
54
  if (sourceRawAudio.sampleRate != 16000) {
51
- throw new Error("Source audio must have a sampling rate of 16000");
55
+ throw new Error('Source audio must have a sampling rate of 16000');
56
+ }
57
+ if (!isMultilingualModel(modelName)) {
58
+ throw new Error(`Language detection is only supported with multilingual models.`);
59
+ }
60
+ if (temperature < 0) {
61
+ throw new Error(`Temperature cannot be negative`);
52
62
  }
53
- const whisper = new Whisper(modelName, modelDir, tokenizerDir);
54
- await whisper.initialize();
63
+ const whisper = new Whisper(modelName, modelDir);
55
64
  async function detectLanguageForPart(partAudio) {
56
65
  const audioFeatures = await whisper.encodeAudio(partAudio);
57
- const partResults = await whisper.detectLanguage(audioFeatures);
66
+ const partResults = await whisper.detectLanguage(audioFeatures, temperature);
58
67
  return partResults;
59
68
  }
60
69
  const results = await detectSpeechLanguageByParts(sourceRawAudio, detectLanguageForPart);
@@ -64,193 +73,244 @@ export async function detectLanguage(sourceRawAudio, modelName, modelDir, tokeni
64
73
  export class Whisper {
65
74
  modelName;
66
75
  modelDir;
67
- tokenizerDir;
68
76
  isMultiligualModel;
69
77
  audioEncoder;
70
78
  textDecoder;
71
- textToTokenLookup = new Map();
72
- tokenToTextLookup = new Map();
73
- merges = [];
79
+ tiktoken;
74
80
  onnxOptions = {
75
81
  logSeverityLevel: 2,
76
82
  executionProviders: ['cpu']
77
83
  };
78
84
  tokenConfig;
79
- randomGen = new XorShift32RNG(23948203);
80
- constructor(modelName, modelDir, tokenizerDir) {
81
- this.modelDir = modelDir;
85
+ randomGen;
86
+ constructor(modelName, modelDir, rngSeed = 461845907) {
82
87
  this.modelName = modelName;
83
- this.tokenizerDir = tokenizerDir;
84
- this.isMultiligualModel = isMultiligualModel(this.modelName);
88
+ this.modelDir = modelDir;
89
+ this.isMultiligualModel = isMultilingualModel(this.modelName);
85
90
  if (this.isMultiligualModel) {
86
91
  this.tokenConfig = {
87
- sotToken: 50258,
88
- sotPrevToken: 50361,
89
- eotToken: 50257,
90
- noSpeechToken: 50362,
92
+ endOfTextToken: 50257,
93
+ startOfTextToken: 50258,
94
+ languageTokensStart: 50259,
95
+ languageTokensEnd: 50358,
96
+ translateTaskToken: 50358,
97
+ transcribeTaskToken: 50359,
98
+ startOfPromptToken: 50361,
99
+ nonSpeechToken: 50362,
91
100
  noTimestampsToken: 50363,
92
101
  timestampTokensStart: 50364,
93
- suppressedTokens: [1, 2, 6, 7, 8, 9, 10, 12, 14, 25, 26, 27, 28, 29, 31, 58, 59, 60, 61, 62, 63, 90, 91, 92, 93, 359, 503, 522, 542, 873, 893, 902, 918, 922, 931, 1350, 1853, 1982, 2460, 2627, 3246, 3253, 3268, 3536, 3846, 3961, 4183, 4667, 6585, 6647, 7273, 9061, 9383, 10428, 10929, 11938, 12033, 12331, 12562, 13793, 14157, 14635, 15265, 15618, 16553, 16604, 18362, 18956, 20075, 21675, 22520, 26130, 26161, 26435, 28279, 29464, 31650, 32302, 32470, 36865, 42863, 47425, 49870, 50254, 50258, 50360, 50361, 50362]
102
+ timestampTokensEnd: 50364 + 1501,
103
+ suppressedTokens: new Set()
94
104
  };
105
+ const suppressedTextTokens = [1, 2, 6, 7, 8, 9, 10, 12, 14, 25, 26, 27, 28, 29, 31, 58, 59, 60, 61, 62, 63, 90, 91, 92, 93, 359, 503, 522, 542, 873, 893, 902, 918, 922, 931, 1350, 1853, 1982, 2460, 2627, 3246, 3253, 3268, 3536, 3846, 3961, 4183, 4667, 6585, 6647, 7273, 9061, 9383, 10428, 10929, 11938, 12033, 12331, 12562, 13793, 14157, 14635, 15265, 15618, 16553, 16604, 18362, 18956, 20075, 21675, 22520, 26130, 26161, 26435, 28279, 29464, 31650, 32302, 32470, 36865, 42863, 47425, 49870, 50254];
106
+ const suppressedMetadataTokens = [50256, ...getIntegerRange(50258, 50364)];
107
+ const suppressedTokens = [...suppressedTextTokens, ...suppressedMetadataTokens];
108
+ this.tokenConfig.suppressedTokens = new Set(suppressedTokens);
95
109
  }
96
110
  else {
97
111
  this.tokenConfig = {
98
- sotToken: 50257,
99
- sotPrevToken: 50360,
100
- eotToken: 50256,
101
- noSpeechToken: 50361,
112
+ endOfTextToken: 50256,
113
+ startOfTextToken: 50257,
114
+ languageTokensStart: 50258,
115
+ languageTokensEnd: 50358,
116
+ translateTaskToken: 50358,
117
+ transcribeTaskToken: 50359,
118
+ startOfPromptToken: 50360,
119
+ nonSpeechToken: 50361,
102
120
  noTimestampsToken: 50362,
103
121
  timestampTokensStart: 50363,
104
- suppressedTokens: [1, 2, 7, 8, 9, 10, 14, 25, 26, 27, 28, 29, 31, 58, 59, 60, 61, 62, 63, 90, 91, 92, 93, 357, 366, 438, 532, 685, 705, 796, 930, 1058, 1220, 1267, 1279, 1303, 1343, 1377, 1391, 1635, 1782, 1875, 2162, 2361, 2488, 3467, 4008, 4211, 4600, 4808, 5299, 5855, 6329, 7203, 9609, 9959, 10563, 10786, 11420, 11709, 11907, 13163, 13697, 13700, 14808, 15306, 16410, 16791, 17992, 19203, 19510, 20724, 22305, 22935, 27007, 30109, 30420, 33409, 34949, 40283, 40493, 40549, 47282, 49146, 50257, 50359, 50360, 50361]
122
+ timestampTokensEnd: 50363 + 1501,
123
+ suppressedTokens: new Set()
105
124
  };
125
+ const suppressedTextTokens = [1, 2, 7, 8, 9, 10, 14, 25, 26, 27, 28, 29, 31, 58, 59, 60, 61, 62, 63, 90, 91, 92, 93, 357, 366, 438, 532, 685, 705, 796, 930, 1058, 1220, 1267, 1279, 1303, 1343, 1377, 1391, 1635, 1782, 1875, 2162, 2361, 2488, 3467, 4008, 4211, 4600, 4808, 5299, 5855, 6329, 7203, 9609, 9959, 10563, 10786, 11420, 11709, 11907, 13163, 13697, 13700, 14808, 15306, 16410, 16791, 17992, 19203, 19510, 20724, 22305, 22935, 27007, 30109, 30420, 33409, 34949, 40283, 40493, 40549, 47282, 49146];
126
+ const suppressedMetadataTokens = [...getIntegerRange(50257, 50363)];
127
+ const suppressedTokens = [...suppressedTextTokens, ...suppressedMetadataTokens];
128
+ this.tokenConfig.suppressedTokens = new Set(suppressedTokens);
106
129
  }
130
+ this.randomGen = new XorShift32RNG(rngSeed);
131
+ }
132
+ async initializeIfNeeded() {
133
+ await this.initializeTokenizerIfNeeded();
134
+ await this.initializeEncoderSessionIfNeeded();
135
+ await this.initializeDecoderSessionIfNeeded();
107
136
  }
108
- async initialize() {
137
+ async initializeTokenizerIfNeeded() {
138
+ if (this.tiktoken) {
139
+ return;
140
+ }
109
141
  const logger = new Logger();
110
- await logger.startAsync("Load tokenizer data");
111
- const encoderFilePath = path.join(this.modelDir, "encoder.onnx");
112
- const decoderFilePath = path.join(this.modelDir, "decoder.onnx");
113
- const vocabFilePath = path.join(this.tokenizerDir, "vocab.json");
114
- const mergesFilePath = path.join(this.tokenizerDir, "merges.txt");
115
- const vocabObject = await readAndParseJsonFile(vocabFilePath);
116
- function bpeEncodedStrToString(str) {
117
- const decodedChars = [];
118
- for (const char of str) {
119
- const decodedChar = vocabCharacterSetLookup[char];
120
- if (decodedChar == undefined) {
121
- throw new Error(`Invalid char: '${char}'`);
122
- }
123
- decodedChars.push(decodedChar);
124
- }
125
- return Buffer.from(decodedChars).toString("utf-8");
142
+ await logger.startAsync('Load tokenizer data');
143
+ const tiktokenModulePackagePath = await loadPackage('whisper-tiktoken-data');
144
+ const tiktokenDataFilePath = path.join(tiktokenModulePackagePath, this.isMultiligualModel ? 'multilingual.tiktoken' : 'gpt2.tiktoken');
145
+ let tiktokenData = await readFile(tiktokenDataFilePath, { encoding: 'utf8' });
146
+ const tokenConfig = this.tokenConfig;
147
+ const metadataTokens = {
148
+ [tokenConfig.endOfTextToken]: '[EndOfText]',
149
+ [tokenConfig.startOfTextToken]: '[StartOfText]',
150
+ [tokenConfig.translateTaskToken]: '[TranslateTask]',
151
+ [tokenConfig.transcribeTaskToken]: '[TranscribeTask]',
152
+ [tokenConfig.startOfPromptToken]: '[StartOfPrompt]',
153
+ [tokenConfig.nonSpeechToken]: '[NonSpeech]',
154
+ [tokenConfig.noTimestampsToken]: '[NoTimestamps]',
155
+ };
156
+ if (this.isMultiligualModel) {
157
+ metadataTokens[50256] = '[Unused_50256]';
158
+ metadataTokens[50360] = '[Unused_50360]';
126
159
  }
127
- for (const key in vocabObject) {
128
- const value = vocabObject[key];
129
- const decodedKey = bpeEncodedStrToString(key);
130
- this.textToTokenLookup.set(decodedKey, value);
131
- this.tokenToTextLookup.set(value, decodedKey);
160
+ const languageTokenCount = tokenConfig.languageTokensEnd - tokenConfig.languageTokensStart;
161
+ for (let i = 0; i < languageTokenCount; i++) {
162
+ const tokenIndex = this.tokenConfig.languageTokensStart + i;
163
+ metadataTokens[tokenIndex] = `[Language_${i}]`;
132
164
  }
133
- const mergesFileRawLines = (await readFile(mergesFilePath, "utf8")).trim().split(/\r?\n/g);
134
- const mergesFileRawEntries = mergesFileRawLines.map(line => line.trim().split(" "));
135
- this.merges = mergesFileRawEntries.map(entry => [bpeEncodedStrToString(entry[0]), bpeEncodedStrToString(entry[1])]);
136
- await logger.startAsync(`Create ONNX inference session for model '${this.modelName}'`);
165
+ const timestampTokensCount = 1501;
166
+ for (let i = 0; i < timestampTokensCount; i++) {
167
+ const tokenIndex = this.tokenConfig.timestampTokensStart + i;
168
+ const tokenTime = this.timestampTokenToSeconds(tokenIndex);
169
+ metadataTokens[tokenIndex] = `[Timestamp_${tokenTime.toFixed(2)}]`;
170
+ }
171
+ const inverseMetadataTokensLookup = {};
172
+ for (const [key, value] of Object.entries(metadataTokens)) {
173
+ inverseMetadataTokensLookup[value] = parseInt(key);
174
+ }
175
+ const patternString = `'s|'t|'re|'ve|'m|'ll|'d| ?\\p{L}+| ?\\p{N}+| ?[^\\s\\p{L}\\p{N}]+|\\s+(?!\\S)|\\s+`;
176
+ const { Tiktoken } = await import('tiktoken/lite');
177
+ this.tiktoken = new Tiktoken(tiktokenData, inverseMetadataTokensLookup, patternString);
178
+ logger.end();
179
+ }
180
+ async initializeEncoderSessionIfNeeded() {
181
+ if (this.audioEncoder) {
182
+ return;
183
+ }
184
+ const logger = new Logger();
185
+ await logger.startAsync(`Create encoder model inference session for model '${this.modelName}'`);
186
+ const encoderFilePath = path.join(this.modelDir, 'encoder.onnx');
137
187
  this.audioEncoder = await Onnx.InferenceSession.create(encoderFilePath, this.onnxOptions);
188
+ logger.end();
189
+ }
190
+ async initializeDecoderSessionIfNeeded() {
191
+ if (this.textDecoder) {
192
+ return;
193
+ }
194
+ const logger = new Logger();
195
+ await logger.startAsync(`Create decoder model inference session for model '${this.modelName}'`);
196
+ const decoderFilePath = path.join(this.modelDir, 'decoder.onnx');
138
197
  this.textDecoder = await Onnx.InferenceSession.create(decoderFilePath, this.onnxOptions);
139
198
  logger.end();
140
199
  }
141
200
  async recognize(rawAudio, task, language, options) {
201
+ await this.initializeIfNeeded();
142
202
  const logger = new Logger();
143
- const timestampTokensStart = this.tokenConfig.timestampTokensStart;
144
203
  const audioSamples = rawAudio.audioChannels[0];
145
204
  const sampleRate = rawAudio.sampleRate;
146
- const audioDuration = getRawAudioDuration(rawAudio);
147
205
  const prompt = options.prompt;
148
- const maxAudioSamples = sampleRate * 30;
149
- let previousPartTokens = [];
206
+ const maxAudioSamplesPerPart = sampleRate * 30;
207
+ const decodeTimestampTokens = options.decodeTimestampTokens;
208
+ let previousPartTextTokens = [];
150
209
  let timeline = [];
151
210
  let allDecodedTokens = [];
152
211
  for (let audioOffset = 0; audioOffset < audioSamples.length;) {
153
212
  const segmentStartTime = audioOffset / sampleRate;
154
213
  await logger.startAsync(`\nPrepare audio part at time position ${segmentStartTime.toFixed(2)}`, undefined, chalk.magentaBright);
155
- const audioPartSamples = audioSamples.slice(audioOffset, audioOffset + maxAudioSamples);
214
+ const audioPartSamples = audioSamples.slice(audioOffset, audioOffset + maxAudioSamplesPerPart);
156
215
  const audioPartRawAudio = { audioChannels: [audioPartSamples], sampleRate };
157
216
  const audioPartDuration = getRawAudioDuration(audioPartRawAudio);
158
217
  logger.end();
159
218
  const audioPartFeatures = await this.encodeAudio(audioPartRawAudio);
160
- const isFirstPart = audioOffset == 0;
161
- const isFinalPart = audioOffset + maxAudioSamples > audioSamples.length;
219
+ const isFirstPart = audioOffset === 0;
220
+ const isFinalPart = audioOffset + maxAudioSamplesPerPart >= audioSamples.length;
162
221
  let initialTokens = [];
163
222
  if (isFirstPart && prompt) {
164
- const promptTokens = await this.textToTokens(prompt, language);
165
- initialTokens = [this.tokenConfig.sotPrevToken, ...promptTokens];
223
+ const promptTokens = this.textToTokens(prompt);
224
+ initialTokens = [this.tokenConfig.startOfPromptToken, ...promptTokens];
166
225
  }
167
- else if (options.autoPromptParts && previousPartTokens.length > 0) {
168
- initialTokens = [this.tokenConfig.sotPrevToken, ...previousPartTokens];
226
+ else if (options.autoPromptParts && previousPartTextTokens.length > 0) {
227
+ initialTokens = [this.tokenConfig.startOfPromptToken, ...previousPartTextTokens];
169
228
  }
170
- initialTokens = [...initialTokens, ...this.getInitialTokens(language, task)];
229
+ initialTokens = [...initialTokens, ...this.getTextStartTokens(language, task, !decodeTimestampTokens)];
171
230
  logger.end();
172
231
  let { decodedTokens: partTokens, crossAttentionQKs: partCrossAttentionQKs, decodedTokensConfidence } = await this.decodeTokens(audioPartFeatures, initialTokens, audioPartDuration, isFirstPart, isFinalPart, options);
173
232
  const lastToken = partTokens[partTokens.length - 1];
174
- const lastTokenIsTimestamp = lastToken >= timestampTokensStart;
233
+ const lastTokenIsTimestamp = this.isTimestampToken(lastToken);
175
234
  let audioEndOffset;
176
235
  if (!isFinalPart && lastTokenIsTimestamp) {
177
- const timePosition = (lastToken - timestampTokensStart) * 0.02;
236
+ const timePosition = this.timestampTokenToSeconds(lastToken);
178
237
  audioEndOffset = audioOffset + Math.floor(timePosition * sampleRate);
179
238
  }
180
239
  else {
181
- audioEndOffset = Math.min(audioOffset + maxAudioSamples, audioSamples.length);
240
+ audioEndOffset = Math.min(audioOffset + maxAudioSamplesPerPart, audioSamples.length);
182
241
  }
183
242
  const segmentEndTime = audioEndOffset / sampleRate;
184
- const segmentFrameCount = Math.floor((segmentEndTime - segmentStartTime) / 0.02);
243
+ const segmentFrameCount = this.secondsRangeToFrameCount(segmentStartTime, segmentEndTime);
185
244
  await logger.startAsync(`Extract timeline for part`);
186
245
  if (partTokens.length != partCrossAttentionQKs.length) {
187
- throw new Error("Unexpected: partTokens.length != partCrossAttentionQKs.length");
246
+ throw new Error('Unexpected: partTokens.length != partCrossAttentionQKs.length');
188
247
  }
189
- //partTokens = partTokens.filter(token => token < timestampTokensStart)
190
- //partCrossAttentionQKs = await this.inferCrossAttentionQKs(partTokens, audioPartFeatures)
191
248
  partTokens = partTokens.slice(initialTokens.length);
249
+ //const compressionRatioForPart = (await getDeflateCompressionMetricsForString(this.tokensToText(partTokens))).ratio
192
250
  partCrossAttentionQKs = partCrossAttentionQKs.slice(initialTokens.length);
193
- //await this.addWordsToTimeline(timeline, partTokens, audioPartRawAudio, partCrossAttentionQKs, initialAudioTimeOffset, audioPartSamples.length / sampleRate)
194
251
  const alignmentPath = await this.findAlignmentPathFromQKs(partCrossAttentionQKs, partTokens, 0, segmentFrameCount); //, alignmentHeadsIndexes[this.modelName])
195
- const partTimeline = await this.getWordTimelineFromAlignmentPath(alignmentPath, partTokens, segmentStartTime, segmentEndTime, decodedTokensConfidence);
196
- timeline.push(...partTimeline);
252
+ const partTimeline = await this.getTokenTimelineFromAlignmentPath(alignmentPath, partTokens, segmentStartTime, segmentEndTime, decodedTokensConfidence);
197
253
  audioOffset = audioEndOffset;
198
- previousPartTokens = partTokens.filter(token => token < this.tokenConfig.eotToken);
199
- allDecodedTokens.push(...previousPartTokens);
254
+ allDecodedTokens.push(...partTokens);
255
+ timeline.push(...partTimeline);
256
+ previousPartTextTokens = partTokens.filter(token => this.isTextToken(token));
200
257
  logger.end();
201
258
  }
202
- if (timeline.length > 0) {
203
- timeline[timeline.length - 1].endTime = audioDuration;
204
- }
205
- timeline = this.mergeSuccessiveWordFragmentsInTimeline(timeline);
206
- timeline.forEach(entry => { entry.text = entry.text.trim(); });
207
- const transcript = this.tokensToText(allDecodedTokens);
259
+ timeline = this.tokenTimelineToWordTimeline(timeline);
260
+ const transcript = this.tokensToText(allDecodedTokens).trim();
208
261
  logger.end();
209
262
  return { transcript, timeline };
210
263
  }
211
264
  async align(rawAudio, referenceText, language) {
265
+ await this.initializeIfNeeded();
212
266
  const logger = new Logger();
213
- await logger.startAsync("Prepare for alignment");
267
+ await logger.startAsync('Prepare for alignment');
268
+ referenceText = referenceText.replaceAll(/\s+/g, ' ');
214
269
  const audioDuration = Math.min(getRawAudioDuration(rawAudio), 30);
215
- const audioFrameCount = Math.floor(audioDuration / 0.02);
216
- const initialTokens = this.getInitialTokens(language, "transcribe", true);
217
- const timestampTokensStart = this.tokenConfig.timestampTokensStart;
218
- const eotToken = this.tokenConfig.eotToken;
219
- let tokens = [...initialTokens, ...await this.textToTokens(referenceText, language), eotToken];
270
+ const audioFrameCount = this.secondsToFrame(audioDuration);
271
+ const initialTokens = this.getTextStartTokens(language, 'transcribe', true);
272
+ const endOfTextToken = this.tokenConfig.endOfTextToken;
273
+ let tokens = [...initialTokens, ...this.textToTokens(referenceText), endOfTextToken];
220
274
  logger.end();
221
275
  const audioFeatures = await this.encodeAudio(rawAudio);
222
- await logger.startAsync("Infer cross-attention QKs");
276
+ await logger.startAsync('Infer cross-attention QKs');
223
277
  let crossAttentionQKs = await this.inferCrossAttentionQKs(tokens, audioFeatures);
224
278
  tokens = tokens.slice(initialTokens.length, tokens.length - 1);
225
279
  crossAttentionQKs = crossAttentionQKs.slice(initialTokens.length, crossAttentionQKs.length - 1);
226
- await logger.startAsync("Extract word timeline");
280
+ await logger.startAsync('Extract word timeline');
227
281
  const alignmentPath = await this.findAlignmentPathFromQKs(crossAttentionQKs, tokens, 0, audioFrameCount); //, this.getAlignmentHeadIndexes())
228
- let timeline = await this.getWordTimelineFromAlignmentPath(alignmentPath, tokens, 0, audioDuration);
229
- timeline = this.mergeSuccessiveWordFragmentsInTimeline(timeline);
230
- timeline.forEach(entry => { entry.text = entry.text.trim(); });
231
- //timeline = timeline.filter(entry => isWordOrSymbolWord(entry.text))
282
+ let timeline = await this.getTokenTimelineFromAlignmentPath(alignmentPath, tokens, 0, audioDuration);
283
+ timeline = this.tokenTimelineToWordTimeline(timeline);
232
284
  logger.end();
233
285
  return timeline;
234
286
  }
235
- async detectLanguage(audioFeatures) {
236
- const logger = new Logger();
287
+ async detectLanguage(audioFeatures, temperature) {
237
288
  if (!this.isMultiligualModel) {
238
- throw new Error("Language detection only works for a multilingual model");
289
+ throw new Error('Language detection is only supported with multilingual models');
239
290
  }
291
+ await this.initializeTokenizerIfNeeded();
292
+ await this.initializeDecoderSessionIfNeeded();
240
293
  // Prepare and run decoder
241
- await logger.startAsync("Detect language with Whisper model");
242
- const sotToken = this.tokenConfig.sotToken;
294
+ const logger = new Logger();
295
+ await logger.startAsync('Detect language with Whisper model');
296
+ const sotToken = this.tokenConfig.startOfTextToken;
243
297
  const initialTokens = [sotToken];
244
298
  const offset = 0;
245
299
  const initialKvDimensions = this.getKvDimensions(1, initialTokens.length);
246
300
  const kvCacheTensor = new Onnx.Tensor('float32', new Float32Array(initialKvDimensions[0] * initialKvDimensions[1] * initialKvDimensions[2] * initialKvDimensions[3]), initialKvDimensions);
247
301
  const tokensTensor = new Onnx.Tensor('int64', new BigInt64Array(initialTokens.map(token => BigInt(token))), [1, initialTokens.length]);
248
302
  const offsetTensor = new Onnx.Tensor('int64', new BigInt64Array([BigInt(offset)]), []);
249
- const decoderInputs = { tokens: tokensTensor, audio_features: audioFeatures, kv_cache: kvCacheTensor, offset: offsetTensor };
303
+ const decoderInputs = {
304
+ tokens: tokensTensor,
305
+ audio_features: audioFeatures,
306
+ kv_cache: kvCacheTensor,
307
+ offset: offsetTensor
308
+ };
250
309
  const decoderOutputs = await this.textDecoder.run(decoderInputs);
251
- const logitsBuffer = decoderOutputs["logits"].data;
252
- const languageTokensLogits = Array.from(logitsBuffer.slice(sotToken + 1, sotToken + 1 + 99));
253
- const languageTokensProbabilities = softmax(languageTokensLogits, 1.0);
310
+ const logitsBuffer = decoderOutputs['logits'].data;
311
+ const tokenConfig = this.tokenConfig;
312
+ const languageTokensLogits = Array.from(logitsBuffer.slice(tokenConfig.languageTokensStart, tokenConfig.languageTokensEnd));
313
+ const languageTokensProbabilities = softmax(languageTokensLogits, temperature);
254
314
  const results = [];
255
315
  for (const language in languageIdLookup) {
256
316
  const langId = languageIdLookup[language];
@@ -265,18 +325,17 @@ export class Whisper {
265
325
  return results;
266
326
  }
267
327
  async decodeTokens(audioFeatures, initialTokens, audioDuration, isFirstPart, isFinalPart, options) {
328
+ await this.initializeTokenizerIfNeeded();
329
+ await this.initializeDecoderSessionIfNeeded();
268
330
  const logger = new Logger();
269
- await logger.startAsync("Decode text tokens with Whisper decoder model");
270
- options = extendDeep(whisperOptionsDefaults, options);
271
- const noSpeechThreshold = 0.6;
272
- const blankToken = this.textToTokenLookup.get(" ");
273
- const suppressedTokens = this.tokenConfig.suppressedTokens;
274
- const sotToken = this.tokenConfig.sotToken;
275
- const eotToken = this.tokenConfig.eotToken;
276
- const noTimestampsToken = this.tokenConfig.noTimestampsToken;
277
- const noSpeechToken = this.tokenConfig.noSpeechToken;
331
+ await logger.startAsync('Decode text tokens with Whisper decoder model');
332
+ options = extendDeep(defaultWhisperOptions, options);
333
+ const endOfTextToken = this.tokenConfig.endOfTextToken;
278
334
  const timestampTokensStart = this.tokenConfig.timestampTokensStart;
279
- const maxDecodedTokenCount = 250;
335
+ const suppressedTokens = this.tokenConfig.suppressedTokens;
336
+ const spaceToken = this.textToTokens(' ')[0];
337
+ const maxDecodedTokenCount = options.maxTokensPerPart;
338
+ //const suppressedTokensText = Array.from(this.tokenConfig.suppressedTokens).map(token => this.tokenToText(token, true))
280
339
  let decodedTokens = initialTokens.slice();
281
340
  const initialKvDimensions = this.getKvDimensions(1, decodedTokens.length);
282
341
  let kvCacheTensor = new Onnx.Tensor('float32', new Float32Array(initialKvDimensions[0] * initialKvDimensions[1] * initialKvDimensions[2] * initialKvDimensions[3]), initialKvDimensions);
@@ -309,119 +368,144 @@ export class Whisper {
309
368
  const offsetTensor = new Onnx.Tensor('int64', new BigInt64Array([BigInt(offset)]), []);
310
369
  const decoderInputs = { tokens: tokensTensor, audio_features: audioFeatures, kv_cache: kvCacheTensor, offset: offsetTensor };
311
370
  const decoderOutputs = await this.textDecoder.run(decoderInputs);
312
- const logitsBuffer = decoderOutputs["logits"].data;
313
- kvCacheTensor = decoderOutputs["output_kv_cache"];
371
+ const logitsBuffer = decoderOutputs['logits'].data;
372
+ kvCacheTensor = decoderOutputs['output_kv_cache'];
314
373
  // Compute logits
315
- const resultLogits = splitFloat32Array(logitsBuffer, logitsBuffer.length / decoderOutputs["logits"].dims[1]);
316
- const tokenLogits = resultLogits[resultLogits.length - 1];
317
- const tokenTimestampLogits = Array.from(tokenLogits.slice(timestampTokensStart));
374
+ const resultLogits = splitFloat32Array(logitsBuffer, logitsBuffer.length / decoderOutputs['logits'].dims[1]);
375
+ const allTokenLogits = Array.from(resultLogits[resultLogits.length - 1]);
376
+ const timestampTokenLogits = allTokenLogits.slice(timestampTokensStart);
318
377
  // Suppress tokens
319
- for (let logitIndex = 0; logitIndex < tokenLogits.length; logitIndex++) {
320
- const isWrongTokenForInitialState = isInitialState && (logitIndex == blankToken || logitIndex == eotToken);
321
- const isInSupressedList = suppressedTokens.includes(logitIndex);
322
- const isNoTimestampsToken = logitIndex == noTimestampsToken;
323
- const shouldSupressToken = isWrongTokenForInitialState || isInSupressedList || isNoTimestampsToken;
324
- if (shouldSupressToken) {
325
- tokenLogits[logitIndex] = -Infinity;
378
+ for (let logitIndex = 0; logitIndex < allTokenLogits.length; logitIndex++) {
379
+ const isWrongTokenForInitialState = isInitialState &&
380
+ (logitIndex === spaceToken || logitIndex === endOfTextToken);
381
+ const isInSuppressedList = suppressedTokens.has(logitIndex);
382
+ const shouldSuppressToken = isWrongTokenForInitialState || isInSuppressedList;
383
+ if (shouldSuppressToken) {
384
+ allTokenLogits[logitIndex] = -Infinity;
326
385
  }
327
386
  }
328
- // Find token distributions and best token
329
- const probs = softmax(tokenLogits);
330
- const logProbs = logOfVector(probs);
331
- const textTokenLogProbs = logProbs.slice(0, timestampTokensStart);
332
- const timestampTokenLogProbs = logProbs.slice(timestampTokensStart);
333
- const indexOfMaxTextLogProb = indexOfMax(textTokenLogProbs);
334
- const valueOfMaxTextLogProb = textTokenLogProbs[indexOfMaxTextLogProb];
335
- const indexOfMaxTimestampLogProb = indexOfMax(timestampTokenLogProbs);
336
- const logSumExpOfTimestampTokenLogProbs = logSumExp(timestampTokenLogProbs);
337
- const isTimestampToken = logSumExpOfTimestampTokenLogProbs > valueOfMaxTextLogProb;
338
- const previousTokenWasTimestamp = decodedTokens[decodedTokens.length - 1] >= timestampTokensStart;
339
- const secondPreviousTokenWasTimestamp = decodedTokens.length < 2 || decodedTokens[decodedTokens.length - 2] >= timestampTokensStart;
340
- if (isTimestampToken && !previousTokenWasTimestamp) {
341
- timestampsSeenCount += 1;
342
- }
343
- //
344
- //const topLogits = [...tokenLogits].map((logit, index) => ({ index, logit, token: this.tokenToTextLookup.get(index) || "", prob: probs[index] }))
345
- //topLogits.sort((a, b) => b.logit - a.logit)
346
- ///
387
+ // Derive token probabilities
388
+ let bufferedTokensToPrint = [];
347
389
  // Add best token
348
390
  function addToken(tokenToAdd, timestampLogits, confidence) {
349
391
  decodedTokens.push(tokenToAdd);
350
392
  decodedTokensTimestampLogits.push(timestampLogits);
351
- decodedTokensCrossAttentionQKs.push(decoderOutputs["cross_attention_qks"]);
393
+ decodedTokensCrossAttentionQKs.push(decoderOutputs['cross_attention_qks']);
352
394
  decodedTokensConfidence.push(confidence);
353
395
  }
354
- if (isTimestampToken || (previousTokenWasTimestamp && !secondPreviousTokenWasTimestamp)) {
355
- if (previousTokenWasTimestamp) {
356
- const previousToken = decodedTokens[decodedTokens.length - 1];
357
- const previousTokenTimestampLogits = decodedTokensTimestampLogits[decodedTokensTimestampLogits.length - 1];
358
- const previousTokenConfidence = decodedTokensConfidence[decodedTokensConfidence.length - 1];
359
- addToken(previousToken, previousTokenTimestampLogits, previousTokenConfidence);
360
- lastTimestampTokenIndex = decodedTokens.length;
361
- const previousTokenTimestamp = (previousToken - timestampTokensStart) * 0.02;
362
- if (previousTokenTimestamp >= audioDuration) {
363
- break;
364
- }
396
+ let shouldDecodeNonTimestampToken = true;
397
+ if (options.decodeTimestampTokens) {
398
+ const probabilities = softmax(allTokenLogits, 1.0);
399
+ const logProbabilities = logOfVector(probabilities);
400
+ const nonTimestampTokenLogProbs = logProbabilities.slice(0, timestampTokensStart);
401
+ const indexOfMaxNonTimestampLogProb = indexOfMax(nonTimestampTokenLogProbs);
402
+ const valueOfMaxNonTimestampLogProb = nonTimestampTokenLogProbs[indexOfMaxNonTimestampLogProb];
403
+ const timestampTokenLogProbs = logProbabilities.slice(timestampTokensStart);
404
+ const indexOfMaxTimestampLogProb = indexOfMax(timestampTokenLogProbs);
405
+ const logSumExpOfTimestampTokenLogProbs = logSumExp(timestampTokenLogProbs);
406
+ const shouldDecodeTimestampToken = logSumExpOfTimestampTokenLogProbs > valueOfMaxNonTimestampLogProb;
407
+ const previousTokenWasTimestamp = this.isTimestampToken(decodedTokens[decodedTokens.length - 1]);
408
+ const secondPreviousTokenWasTimestamp = decodedTokens.length < 2 || this.isTimestampToken(decodedTokens[decodedTokens.length - 2]);
409
+ if (shouldDecodeTimestampToken && !previousTokenWasTimestamp) {
410
+ timestampsSeenCount += 1;
365
411
  }
366
- else {
367
- const timestampToken = timestampTokensStart + indexOfMaxTimestampLogProb;
368
- const confidence = probs[timestampToken];
369
- addToken(timestampToken, tokenTimestampLogits, confidence);
412
+ if (shouldDecodeTimestampToken || (previousTokenWasTimestamp && !secondPreviousTokenWasTimestamp)) {
413
+ if (previousTokenWasTimestamp) {
414
+ const previousToken = decodedTokens[decodedTokens.length - 1];
415
+ const previousTokenTimestampLogits = decodedTokensTimestampLogits[decodedTokensTimestampLogits.length - 1];
416
+ const previousTokenConfidence = decodedTokensConfidence[decodedTokensConfidence.length - 1];
417
+ addToken(previousToken, previousTokenTimestampLogits, previousTokenConfidence);
418
+ lastTimestampTokenIndex = decodedTokens.length;
419
+ const previousTokenTimestamp = this.timestampTokenToSeconds(previousToken);
420
+ if (previousTokenTimestamp >= audioDuration) {
421
+ break;
422
+ }
423
+ }
424
+ else {
425
+ const timestampToken = timestampTokensStart + indexOfMaxTimestampLogProb;
426
+ const confidence = probabilities[timestampToken];
427
+ addToken(timestampToken, timestampTokenLogits, confidence);
428
+ }
429
+ shouldDecodeNonTimestampToken = false;
370
430
  }
371
431
  }
372
- else if (indexOfMaxTextLogProb == eotToken) {
373
- break;
374
- }
375
- else {
376
- let chosenTokenIndex;
377
- if (options.temperature == 0.0) {
378
- chosenTokenIndex = indexOfMaxTextLogProb;
379
- }
380
- else {
381
- const topLogitCount = options.topCandidateCount;
382
- const textTokenLogits = tokenLogits.slice(0, timestampTokensStart);
383
- const sortedTextTokenLogitsWithIndexes = Array.from(textTokenLogits).map((logit, index) => ({ logit, index }));
384
- sortedTextTokenLogitsWithIndexes.sort((a, b) => b.logit - a.logit);
385
- let topLogitsWithIndexes = sortedTextTokenLogitsWithIndexes.slice(0, topLogitCount);
386
- ////
387
- /*
388
- topLogitsWithIndexes = topLogitsWithIndexes.filter(entry => {
389
- const lastDecodedTextTokens = decodedTokens.filter(token => token < eotToken).reverse().slice(0, 20)
390
- const { maxScore } = getRepetitionScoreRelativeToFirstSubstring([entry.index, ...lastDecodedTextTokens])
391
-
392
- if (maxScore < 4) {
393
- return true
394
- } else {
395
- return false
396
- }
397
- })
398
- */
399
- ////
400
- const topLogits = topLogitsWithIndexes.map(a => a.logit);
401
- const textTokenProbs = softmax(topLogits, options.temperature);
402
- const topIndexOfPromisingPunctuationLogit = topLogitsWithIndexes.findIndex(entry => {
403
- const tokenText = (this.tokenToTextLookup.get(entry.index) || "").trim();
404
- const tokenProb = probs[entry.index];
405
- return tokenProb >= options.punctuationThreshold && [',', ',', '.', '。', '!', '?'].includes(tokenText);
432
+ if (shouldDecodeNonTimestampToken) {
433
+ const topLogitCount = options.topCandidateCount;
434
+ const nonTimestampTokenLogits = allTokenLogits.slice(0, timestampTokensStart);
435
+ const sortedNonTimestampTokenLogitsWithIndexes = Array.from(nonTimestampTokenLogits).map((logit, index) => ({ token: index, logit }));
436
+ sortedNonTimestampTokenLogitsWithIndexes.sort((a, b) => b.logit - a.logit);
437
+ let topCandidates = sortedNonTimestampTokenLogitsWithIndexes.slice(0, topLogitCount)
438
+ .map(entry => ({
439
+ token: entry.token,
440
+ logit: entry.logit,
441
+ text: this.tokenToText(entry.token, true)
442
+ }));
443
+ //// Repetition suppression code
444
+ if (options.suppressRepetition) {
445
+ const topCandidatesRepetitionScores = topCandidates.map(entry => {
446
+ const lastDecodedTextTokens = decodedTokens.filter(token => this.isTextToken(token)).reverse().slice(0, 20);
447
+ const { maxScore } = getRepetitionScoreRelativeToFirstSubstring([entry.token, ...lastDecodedTextTokens]);
448
+ return maxScore;
406
449
  });
407
- let chosenTokenIndexInTopLogits;
408
- if (topIndexOfPromisingPunctuationLogit >= 0) {
409
- chosenTokenIndexInTopLogits = topIndexOfPromisingPunctuationLogit;
450
+ const thresholdRepetitionScore = 3;
451
+ if (topCandidatesRepetitionScores.every(score => score >= thresholdRepetitionScore)) {
452
+ const indexOfMaxScore = topCandidatesRepetitionScores.indexOf(Math.max(...topCandidatesRepetitionScores));
453
+ topCandidates = [topCandidates[indexOfMaxScore]];
410
454
  }
411
455
  else {
412
- chosenTokenIndexInTopLogits = this.randomGen.selectRandomIndexFromDistribution(textTokenProbs);
456
+ topCandidates = topCandidates.filter((candidate, index) => topCandidatesRepetitionScores[index] < thresholdRepetitionScore);
413
457
  }
414
- chosenTokenIndex = sortedTextTokenLogitsWithIndexes[chosenTokenIndexInTopLogits].index;
415
458
  }
416
- if (chosenTokenIndex < eotToken) {
417
- let chosenTokenText = this.tokenToTextLookup.get(chosenTokenIndex) || "";
418
- if (isFirstPart && decodedTokens.every(token => token >= eotToken)) {
419
- chosenTokenText = chosenTokenText.trimStart();
459
+ ////
460
+ const topCandidateProbabilities = softmax(topCandidates.map(a => a.logit), options.temperature);
461
+ //// Remove end-of-text token from candidates if its probability isn't high enough
462
+ if (options.decodeTimestampTokens === false) {
463
+ topCandidates = topCandidates.filter((candidate, index) => {
464
+ if (candidate.token === endOfTextToken) {
465
+ return topCandidateProbabilities[index] >= 0.9;
466
+ }
467
+ return true;
468
+ });
469
+ }
470
+ ////
471
+ const rankOfPromisingPunctuationToken = topCandidates.findIndex((entry, index) => {
472
+ const tokenText = this.tokenToText(entry.token).trim();
473
+ const isPunctuationToken = [',', ',', '、', '.', '。', '!', '?'].includes(tokenText);
474
+ if (!isPunctuationToken) {
475
+ return false;
420
476
  }
421
- writeToStderr(chosenTokenText);
477
+ const tokenProb = topCandidateProbabilities[index];
478
+ return tokenProb >= options.punctuationThreshold;
479
+ });
480
+ let rankOfSpaceToken = topCandidates.findIndex(candidate => candidate.token === spaceToken);
481
+ if (rankOfSpaceToken < 0) {
482
+ rankOfSpaceToken = Infinity;
483
+ }
484
+ let chosenCandidateRank;
485
+ if (rankOfPromisingPunctuationToken >= 0 &&
486
+ rankOfPromisingPunctuationToken < rankOfSpaceToken) {
487
+ chosenCandidateRank = rankOfPromisingPunctuationToken;
488
+ }
489
+ else {
490
+ chosenCandidateRank = this.randomGen.selectRandomIndexFromDistribution(topCandidateProbabilities);
491
+ }
492
+ const chosenToken = topCandidates[chosenCandidateRank].token;
493
+ if (this.isTextToken(chosenToken)) {
494
+ bufferedTokensToPrint.push(chosenToken);
495
+ let textToPrint = this.tokensToText(bufferedTokensToPrint);
496
+ if (textToPrint.codePointAt(0) !== 65533) {
497
+ if (isFirstPart && decodedTokens.every(token => this.isMetadataToken(token))) {
498
+ textToPrint = textToPrint.trimStart();
499
+ }
500
+ logger.write(textToPrint);
501
+ bufferedTokensToPrint = [];
502
+ }
503
+ }
504
+ const confidence = topCandidateProbabilities[chosenCandidateRank];
505
+ addToken(chosenToken, timestampTokenLogits, confidence);
506
+ if (chosenToken === endOfTextToken) {
507
+ break;
422
508
  }
423
- const confidence = probs[chosenTokenIndex];
424
- addToken(chosenTokenIndex, tokenTimestampLogits, confidence);
425
509
  }
426
510
  await yieldToEventLoop();
427
511
  }
@@ -430,10 +514,15 @@ export class Whisper {
430
514
  decodedTokensTimestampLogits = decodedTokensTimestampLogits.slice(0, lastTimestampTokenIndex);
431
515
  decodedTokensCrossAttentionQKs = decodedTokensCrossAttentionQKs.slice(0, lastTimestampTokenIndex);
432
516
  }
433
- writeToStderr("\n");
517
+ logger.write('\n');
434
518
  logger.end();
435
519
  // Return the tokens
436
- return { decodedTokens, decodedTokensTimestampLogits, crossAttentionQKs: decodedTokensCrossAttentionQKs, decodedTokensConfidence };
520
+ return {
521
+ decodedTokens,
522
+ decodedTokensTimestampLogits,
523
+ crossAttentionQKs: decodedTokensCrossAttentionQKs,
524
+ decodedTokensConfidence
525
+ };
437
526
  }
438
527
  async inferCrossAttentionQKs(tokens, audioFeatures) {
439
528
  const offset = 0;
@@ -443,7 +532,7 @@ export class Whisper {
443
532
  const kvCacheTensor = new Onnx.Tensor('float32', new Float32Array(initialKvDimensions[0] * initialKvDimensions[1] * initialKvDimensions[2] * initialKvDimensions[3]), initialKvDimensions);
444
533
  const decoderInputs = { tokens: tokensTensor, audio_features: audioFeatures, kv_cache: kvCacheTensor, offset: offsetTensor };
445
534
  const decoderOutputs = await this.textDecoder.run(decoderInputs);
446
- const crossAttentionQKsTensor = decoderOutputs["cross_attention_qks"];
535
+ const crossAttentionQKsTensor = decoderOutputs['cross_attention_qks'];
447
536
  const tensorShape = crossAttentionQKsTensor.dims.slice();
448
537
  const ndarray = (await import('ndarray')).default;
449
538
  let qkArray = ndarray(crossAttentionQKsTensor.data, crossAttentionQKsTensor.dims.slice());
@@ -468,6 +557,7 @@ export class Whisper {
468
557
  return tokenCrossAttentionQKsTensors;
469
558
  }
470
559
  async encodeAudio(rawAudio) {
560
+ await this.initializeEncoderSessionIfNeeded();
471
561
  const logger = new Logger();
472
562
  const audioSamples = rawAudio.audioChannels[0];
473
563
  const sampleRate = rawAudio.sampleRate;
@@ -476,12 +566,12 @@ export class Whisper {
476
566
  const filterbankCount = 80;
477
567
  const maxAudioSamples = sampleRate * 30;
478
568
  const maxAudioFrames = 3000;
479
- await logger.startAsync("Extract mel spectogram from audio part");
569
+ await logger.startAsync('Extract mel spectogram from audio part');
480
570
  const paddedAudioSamples = new Float32Array(maxAudioSamples);
481
571
  paddedAudioSamples.set(audioSamples.subarray(0, maxAudioSamples), 0);
482
572
  const rawAudioPart = { audioChannels: [paddedAudioSamples], sampleRate };
483
573
  const { melSpectogram } = await computeMelSpectogramUsingFilterbanks(rawAudioPart, fftOrder, fftOrder, hopLength, filterbanks);
484
- await logger.startAsync("Normalize mel spectogram");
574
+ await logger.startAsync('Normalize mel spectogram');
485
575
  const logMelSpectogram = melSpectogram.map(spectrum => spectrum.map(mel => Math.log10(Math.max(mel, 1e-10))));
486
576
  let maxLogMel = -Infinity;
487
577
  for (const spectrum of logMelSpectogram) {
@@ -498,11 +588,11 @@ export class Whisper {
498
588
  flattenedNormalizedLogMelSpectogram[(i * maxAudioFrames) + j] = normalizedLogMelSpectogram[j][i];
499
589
  }
500
590
  }
501
- await logger.startAsync("Encode mel spectogram with Whisper encoder model");
591
+ await logger.startAsync('Encode mel spectogram with Whisper encoder model');
502
592
  const inputTensor = new Onnx.Tensor('float32', flattenedNormalizedLogMelSpectogram, [1, filterbankCount, maxAudioFrames]);
503
593
  const encoderInputs = { mel: inputTensor };
504
594
  const encoderOutputs = await this.audioEncoder.run(encoderInputs);
505
- const encodedAudioFeatures = encoderOutputs["output"];
595
+ const encodedAudioFeatures = encoderOutputs['output'];
506
596
  logger.end();
507
597
  return encodedAudioFeatures;
508
598
  }
@@ -510,7 +600,7 @@ export class Whisper {
510
600
  const timestampTokensStart = this.tokenConfig.timestampTokensStart;
511
601
  for (let i = 0; i < tokens.length; i++) {
512
602
  const token = tokens[i];
513
- if (token == this.tokenConfig.sotToken || token == this.tokenConfig.eotToken) {
603
+ if (token == this.tokenConfig.startOfTextToken || token == this.tokenConfig.endOfTextToken) {
514
604
  continue;
515
605
  }
516
606
  const tokenIsTimestamp = token >= timestampTokensStart;
@@ -519,14 +609,14 @@ export class Whisper {
519
609
  if (previousTokenWasTimestamp) {
520
610
  continue;
521
611
  }
522
- let startTime = initialTimeOffset + (token - timestampTokensStart) * 0.02;
612
+ let startTime = initialTimeOffset + this.timestampTokenToSeconds(token);
523
613
  startTime = Math.min(startTime, audioDuration);
524
614
  if (timeline.length > 0) {
525
615
  timeline[timeline.length - 1].endTime = startTime;
526
616
  }
527
617
  timeline.push({
528
- type: "segment",
529
- text: "",
618
+ type: 'segment',
619
+ text: '',
530
620
  startTime,
531
621
  endTime: -1,
532
622
  });
@@ -534,46 +624,45 @@ export class Whisper {
534
624
  else {
535
625
  if (timeline.length == 0) {
536
626
  timeline.push({
537
- type: "segment",
538
- text: "",
627
+ type: 'segment',
628
+ text: '',
539
629
  startTime: initialTimeOffset,
540
630
  endTime: -1,
541
631
  });
542
632
  }
543
- const tokenText = this.tokenToTextLookup.get(token) || "";
633
+ const tokenText = this.tokenToText(token);
544
634
  timeline[timeline.length - 1].text += tokenText;
545
635
  }
546
636
  }
547
637
  }
548
638
  async addWordsToTimeline(timeline, tokens, rawAudio, crossAttentionQKs, initialAudioTimeOffset, duration) {
549
- const timestampTokensStart = this.tokenConfig.timestampTokensStart;
550
639
  let segmentStartTime = 0;
551
640
  let segmentTokens = [];
552
641
  let segmentCrossAttentionQKs = [];
553
642
  for (let tokenIndex = 0; tokenIndex < tokens.length; tokenIndex++) {
554
643
  const token = tokens[tokenIndex];
555
644
  const tokenCrossAttentionQKs = crossAttentionQKs[tokenIndex];
556
- const segmentTokensWithoutTimestamps = segmentTokens.filter(token => token < this.tokenConfig.timestampTokensStart);
557
- const isTimestamp = token >= timestampTokensStart;
645
+ const segmentTokensWithoutTimestamps = segmentTokens.filter(token => this.isNonTimestampToken(token));
646
+ const isTimestamp = this.isTimestampToken(token);
558
647
  if (isTimestamp || tokenIndex == tokens.length - 1) {
559
648
  let tokenTime;
560
649
  if (isTimestamp) {
561
- tokenTime = (token - timestampTokensStart) * 0.02;
650
+ tokenTime = this.timestampTokenToSeconds(token);
562
651
  }
563
652
  else {
564
653
  tokenTime = duration;
565
654
  }
566
655
  if (segmentTokensWithoutTimestamps.length > 0) {
567
656
  const segmentEndTime = tokenTime;
568
- const segmentStartFrame = Math.floor(segmentStartTime / 0.02);
569
- let segmentEndFrame = Math.floor(segmentEndTime / 0.02);
657
+ const segmentStartFrame = this.secondsToFrame(segmentStartTime);
658
+ let segmentEndFrame = this.secondsToFrame(segmentEndTime);
570
659
  if (segmentStartFrame == segmentEndFrame) {
571
660
  segmentEndFrame += 1;
572
661
  }
573
662
  const segmentFrameCount = segmentEndFrame - segmentStartFrame;
574
663
  const reinferCrossAttentionQKs = true;
575
664
  if (reinferCrossAttentionQKs) {
576
- const initialTokens = this.getInitialTokens('en', 'transcribe');
665
+ const initialTokens = this.getTextStartTokens('en', 'transcribe');
577
666
  const tokensToDecode = [...initialTokens, ...segmentTokensWithoutTimestamps];
578
667
  //const segmentAudioFeaturesBuffer = audioFeatures.data.slice(segmentStartFrame * audioFeatures.dims[2], segmentEndFrame * audioFeatures.dims[2])
579
668
  //const segmentAudioFeatures = new Onnx.Tensor('float32', segmentAudioFeaturesBuffer, [1, segmentFrameCount, audioFeatures.dims[2]])
@@ -583,13 +672,13 @@ export class Whisper {
583
672
  const reinferredCrossAttentionQKs = await this.inferCrossAttentionQKs(tokensToDecode, segmentAudioFeatures);
584
673
  reinferredCrossAttentionQKs.slice(initialTokens.length);
585
674
  const alignmentPath = await this.findAlignmentPathFromQKs(reinferredCrossAttentionQKs, tokensToDecode, 0, segmentFrameCount); //, alignmentHeadsIndexes[modelName])
586
- const wordTimeline = await this.getWordTimelineFromAlignmentPath(alignmentPath, segmentTokensWithoutTimestamps, initialAudioTimeOffset + segmentStartTime, initialAudioTimeOffset + segmentEndTime);
587
- timeline.push(...wordTimeline);
675
+ const tokenTimeline = await this.getTokenTimelineFromAlignmentPath(alignmentPath, segmentTokensWithoutTimestamps, initialAudioTimeOffset + segmentStartTime, initialAudioTimeOffset + segmentEndTime);
676
+ timeline.push(...tokenTimeline);
588
677
  }
589
678
  else {
590
679
  const alignmentPath = await this.findAlignmentPathFromQKs(segmentCrossAttentionQKs, segmentTokens, segmentStartFrame, segmentEndFrame); //, alignmentHeadsIndexes[modelName])
591
- const wordTimeline = await this.getWordTimelineFromAlignmentPath(alignmentPath, segmentTokens, initialAudioTimeOffset, initialAudioTimeOffset + segmentEndTime);
592
- timeline.push(...wordTimeline);
680
+ const tokenTimeline = await this.getTokenTimelineFromAlignmentPath(alignmentPath, segmentTokens, initialAudioTimeOffset, initialAudioTimeOffset + segmentEndTime);
681
+ timeline.push(...tokenTimeline);
593
682
  }
594
683
  }
595
684
  segmentStartTime = tokenTime;
@@ -600,14 +689,25 @@ export class Whisper {
600
689
  segmentCrossAttentionQKs.push(tokenCrossAttentionQKs);
601
690
  }
602
691
  }
603
- mergeSuccessiveWordFragmentsInTimeline(timeline) {
692
+ tokenTimelineToWordTimeline(tokenTimeline) {
693
+ const separatorChars = [' ', '–', '一', ',', '、', '|', '/', '\\', ';', '"', '“', '”', '…', '(', ')', '[', ']', '{', '}'];
694
+ function startsWithSeparatingPunctuation(text) {
695
+ return separatorChars.some(char => text.startsWith(char));
696
+ }
697
+ function isSeparatorPunctuation(text) {
698
+ return separatorChars.includes(text);
699
+ }
604
700
  const resultTimeline = [];
605
701
  const groups = [];
606
- for (const entry of timeline) {
607
- if (entry.type != "word") {
608
- continue;
609
- }
610
- if (groups.length == 0 || entry.text.startsWith(" ")) {
702
+ for (let i = 0; i < tokenTimeline.length; i++) {
703
+ const entry = tokenTimeline[i];
704
+ const previousEntry = i > 0 ? tokenTimeline[i - 1] : undefined;
705
+ const text = entry.text;
706
+ const previousEntryText = previousEntry?.text;
707
+ if (groups.length == 0 ||
708
+ text === '' ||
709
+ startsWithSeparatingPunctuation(text) ||
710
+ (previousEntryText != null && isSeparatorPunctuation(previousEntryText))) {
611
711
  groups.push([entry]);
612
712
  }
613
713
  else {
@@ -615,34 +715,33 @@ export class Whisper {
615
715
  }
616
716
  }
617
717
  for (const group of groups) {
618
- if (group.length == 1) {
619
- resultTimeline.push(deepClone(group[0]));
718
+ const groupText = this.tokensToText(group.map(entry => entry.id));
719
+ if (groupText === '') {
720
+ continue;
620
721
  }
621
- else {
622
- const text = group.map(entry => entry.text).join("");
623
- const startTime = group[0].startTime;
624
- const endTime = group[group.length - 1].endTime;
625
- let confidence = undefined;
626
- if (group[0].confidence != null) {
627
- confidence = meanOfVector(group.map(entry => entry.confidence));
628
- }
629
- const newEntry = {
630
- type: "word",
631
- text,
632
- startTime,
633
- endTime,
634
- confidence
635
- };
636
- resultTimeline.push(newEntry);
722
+ const startTime = group[0].startTime;
723
+ const endTime = group[group.length - 1].endTime;
724
+ let confidence = undefined;
725
+ if (group[0].confidence != null) {
726
+ confidence = meanOfVector(group.map(entry => entry.confidence));
637
727
  }
728
+ const newEntry = {
729
+ type: 'word',
730
+ text: groupText.trim(),
731
+ startTime,
732
+ endTime,
733
+ confidence,
734
+ timeline: group,
735
+ };
736
+ resultTimeline.push(newEntry);
638
737
  }
639
738
  return resultTimeline;
640
739
  }
641
- async getWordTimelineFromAlignmentPath(alignmentPath, tokens, startTimeOffset, endTimeOffset, tokensConfidence, correctionAmount = 0.0) {
740
+ async getTokenTimelineFromAlignmentPath(alignmentPath, tokens, startTimeOffset, endTimeOffset, tokensConfidence, correctionAmount = 0.0) {
642
741
  if (alignmentPath.length == 0) {
643
742
  return [];
644
743
  }
645
- const wordTimeline = [];
744
+ const tokenTimeline = [];
646
745
  for (let pathIndex = 0; pathIndex < alignmentPath.length; pathIndex++) {
647
746
  if (pathIndex != 0 && alignmentPath[pathIndex].source == alignmentPath[pathIndex - 1].source) {
648
747
  continue;
@@ -651,32 +750,29 @@ export class Whisper {
651
750
  const tokenIndex = tokenMappingEntry.source;
652
751
  const token = tokens[tokenIndex];
653
752
  const tokenConfidence = tokensConfidence ? tokensConfidence[tokenIndex] : undefined;
654
- const tokenText = this.tokenToTextLookup.get(token);
655
- if (token >= this.tokenConfig.eotToken || !tokenText) {
656
- continue;
657
- }
753
+ const tokenText = this.tokenToText(token, true);
658
754
  let startTime = startTimeOffset + (tokenMappingEntry.dest * 0.02);
659
755
  startTime = Math.max(startTime + correctionAmount, startTimeOffset);
660
- if (wordTimeline.length > 0) {
661
- wordTimeline[wordTimeline.length - 1].endTime = startTime;
756
+ if (tokenTimeline.length > 0) {
757
+ tokenTimeline[tokenTimeline.length - 1].endTime = startTime;
662
758
  }
663
- wordTimeline.push({
664
- type: "word",
759
+ tokenTimeline.push({
760
+ type: 'token',
665
761
  text: tokenText,
762
+ id: token,
666
763
  startTime,
667
764
  endTime: -1,
668
765
  confidence: tokenConfidence
669
766
  });
670
767
  }
671
- if (wordTimeline.length > 0) {
672
- wordTimeline[wordTimeline.length - 1].endTime = endTimeOffset;
768
+ if (tokenTimeline.length > 0) {
769
+ tokenTimeline[tokenTimeline.length - 1].endTime = endTimeOffset;
673
770
  }
674
- return wordTimeline;
771
+ return tokenTimeline;
675
772
  }
676
773
  async findAlignmentPathFromQKs(qksTensors, tokens, segmentStartFrame, segmentEndFrame, headIndexes) {
677
774
  const segmentFrameCount = segmentEndFrame - segmentStartFrame;
678
- if (segmentFrameCount == 0) {
679
- //throw new Error("Segment has 0 frames")
775
+ if (segmentFrameCount === 0 || tokens.length === 0 || qksTensors.length === 0) {
680
776
  return [];
681
777
  }
682
778
  const tokenCount = qksTensors.length;
@@ -691,9 +787,9 @@ export class Whisper {
691
787
  }
692
788
  }
693
789
  // Load attention head weights from tensors
694
- const attentionHeads = []; // [heads, tokens, frames]
790
+ const attentionHeads = []; // structure: [heads, tokens, frames]
695
791
  for (const headIndex of headIndexes) {
696
- const attentionHead = []; // [tokens, frames]
792
+ const attentionHead = []; // structure: [tokens, frames]
697
793
  for (let tokenIndex = 0; tokenIndex < tokenCount; tokenIndex++) {
698
794
  const bufferOffset = headIndex * frameCount;
699
795
  const startIndexInBuffer = bufferOffset + segmentStartFrame;
@@ -722,7 +818,7 @@ export class Whisper {
722
818
  for (const head of attentionHeads) {
723
819
  const allWeightsForHead = head.flatMap(tokenFrames => tokenFrames);
724
820
  const meanOfAllWeights = meanOfVector(allWeightsForHead);
725
- const stdDeviationOfAllWeights = stdDeviationOfVector(allWeightsForHead);
821
+ const stdDeviationOfAllWeights = stdDeviationOfVector(allWeightsForHead) + 1e-10;
726
822
  for (const tokenFrames of head) {
727
823
  for (let frameIndex = 0; frameIndex < tokenFrames.length; frameIndex++) {
728
824
  tokenFrames[frameIndex] = (tokenFrames[frameIndex] - meanOfAllWeights) / stdDeviationOfAllWeights;
@@ -755,10 +851,12 @@ export class Whisper {
755
851
  }
756
852
  }
757
853
  if (fixateTimestampTokens) {
854
+ // Fixate timestamp tokens to the original ones detected
758
855
  const timestampTokensStart = this.tokenConfig.timestampTokensStart;
759
856
  for (let tokenIndex = 0; tokenIndex < tokens.length; tokenIndex++) {
760
- if (tokens[tokenIndex] >= timestampTokensStart) {
761
- let timestampFrame = tokens[tokenIndex] - timestampTokensStart;
857
+ const token = tokens[tokenIndex];
858
+ if (this.isTimestampToken(token)) {
859
+ let timestampFrame = token - timestampTokensStart;
762
860
  timestampFrame = clip(timestampFrame, segmentStartFrame, segmentEndFrame - 1);
763
861
  frameMeansForToken[tokenIndex][timestampFrame] = 100;
764
862
  }
@@ -767,92 +865,123 @@ export class Whisper {
767
865
  // Perform DTW
768
866
  const tokenIndexes = [...Array(tokenCount).keys()];
769
867
  const frameIndexes = [...Array(segmentFrameCount).keys()];
770
- let { path } = await alignDTWWindowed(tokenIndexes, frameIndexes, (tokenIndex, frameIndex) => {
868
+ let { path } = alignDTWWindowed(tokenIndexes, frameIndexes, (tokenIndex, frameIndex) => {
771
869
  return -frameMeansForToken[tokenIndex][frameIndex];
772
- }, 1000);
870
+ }, segmentFrameCount);
773
871
  path = path.map(entry => ({ source: entry.source, dest: segmentStartFrame + entry.dest }));
774
872
  return path;
775
873
  }
776
874
  getKvDimensions(groupCount, length) {
777
875
  const modelName = this.modelName;
778
- if (modelName == "tiny" || modelName == "tiny.en") {
876
+ if (modelName == 'tiny' || modelName == 'tiny.en') {
779
877
  return [8, groupCount, length, 384];
780
878
  }
781
- else if (modelName == "base" || modelName == "base.en") {
879
+ else if (modelName == 'base' || modelName == 'base.en') {
782
880
  return [12, groupCount, length, 512];
783
881
  }
784
- else if (modelName == "small" || modelName == "small.en") {
882
+ else if (modelName == 'small' || modelName == 'small.en') {
785
883
  return [24, groupCount, length, 768];
786
884
  }
787
- else if (modelName == "medium" || modelName == "medium.en") {
885
+ else if (modelName == 'medium' || modelName == 'medium.en') {
788
886
  return [48, groupCount, length, 1024];
789
887
  }
790
- else if (modelName == "large" || modelName == "large-v1" || modelName == "large-v2" || modelName == "large-v3") {
888
+ else if (modelName == 'large' || modelName == 'large-v1' || modelName == 'large-v2' || modelName == 'large-v3') {
791
889
  return [64, groupCount, length, 1280];
792
890
  }
793
891
  else {
794
892
  throw new Error(`Unsupported model: ${modelName}`);
795
893
  }
796
894
  }
797
- getInitialTokens(language, task, disableTimestamps = false) {
798
- const sotToken = this.tokenConfig.sotToken;
799
- let initialTokens;
895
+ getTextStartTokens(language, task, disableTimestamps = false) {
896
+ const startOfTextToken = this.tokenConfig.startOfTextToken;
897
+ let tokens;
800
898
  if (this.isMultiligualModel) {
801
- const languageToken = sotToken + 1 + languageIdLookup[language];
802
- const translateTaskToken = 50358;
803
- const transcribeTaskToken = 50359;
804
- const taskToken = task == "transcribe" ? transcribeTaskToken : translateTaskToken;
805
- initialTokens = [sotToken, languageToken, taskToken];
899
+ const languageToken = this.tokenConfig.languageTokensStart + languageIdLookup[language];
900
+ const taskToken = task == 'translate' ? this.tokenConfig.translateTaskToken : this.tokenConfig.transcribeTaskToken;
901
+ tokens = [startOfTextToken, languageToken, taskToken];
806
902
  }
807
903
  else {
808
- initialTokens = [sotToken];
904
+ tokens = [startOfTextToken];
809
905
  }
810
906
  if (disableTimestamps) {
811
- initialTokens.push(this.tokenConfig.noTimestampsToken);
907
+ tokens.push(this.tokenConfig.noTimestampsToken);
812
908
  }
813
- return initialTokens;
909
+ return tokens;
814
910
  }
815
- getAlignmentHeadIndexes() {
816
- return alignmentHeadsIndexes[this.modelName];
911
+ tokenToText(token, includeMetadataTokens = false) {
912
+ return this.tokensToText([token], includeMetadataTokens);
913
+ }
914
+ tokensToText(tokens, includeMetadataTokens = false) {
915
+ tokens.forEach(token => this.assertIsValidToken(token));
916
+ if (includeMetadataTokens === false) {
917
+ tokens = tokens.filter(token => this.isTextToken(token));
918
+ }
919
+ if (tokens.length === 0) {
920
+ return '';
921
+ }
922
+ try {
923
+ return Buffer.from(this.tiktoken.decode(new Uint32Array(tokens))).toString('utf8');
924
+ }
925
+ catch {
926
+ return '[TOKENIZER_FAILED]';
927
+ }
928
+ }
929
+ textToTokens(text) {
930
+ return Array.from(this.tiktoken.encode(text));
931
+ }
932
+ isTextToken(token) {
933
+ return token < this.tokenConfig.endOfTextToken;
934
+ }
935
+ isMetadataToken(token) {
936
+ return token >= this.tokenConfig.endOfTextToken;
937
+ }
938
+ isLanguageToken(token) {
939
+ return token >= this.tokenConfig.languageTokensStart && token < this.tokenConfig.languageTokensEnd;
940
+ }
941
+ isTimestampToken(token) {
942
+ return token >= this.tokenConfig.timestampTokensStart;
817
943
  }
818
- tokensToText(tokens) {
819
- return tokens.map(token => this.tokenToTextLookup.get(token) || "").join("").trim();
944
+ isNonTimestampToken(token) {
945
+ return token < this.tokenConfig.timestampTokensStart;
820
946
  }
821
- async textToTokens(text, language) {
822
- const resultTokens = [];
823
- const words = (await splitToWords(text, language)).filter(w => w.trim().length > 0);
824
- //words = words.filter(word => wordCharacterPattern.test(word))
825
- for (let i = 1; i < words.length; i++) {
826
- words[i] = ` ${words[i]}`;
947
+ timestampTokenToSeconds(timestampToken) {
948
+ this.assertIsValidToken(timestampToken);
949
+ if (this.isNonTimestampToken(timestampToken)) {
950
+ throw new Error(`Invalid timestamp token: ${timestampToken}`);
827
951
  }
828
- const allResultingSubwords = [];
829
- for (const word of words) {
830
- const tokenForEntireWord = this.textToTokenLookup.get(word);
831
- if (tokenForEntireWord) {
832
- resultTokens.push(tokenForEntireWord);
833
- allResultingSubwords.push([word]);
834
- continue;
835
- }
836
- const subwords = word.split("");
837
- for (const mergeRule of this.merges) {
838
- for (let i = 0; i < subwords.length - 1; i++) {
839
- const currentSubword = subwords[i];
840
- const nextSubword = subwords[i + 1];
841
- if (currentSubword == mergeRule[0] && nextSubword == mergeRule[1]) {
842
- subwords.splice(i, 2, mergeRule[0] + mergeRule[1]);
843
- }
844
- }
845
- }
846
- for (const subword of subwords) {
847
- const tokenForSubword = this.textToTokenLookup.get(subword);
848
- if (!tokenForSubword) {
849
- throw new Error(`Failed tokenizing the given text. The word '${word}' contains a subword '${subword}' which is not in the vocabulary.`);
850
- }
851
- resultTokens.push(tokenForSubword);
852
- }
853
- allResultingSubwords.push(subwords);
952
+ return (timestampToken - this.tokenConfig.timestampTokensStart) * 0.02;
953
+ }
954
+ isValidToken(token) {
955
+ return token < this.tokenConfig.timestampTokensEnd;
956
+ }
957
+ assertIsValidToken(token) {
958
+ if (!this.isValidToken(token)) {
959
+ throw new Error(`Invalid token: ${token}`);
960
+ }
961
+ }
962
+ secondsToFrame(seconds) {
963
+ return Math.floor(seconds / 0.02);
964
+ }
965
+ secondsRangeToFrameCount(startSeconds, endSeconds) {
966
+ if (startSeconds > endSeconds) {
967
+ throw new Error(`Invalid range: ${startSeconds} > ${endSeconds}`);
968
+ }
969
+ return this.secondsToFrame(endSeconds - startSeconds);
970
+ }
971
+ languageTokenToLanguageIndex(languageToken) {
972
+ if (!this.isLanguageToken(languageToken)) {
973
+ throw new Error(`Invalid language token: ${languageToken}`);
974
+ }
975
+ let languageIndex = languageToken - this.tokenConfig.languageTokensStart;
976
+ if (this.isEnglishOnlyModel) {
977
+ languageIndex += 1;
854
978
  }
855
- return resultTokens;
979
+ }
980
+ get isEnglishOnlyModel() {
981
+ return this.isMultiligualModel === false;
982
+ }
983
+ getAlignmentHeadIndexes() {
984
+ return alignmentHeadsIndexes[this.modelName];
856
985
  }
857
986
  }
858
987
  const filterbanks = [
@@ -938,164 +1067,180 @@ const filterbanks = [
938
1067
  /* 79 */ { startIndex: 186, weights: [0.000366741674952209, 0.0008330700220540166, 0.0012993983691558242, 0.0017657268326729536, 0.0022320549469441175, 0.002698383294045925, 0.0031647118739783764, 0.003141313325613737, 0.002692554146051407, 0.0022437951993197203, 0.00179503601975739, 0.0013462770730257034, 0.000897518009878695, 0.0004487590049393475,] },
939
1068
  ];
940
1069
  export async function loadPackagesAndGetPaths(modelName, languageCode) {
941
- if (!modelName) {
1070
+ if (modelName) {
1071
+ modelName = normalizeWhisperModelName(modelName, languageCode);
1072
+ }
1073
+ else {
942
1074
  if (languageCode) {
943
1075
  const shortLanguageCode = getShortLanguageCode(languageCode);
944
- modelName = shortLanguageCode == "en" ? "tiny.en" : "tiny";
1076
+ modelName = shortLanguageCode == 'en' ? 'tiny.en' : 'tiny';
945
1077
  }
946
1078
  else {
947
- modelName = "tiny";
1079
+ modelName = 'tiny';
948
1080
  }
949
1081
  }
1082
+ if (modelName.startsWith('large')) {
1083
+ throw new Error(`Large models are not currently supported by the integrated Whisper engine due to model size restrictions of onnxruntime-node. To use large models, you can select the whisper.cpp engine instead.`);
1084
+ }
950
1085
  const packageName = modelNameToPackageName[modelName];
951
1086
  const modelDir = await loadPackage(packageName);
952
- const tokenizerPackagePath = await loadPackage(tokenizerPackageName);
953
- const tokenizerDir = isMultiligualModel(modelName) ? path.join(tokenizerPackagePath, "multilingual") : path.join(tokenizerPackagePath, "gpt2");
954
- return { modelName, modelDir, tokenizerDir };
1087
+ return { modelName, modelDir };
1088
+ }
1089
+ export function normalizeWhisperModelName(modelName, languageCode) {
1090
+ if (languageCode != 'en' && modelName.endsWith('.en')) {
1091
+ const originalModelName = modelName;
1092
+ modelName = modelName.slice(0, modelName.length - 3);
1093
+ const logger = new Logger();
1094
+ logger.logTitledMessage(`Warning`, `The model '${originalModelName}' is English only and cannot be used to transcribe language '${languageCode}'. using '${modelName}' instead.`, chalk.yellowBright);
1095
+ }
1096
+ return modelName;
1097
+ }
1098
+ export function isMultilingualModel(modelName) {
1099
+ return !isEnglishOnlyModel(modelName);
955
1100
  }
956
- export function isMultiligualModel(modelName) {
957
- return !modelName.endsWith(".en");
1101
+ export function isEnglishOnlyModel(modelName) {
1102
+ return modelName.endsWith('.en');
958
1103
  }
959
1104
  export const modelNameToPackageName = {
960
- "tiny": "whisper-tiny",
961
- "tiny.en": "whisper-tiny.en",
962
- "base": "whisper-base",
963
- "base.en": "whisper-base.en",
964
- "small": "whisper-small",
965
- "small.en": "whisper-small.en",
966
- "medium": "whisper-medium",
967
- "medium.en": "whisper-medium.en",
968
- "large": "whisper-large-v3",
969
- "large-v1": "whisper-large-v1",
970
- "large-v2": "whisper-large-v2",
971
- "large-v3": "whisper-large-v3"
972
- };
973
- export const tokenizerPackageName = "whisper-tokenizer";
974
- const vocabCharacterSetLookup = {
975
- "!": 33, "\"": 34, "#": 35, "$": 36, "%": 37, "&": 38, "'": 39, "(": 40, ")": 41, "*": 42, "+": 43, ",": 44, "-": 45, ".": 46, "/": 47, "0": 48, "1": 49, "2": 50, "3": 51, "4": 52, "5": 53, "6": 54,
976
- "7": 55, "8": 56, "9": 57, ":": 58, ";": 59, "<": 60, "=": 61, ">": 62, "?": 63, "@": 64, "A": 65, "B": 66, "C": 67, "D": 68, "E": 69, "F": 70, "G": 71, "H": 72, "I": 73, "J": 74, "K": 75, "L": 76, "M": 77, "N": 78, "O": 79, "P": 80, "Q": 81, "R": 82, "S": 83, "T": 84, "U": 85, "V": 86, "W": 87, "X": 88, "Y": 89, "Z": 90, "[": 91, "\\": 92, "]": 93, "^": 94, "_": 95, "`": 96, "a": 97, "b": 98, "c": 99, "d": 100, "e": 101, "f": 102, "g": 103, "h": 104, "i": 105, "j": 106, "k": 107, "l": 108, "m": 109, "n": 110, "o": 111, "p": 112, "q": 113, "r": 114, "s": 115, "t": 116, "u": 117, "v": 118, "w": 119, "x": 120, "y": 121, "z": 122, "{": 123, "|": 124, "}": 125, "~": 126, "¡": 161, "¢": 162, "£": 163, "¤": 164, "¥": 165, "¦": 166, "§": 167, "¨": 168, "©": 169, "ª": 170, "«": 171, "¬": 172, "®": 174, "¯": 175, "°": 176, "±": 177, "²": 178, "³": 179, "´": 180, "µ": 181, "¶": 182, "·": 183, "¸": 184, "¹": 185, "º": 186, "»": 187, "¼": 188, "½": 189, "¾": 190, "¿": 191, "À": 192, "Á": 193, "Â": 194, "Ã": 195, "Ä": 196, "Å": 197, "Æ": 198, "Ç": 199, "È": 200, "É": 201, "Ê": 202, "Ë": 203, "Ì": 204, "Í": 205, "Î": 206, "Ï": 207, "Ð": 208, "Ñ": 209, "Ò": 210, "Ó": 211, "Ô": 212, "Õ": 213, "Ö": 214, "×": 215, "Ø": 216, "Ù": 217, "Ú": 218, "Û": 219, "Ü": 220, "Ý": 221, "Þ": 222, "ß": 223, "à": 224, "á": 225, "â": 226, "ã": 227, "ä": 228, "å": 229, "æ": 230, "ç": 231, "è": 232, "é": 233, "ê": 234, "ë": 235, "ì": 236, "í": 237, "î": 238, "ï": 239, "ð": 240, "ñ": 241, "ò": 242, "ó": 243, "ô": 244, "õ": 245, "ö": 246, "÷": 247, "ø": 248, "ù": 249, "ú": 250, "û": 251, "ü": 252, "ý": 253, "þ": 254, "ÿ": 255, "Ā": 0, "ā": 1, "Ă": 2, "ă": 3, "Ą": 4, "ą": 5, "Ć": 6, "ć": 7, "Ĉ": 8, "ĉ": 9, "Ċ": 10, "ċ": 11, "Č": 12, "č": 13, "Ď": 14, "ď": 15, "Đ": 16, "đ": 17, "Ē": 18, "ē": 19, "Ĕ": 20, "ĕ": 21, "Ė": 22, "ė": 23, "Ę": 24, "ę": 25, "Ě": 26, "ě": 27, "Ĝ": 28, "ĝ": 29, "Ğ": 30, "ğ": 31, "Ġ": 32, "ġ": 127, "Ģ": 128, "ģ": 129, "Ĥ": 130, "ĥ": 131, "Ħ": 132, "ħ": 133, "Ĩ": 134, "ĩ": 135, "Ī": 136, "ī": 137, "Ĭ": 138, "ĭ": 139, "Į": 140, "į": 141, "İ": 142, "ı": 143, "IJ": 144, "ij": 145, "Ĵ": 146, "ĵ": 147, "Ķ": 148, "ķ": 149, "ĸ": 150, "Ĺ": 151, "ĺ": 152, "Ļ": 153, "ļ": 154, "Ľ": 155, "ľ": 156, "Ŀ": 157, "ŀ": 158, "Ł": 159, "ł": 160, "Ń": 173
1105
+ 'tiny': 'whisper-tiny',
1106
+ 'tiny.en': 'whisper-tiny.en',
1107
+ 'base': 'whisper-base',
1108
+ 'base.en': 'whisper-base.en',
1109
+ 'small': 'whisper-small',
1110
+ 'small.en': 'whisper-small.en',
1111
+ 'medium': 'whisper-medium',
1112
+ 'medium.en': 'whisper-medium.en',
1113
+ 'large': 'whisper-large-v3',
1114
+ 'large-v1': 'whisper-large-v1',
1115
+ 'large-v2': 'whisper-large-v2',
1116
+ 'large-v3': 'whisper-large-v3'
977
1117
  };
1118
+ export const tokenizerPackageName = 'whisper-tokenizer';
978
1119
  const languageIdLookup = {
979
- "en": 0,
980
- "zh": 1,
981
- "de": 2,
982
- "es": 3,
983
- "ru": 4,
984
- "ko": 5,
985
- "fr": 6,
986
- "ja": 7,
987
- "pt": 8,
988
- "tr": 9,
989
- "pl": 10,
990
- "ca": 11,
991
- "nl": 12,
992
- "ar": 13,
993
- "sv": 14,
994
- "it": 15,
995
- "id": 16,
996
- "hi": 17,
997
- "fi": 18,
998
- "vi": 19,
999
- "iw": 20,
1000
- "uk": 21,
1001
- "el": 22,
1002
- "ms": 23,
1003
- "cs": 24,
1004
- "ro": 25,
1005
- "da": 26,
1006
- "hu": 27,
1007
- "ta": 28,
1008
- "no": 29,
1009
- "th": 30,
1010
- "ur": 31,
1011
- "hr": 32,
1012
- "bg": 33,
1013
- "lt": 34,
1014
- "la": 35,
1015
- "mi": 36,
1016
- "ml": 37,
1017
- "cy": 38,
1018
- "sk": 39,
1019
- "te": 40,
1020
- "fa": 41,
1021
- "lv": 42,
1022
- "bn": 43,
1023
- "sr": 44,
1024
- "az": 45,
1025
- "sl": 46,
1026
- "kn": 47,
1027
- "et": 48,
1028
- "mk": 49,
1029
- "br": 50,
1030
- "eu": 51,
1031
- "is": 52,
1032
- "hy": 53,
1033
- "ne": 54,
1034
- "mn": 55,
1035
- "bs": 56,
1036
- "kk": 57,
1037
- "sq": 58,
1038
- "sw": 59,
1039
- "gl": 60,
1040
- "mr": 61,
1041
- "pa": 62,
1042
- "si": 63,
1043
- "km": 64,
1044
- "sn": 65,
1045
- "yo": 66,
1046
- "so": 67,
1047
- "af": 68,
1048
- "oc": 69,
1049
- "ka": 70,
1050
- "be": 71,
1051
- "tg": 72,
1052
- "sd": 73,
1053
- "gu": 74,
1054
- "am": 75,
1055
- "yi": 76,
1056
- "lo": 77,
1057
- "uz": 78,
1058
- "fo": 79,
1059
- "ht": 80,
1060
- "ps": 81,
1061
- "tk": 82,
1062
- "nn": 83,
1063
- "mt": 84,
1064
- "sa": 85,
1065
- "lb": 86,
1066
- "my": 87,
1067
- "bo": 88,
1068
- "tl": 89,
1069
- "mg": 90,
1070
- "as": 91,
1071
- "tt": 92,
1072
- "haw": 93,
1073
- "ln": 94,
1074
- "ha": 95,
1075
- "ba": 96,
1076
- "jw": 97,
1077
- "su": 98,
1120
+ 'en': 0,
1121
+ 'zh': 1,
1122
+ 'de': 2,
1123
+ 'es': 3,
1124
+ 'ru': 4,
1125
+ 'ko': 5,
1126
+ 'fr': 6,
1127
+ 'ja': 7,
1128
+ 'pt': 8,
1129
+ 'tr': 9,
1130
+ 'pl': 10,
1131
+ 'ca': 11,
1132
+ 'nl': 12,
1133
+ 'ar': 13,
1134
+ 'sv': 14,
1135
+ 'it': 15,
1136
+ 'id': 16,
1137
+ 'hi': 17,
1138
+ 'fi': 18,
1139
+ 'vi': 19,
1140
+ 'iw': 20,
1141
+ 'uk': 21,
1142
+ 'el': 22,
1143
+ 'ms': 23,
1144
+ 'cs': 24,
1145
+ 'ro': 25,
1146
+ 'da': 26,
1147
+ 'hu': 27,
1148
+ 'ta': 28,
1149
+ 'no': 29,
1150
+ 'th': 30,
1151
+ 'ur': 31,
1152
+ 'hr': 32,
1153
+ 'bg': 33,
1154
+ 'lt': 34,
1155
+ 'la': 35,
1156
+ 'mi': 36,
1157
+ 'ml': 37,
1158
+ 'cy': 38,
1159
+ 'sk': 39,
1160
+ 'te': 40,
1161
+ 'fa': 41,
1162
+ 'lv': 42,
1163
+ 'bn': 43,
1164
+ 'sr': 44,
1165
+ 'az': 45,
1166
+ 'sl': 46,
1167
+ 'kn': 47,
1168
+ 'et': 48,
1169
+ 'mk': 49,
1170
+ 'br': 50,
1171
+ 'eu': 51,
1172
+ 'is': 52,
1173
+ 'hy': 53,
1174
+ 'ne': 54,
1175
+ 'mn': 55,
1176
+ 'bs': 56,
1177
+ 'kk': 57,
1178
+ 'sq': 58,
1179
+ 'sw': 59,
1180
+ 'gl': 60,
1181
+ 'mr': 61,
1182
+ 'pa': 62,
1183
+ 'si': 63,
1184
+ 'km': 64,
1185
+ 'sn': 65,
1186
+ 'yo': 66,
1187
+ 'so': 67,
1188
+ 'af': 68,
1189
+ 'oc': 69,
1190
+ 'ka': 70,
1191
+ 'be': 71,
1192
+ 'tg': 72,
1193
+ 'sd': 73,
1194
+ 'gu': 74,
1195
+ 'am': 75,
1196
+ 'yi': 76,
1197
+ 'lo': 77,
1198
+ 'uz': 78,
1199
+ 'fo': 79,
1200
+ 'ht': 80,
1201
+ 'ps': 81,
1202
+ 'tk': 82,
1203
+ 'nn': 83,
1204
+ 'mt': 84,
1205
+ 'sa': 85,
1206
+ 'lb': 86,
1207
+ 'my': 87,
1208
+ 'bo': 88,
1209
+ 'tl': 89,
1210
+ 'mg': 90,
1211
+ 'as': 91,
1212
+ 'tt': 92,
1213
+ 'haw': 93,
1214
+ 'ln': 94,
1215
+ 'ha': 95,
1216
+ 'ba': 96,
1217
+ 'jw': 97,
1218
+ 'su': 98,
1078
1219
  };
1079
1220
  const alignmentHeadsIndexes = {
1080
- "tiny.en": [6, 12, 17, 18, 19, 20, 21, 22],
1081
- "tiny": [14, 18, 20, 21, 22, 23],
1082
- "base.en": [27, 39, 41, 45, 47],
1083
- "base": [25, 34, 35, 39, 41, 42, 44, 46],
1084
- "small.en": [78, 84, 87, 92, 98, 101, 103, 108, 112, 116, 118, 120, 121, 122, 123, 126, 131, 134, 136],
1085
- "small": [63, 69, 96, 100, 103, 104, 108, 115, 117, 125],
1086
- "medium.en": [180, 225, 236, 238, 244, 256, 260, 265, 284, 286, 295, 298, 303, 320, 323, 329, 334, 348],
1087
- "medium": [223, 244, 255, 257, 320, 372],
1088
- "large-v1": [199, 222, 224, 237, 447, 451, 457, 462, 475],
1089
- "large-v2": [212, 277, 331, 332, 333, 355, 356, 364, 371, 379, 391, 422, 423, 443, 449, 452, 465, 467, 473, 505, 521, 532, 555],
1090
- "large-v3": [212, 277, 331, 332, 333, 355, 356, 364, 371, 379, 391, 422, 423, 443, 449, 452, 465, 467, 473, 505, 521, 532, 555], // Temporary (may not be correct)
1091
- "large": [212, 277, 331, 332, 333, 355, 356, 364, 371, 379, 391, 422, 423, 443, 449, 452, 465, 467, 473, 505, 521, 532, 555],
1221
+ 'tiny.en': [6, 12, 17, 18, 19, 20, 21, 22],
1222
+ 'tiny': [14, 18, 20, 21, 22, 23],
1223
+ 'base.en': [27, 39, 41, 45, 47],
1224
+ 'base': [25, 34, 35, 39, 41, 42, 44, 46],
1225
+ 'small.en': [78, 84, 87, 92, 98, 101, 103, 108, 112, 116, 118, 120, 121, 122, 123, 126, 131, 134, 136],
1226
+ 'small': [63, 69, 96, 100, 103, 104, 108, 115, 117, 125],
1227
+ 'medium.en': [180, 225, 236, 238, 244, 256, 260, 265, 284, 286, 295, 298, 303, 320, 323, 329, 334, 348],
1228
+ 'medium': [223, 244, 255, 257, 320, 372],
1229
+ 'large-v1': [199, 222, 224, 237, 447, 451, 457, 462, 475],
1230
+ 'large-v2': [212, 277, 331, 332, 333, 355, 356, 364, 371, 379, 391, 422, 423, 443, 449, 452, 465, 467, 473, 505, 521, 532, 555],
1231
+ 'large-v3': [212, 277, 331, 332, 333, 355, 356, 364, 371, 379, 391, 422, 423, 443, 449, 452, 465, 467, 473, 505, 521, 532, 555], // Temporary (may not be correct)
1232
+ 'large': [212, 277, 331, 332, 333, 355, 356, 364, 371, 379, 391, 422, 423, 443, 449, 452, 465, 467, 473, 505, 521, 532, 555],
1092
1233
  };
1093
- export const whisperOptionsDefaults = {
1234
+ export const defaultWhisperOptions = {
1094
1235
  model: undefined,
1095
1236
  temperature: 0.1,
1096
1237
  prompt: undefined,
1097
1238
  topCandidateCount: 5,
1098
1239
  punctuationThreshold: 0.2,
1099
- autoPromptParts: true
1240
+ autoPromptParts: true,
1241
+ maxTokensPerPart: 250,
1242
+ suppressRepetition: true,
1243
+ seed: undefined,
1244
+ decodeTimestampTokens: false,
1100
1245
  };
1101
1246
  //# sourceMappingURL=WhisperSTT.js.map