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.
- package/README.md +15 -14
- package/data/schemas/options.json +398 -111
- package/dist/alignment/DTWMfccSequenceAlignment.d.ts +1 -1
- package/dist/alignment/DTWMfccSequenceAlignment.js +8 -8
- package/dist/alignment/DTWSequenceAlignment.d.ts +1 -1
- package/dist/alignment/DTWSequenceAlignment.js +1 -1
- package/dist/alignment/DTWSequenceAlignmentWindowed.d.ts +1 -1
- package/dist/alignment/DTWSequenceAlignmentWindowed.js +2 -2
- package/dist/alignment/LevenshteinSequenceAlignment.d.ts +1 -1
- package/dist/alignment/LevenshteinSequenceAlignment.js +1 -1
- package/dist/alignment/SpeechAlignment.d.ts +9 -10
- package/dist/alignment/SpeechAlignment.js +136 -105
- package/dist/alignment/SpeechAlignment.js.map +1 -1
- package/dist/api/API.d.ts +13 -12
- package/dist/api/API.js +14 -13
- package/dist/api/API.js.map +1 -1
- package/dist/api/APIOptions.d.ts +5 -4
- package/dist/api/Alignment.d.ts +15 -9
- package/dist/api/Alignment.js +88 -74
- package/dist/api/Alignment.js.map +1 -1
- package/dist/api/Common.js +1 -1
- package/dist/api/Denoising.d.ts +6 -6
- package/dist/api/Denoising.js +23 -23
- package/dist/api/Denoising.js.map +1 -1
- package/dist/api/LanguageDetection.d.ts +19 -12
- package/dist/api/LanguageDetection.js +88 -38
- package/dist/api/LanguageDetection.js.map +1 -1
- package/dist/api/Recognition.d.ts +16 -6
- package/dist/api/Recognition.js +129 -55
- package/dist/api/Recognition.js.map +1 -1
- package/dist/api/SourceSeparation.d.ts +17 -0
- package/dist/api/SourceSeparation.js +61 -0
- package/dist/api/SourceSeparation.js.map +1 -0
- package/dist/api/Synthesis.d.ts +18 -18
- package/dist/api/Synthesis.js +191 -164
- package/dist/api/Synthesis.js.map +1 -1
- package/dist/api/Translation.d.ts +19 -8
- package/dist/api/Translation.js +132 -35
- package/dist/api/Translation.js.map +1 -1
- package/dist/api/Vad.d.ts +10 -5
- package/dist/api/Vad.js +76 -38
- package/dist/api/Vad.js.map +1 -1
- package/dist/audio/AudioBufferConversion.d.ts +1 -1
- package/dist/audio/AudioBufferConversion.js +4 -4
- package/dist/audio/AudioPlayer.d.ts +1 -1
- package/dist/audio/AudioPlayer.js +26 -26
- package/dist/audio/AudioPlayer.js.map +1 -1
- package/dist/audio/AudioRecorder.d.ts +1 -1
- package/dist/audio/AudioRecorder.js +5 -5
- package/dist/audio/AudioUtilities.d.ts +13 -9
- package/dist/audio/AudioUtilities.js +86 -24
- package/dist/audio/AudioUtilities.js.map +1 -1
- package/dist/cli/CLI.d.ts +3 -3
- package/dist/cli/CLI.js +271 -162
- package/dist/cli/CLI.js.map +1 -1
- package/dist/cli/CLIConfigFile.js +8 -8
- package/dist/cli/CLILauncher.js +6 -6
- package/dist/cli/CLIOptionsSchema.js +2 -2
- package/dist/cli/CLIParser.js +5 -5
- package/dist/cli/CLIStarter.js +4 -4
- package/dist/codecs/FFMpegTranscoder.d.ts +2 -2
- package/dist/codecs/FFMpegTranscoder.js +37 -37
- package/dist/codecs/FFMpegTranscoder.js.map +1 -1
- package/dist/codecs/TIMITCodec.js +5 -5
- package/dist/codecs/WaveCodec.d.ts +1 -1
- package/dist/codecs/WaveCodec.js +22 -22
- package/dist/denoising/RNNoise.d.ts +1 -1
- package/dist/denoising/RNNoise.js +9 -9
- package/dist/dsp/BiquadFilter.d.ts +3 -2
- package/dist/dsp/BiquadFilter.js +18 -11
- package/dist/dsp/BiquadFilter.js.map +1 -1
- package/dist/dsp/DecayingPeakEstimator.d.ts +16 -0
- package/dist/dsp/DecayingPeakEstimator.js +23 -0
- package/dist/dsp/DecayingPeakEstimator.js.map +1 -0
- package/dist/dsp/FFT.d.ts +8 -4
- package/dist/dsp/FFT.js +76 -30
- package/dist/dsp/FFT.js.map +1 -1
- package/dist/dsp/KWeightingFilter.d.ts +9 -0
- package/dist/dsp/KWeightingFilter.js +40 -0
- package/dist/dsp/KWeightingFilter.js.map +1 -0
- package/dist/dsp/LoudnessEstimator.d.ts +21 -0
- package/dist/dsp/LoudnessEstimator.js +47 -0
- package/dist/dsp/LoudnessEstimator.js.map +1 -0
- package/dist/dsp/MFCC.d.ts +2 -2
- package/dist/dsp/MFCC.js +15 -15
- package/dist/dsp/MelSpectogram.d.ts +1 -1
- package/dist/dsp/MelSpectogram.js +6 -6
- package/dist/dsp/Rubberband.d.ts +11 -11
- package/dist/dsp/Rubberband.js +27 -27
- package/dist/dsp/Sonic.d.ts +1 -1
- package/dist/dsp/Sonic.js +3 -3
- package/dist/dsp/SpeexResampler.d.ts +1 -1
- package/dist/dsp/SpeexResampler.js +2 -2
- package/dist/math/VectorMath.d.ts +12 -8
- package/dist/math/VectorMath.js +35 -32
- package/dist/math/VectorMath.js.map +1 -1
- package/dist/nlp/ChineseSegmentation.js +2 -2
- package/dist/nlp/CompromiseNLP.js +3 -3
- package/dist/nlp/EspeakPhonemizer.js +30 -30
- package/dist/nlp/IPA.js +20 -20
- package/dist/nlp/JapaneseSegmentation.js +6 -6
- package/dist/nlp/Lexicon.d.ts +1 -1
- package/dist/nlp/Lexicon.js +7 -7
- package/dist/nlp/Segmentation.d.ts +3 -0
- package/dist/nlp/Segmentation.js +21 -14
- package/dist/nlp/Segmentation.js.map +1 -1
- package/dist/nlp/TextNormalizer.js +16 -16
- package/dist/recognition/AmazonTranscribeSTT.d.ts +2 -2
- package/dist/recognition/AmazonTranscribeSTT.js +13 -14
- package/dist/recognition/AmazonTranscribeSTT.js.map +1 -1
- package/dist/recognition/AzureCognitiveServicesSTT.js +5 -6
- package/dist/recognition/AzureCognitiveServicesSTT.js.map +1 -1
- package/dist/recognition/GoogleCloudSTT.d.ts +3 -3
- package/dist/recognition/GoogleCloudSTT.js +18 -18
- package/dist/recognition/OpenAICloudSTT.d.ts +19 -0
- package/dist/recognition/OpenAICloudSTT.js +81 -0
- package/dist/recognition/OpenAICloudSTT.js.map +1 -0
- package/dist/recognition/SileroSTT.d.ts +2 -2
- package/dist/recognition/SileroSTT.js +25 -25
- package/dist/recognition/VoskSTT.d.ts +2 -2
- package/dist/recognition/VoskSTT.js +8 -8
- package/dist/recognition/WhisperCppSTT.d.ts +88 -0
- package/dist/recognition/WhisperCppSTT.js +332 -0
- package/dist/recognition/WhisperCppSTT.js.map +1 -0
- package/dist/recognition/WhisperSTT.d.ts +49 -25
- package/dist/recognition/WhisperSTT.js +626 -481
- package/dist/recognition/WhisperSTT.js.map +1 -1
- package/dist/server/Client.d.ts +1 -1
- package/dist/server/Client.js +22 -22
- package/dist/server/Server.js +9 -9
- package/dist/server/Server.js.map +1 -1
- package/dist/server/Worker.d.ts +22 -22
- package/dist/server/Worker.js +36 -36
- package/dist/server/Worker.js.map +1 -1
- package/dist/server/WorkerStarter.js +2 -2
- package/dist/source-separation/MDXNetSourceSeparation.d.ts +11 -0
- package/dist/source-separation/MDXNetSourceSeparation.js +161 -0
- package/dist/source-separation/MDXNetSourceSeparation.js.map +1 -0
- package/dist/speech-language-detection/SileroLanguageDetection.d.ts +1 -1
- package/dist/speech-language-detection/SileroLanguageDetection.js +7 -7
- package/dist/subtitles/Subtitles.d.ts +10 -0
- package/dist/subtitles/Subtitles.js +2 -2
- package/dist/subtitles/Subtitles.js.map +1 -1
- package/dist/synthesis/AwsPollyTTS.d.ts +1 -1
- package/dist/synthesis/AwsPollyTTS.js +12 -12
- package/dist/synthesis/AzureCognitiveServicesTTS.js +7 -7
- package/dist/synthesis/CoquiServerTTS.js +10 -10
- package/dist/synthesis/CoquiServerTTS.js.map +1 -1
- package/dist/synthesis/ElevenlabsTTS.d.ts +23 -0
- package/dist/synthesis/ElevenlabsTTS.js +103 -0
- package/dist/synthesis/ElevenlabsTTS.js.map +1 -0
- package/dist/synthesis/EspeakTTS.d.ts +6 -5
- package/dist/synthesis/EspeakTTS.js +81 -69
- package/dist/synthesis/EspeakTTS.js.map +1 -1
- package/dist/synthesis/FliteTTS.d.ts +3 -3
- package/dist/synthesis/FliteTTS.js +154 -154
- package/dist/synthesis/FliteTTS.js.map +1 -1
- package/dist/synthesis/GoogleCloudTTS.d.ts +3 -3
- package/dist/synthesis/GoogleCloudTTS.js +17 -17
- package/dist/synthesis/GoogleCloudTTS.js.map +1 -1
- package/dist/synthesis/GoogleTranslateTTS.d.ts +1 -1
- package/dist/synthesis/GoogleTranslateTTS.js +103 -103
- package/dist/synthesis/MicrosoftEdgeTTS.d.ts +2 -2
- package/dist/synthesis/MicrosoftEdgeTTS.js +74 -74
- package/dist/synthesis/OpenAICloudTTS.d.ts +13 -0
- package/dist/synthesis/OpenAICloudTTS.js +169 -0
- package/dist/synthesis/OpenAICloudTTS.js.map +1 -0
- package/dist/synthesis/SamTTS.js +3 -3
- package/dist/synthesis/SapiTTS.d.ts +3 -3
- package/dist/synthesis/SapiTTS.js +26 -26
- package/dist/synthesis/StreamlabsPollyTTS.d.ts +2 -2
- package/dist/synthesis/StreamlabsPollyTTS.js +27 -27
- package/dist/synthesis/SvoxPicoTTS.d.ts +2 -2
- package/dist/synthesis/SvoxPicoTTS.js +65 -65
- package/dist/synthesis/SvoxPicoTTS.js.map +1 -1
- package/dist/synthesis/VitsTTS.d.ts +3 -3
- package/dist/synthesis/VitsTTS.js +378 -378
- package/dist/synthesis/VitsTTS.js.map +1 -1
- package/dist/tests/Test.js +2 -2
- package/dist/utilities/Compression.d.ts +5 -0
- package/dist/utilities/Compression.js +29 -13
- package/dist/utilities/Compression.js.map +1 -1
- package/dist/utilities/FileDownloader.d.ts +1 -1
- package/dist/utilities/FileDownloader.js +16 -16
- package/dist/utilities/FileSystem.js +7 -7
- package/dist/utilities/Locale.d.ts +7 -7
- package/dist/utilities/Locale.js +15 -15
- package/dist/utilities/Logger.js +3 -3
- package/dist/utilities/ObjectUtilities.js +19 -19
- package/dist/utilities/OpenPromise.js +2 -2
- package/dist/utilities/OpenPromise.js.map +1 -1
- package/dist/utilities/PackageManager.js +31 -0
- package/dist/utilities/PackageManager.js.map +1 -1
- package/dist/utilities/PathUtilities.js +8 -8
- package/dist/utilities/RandomGenerator.js +2 -2
- package/dist/utilities/SmoothEstimator.d.ts +8 -0
- package/dist/utilities/SmoothEstimator.js +25 -0
- package/dist/utilities/SmoothEstimator.js.map +1 -0
- package/dist/utilities/TarballMaker.js +8 -8
- package/dist/utilities/Timeline.d.ts +3 -2
- package/dist/utilities/Timeline.js +11 -11
- package/dist/utilities/Timeline.js.map +1 -1
- package/dist/utilities/Timer.js +4 -4
- package/dist/utilities/Utilities.d.ts +4 -0
- package/dist/utilities/Utilities.js +38 -15
- package/dist/utilities/Utilities.js.map +1 -1
- package/dist/utilities/WasmMemoryManager.js +7 -7
- package/dist/utilities/WebReader.js +23 -23
- package/dist/utilities/WikipediaReader.js +2 -2
- package/dist/voice-activity-detection/AdaptiveGateVAD.d.ts +28 -0
- package/dist/voice-activity-detection/AdaptiveGateVAD.js +138 -0
- package/dist/voice-activity-detection/AdaptiveGateVAD.js.map +1 -0
- package/dist/voice-activity-detection/SileroVAD.d.ts +1 -1
- package/dist/voice-activity-detection/SileroVAD.js +5 -5
- package/dist/voice-activity-detection/SileroVAD.js.map +1 -1
- package/dist/voice-activity-detection/WebRtcVAD.d.ts +1 -1
- package/dist/voice-activity-detection/WebRtcVAD.js +4 -4
- package/docs/API.md +29 -11
- package/docs/CLI.md +31 -7
- package/docs/Contributing.md +38 -0
- package/docs/Development.md +93 -19
- package/docs/Engines.md +28 -16
- package/docs/Licenses.md +4 -1
- package/docs/Options.md +158 -78
- package/docs/Releases.md +262 -0
- package/docs/Server.md +7 -7
- package/docs/Tasklist.md +95 -76
- package/docs/Technical.md +4 -4
- package/package.json +13 -14
- package/src/alignment/DTWMfccSequenceAlignment.ts +9 -9
- package/src/alignment/DTWSequenceAlignment.ts +2 -2
- package/src/alignment/DTWSequenceAlignmentWindowed.ts +3 -3
- package/src/alignment/LevenshteinSequenceAlignment.ts +2 -2
- package/src/alignment/SpeechAlignment.ts +204 -119
- package/src/api/API.ts +14 -13
- package/src/api/APIOptions.ts +12 -11
- package/src/api/Alignment.ts +147 -90
- package/src/api/Common.ts +1 -1
- package/src/api/Denoising.ts +28 -28
- package/src/api/LanguageDetection.ts +135 -48
- package/src/api/Recognition.ts +198 -59
- package/src/api/SourceSeparation.ts +99 -0
- package/src/api/Synthesis.ts +217 -181
- package/src/api/Translation.ts +193 -40
- package/src/api/Vad.ts +110 -41
- package/src/audio/AudioBufferConversion.ts +4 -4
- package/src/audio/AudioPlayer.ts +27 -27
- package/src/audio/AudioRecorder.ts +5 -5
- package/src/audio/AudioUtilities.ts +107 -24
- package/src/cli/CLI.ts +313 -164
- package/src/cli/CLIConfigFile.ts +8 -8
- package/src/cli/CLILauncher.ts +6 -6
- package/src/cli/CLIOptionsSchema.ts +2 -2
- package/src/cli/CLIParser.ts +5 -5
- package/src/cli/CLIStarter.ts +4 -4
- package/src/codecs/FFMpegTranscoder.ts +38 -38
- package/src/codecs/TIMITCodec.ts +5 -5
- package/src/codecs/WaveCodec.ts +22 -22
- package/src/denoising/RNNoise.ts +9 -9
- package/src/dsp/BiquadFilter.ts +19 -11
- package/src/dsp/DecayingPeakEstimator.ts +35 -0
- package/src/dsp/FFT.ts +103 -35
- package/src/dsp/KWeightingFilter.ts +43 -0
- package/src/dsp/LoudnessEstimator.ts +74 -0
- package/src/dsp/MFCC.ts +15 -15
- package/src/dsp/MelSpectogram.ts +7 -7
- package/src/dsp/Rubberband.ts +38 -38
- package/src/dsp/Sonic.ts +4 -4
- package/src/dsp/SpeexResampler.ts +2 -2
- package/src/math/VectorMath.ts +42 -33
- package/src/nlp/ChineseSegmentation.ts +3 -3
- package/src/nlp/CompromiseNLP.ts +3 -3
- package/src/nlp/EspeakPhonemizer.ts +30 -30
- package/src/nlp/IPA.ts +20 -20
- package/src/nlp/JapaneseSegmentation.ts +6 -6
- package/src/nlp/Lexicon.ts +8 -8
- package/src/nlp/Segmentation.ts +23 -14
- package/src/nlp/TextNormalizer.ts +16 -16
- package/src/recognition/AmazonTranscribeSTT.ts +16 -17
- package/src/recognition/AzureCognitiveServicesSTT.ts +8 -6
- package/src/recognition/GoogleCloudSTT.ts +21 -21
- package/src/recognition/OpenAICloudSTT.ts +142 -0
- package/src/recognition/SileroSTT.ts +26 -26
- package/src/recognition/VoskSTT.ts +10 -10
- package/src/recognition/WhisperCppSTT.ts +555 -0
- package/src/recognition/WhisperSTT.ts +760 -507
- package/src/server/Client.ts +23 -23
- package/src/server/Server.ts +9 -9
- package/src/server/Worker.ts +53 -53
- package/src/server/WorkerStarter.ts +2 -2
- package/src/source-separation/MDXNetSourceSeparation.ts +228 -0
- package/src/speech-language-detection/SileroLanguageDetection.ts +8 -8
- package/src/subtitles/Subtitles.ts +3 -3
- package/src/synthesis/AwsPollyTTS.ts +14 -14
- package/src/synthesis/AzureCognitiveServicesTTS.ts +10 -10
- package/src/synthesis/CoquiServerTTS.ts +10 -10
- package/src/synthesis/ElevenlabsTTS.ts +137 -0
- package/src/synthesis/EspeakTTS.ts +90 -71
- package/src/synthesis/FliteTTS.ts +157 -157
- package/src/synthesis/GoogleCloudTTS.ts +19 -19
- package/src/synthesis/GoogleTranslateTTS.ts +104 -104
- package/src/synthesis/MicrosoftEdgeTTS.ts +80 -80
- package/src/synthesis/OpenAICloudTTS.ts +196 -0
- package/src/synthesis/SamTTS.ts +3 -3
- package/src/synthesis/SapiTTS.ts +29 -29
- package/src/synthesis/StreamlabsPollyTTS.ts +29 -29
- package/src/synthesis/SvoxPicoTTS.ts +67 -67
- package/src/synthesis/VitsTTS.ts +380 -380
- package/src/tests/Test.ts +4 -4
- package/src/utilities/Compression.ts +34 -13
- package/src/utilities/FileDownloader.ts +19 -19
- package/src/utilities/FileSystem.ts +7 -7
- package/src/utilities/Locale.ts +22 -22
- package/src/utilities/Logger.ts +4 -4
- package/src/utilities/ObjectUtilities.ts +19 -19
- package/src/utilities/OpenPromise.ts +2 -2
- package/src/utilities/PackageManager.ts +40 -0
- package/src/utilities/PathUtilities.ts +8 -8
- package/src/utilities/RandomGenerator.ts +3 -3
- package/src/utilities/SmoothEstimator.ts +35 -0
- package/src/utilities/TarballMaker.ts +9 -9
- package/src/utilities/Timeline.ts +15 -13
- package/src/utilities/Timer.ts +4 -4
- package/src/utilities/Utilities.ts +49 -15
- package/src/utilities/WasmMemoryManager.ts +7 -7
- package/src/utilities/WebReader.ts +23 -23
- package/src/utilities/WikipediaReader.ts +2 -2
- package/src/voice-activity-detection/AdaptiveGateVAD.ts +202 -0
- package/src/voice-activity-detection/SileroVAD.ts +5 -5
- package/src/voice-activity-detection/WebRtcVAD.ts +5 -5
- package/dist/synthesis/ElevenLabsTTS.d.ts +0 -8
- package/dist/synthesis/ElevenLabsTTS.js +0 -82
- package/dist/synthesis/ElevenLabsTTS.js.map +0 -1
- 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
|
|
4
|
-
import { clip,
|
|
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 {
|
|
7
|
+
import { extendDeep } from '../utilities/ObjectUtilities.js';
|
|
9
8
|
import { getRawAudioDuration } from '../audio/AudioUtilities.js';
|
|
10
|
-
import {
|
|
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,
|
|
16
|
+
export async function recognize(sourceRawAudio, modelName, modelDir, task, sourceLanguage, options) {
|
|
18
17
|
if (sourceRawAudio.sampleRate != 16000) {
|
|
19
|
-
throw new Error(
|
|
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 (
|
|
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
|
-
|
|
29
|
-
|
|
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,
|
|
38
|
+
export async function align(sourceRawAudio, referenceText, modelName, modelDir, sourceLanguage) {
|
|
34
39
|
if (sourceRawAudio.sampleRate != 16000) {
|
|
35
|
-
throw new Error(
|
|
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 (
|
|
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
|
|
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,
|
|
53
|
+
export async function detectLanguage(sourceRawAudio, modelName, modelDir, temperature) {
|
|
50
54
|
if (sourceRawAudio.sampleRate != 16000) {
|
|
51
|
-
throw new Error(
|
|
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
|
|
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
|
-
|
|
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
|
|
80
|
-
constructor(modelName, modelDir,
|
|
81
|
-
this.modelDir = modelDir;
|
|
85
|
+
randomGen;
|
|
86
|
+
constructor(modelName, modelDir, rngSeed = 461845907) {
|
|
82
87
|
this.modelName = modelName;
|
|
83
|
-
this.
|
|
84
|
-
this.isMultiligualModel =
|
|
88
|
+
this.modelDir = modelDir;
|
|
89
|
+
this.isMultiligualModel = isMultilingualModel(this.modelName);
|
|
85
90
|
if (this.isMultiligualModel) {
|
|
86
91
|
this.tokenConfig = {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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
|
-
|
|
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
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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
|
-
|
|
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
|
|
137
|
+
async initializeTokenizerIfNeeded() {
|
|
138
|
+
if (this.tiktoken) {
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
109
141
|
const logger = new Logger();
|
|
110
|
-
await logger.startAsync(
|
|
111
|
-
const
|
|
112
|
-
const
|
|
113
|
-
|
|
114
|
-
const
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
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
|
-
|
|
128
|
-
|
|
129
|
-
const
|
|
130
|
-
|
|
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
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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
|
|
149
|
-
|
|
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 +
|
|
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
|
|
161
|
-
const isFinalPart = audioOffset +
|
|
219
|
+
const isFirstPart = audioOffset === 0;
|
|
220
|
+
const isFinalPart = audioOffset + maxAudioSamplesPerPart >= audioSamples.length;
|
|
162
221
|
let initialTokens = [];
|
|
163
222
|
if (isFirstPart && prompt) {
|
|
164
|
-
const promptTokens =
|
|
165
|
-
initialTokens = [this.tokenConfig.
|
|
223
|
+
const promptTokens = this.textToTokens(prompt);
|
|
224
|
+
initialTokens = [this.tokenConfig.startOfPromptToken, ...promptTokens];
|
|
166
225
|
}
|
|
167
|
-
else if (options.autoPromptParts &&
|
|
168
|
-
initialTokens = [this.tokenConfig.
|
|
226
|
+
else if (options.autoPromptParts && previousPartTextTokens.length > 0) {
|
|
227
|
+
initialTokens = [this.tokenConfig.startOfPromptToken, ...previousPartTextTokens];
|
|
169
228
|
}
|
|
170
|
-
initialTokens = [...initialTokens, ...this.
|
|
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
|
|
233
|
+
const lastTokenIsTimestamp = this.isTimestampToken(lastToken);
|
|
175
234
|
let audioEndOffset;
|
|
176
235
|
if (!isFinalPart && lastTokenIsTimestamp) {
|
|
177
|
-
const timePosition = (lastToken
|
|
236
|
+
const timePosition = this.timestampTokenToSeconds(lastToken);
|
|
178
237
|
audioEndOffset = audioOffset + Math.floor(timePosition * sampleRate);
|
|
179
238
|
}
|
|
180
239
|
else {
|
|
181
|
-
audioEndOffset = Math.min(audioOffset +
|
|
240
|
+
audioEndOffset = Math.min(audioOffset + maxAudioSamplesPerPart, audioSamples.length);
|
|
182
241
|
}
|
|
183
242
|
const segmentEndTime = audioEndOffset / sampleRate;
|
|
184
|
-
const segmentFrameCount =
|
|
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(
|
|
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.
|
|
196
|
-
timeline.push(...partTimeline);
|
|
252
|
+
const partTimeline = await this.getTokenTimelineFromAlignmentPath(alignmentPath, partTokens, segmentStartTime, segmentEndTime, decodedTokensConfidence);
|
|
197
253
|
audioOffset = audioEndOffset;
|
|
198
|
-
|
|
199
|
-
|
|
254
|
+
allDecodedTokens.push(...partTokens);
|
|
255
|
+
timeline.push(...partTimeline);
|
|
256
|
+
previousPartTextTokens = partTokens.filter(token => this.isTextToken(token));
|
|
200
257
|
logger.end();
|
|
201
258
|
}
|
|
202
|
-
|
|
203
|
-
|
|
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(
|
|
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 =
|
|
216
|
-
const initialTokens = this.
|
|
217
|
-
const
|
|
218
|
-
|
|
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(
|
|
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(
|
|
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.
|
|
229
|
-
timeline = this.
|
|
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(
|
|
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
|
-
|
|
242
|
-
|
|
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 = {
|
|
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[
|
|
252
|
-
const
|
|
253
|
-
const
|
|
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(
|
|
270
|
-
options = extendDeep(
|
|
271
|
-
const
|
|
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
|
|
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[
|
|
313
|
-
kvCacheTensor = decoderOutputs[
|
|
371
|
+
const logitsBuffer = decoderOutputs['logits'].data;
|
|
372
|
+
kvCacheTensor = decoderOutputs['output_kv_cache'];
|
|
314
373
|
// Compute logits
|
|
315
|
-
const resultLogits = splitFloat32Array(logitsBuffer, logitsBuffer.length / decoderOutputs[
|
|
316
|
-
const
|
|
317
|
-
const
|
|
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 <
|
|
320
|
-
const isWrongTokenForInitialState = isInitialState &&
|
|
321
|
-
|
|
322
|
-
const
|
|
323
|
-
const
|
|
324
|
-
if (
|
|
325
|
-
|
|
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
|
-
//
|
|
329
|
-
|
|
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[
|
|
393
|
+
decodedTokensCrossAttentionQKs.push(decoderOutputs['cross_attention_qks']);
|
|
352
394
|
decodedTokensConfidence.push(confidence);
|
|
353
395
|
}
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
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
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
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
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
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
|
-
|
|
408
|
-
if (
|
|
409
|
-
|
|
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
|
-
|
|
456
|
+
topCandidates = topCandidates.filter((candidate, index) => topCandidatesRepetitionScores[index] < thresholdRepetitionScore);
|
|
413
457
|
}
|
|
414
|
-
chosenTokenIndex = sortedTextTokenLogitsWithIndexes[chosenTokenIndexInTopLogits].index;
|
|
415
458
|
}
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
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
|
-
|
|
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
|
-
|
|
517
|
+
logger.write('\n');
|
|
434
518
|
logger.end();
|
|
435
519
|
// Return the tokens
|
|
436
|
-
return {
|
|
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[
|
|
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(
|
|
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(
|
|
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(
|
|
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[
|
|
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.
|
|
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
|
|
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:
|
|
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:
|
|
538
|
-
text:
|
|
627
|
+
type: 'segment',
|
|
628
|
+
text: '',
|
|
539
629
|
startTime: initialTimeOffset,
|
|
540
630
|
endTime: -1,
|
|
541
631
|
});
|
|
542
632
|
}
|
|
543
|
-
const tokenText = this.
|
|
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 =>
|
|
557
|
-
const isTimestamp = token
|
|
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
|
|
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 =
|
|
569
|
-
let segmentEndFrame =
|
|
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.
|
|
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
|
|
587
|
-
timeline.push(...
|
|
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
|
|
592
|
-
timeline.push(...
|
|
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
|
-
|
|
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 (
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
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
|
-
|
|
619
|
-
|
|
718
|
+
const groupText = this.tokensToText(group.map(entry => entry.id));
|
|
719
|
+
if (groupText === '') {
|
|
720
|
+
continue;
|
|
620
721
|
}
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
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
|
|
740
|
+
async getTokenTimelineFromAlignmentPath(alignmentPath, tokens, startTimeOffset, endTimeOffset, tokensConfidence, correctionAmount = 0.0) {
|
|
642
741
|
if (alignmentPath.length == 0) {
|
|
643
742
|
return [];
|
|
644
743
|
}
|
|
645
|
-
const
|
|
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.
|
|
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 (
|
|
661
|
-
|
|
756
|
+
if (tokenTimeline.length > 0) {
|
|
757
|
+
tokenTimeline[tokenTimeline.length - 1].endTime = startTime;
|
|
662
758
|
}
|
|
663
|
-
|
|
664
|
-
type:
|
|
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 (
|
|
672
|
-
|
|
768
|
+
if (tokenTimeline.length > 0) {
|
|
769
|
+
tokenTimeline[tokenTimeline.length - 1].endTime = endTimeOffset;
|
|
673
770
|
}
|
|
674
|
-
return
|
|
771
|
+
return tokenTimeline;
|
|
675
772
|
}
|
|
676
773
|
async findAlignmentPathFromQKs(qksTensors, tokens, segmentStartFrame, segmentEndFrame, headIndexes) {
|
|
677
774
|
const segmentFrameCount = segmentEndFrame - segmentStartFrame;
|
|
678
|
-
if (segmentFrameCount
|
|
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
|
-
|
|
761
|
-
|
|
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 } =
|
|
868
|
+
let { path } = alignDTWWindowed(tokenIndexes, frameIndexes, (tokenIndex, frameIndex) => {
|
|
771
869
|
return -frameMeansForToken[tokenIndex][frameIndex];
|
|
772
|
-
},
|
|
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 ==
|
|
876
|
+
if (modelName == 'tiny' || modelName == 'tiny.en') {
|
|
779
877
|
return [8, groupCount, length, 384];
|
|
780
878
|
}
|
|
781
|
-
else if (modelName ==
|
|
879
|
+
else if (modelName == 'base' || modelName == 'base.en') {
|
|
782
880
|
return [12, groupCount, length, 512];
|
|
783
881
|
}
|
|
784
|
-
else if (modelName ==
|
|
882
|
+
else if (modelName == 'small' || modelName == 'small.en') {
|
|
785
883
|
return [24, groupCount, length, 768];
|
|
786
884
|
}
|
|
787
|
-
else if (modelName ==
|
|
885
|
+
else if (modelName == 'medium' || modelName == 'medium.en') {
|
|
788
886
|
return [48, groupCount, length, 1024];
|
|
789
887
|
}
|
|
790
|
-
else if (modelName ==
|
|
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
|
-
|
|
798
|
-
const
|
|
799
|
-
let
|
|
895
|
+
getTextStartTokens(language, task, disableTimestamps = false) {
|
|
896
|
+
const startOfTextToken = this.tokenConfig.startOfTextToken;
|
|
897
|
+
let tokens;
|
|
800
898
|
if (this.isMultiligualModel) {
|
|
801
|
-
const languageToken =
|
|
802
|
-
const
|
|
803
|
-
|
|
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
|
-
|
|
904
|
+
tokens = [startOfTextToken];
|
|
809
905
|
}
|
|
810
906
|
if (disableTimestamps) {
|
|
811
|
-
|
|
907
|
+
tokens.push(this.tokenConfig.noTimestampsToken);
|
|
812
908
|
}
|
|
813
|
-
return
|
|
909
|
+
return tokens;
|
|
814
910
|
}
|
|
815
|
-
|
|
816
|
-
return
|
|
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
|
-
|
|
819
|
-
return
|
|
944
|
+
isNonTimestampToken(token) {
|
|
945
|
+
return token < this.tokenConfig.timestampTokensStart;
|
|
820
946
|
}
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
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
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
}
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
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
|
-
|
|
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 (
|
|
1070
|
+
if (modelName) {
|
|
1071
|
+
modelName = normalizeWhisperModelName(modelName, languageCode);
|
|
1072
|
+
}
|
|
1073
|
+
else {
|
|
942
1074
|
if (languageCode) {
|
|
943
1075
|
const shortLanguageCode = getShortLanguageCode(languageCode);
|
|
944
|
-
modelName = shortLanguageCode ==
|
|
1076
|
+
modelName = shortLanguageCode == 'en' ? 'tiny.en' : 'tiny';
|
|
945
1077
|
}
|
|
946
1078
|
else {
|
|
947
|
-
modelName =
|
|
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
|
-
|
|
953
|
-
|
|
954
|
-
|
|
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
|
|
957
|
-
return
|
|
1101
|
+
export function isEnglishOnlyModel(modelName) {
|
|
1102
|
+
return modelName.endsWith('.en');
|
|
958
1103
|
}
|
|
959
1104
|
export const modelNameToPackageName = {
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
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
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
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
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
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
|
|
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
|