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,17 +1,16 @@
|
|
|
1
1
|
import Onnx from 'onnxruntime-node'
|
|
2
2
|
|
|
3
3
|
import { Logger } from '../utilities/Logger.js'
|
|
4
|
-
import { computeMelSpectogramUsingFilterbanks, Filterbank } from
|
|
5
|
-
import { clip,
|
|
4
|
+
import { computeMelSpectogramUsingFilterbanks, Filterbank } from '../dsp/MelSpectogram.js'
|
|
5
|
+
import { clip, getIntegerRange, getRepetitionScoreRelativeToFirstSubstring, splitFloat32Array, yieldToEventLoop } from '../utilities/Utilities.js'
|
|
6
6
|
import { indexOfMax, logOfVector, logSumExp, meanOfVector, medianFilter, softmax, stdDeviationOfVector } from '../math/VectorMath.js'
|
|
7
|
-
import { isWordOrSymbolWord, splitToWords } from '../nlp/Segmentation.js'
|
|
8
7
|
|
|
9
8
|
import { alignDTWWindowed } from '../alignment/DTWSequenceAlignmentWindowed.js'
|
|
10
|
-
import {
|
|
9
|
+
import { extendDeep } from '../utilities/ObjectUtilities.js'
|
|
11
10
|
import { Timeline, TimelineEntry } from '../utilities/Timeline.js'
|
|
12
11
|
import { AlignmentPath } from '../alignment/SpeechAlignment.js'
|
|
13
12
|
import { getRawAudioDuration, RawAudio } from '../audio/AudioUtilities.js'
|
|
14
|
-
import {
|
|
13
|
+
import { readFile } from '../utilities/FileSystem.js'
|
|
15
14
|
import path from 'path'
|
|
16
15
|
import type { LanguageDetectionResults } from '../api/API.js'
|
|
17
16
|
import { getShortLanguageCode, languageCodeToName } from '../utilities/Locale.js'
|
|
@@ -19,10 +18,11 @@ import { loadPackage } from '../utilities/PackageManager.js'
|
|
|
19
18
|
import chalk from 'chalk'
|
|
20
19
|
import { XorShift32RNG } from '../utilities/RandomGenerator.js'
|
|
21
20
|
import { detectSpeechLanguageByParts } from '../api/LanguageDetection.js'
|
|
21
|
+
import { type Tiktoken } from 'tiktoken/lite'
|
|
22
22
|
|
|
23
|
-
export async function recognize(sourceRawAudio: RawAudio, modelName: WhisperModelName, modelDir: string,
|
|
23
|
+
export async function recognize(sourceRawAudio: RawAudio, modelName: WhisperModelName, modelDir: string, task: WhisperTask, sourceLanguage: string, options: WhisperOptions) {
|
|
24
24
|
if (sourceRawAudio.sampleRate != 16000) {
|
|
25
|
-
throw new Error(
|
|
25
|
+
throw new Error('Source audio must have a sampling rate of 16000')
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
sourceLanguage = getShortLanguageCode(sourceLanguage)
|
|
@@ -31,21 +31,30 @@ export async function recognize(sourceRawAudio: RawAudio, modelName: WhisperMode
|
|
|
31
31
|
throw new Error(`The language ${languageCodeToName(sourceLanguage)} is not supported by the Whisper engine.`)
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
if (
|
|
34
|
+
if (isEnglishOnlyModel(modelName) && sourceLanguage != 'en') {
|
|
35
35
|
throw new Error(`The model '${modelName}' can only be used with English inputs. However, the given source language was ${languageCodeToName(sourceLanguage)}.`)
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
if (options.temperature && options.temperature < 0) {
|
|
39
|
+
throw new Error(`Temperature can't be negative`)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
let seed = options.seed
|
|
43
|
+
|
|
44
|
+
if (seed) {
|
|
45
|
+
seed = Math.max(Math.floor(seed), 1) | 0
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const whisper = new Whisper(modelName, modelDir, seed)
|
|
40
49
|
|
|
41
50
|
const result = await whisper.recognize(sourceRawAudio, task, sourceLanguage, options)
|
|
42
51
|
|
|
43
52
|
return result
|
|
44
53
|
}
|
|
45
54
|
|
|
46
|
-
export async function align(sourceRawAudio: RawAudio, referenceText: string, modelName: WhisperModelName, modelDir: string,
|
|
55
|
+
export async function align(sourceRawAudio: RawAudio, referenceText: string, modelName: WhisperModelName, modelDir: string, sourceLanguage: string) {
|
|
47
56
|
if (sourceRawAudio.sampleRate != 16000) {
|
|
48
|
-
throw new Error(
|
|
57
|
+
throw new Error('Source audio must have a sampling rate of 16000')
|
|
49
58
|
}
|
|
50
59
|
|
|
51
60
|
sourceLanguage = getShortLanguageCode(sourceLanguage)
|
|
@@ -54,29 +63,35 @@ export async function align(sourceRawAudio: RawAudio, referenceText: string, mod
|
|
|
54
63
|
throw new Error(`The language ${languageCodeToName(sourceLanguage)} is not supported by the Whisper engine.`)
|
|
55
64
|
}
|
|
56
65
|
|
|
57
|
-
if (
|
|
66
|
+
if (isEnglishOnlyModel(modelName) && sourceLanguage != 'en') {
|
|
58
67
|
throw new Error(`The model '${modelName}' can only be used with English inputs. However, the given source language was ${languageCodeToName(sourceLanguage)}.`)
|
|
59
68
|
}
|
|
60
69
|
|
|
61
|
-
const whisper = new Whisper(modelName, modelDir
|
|
62
|
-
await whisper.initialize()
|
|
70
|
+
const whisper = new Whisper(modelName, modelDir)
|
|
63
71
|
|
|
64
72
|
const timeline = await whisper.align(sourceRawAudio, referenceText, sourceLanguage)
|
|
65
73
|
|
|
66
74
|
return timeline
|
|
67
75
|
}
|
|
68
76
|
|
|
69
|
-
export async function detectLanguage(sourceRawAudio: RawAudio, modelName: WhisperModelName, modelDir: string,
|
|
77
|
+
export async function detectLanguage(sourceRawAudio: RawAudio, modelName: WhisperModelName, modelDir: string, temperature: number) {
|
|
70
78
|
if (sourceRawAudio.sampleRate != 16000) {
|
|
71
|
-
throw new Error(
|
|
79
|
+
throw new Error('Source audio must have a sampling rate of 16000')
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (!isMultilingualModel(modelName)) {
|
|
83
|
+
throw new Error(`Language detection is only supported with multilingual models.`)
|
|
72
84
|
}
|
|
73
85
|
|
|
74
|
-
|
|
75
|
-
|
|
86
|
+
if (temperature < 0) {
|
|
87
|
+
throw new Error(`Temperature cannot be negative`)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const whisper = new Whisper(modelName, modelDir)
|
|
76
91
|
|
|
77
92
|
async function detectLanguageForPart(partAudio: RawAudio) {
|
|
78
93
|
const audioFeatures = await whisper.encodeAudio(partAudio)
|
|
79
|
-
const partResults = await whisper.detectLanguage(audioFeatures)
|
|
94
|
+
const partResults = await whisper.detectLanguage(audioFeatures, temperature)
|
|
80
95
|
|
|
81
96
|
return partResults
|
|
82
97
|
}
|
|
@@ -91,17 +106,13 @@ export async function detectLanguage(sourceRawAudio: RawAudio, modelName: Whispe
|
|
|
91
106
|
export class Whisper {
|
|
92
107
|
modelName: WhisperModelName
|
|
93
108
|
modelDir: string
|
|
94
|
-
tokenizerDir: string
|
|
95
109
|
|
|
96
110
|
isMultiligualModel: boolean
|
|
97
111
|
|
|
98
112
|
audioEncoder?: Onnx.InferenceSession
|
|
99
113
|
textDecoder?: Onnx.InferenceSession
|
|
100
114
|
|
|
101
|
-
|
|
102
|
-
tokenToTextLookup = new Map<number, string>()
|
|
103
|
-
|
|
104
|
-
merges: [string, string][] = []
|
|
115
|
+
tiktoken?: Tiktoken
|
|
105
116
|
|
|
106
117
|
onnxOptions: Onnx.InferenceSession.SessionOptions = {
|
|
107
118
|
logSeverityLevel: 2,
|
|
@@ -109,109 +120,203 @@ export class Whisper {
|
|
|
109
120
|
}
|
|
110
121
|
|
|
111
122
|
tokenConfig: {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
123
|
+
endOfTextToken: number
|
|
124
|
+
startOfTextToken: number
|
|
125
|
+
|
|
126
|
+
languageTokensStart: number
|
|
127
|
+
languageTokensEnd: number
|
|
128
|
+
|
|
129
|
+
translateTaskToken: number
|
|
130
|
+
transcribeTaskToken: number
|
|
131
|
+
startOfPromptToken: number
|
|
132
|
+
nonSpeechToken: number
|
|
116
133
|
noTimestampsToken: number
|
|
117
|
-
|
|
134
|
+
|
|
118
135
|
timestampTokensStart: number
|
|
136
|
+
timestampTokensEnd: number
|
|
137
|
+
|
|
138
|
+
suppressedTokens: Set<number>
|
|
119
139
|
}
|
|
120
140
|
|
|
121
|
-
randomGen
|
|
141
|
+
randomGen: XorShift32RNG
|
|
122
142
|
|
|
123
|
-
constructor(modelName: WhisperModelName, modelDir: string,
|
|
124
|
-
this.modelDir = modelDir
|
|
143
|
+
constructor(modelName: WhisperModelName, modelDir: string, rngSeed = 461845907) {
|
|
125
144
|
this.modelName = modelName
|
|
126
|
-
this.
|
|
145
|
+
this.modelDir = modelDir
|
|
127
146
|
|
|
128
|
-
this.isMultiligualModel =
|
|
147
|
+
this.isMultiligualModel = isMultilingualModel(this.modelName)
|
|
129
148
|
|
|
130
149
|
if (this.isMultiligualModel) {
|
|
131
150
|
this.tokenConfig = {
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
151
|
+
endOfTextToken: 50257,
|
|
152
|
+
startOfTextToken: 50258,
|
|
153
|
+
|
|
154
|
+
languageTokensStart: 50259,
|
|
155
|
+
languageTokensEnd: 50358,
|
|
156
|
+
|
|
157
|
+
translateTaskToken: 50358,
|
|
158
|
+
transcribeTaskToken: 50359,
|
|
159
|
+
startOfPromptToken: 50361,
|
|
160
|
+
nonSpeechToken: 50362,
|
|
136
161
|
noTimestampsToken: 50363,
|
|
162
|
+
|
|
137
163
|
timestampTokensStart: 50364,
|
|
138
|
-
|
|
164
|
+
timestampTokensEnd: 50364 + 1501,
|
|
165
|
+
|
|
166
|
+
suppressedTokens: new Set()
|
|
139
167
|
}
|
|
168
|
+
|
|
169
|
+
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]
|
|
170
|
+
const suppressedMetadataTokens = [50256, ...getIntegerRange(50258, 50364)]
|
|
171
|
+
|
|
172
|
+
const suppressedTokens = [...suppressedTextTokens, ...suppressedMetadataTokens]
|
|
173
|
+
|
|
174
|
+
this.tokenConfig.suppressedTokens = new Set(suppressedTokens)
|
|
140
175
|
} else {
|
|
141
176
|
this.tokenConfig = {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
177
|
+
endOfTextToken: 50256,
|
|
178
|
+
startOfTextToken: 50257,
|
|
179
|
+
|
|
180
|
+
languageTokensStart: 50258,
|
|
181
|
+
languageTokensEnd: 50358,
|
|
182
|
+
|
|
183
|
+
translateTaskToken: 50358,
|
|
184
|
+
transcribeTaskToken: 50359,
|
|
185
|
+
startOfPromptToken: 50360,
|
|
186
|
+
nonSpeechToken: 50361,
|
|
146
187
|
noTimestampsToken: 50362,
|
|
188
|
+
|
|
147
189
|
timestampTokensStart: 50363,
|
|
148
|
-
|
|
190
|
+
timestampTokensEnd: 50363 + 1501,
|
|
191
|
+
|
|
192
|
+
suppressedTokens: new Set()
|
|
149
193
|
}
|
|
194
|
+
|
|
195
|
+
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]
|
|
196
|
+
const suppressedMetadataTokens = [...getIntegerRange(50257, 50363)]
|
|
197
|
+
|
|
198
|
+
const suppressedTokens = [...suppressedTextTokens, ...suppressedMetadataTokens]
|
|
199
|
+
|
|
200
|
+
this.tokenConfig.suppressedTokens = new Set(suppressedTokens)
|
|
150
201
|
}
|
|
202
|
+
|
|
203
|
+
this.randomGen = new XorShift32RNG(rngSeed)
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
async initializeIfNeeded() {
|
|
207
|
+
await this.initializeTokenizerIfNeeded()
|
|
208
|
+
await this.initializeEncoderSessionIfNeeded()
|
|
209
|
+
await this.initializeDecoderSessionIfNeeded()
|
|
151
210
|
}
|
|
152
211
|
|
|
153
|
-
async
|
|
212
|
+
async initializeTokenizerIfNeeded() {
|
|
213
|
+
if (this.tiktoken) {
|
|
214
|
+
return
|
|
215
|
+
}
|
|
216
|
+
|
|
154
217
|
const logger = new Logger()
|
|
155
|
-
await logger.startAsync(
|
|
218
|
+
await logger.startAsync('Load tokenizer data')
|
|
219
|
+
|
|
220
|
+
const tiktokenModulePackagePath = await loadPackage('whisper-tiktoken-data')
|
|
156
221
|
|
|
157
|
-
const
|
|
158
|
-
|
|
222
|
+
const tiktokenDataFilePath = path.join(tiktokenModulePackagePath, this.isMultiligualModel ? 'multilingual.tiktoken' : 'gpt2.tiktoken')
|
|
223
|
+
let tiktokenData = await readFile(tiktokenDataFilePath, { encoding: 'utf8' })
|
|
159
224
|
|
|
160
|
-
const
|
|
161
|
-
const mergesFilePath = path.join(this.tokenizerDir, "merges.txt")
|
|
225
|
+
const tokenConfig = this.tokenConfig
|
|
162
226
|
|
|
163
|
-
const
|
|
227
|
+
const metadataTokens: Record<number, string> = {
|
|
228
|
+
[tokenConfig.endOfTextToken]: '[EndOfText]',
|
|
229
|
+
[tokenConfig.startOfTextToken]: '[StartOfText]',
|
|
230
|
+
[tokenConfig.translateTaskToken]: '[TranslateTask]',
|
|
231
|
+
[tokenConfig.transcribeTaskToken]: '[TranscribeTask]',
|
|
232
|
+
[tokenConfig.startOfPromptToken]: '[StartOfPrompt]',
|
|
233
|
+
[tokenConfig.nonSpeechToken]: '[NonSpeech]',
|
|
234
|
+
[tokenConfig.noTimestampsToken]: '[NoTimestamps]',
|
|
235
|
+
}
|
|
164
236
|
|
|
165
|
-
|
|
166
|
-
|
|
237
|
+
if (this.isMultiligualModel) {
|
|
238
|
+
metadataTokens[50256] = '[Unused_50256]'
|
|
239
|
+
metadataTokens[50360] = '[Unused_50360]'
|
|
240
|
+
}
|
|
167
241
|
|
|
168
|
-
|
|
169
|
-
const decodedChar = vocabCharacterSetLookup[char]
|
|
242
|
+
const languageTokenCount = tokenConfig.languageTokensEnd - tokenConfig.languageTokensStart
|
|
170
243
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
}
|
|
244
|
+
for (let i = 0; i < languageTokenCount; i++) {
|
|
245
|
+
const tokenIndex = this.tokenConfig.languageTokensStart + i
|
|
174
246
|
|
|
175
|
-
|
|
176
|
-
|
|
247
|
+
metadataTokens[tokenIndex] = `[Language_${i}]`
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const timestampTokensCount = 1501
|
|
177
251
|
|
|
178
|
-
|
|
252
|
+
for (let i = 0; i < timestampTokensCount; i++) {
|
|
253
|
+
const tokenIndex = this.tokenConfig.timestampTokensStart + i
|
|
254
|
+
const tokenTime = this.timestampTokenToSeconds(tokenIndex)
|
|
255
|
+
|
|
256
|
+
metadataTokens[tokenIndex] = `[Timestamp_${tokenTime.toFixed(2)}]`
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const inverseMetadataTokensLookup: Record<string, number> = {}
|
|
260
|
+
|
|
261
|
+
for (const [key, value] of Object.entries(metadataTokens)) {
|
|
262
|
+
inverseMetadataTokensLookup[value] = parseInt(key)
|
|
179
263
|
}
|
|
180
264
|
|
|
181
|
-
|
|
182
|
-
const value = vocabObject[key]
|
|
265
|
+
const patternString = `'s|'t|'re|'ve|'m|'ll|'d| ?\\p{L}+| ?\\p{N}+| ?[^\\s\\p{L}\\p{N}]+|\\s+(?!\\S)|\\s+`
|
|
183
266
|
|
|
184
|
-
|
|
267
|
+
const { Tiktoken } = await import('tiktoken/lite')
|
|
268
|
+
|
|
269
|
+
this.tiktoken = new Tiktoken(tiktokenData, inverseMetadataTokensLookup, patternString)
|
|
270
|
+
|
|
271
|
+
logger.end()
|
|
272
|
+
}
|
|
185
273
|
|
|
186
|
-
|
|
187
|
-
|
|
274
|
+
async initializeEncoderSessionIfNeeded() {
|
|
275
|
+
if (this.audioEncoder) {
|
|
276
|
+
return
|
|
188
277
|
}
|
|
189
278
|
|
|
190
|
-
const
|
|
191
|
-
|
|
192
|
-
|
|
279
|
+
const logger = new Logger()
|
|
280
|
+
|
|
281
|
+
await logger.startAsync(`Create encoder model inference session for model '${this.modelName}'`)
|
|
193
282
|
|
|
194
|
-
|
|
283
|
+
const encoderFilePath = path.join(this.modelDir, 'encoder.onnx')
|
|
195
284
|
|
|
196
285
|
this.audioEncoder = await Onnx.InferenceSession.create(encoderFilePath, this.onnxOptions)
|
|
286
|
+
|
|
287
|
+
logger.end()
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
async initializeDecoderSessionIfNeeded() {
|
|
291
|
+
if (this.textDecoder) {
|
|
292
|
+
return
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const logger = new Logger()
|
|
296
|
+
|
|
297
|
+
await logger.startAsync(`Create decoder model inference session for model '${this.modelName}'`)
|
|
298
|
+
|
|
299
|
+
const decoderFilePath = path.join(this.modelDir, 'decoder.onnx')
|
|
300
|
+
|
|
197
301
|
this.textDecoder = await Onnx.InferenceSession.create(decoderFilePath, this.onnxOptions)
|
|
198
302
|
|
|
199
303
|
logger.end()
|
|
200
304
|
}
|
|
201
305
|
|
|
202
306
|
async recognize(rawAudio: RawAudio, task: WhisperTask, language: string, options: WhisperOptions) {
|
|
203
|
-
|
|
307
|
+
await this.initializeIfNeeded()
|
|
204
308
|
|
|
205
|
-
const
|
|
309
|
+
const logger = new Logger()
|
|
206
310
|
|
|
207
311
|
const audioSamples = rawAudio.audioChannels[0]
|
|
208
312
|
const sampleRate = rawAudio.sampleRate
|
|
209
|
-
const audioDuration = getRawAudioDuration(rawAudio)
|
|
210
313
|
const prompt = options.prompt
|
|
211
314
|
|
|
212
|
-
const
|
|
315
|
+
const maxAudioSamplesPerPart = sampleRate * 30
|
|
213
316
|
|
|
214
|
-
|
|
317
|
+
const decodeTimestampTokens = options.decodeTimestampTokens!
|
|
318
|
+
|
|
319
|
+
let previousPartTextTokens: number[] = []
|
|
215
320
|
|
|
216
321
|
let timeline: Timeline = []
|
|
217
322
|
let allDecodedTokens: number[] = []
|
|
@@ -221,7 +326,7 @@ export class Whisper {
|
|
|
221
326
|
|
|
222
327
|
await logger.startAsync(`\nPrepare audio part at time position ${segmentStartTime.toFixed(2)}`, undefined, chalk.magentaBright)
|
|
223
328
|
|
|
224
|
-
const audioPartSamples = audioSamples.slice(audioOffset, audioOffset +
|
|
329
|
+
const audioPartSamples = audioSamples.slice(audioOffset, audioOffset + maxAudioSamplesPerPart)
|
|
225
330
|
const audioPartRawAudio: RawAudio = { audioChannels: [audioPartSamples], sampleRate }
|
|
226
331
|
const audioPartDuration = getRawAudioDuration(audioPartRawAudio)
|
|
227
332
|
|
|
@@ -229,77 +334,73 @@ export class Whisper {
|
|
|
229
334
|
|
|
230
335
|
const audioPartFeatures = await this.encodeAudio(audioPartRawAudio)
|
|
231
336
|
|
|
232
|
-
const isFirstPart = audioOffset
|
|
233
|
-
const isFinalPart = audioOffset +
|
|
337
|
+
const isFirstPart = audioOffset === 0
|
|
338
|
+
const isFinalPart = audioOffset + maxAudioSamplesPerPart >= audioSamples.length
|
|
234
339
|
|
|
235
340
|
let initialTokens: number[] = []
|
|
236
341
|
|
|
237
342
|
if (isFirstPart && prompt) {
|
|
238
|
-
const promptTokens =
|
|
343
|
+
const promptTokens = this.textToTokens(prompt)
|
|
239
344
|
|
|
240
|
-
initialTokens = [this.tokenConfig.
|
|
241
|
-
} else if (options.autoPromptParts &&
|
|
242
|
-
initialTokens = [this.tokenConfig.
|
|
345
|
+
initialTokens = [this.tokenConfig.startOfPromptToken, ...promptTokens]
|
|
346
|
+
} else if (options.autoPromptParts && previousPartTextTokens.length > 0) {
|
|
347
|
+
initialTokens = [this.tokenConfig.startOfPromptToken, ...previousPartTextTokens]
|
|
243
348
|
}
|
|
244
349
|
|
|
245
|
-
initialTokens = [...initialTokens, ...this.
|
|
350
|
+
initialTokens = [...initialTokens, ...this.getTextStartTokens(language, task, !decodeTimestampTokens)]
|
|
246
351
|
|
|
247
352
|
logger.end()
|
|
248
353
|
|
|
249
|
-
let {
|
|
354
|
+
let {
|
|
355
|
+
decodedTokens: partTokens,
|
|
356
|
+
crossAttentionQKs: partCrossAttentionQKs,
|
|
357
|
+
decodedTokensConfidence
|
|
358
|
+
} = await this.decodeTokens(audioPartFeatures, initialTokens, audioPartDuration, isFirstPart, isFinalPart, options)
|
|
250
359
|
|
|
251
360
|
const lastToken = partTokens[partTokens.length - 1]
|
|
252
|
-
const lastTokenIsTimestamp = lastToken
|
|
361
|
+
const lastTokenIsTimestamp = this.isTimestampToken(lastToken)
|
|
253
362
|
|
|
254
363
|
let audioEndOffset: number
|
|
255
364
|
|
|
256
365
|
if (!isFinalPart && lastTokenIsTimestamp) {
|
|
257
|
-
const timePosition = (lastToken
|
|
366
|
+
const timePosition = this.timestampTokenToSeconds(lastToken)
|
|
258
367
|
|
|
259
368
|
audioEndOffset = audioOffset + Math.floor(timePosition * sampleRate)
|
|
260
369
|
} else {
|
|
261
|
-
audioEndOffset = Math.min(audioOffset +
|
|
370
|
+
audioEndOffset = Math.min(audioOffset + maxAudioSamplesPerPart, audioSamples.length)
|
|
262
371
|
}
|
|
263
372
|
|
|
264
373
|
const segmentEndTime = audioEndOffset / sampleRate
|
|
265
|
-
const segmentFrameCount =
|
|
374
|
+
const segmentFrameCount = this.secondsRangeToFrameCount(segmentStartTime, segmentEndTime)
|
|
266
375
|
|
|
267
376
|
await logger.startAsync(`Extract timeline for part`)
|
|
268
377
|
|
|
269
378
|
if (partTokens.length != partCrossAttentionQKs.length) {
|
|
270
|
-
throw new Error(
|
|
379
|
+
throw new Error('Unexpected: partTokens.length != partCrossAttentionQKs.length')
|
|
271
380
|
}
|
|
272
381
|
|
|
273
|
-
//partTokens = partTokens.filter(token => token < timestampTokensStart)
|
|
274
|
-
//partCrossAttentionQKs = await this.inferCrossAttentionQKs(partTokens, audioPartFeatures)
|
|
275
|
-
|
|
276
382
|
partTokens = partTokens.slice(initialTokens.length)
|
|
277
|
-
partCrossAttentionQKs = partCrossAttentionQKs.slice(initialTokens.length)
|
|
278
383
|
|
|
279
|
-
//await this.
|
|
384
|
+
//const compressionRatioForPart = (await getDeflateCompressionMetricsForString(this.tokensToText(partTokens))).ratio
|
|
280
385
|
|
|
281
|
-
|
|
282
|
-
const partTimeline = await this.getWordTimelineFromAlignmentPath(alignmentPath, partTokens, segmentStartTime, segmentEndTime, decodedTokensConfidence)
|
|
386
|
+
partCrossAttentionQKs = partCrossAttentionQKs.slice(initialTokens.length)
|
|
283
387
|
|
|
284
|
-
|
|
388
|
+
const alignmentPath = await this.findAlignmentPathFromQKs(partCrossAttentionQKs, partTokens, 0, segmentFrameCount) //, alignmentHeadsIndexes[this.modelName])
|
|
389
|
+
const partTimeline = await this.getTokenTimelineFromAlignmentPath(alignmentPath, partTokens, segmentStartTime, segmentEndTime, decodedTokensConfidence)
|
|
285
390
|
|
|
286
391
|
audioOffset = audioEndOffset
|
|
287
392
|
|
|
288
|
-
|
|
393
|
+
allDecodedTokens.push(...partTokens)
|
|
394
|
+
timeline.push(...partTimeline)
|
|
289
395
|
|
|
290
|
-
|
|
396
|
+
previousPartTextTokens = partTokens.filter(token => this.isTextToken(token))
|
|
291
397
|
|
|
292
398
|
logger.end()
|
|
293
399
|
}
|
|
294
400
|
|
|
295
|
-
|
|
296
|
-
timeline[timeline.length - 1].endTime = audioDuration
|
|
297
|
-
}
|
|
401
|
+
timeline = this.tokenTimelineToWordTimeline(timeline)
|
|
298
402
|
|
|
299
|
-
|
|
300
|
-
timeline.forEach(entry => { entry.text = entry.text.trim() })
|
|
301
|
-
|
|
302
|
-
const transcript = this.tokensToText(allDecodedTokens)
|
|
403
|
+
const transcript = this.tokensToText(allDecodedTokens).trim()
|
|
303
404
|
|
|
304
405
|
logger.end()
|
|
305
406
|
|
|
@@ -307,51 +408,56 @@ export class Whisper {
|
|
|
307
408
|
}
|
|
308
409
|
|
|
309
410
|
async align(rawAudio: RawAudio, referenceText: string, language: string) {
|
|
411
|
+
await this.initializeIfNeeded()
|
|
412
|
+
|
|
310
413
|
const logger = new Logger()
|
|
311
414
|
|
|
312
|
-
await logger.startAsync(
|
|
415
|
+
await logger.startAsync('Prepare for alignment')
|
|
416
|
+
|
|
417
|
+
referenceText = referenceText.replaceAll(/\s+/g, ' ')
|
|
418
|
+
|
|
313
419
|
const audioDuration = Math.min(getRawAudioDuration(rawAudio), 30)
|
|
314
|
-
const audioFrameCount =
|
|
420
|
+
const audioFrameCount = this.secondsToFrame(audioDuration)
|
|
315
421
|
|
|
316
|
-
const initialTokens = this.
|
|
317
|
-
const timestampTokensStart = this.tokenConfig.timestampTokensStart
|
|
318
|
-
const eotToken = this.tokenConfig.eotToken
|
|
422
|
+
const initialTokens = this.getTextStartTokens(language, 'transcribe', true)
|
|
319
423
|
|
|
320
|
-
|
|
424
|
+
const endOfTextToken = this.tokenConfig.endOfTextToken
|
|
425
|
+
|
|
426
|
+
let tokens = [...initialTokens, ...this.textToTokens(referenceText), endOfTextToken]
|
|
321
427
|
|
|
322
428
|
logger.end()
|
|
323
429
|
const audioFeatures = await this.encodeAudio(rawAudio)
|
|
324
430
|
|
|
325
|
-
await logger.startAsync(
|
|
431
|
+
await logger.startAsync('Infer cross-attention QKs')
|
|
326
432
|
let crossAttentionQKs = await this.inferCrossAttentionQKs(tokens, audioFeatures)
|
|
327
433
|
|
|
328
434
|
tokens = tokens.slice(initialTokens.length, tokens.length - 1)
|
|
329
435
|
crossAttentionQKs = crossAttentionQKs.slice(initialTokens.length, crossAttentionQKs.length - 1)
|
|
330
436
|
|
|
331
|
-
await logger.startAsync(
|
|
437
|
+
await logger.startAsync('Extract word timeline')
|
|
332
438
|
const alignmentPath = await this.findAlignmentPathFromQKs(crossAttentionQKs, tokens, 0, audioFrameCount)//, this.getAlignmentHeadIndexes())
|
|
333
|
-
let timeline = await this.
|
|
439
|
+
let timeline = await this.getTokenTimelineFromAlignmentPath(alignmentPath, tokens, 0, audioDuration)
|
|
334
440
|
|
|
335
|
-
timeline = this.
|
|
336
|
-
timeline.forEach(entry => { entry.text = entry.text.trim() })
|
|
337
|
-
//timeline = timeline.filter(entry => isWordOrSymbolWord(entry.text))
|
|
441
|
+
timeline = this.tokenTimelineToWordTimeline(timeline)
|
|
338
442
|
|
|
339
443
|
logger.end()
|
|
340
444
|
|
|
341
445
|
return timeline
|
|
342
446
|
}
|
|
343
447
|
|
|
344
|
-
async detectLanguage(audioFeatures: Onnx.Tensor): Promise<LanguageDetectionResults> {
|
|
345
|
-
const logger = new Logger()
|
|
346
|
-
|
|
448
|
+
async detectLanguage(audioFeatures: Onnx.Tensor, temperature: number): Promise<LanguageDetectionResults> {
|
|
347
449
|
if (!this.isMultiligualModel) {
|
|
348
|
-
throw new Error(
|
|
450
|
+
throw new Error('Language detection is only supported with multilingual models')
|
|
349
451
|
}
|
|
350
452
|
|
|
453
|
+
await this.initializeTokenizerIfNeeded()
|
|
454
|
+
await this.initializeDecoderSessionIfNeeded()
|
|
455
|
+
|
|
351
456
|
// Prepare and run decoder
|
|
352
|
-
|
|
457
|
+
const logger = new Logger()
|
|
458
|
+
await logger.startAsync('Detect language with Whisper model')
|
|
353
459
|
|
|
354
|
-
const sotToken = this.tokenConfig.
|
|
460
|
+
const sotToken = this.tokenConfig.startOfTextToken
|
|
355
461
|
|
|
356
462
|
const initialTokens = [sotToken]
|
|
357
463
|
const offset = 0
|
|
@@ -362,13 +468,20 @@ export class Whisper {
|
|
|
362
468
|
const tokensTensor = new Onnx.Tensor('int64', new BigInt64Array(initialTokens.map(token => BigInt(token))), [1, initialTokens.length])
|
|
363
469
|
const offsetTensor = new Onnx.Tensor('int64', new BigInt64Array([BigInt(offset)]), [])
|
|
364
470
|
|
|
365
|
-
const decoderInputs = {
|
|
471
|
+
const decoderInputs = {
|
|
472
|
+
tokens: tokensTensor,
|
|
473
|
+
audio_features: audioFeatures,
|
|
474
|
+
kv_cache: kvCacheTensor,
|
|
475
|
+
offset: offsetTensor
|
|
476
|
+
}
|
|
366
477
|
|
|
367
478
|
const decoderOutputs = await this.textDecoder!.run(decoderInputs)
|
|
368
|
-
const logitsBuffer = decoderOutputs[
|
|
479
|
+
const logitsBuffer = decoderOutputs['logits'].data as Float32Array
|
|
369
480
|
|
|
370
|
-
const
|
|
371
|
-
|
|
481
|
+
const tokenConfig = this.tokenConfig
|
|
482
|
+
|
|
483
|
+
const languageTokensLogits = Array.from(logitsBuffer.slice(tokenConfig.languageTokensStart, tokenConfig.languageTokensEnd))
|
|
484
|
+
const languageTokensProbabilities = softmax(languageTokensLogits, temperature)
|
|
372
485
|
|
|
373
486
|
const results: LanguageDetectionResults = []
|
|
374
487
|
|
|
@@ -388,24 +501,33 @@ export class Whisper {
|
|
|
388
501
|
return results
|
|
389
502
|
}
|
|
390
503
|
|
|
391
|
-
async decodeTokens(
|
|
504
|
+
async decodeTokens(
|
|
505
|
+
audioFeatures: Onnx.Tensor,
|
|
506
|
+
initialTokens: number[],
|
|
507
|
+
audioDuration: number,
|
|
508
|
+
isFirstPart: boolean,
|
|
509
|
+
isFinalPart: boolean,
|
|
510
|
+
options: WhisperOptions) {
|
|
511
|
+
|
|
512
|
+
await this.initializeTokenizerIfNeeded()
|
|
513
|
+
await this.initializeDecoderSessionIfNeeded()
|
|
514
|
+
|
|
392
515
|
const logger = new Logger()
|
|
393
|
-
await logger.startAsync("Decode text tokens with Whisper decoder model")
|
|
394
516
|
|
|
395
|
-
|
|
517
|
+
await logger.startAsync('Decode text tokens with Whisper decoder model')
|
|
396
518
|
|
|
397
|
-
|
|
519
|
+
options = extendDeep(defaultWhisperOptions, options)
|
|
398
520
|
|
|
399
|
-
const
|
|
521
|
+
const endOfTextToken = this.tokenConfig.endOfTextToken
|
|
400
522
|
|
|
401
|
-
const suppressedTokens = this.tokenConfig.suppressedTokens
|
|
402
|
-
const sotToken = this.tokenConfig.sotToken
|
|
403
|
-
const eotToken = this.tokenConfig.eotToken
|
|
404
|
-
const noTimestampsToken = this.tokenConfig.noTimestampsToken
|
|
405
|
-
const noSpeechToken = this.tokenConfig.noSpeechToken
|
|
406
523
|
const timestampTokensStart = this.tokenConfig.timestampTokensStart
|
|
524
|
+
const suppressedTokens = this.tokenConfig.suppressedTokens
|
|
407
525
|
|
|
408
|
-
const
|
|
526
|
+
const spaceToken = this.textToTokens(' ')[0]
|
|
527
|
+
|
|
528
|
+
const maxDecodedTokenCount = options.maxTokensPerPart!
|
|
529
|
+
|
|
530
|
+
//const suppressedTokensText = Array.from(this.tokenConfig.suppressedTokens).map(token => this.tokenToText(token, true))
|
|
409
531
|
|
|
410
532
|
let decodedTokens = initialTokens.slice()
|
|
411
533
|
const initialKvDimensions = this.getKvDimensions(1, decodedTokens.length)
|
|
@@ -455,147 +577,196 @@ export class Whisper {
|
|
|
455
577
|
|
|
456
578
|
const decoderOutputs = await this.textDecoder!.run(decoderInputs)
|
|
457
579
|
|
|
458
|
-
const logitsBuffer = decoderOutputs[
|
|
459
|
-
kvCacheTensor = decoderOutputs[
|
|
580
|
+
const logitsBuffer = decoderOutputs['logits'].data as Float32Array
|
|
581
|
+
kvCacheTensor = decoderOutputs['output_kv_cache'] as any
|
|
460
582
|
|
|
461
583
|
// Compute logits
|
|
462
|
-
const resultLogits = splitFloat32Array(logitsBuffer, logitsBuffer.length / decoderOutputs[
|
|
463
|
-
const
|
|
464
|
-
const
|
|
584
|
+
const resultLogits = splitFloat32Array(logitsBuffer, logitsBuffer.length / decoderOutputs['logits'].dims[1])
|
|
585
|
+
const allTokenLogits = Array.from(resultLogits[resultLogits.length - 1])
|
|
586
|
+
const timestampTokenLogits = allTokenLogits.slice(timestampTokensStart)
|
|
465
587
|
|
|
466
588
|
// Suppress tokens
|
|
467
|
-
for (let logitIndex = 0; logitIndex <
|
|
468
|
-
const isWrongTokenForInitialState =
|
|
469
|
-
|
|
470
|
-
|
|
589
|
+
for (let logitIndex = 0; logitIndex < allTokenLogits.length; logitIndex++) {
|
|
590
|
+
const isWrongTokenForInitialState =
|
|
591
|
+
isInitialState &&
|
|
592
|
+
(logitIndex === spaceToken || logitIndex === endOfTextToken)
|
|
471
593
|
|
|
472
|
-
const
|
|
594
|
+
const isInSuppressedList = suppressedTokens.has(logitIndex)
|
|
473
595
|
|
|
474
|
-
|
|
475
|
-
|
|
596
|
+
const shouldSuppressToken = isWrongTokenForInitialState || isInSuppressedList
|
|
597
|
+
|
|
598
|
+
if (shouldSuppressToken) {
|
|
599
|
+
allTokenLogits[logitIndex] = -Infinity
|
|
476
600
|
}
|
|
477
601
|
}
|
|
478
602
|
|
|
479
|
-
//
|
|
480
|
-
|
|
481
|
-
|
|
603
|
+
// Derive token probabilities
|
|
604
|
+
let bufferedTokensToPrint: number[] = []
|
|
605
|
+
|
|
606
|
+
// Add best token
|
|
607
|
+
function addToken(tokenToAdd: number, timestampLogits: number[], confidence: number) {
|
|
608
|
+
decodedTokens.push(tokenToAdd)
|
|
609
|
+
decodedTokensTimestampLogits.push(timestampLogits)
|
|
610
|
+
decodedTokensCrossAttentionQKs.push(decoderOutputs['cross_attention_qks'])
|
|
611
|
+
decodedTokensConfidence.push(confidence)
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
let shouldDecodeNonTimestampToken = true
|
|
482
615
|
|
|
483
|
-
|
|
484
|
-
|
|
616
|
+
if (options.decodeTimestampTokens) {
|
|
617
|
+
const probabilities = softmax(allTokenLogits as any, 1.0)
|
|
618
|
+
const logProbabilities = logOfVector(probabilities)
|
|
485
619
|
|
|
486
|
-
|
|
487
|
-
const valueOfMaxTextLogProb = textTokenLogProbs[indexOfMaxTextLogProb]
|
|
620
|
+
const nonTimestampTokenLogProbs = logProbabilities.slice(0, timestampTokensStart)
|
|
488
621
|
|
|
489
|
-
|
|
622
|
+
const indexOfMaxNonTimestampLogProb = indexOfMax(nonTimestampTokenLogProbs)
|
|
623
|
+
const valueOfMaxNonTimestampLogProb = nonTimestampTokenLogProbs[indexOfMaxNonTimestampLogProb]
|
|
490
624
|
|
|
491
|
-
|
|
625
|
+
const timestampTokenLogProbs = logProbabilities.slice(timestampTokensStart)
|
|
626
|
+
const indexOfMaxTimestampLogProb = indexOfMax(timestampTokenLogProbs)
|
|
492
627
|
|
|
493
|
-
|
|
494
|
-
const previousTokenWasTimestamp = decodedTokens[decodedTokens.length - 1] >= timestampTokensStart
|
|
495
|
-
const secondPreviousTokenWasTimestamp = decodedTokens.length < 2 || decodedTokens[decodedTokens.length - 2] >= timestampTokensStart
|
|
628
|
+
const logSumExpOfTimestampTokenLogProbs = logSumExp(timestampTokenLogProbs)
|
|
496
629
|
|
|
497
|
-
|
|
498
|
-
timestampsSeenCount += 1
|
|
499
|
-
}
|
|
630
|
+
const shouldDecodeTimestampToken = logSumExpOfTimestampTokenLogProbs > valueOfMaxNonTimestampLogProb
|
|
500
631
|
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
//topLogits.sort((a, b) => b.logit - a.logit)
|
|
504
|
-
///
|
|
632
|
+
const previousTokenWasTimestamp = this.isTimestampToken(decodedTokens[decodedTokens.length - 1])
|
|
633
|
+
const secondPreviousTokenWasTimestamp = decodedTokens.length < 2 || this.isTimestampToken(decodedTokens[decodedTokens.length - 2])
|
|
505
634
|
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
decodedTokensTimestampLogits.push(timestampLogits)
|
|
510
|
-
decodedTokensCrossAttentionQKs.push(decoderOutputs["cross_attention_qks"])
|
|
511
|
-
decodedTokensConfidence.push(confidence)
|
|
512
|
-
}
|
|
635
|
+
if (shouldDecodeTimestampToken && !previousTokenWasTimestamp) {
|
|
636
|
+
timestampsSeenCount += 1
|
|
637
|
+
}
|
|
513
638
|
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
639
|
+
if (shouldDecodeTimestampToken || (previousTokenWasTimestamp && !secondPreviousTokenWasTimestamp)) {
|
|
640
|
+
if (previousTokenWasTimestamp) {
|
|
641
|
+
const previousToken = decodedTokens[decodedTokens.length - 1]
|
|
642
|
+
const previousTokenTimestampLogits = decodedTokensTimestampLogits[decodedTokensTimestampLogits.length - 1]
|
|
643
|
+
const previousTokenConfidence = decodedTokensConfidence[decodedTokensConfidence.length - 1]
|
|
644
|
+
|
|
645
|
+
addToken(previousToken, previousTokenTimestampLogits, previousTokenConfidence)
|
|
519
646
|
|
|
520
|
-
|
|
647
|
+
lastTimestampTokenIndex = decodedTokens.length
|
|
521
648
|
|
|
522
|
-
|
|
649
|
+
const previousTokenTimestamp = this.timestampTokenToSeconds(previousToken)
|
|
523
650
|
|
|
524
|
-
|
|
651
|
+
if (previousTokenTimestamp >= audioDuration) {
|
|
652
|
+
break
|
|
653
|
+
}
|
|
654
|
+
} else {
|
|
655
|
+
const timestampToken = timestampTokensStart + indexOfMaxTimestampLogProb
|
|
656
|
+
const confidence = probabilities[timestampToken]
|
|
525
657
|
|
|
526
|
-
|
|
527
|
-
break
|
|
658
|
+
addToken(timestampToken, timestampTokenLogits, confidence)
|
|
528
659
|
}
|
|
529
|
-
} else {
|
|
530
|
-
const timestampToken = timestampTokensStart + indexOfMaxTimestampLogProb
|
|
531
|
-
const confidence = probs[timestampToken]
|
|
532
660
|
|
|
533
|
-
|
|
661
|
+
shouldDecodeNonTimestampToken = false
|
|
534
662
|
}
|
|
535
|
-
}
|
|
536
|
-
break
|
|
537
|
-
} else {
|
|
538
|
-
let chosenTokenIndex: number
|
|
663
|
+
}
|
|
539
664
|
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
} else {
|
|
543
|
-
const topLogitCount = options.topCandidateCount!
|
|
544
|
-
|
|
545
|
-
const textTokenLogits = tokenLogits.slice(0, timestampTokensStart)
|
|
546
|
-
const sortedTextTokenLogitsWithIndexes = Array.from(textTokenLogits).map((logit, index) => ({ logit, index }))
|
|
547
|
-
sortedTextTokenLogitsWithIndexes.sort((a, b) => b.logit - a.logit)
|
|
548
|
-
let topLogitsWithIndexes = sortedTextTokenLogitsWithIndexes.slice(0, topLogitCount)
|
|
549
|
-
|
|
550
|
-
////
|
|
551
|
-
/*
|
|
552
|
-
topLogitsWithIndexes = topLogitsWithIndexes.filter(entry => {
|
|
553
|
-
const lastDecodedTextTokens = decodedTokens.filter(token => token < eotToken).reverse().slice(0, 20)
|
|
554
|
-
const { maxScore } = getRepetitionScoreRelativeToFirstSubstring([entry.index, ...lastDecodedTextTokens])
|
|
555
|
-
|
|
556
|
-
if (maxScore < 4) {
|
|
557
|
-
return true
|
|
558
|
-
} else {
|
|
559
|
-
return false
|
|
560
|
-
}
|
|
561
|
-
})
|
|
562
|
-
*/
|
|
563
|
-
////
|
|
665
|
+
if (shouldDecodeNonTimestampToken) {
|
|
666
|
+
const topLogitCount = options.topCandidateCount!
|
|
564
667
|
|
|
565
|
-
|
|
566
|
-
const textTokenProbs = softmax(topLogits, options.temperature)
|
|
668
|
+
const nonTimestampTokenLogits = allTokenLogits.slice(0, timestampTokensStart)
|
|
567
669
|
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
const tokenProb = probs[entry.index]
|
|
670
|
+
const sortedNonTimestampTokenLogitsWithIndexes =
|
|
671
|
+
Array.from(nonTimestampTokenLogits).map((logit, index) => ({ token: index, logit }))
|
|
571
672
|
|
|
572
|
-
|
|
673
|
+
sortedNonTimestampTokenLogitsWithIndexes.sort((a, b) => b.logit - a.logit)
|
|
674
|
+
|
|
675
|
+
let topCandidates = sortedNonTimestampTokenLogitsWithIndexes.slice(0, topLogitCount)
|
|
676
|
+
.map(entry => ({
|
|
677
|
+
token: entry.token,
|
|
678
|
+
logit: entry.logit,
|
|
679
|
+
text: this.tokenToText(entry.token, true)
|
|
680
|
+
}))
|
|
681
|
+
|
|
682
|
+
//// Repetition suppression code
|
|
683
|
+
if (options.suppressRepetition) {
|
|
684
|
+
const topCandidatesRepetitionScores = topCandidates.map(entry => {
|
|
685
|
+
const lastDecodedTextTokens = decodedTokens.filter(token => this.isTextToken(token)).reverse().slice(0, 20)
|
|
686
|
+
const { maxScore } = getRepetitionScoreRelativeToFirstSubstring([entry.token, ...lastDecodedTextTokens])
|
|
687
|
+
|
|
688
|
+
return maxScore
|
|
573
689
|
})
|
|
574
690
|
|
|
575
|
-
|
|
691
|
+
const thresholdRepetitionScore = 3
|
|
576
692
|
|
|
577
|
-
if (
|
|
578
|
-
|
|
693
|
+
if (topCandidatesRepetitionScores.every(score => score >= thresholdRepetitionScore)) {
|
|
694
|
+
const indexOfMaxScore = topCandidatesRepetitionScores.indexOf(Math.max(...topCandidatesRepetitionScores))
|
|
695
|
+
topCandidates = [topCandidates[indexOfMaxScore]]
|
|
579
696
|
} else {
|
|
580
|
-
|
|
697
|
+
topCandidates = topCandidates.filter((candidate, index) => topCandidatesRepetitionScores[index] < thresholdRepetitionScore)
|
|
581
698
|
}
|
|
699
|
+
}
|
|
700
|
+
////
|
|
701
|
+
|
|
702
|
+
const topCandidateProbabilities = softmax(topCandidates.map(a => a.logit), options.temperature)
|
|
703
|
+
|
|
704
|
+
//// Remove end-of-text token from candidates if its probability isn't high enough
|
|
705
|
+
if (options.decodeTimestampTokens === false) {
|
|
706
|
+
topCandidates = topCandidates.filter((candidate, index) => {
|
|
707
|
+
if (candidate.token === endOfTextToken) {
|
|
708
|
+
return topCandidateProbabilities[index] >= 0.9
|
|
709
|
+
}
|
|
582
710
|
|
|
583
|
-
|
|
711
|
+
return true
|
|
712
|
+
})
|
|
584
713
|
}
|
|
714
|
+
////
|
|
715
|
+
|
|
716
|
+
const rankOfPromisingPunctuationToken = topCandidates.findIndex((entry, index) => {
|
|
717
|
+
const tokenText = this.tokenToText(entry.token).trim()
|
|
585
718
|
|
|
586
|
-
|
|
587
|
-
let chosenTokenText = this.tokenToTextLookup.get(chosenTokenIndex) || ""
|
|
719
|
+
const isPunctuationToken = [',', ',', '、', '.', '。', '!', '?'].includes(tokenText)
|
|
588
720
|
|
|
589
|
-
if (
|
|
590
|
-
|
|
721
|
+
if (!isPunctuationToken) {
|
|
722
|
+
return false
|
|
591
723
|
}
|
|
592
724
|
|
|
593
|
-
|
|
725
|
+
const tokenProb = topCandidateProbabilities[index]
|
|
726
|
+
|
|
727
|
+
return tokenProb >= options.punctuationThreshold!
|
|
728
|
+
})
|
|
729
|
+
|
|
730
|
+
let rankOfSpaceToken = topCandidates.findIndex(candidate => candidate.token === spaceToken)
|
|
731
|
+
|
|
732
|
+
if (rankOfSpaceToken < 0) {
|
|
733
|
+
rankOfSpaceToken = Infinity
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
let chosenCandidateRank: number
|
|
737
|
+
|
|
738
|
+
if (rankOfPromisingPunctuationToken >= 0 &&
|
|
739
|
+
rankOfPromisingPunctuationToken < rankOfSpaceToken) {
|
|
740
|
+
chosenCandidateRank = rankOfPromisingPunctuationToken
|
|
741
|
+
} else {
|
|
742
|
+
chosenCandidateRank = this.randomGen.selectRandomIndexFromDistribution(topCandidateProbabilities)
|
|
594
743
|
}
|
|
595
744
|
|
|
596
|
-
const
|
|
745
|
+
const chosenToken = topCandidates[chosenCandidateRank].token
|
|
746
|
+
|
|
747
|
+
if (this.isTextToken(chosenToken)) {
|
|
748
|
+
bufferedTokensToPrint.push(chosenToken)
|
|
749
|
+
|
|
750
|
+
let textToPrint = this.tokensToText(bufferedTokensToPrint)
|
|
751
|
+
|
|
752
|
+
if (textToPrint.codePointAt(0) !== 65533) {
|
|
753
|
+
if (isFirstPart && decodedTokens.every(token => this.isMetadataToken(token))) {
|
|
754
|
+
textToPrint = textToPrint.trimStart()
|
|
755
|
+
}
|
|
597
756
|
|
|
598
|
-
|
|
757
|
+
logger.write(textToPrint)
|
|
758
|
+
|
|
759
|
+
bufferedTokensToPrint = []
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
const confidence = topCandidateProbabilities[chosenCandidateRank]
|
|
764
|
+
|
|
765
|
+
addToken(chosenToken, timestampTokenLogits, confidence)
|
|
766
|
+
|
|
767
|
+
if (chosenToken === endOfTextToken) {
|
|
768
|
+
break
|
|
769
|
+
}
|
|
599
770
|
}
|
|
600
771
|
|
|
601
772
|
await yieldToEventLoop()
|
|
@@ -607,11 +778,16 @@ export class Whisper {
|
|
|
607
778
|
decodedTokensCrossAttentionQKs = decodedTokensCrossAttentionQKs.slice(0, lastTimestampTokenIndex)
|
|
608
779
|
}
|
|
609
780
|
|
|
610
|
-
|
|
781
|
+
logger.write('\n')
|
|
611
782
|
logger.end()
|
|
612
783
|
|
|
613
784
|
// Return the tokens
|
|
614
|
-
return {
|
|
785
|
+
return {
|
|
786
|
+
decodedTokens,
|
|
787
|
+
decodedTokensTimestampLogits,
|
|
788
|
+
crossAttentionQKs: decodedTokensCrossAttentionQKs,
|
|
789
|
+
decodedTokensConfidence
|
|
790
|
+
}
|
|
615
791
|
}
|
|
616
792
|
|
|
617
793
|
async inferCrossAttentionQKs(tokens: number[], audioFeatures: Onnx.Tensor) {
|
|
@@ -627,7 +803,7 @@ export class Whisper {
|
|
|
627
803
|
|
|
628
804
|
const decoderOutputs = await this.textDecoder!.run(decoderInputs)
|
|
629
805
|
|
|
630
|
-
const crossAttentionQKsTensor = decoderOutputs[
|
|
806
|
+
const crossAttentionQKsTensor = decoderOutputs['cross_attention_qks']
|
|
631
807
|
|
|
632
808
|
const tensorShape = crossAttentionQKsTensor.dims.slice()
|
|
633
809
|
|
|
@@ -663,6 +839,8 @@ export class Whisper {
|
|
|
663
839
|
}
|
|
664
840
|
|
|
665
841
|
async encodeAudio(rawAudio: RawAudio) {
|
|
842
|
+
await this.initializeEncoderSessionIfNeeded()
|
|
843
|
+
|
|
666
844
|
const logger = new Logger()
|
|
667
845
|
|
|
668
846
|
const audioSamples = rawAudio.audioChannels[0]
|
|
@@ -675,7 +853,7 @@ export class Whisper {
|
|
|
675
853
|
const maxAudioSamples = sampleRate * 30
|
|
676
854
|
const maxAudioFrames = 3000
|
|
677
855
|
|
|
678
|
-
await logger.startAsync(
|
|
856
|
+
await logger.startAsync('Extract mel spectogram from audio part')
|
|
679
857
|
|
|
680
858
|
const paddedAudioSamples = new Float32Array(maxAudioSamples)
|
|
681
859
|
paddedAudioSamples.set(audioSamples.subarray(0, maxAudioSamples), 0)
|
|
@@ -684,7 +862,7 @@ export class Whisper {
|
|
|
684
862
|
|
|
685
863
|
const { melSpectogram } = await computeMelSpectogramUsingFilterbanks(rawAudioPart, fftOrder, fftOrder, hopLength, filterbanks)
|
|
686
864
|
|
|
687
|
-
await logger.startAsync(
|
|
865
|
+
await logger.startAsync('Normalize mel spectogram')
|
|
688
866
|
|
|
689
867
|
const logMelSpectogram = melSpectogram.map(spectrum => spectrum.map(mel => Math.log10(Math.max(mel, 1e-10))))
|
|
690
868
|
let maxLogMel = -Infinity
|
|
@@ -708,14 +886,14 @@ export class Whisper {
|
|
|
708
886
|
}
|
|
709
887
|
}
|
|
710
888
|
|
|
711
|
-
await logger.startAsync(
|
|
889
|
+
await logger.startAsync('Encode mel spectogram with Whisper encoder model')
|
|
712
890
|
|
|
713
891
|
const inputTensor = new Onnx.Tensor('float32', flattenedNormalizedLogMelSpectogram, [1, filterbankCount, maxAudioFrames])
|
|
714
892
|
|
|
715
893
|
const encoderInputs = { mel: inputTensor }
|
|
716
894
|
|
|
717
895
|
const encoderOutputs = await this.audioEncoder!.run(encoderInputs)
|
|
718
|
-
const encodedAudioFeatures = encoderOutputs[
|
|
896
|
+
const encodedAudioFeatures = encoderOutputs['output']
|
|
719
897
|
|
|
720
898
|
logger.end()
|
|
721
899
|
|
|
@@ -728,7 +906,7 @@ export class Whisper {
|
|
|
728
906
|
for (let i = 0; i < tokens.length; i++) {
|
|
729
907
|
const token = tokens[i]
|
|
730
908
|
|
|
731
|
-
if (token == this.tokenConfig.
|
|
909
|
+
if (token == this.tokenConfig.startOfTextToken || token == this.tokenConfig.endOfTextToken) {
|
|
732
910
|
continue
|
|
733
911
|
}
|
|
734
912
|
|
|
@@ -740,7 +918,7 @@ export class Whisper {
|
|
|
740
918
|
continue
|
|
741
919
|
}
|
|
742
920
|
|
|
743
|
-
let startTime = initialTimeOffset + (token
|
|
921
|
+
let startTime = initialTimeOffset + this.timestampTokenToSeconds(token)
|
|
744
922
|
|
|
745
923
|
startTime = Math.min(startTime, audioDuration)
|
|
746
924
|
|
|
@@ -749,22 +927,22 @@ export class Whisper {
|
|
|
749
927
|
}
|
|
750
928
|
|
|
751
929
|
timeline.push({
|
|
752
|
-
type:
|
|
753
|
-
text:
|
|
930
|
+
type: 'segment',
|
|
931
|
+
text: '',
|
|
754
932
|
startTime,
|
|
755
933
|
endTime: -1,
|
|
756
934
|
})
|
|
757
935
|
} else {
|
|
758
936
|
if (timeline.length == 0) {
|
|
759
937
|
timeline.push({
|
|
760
|
-
type:
|
|
761
|
-
text:
|
|
938
|
+
type: 'segment',
|
|
939
|
+
text: '',
|
|
762
940
|
startTime: initialTimeOffset,
|
|
763
941
|
endTime: -1,
|
|
764
942
|
})
|
|
765
943
|
}
|
|
766
944
|
|
|
767
|
-
const tokenText = this.
|
|
945
|
+
const tokenText = this.tokenToText(token)
|
|
768
946
|
|
|
769
947
|
timeline[timeline.length - 1].text += tokenText
|
|
770
948
|
}
|
|
@@ -772,8 +950,6 @@ export class Whisper {
|
|
|
772
950
|
}
|
|
773
951
|
|
|
774
952
|
async addWordsToTimeline(timeline: Timeline, tokens: number[], rawAudio: RawAudio, crossAttentionQKs: Onnx.Tensor[], initialAudioTimeOffset: number, duration: number) {
|
|
775
|
-
const timestampTokensStart = this.tokenConfig.timestampTokensStart
|
|
776
|
-
|
|
777
953
|
let segmentStartTime = 0
|
|
778
954
|
let segmentTokens: number[] = []
|
|
779
955
|
let segmentCrossAttentionQKs: Onnx.Tensor[] = []
|
|
@@ -782,15 +958,15 @@ export class Whisper {
|
|
|
782
958
|
const token = tokens[tokenIndex]
|
|
783
959
|
const tokenCrossAttentionQKs = crossAttentionQKs[tokenIndex]
|
|
784
960
|
|
|
785
|
-
const segmentTokensWithoutTimestamps = segmentTokens.filter(token =>
|
|
961
|
+
const segmentTokensWithoutTimestamps = segmentTokens.filter(token => this.isNonTimestampToken(token))
|
|
786
962
|
|
|
787
|
-
const isTimestamp = token
|
|
963
|
+
const isTimestamp = this.isTimestampToken(token)
|
|
788
964
|
|
|
789
965
|
if (isTimestamp || tokenIndex == tokens.length - 1) {
|
|
790
966
|
let tokenTime: number
|
|
791
967
|
|
|
792
968
|
if (isTimestamp) {
|
|
793
|
-
tokenTime = (token
|
|
969
|
+
tokenTime = this.timestampTokenToSeconds(token)
|
|
794
970
|
} else {
|
|
795
971
|
tokenTime = duration
|
|
796
972
|
}
|
|
@@ -798,8 +974,8 @@ export class Whisper {
|
|
|
798
974
|
if (segmentTokensWithoutTimestamps.length > 0) {
|
|
799
975
|
const segmentEndTime = tokenTime
|
|
800
976
|
|
|
801
|
-
const segmentStartFrame =
|
|
802
|
-
let segmentEndFrame =
|
|
977
|
+
const segmentStartFrame = this.secondsToFrame(segmentStartTime)
|
|
978
|
+
let segmentEndFrame = this.secondsToFrame(segmentEndTime)
|
|
803
979
|
|
|
804
980
|
if (segmentStartFrame == segmentEndFrame) {
|
|
805
981
|
segmentEndFrame += 1
|
|
@@ -810,7 +986,7 @@ export class Whisper {
|
|
|
810
986
|
const reinferCrossAttentionQKs = true
|
|
811
987
|
|
|
812
988
|
if (reinferCrossAttentionQKs) {
|
|
813
|
-
const initialTokens = this.
|
|
989
|
+
const initialTokens = this.getTextStartTokens('en', 'transcribe')
|
|
814
990
|
const tokensToDecode = [...initialTokens, ...segmentTokensWithoutTimestamps]
|
|
815
991
|
|
|
816
992
|
//const segmentAudioFeaturesBuffer = audioFeatures.data.slice(segmentStartFrame * audioFeatures.dims[2], segmentEndFrame * audioFeatures.dims[2])
|
|
@@ -825,14 +1001,14 @@ export class Whisper {
|
|
|
825
1001
|
reinferredCrossAttentionQKs.slice(initialTokens.length)
|
|
826
1002
|
|
|
827
1003
|
const alignmentPath = await this.findAlignmentPathFromQKs(reinferredCrossAttentionQKs, tokensToDecode, 0, segmentFrameCount)//, alignmentHeadsIndexes[modelName])
|
|
828
|
-
const
|
|
1004
|
+
const tokenTimeline = await this.getTokenTimelineFromAlignmentPath(alignmentPath, segmentTokensWithoutTimestamps, initialAudioTimeOffset + segmentStartTime, initialAudioTimeOffset + segmentEndTime)
|
|
829
1005
|
|
|
830
|
-
timeline.push(...
|
|
1006
|
+
timeline.push(...tokenTimeline)
|
|
831
1007
|
} else {
|
|
832
1008
|
const alignmentPath = await this.findAlignmentPathFromQKs(segmentCrossAttentionQKs, segmentTokens, segmentStartFrame, segmentEndFrame)//, alignmentHeadsIndexes[modelName])
|
|
833
|
-
const
|
|
1009
|
+
const tokenTimeline = await this.getTokenTimelineFromAlignmentPath(alignmentPath, segmentTokens, initialAudioTimeOffset, initialAudioTimeOffset + segmentEndTime)
|
|
834
1010
|
|
|
835
|
-
timeline.push(...
|
|
1011
|
+
timeline.push(...tokenTimeline)
|
|
836
1012
|
}
|
|
837
1013
|
}
|
|
838
1014
|
|
|
@@ -846,17 +1022,34 @@ export class Whisper {
|
|
|
846
1022
|
}
|
|
847
1023
|
}
|
|
848
1024
|
|
|
849
|
-
|
|
1025
|
+
tokenTimelineToWordTimeline(tokenTimeline: Timeline) {
|
|
1026
|
+
const separatorChars =
|
|
1027
|
+
[' ', '–', '一', ',', '、', '|', '/', '\\', ';', '"', '“', '”', '…', '(', ')', '[', ']', '{', '}']
|
|
1028
|
+
|
|
1029
|
+
function startsWithSeparatingPunctuation(text: string) {
|
|
1030
|
+
return separatorChars.some(char => text.startsWith(char))
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
function isSeparatorPunctuation(text: string) {
|
|
1034
|
+
return separatorChars.includes(text)
|
|
1035
|
+
}
|
|
1036
|
+
|
|
850
1037
|
const resultTimeline: Timeline = []
|
|
851
1038
|
|
|
852
1039
|
const groups: TimelineEntry[][] = []
|
|
853
1040
|
|
|
854
|
-
for (
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
1041
|
+
for (let i = 0; i < tokenTimeline.length; i++) {
|
|
1042
|
+
const entry = tokenTimeline[i]
|
|
1043
|
+
const previousEntry = i > 0 ? tokenTimeline[i - 1] : undefined
|
|
1044
|
+
|
|
1045
|
+
const text = entry.text
|
|
1046
|
+
const previousEntryText = previousEntry?.text
|
|
1047
|
+
|
|
1048
|
+
if (groups.length == 0 ||
|
|
1049
|
+
text === '' ||
|
|
1050
|
+
startsWithSeparatingPunctuation(text) ||
|
|
1051
|
+
(previousEntryText != null && isSeparatorPunctuation(previousEntryText))) {
|
|
858
1052
|
|
|
859
|
-
if (groups.length == 0 || entry.text.startsWith(" ")) {
|
|
860
1053
|
groups.push([entry])
|
|
861
1054
|
} else {
|
|
862
1055
|
groups[groups.length - 1].push(entry)
|
|
@@ -864,39 +1057,41 @@ export class Whisper {
|
|
|
864
1057
|
}
|
|
865
1058
|
|
|
866
1059
|
for (const group of groups) {
|
|
867
|
-
|
|
868
|
-
resultTimeline.push(deepClone(group[0]))
|
|
869
|
-
} else {
|
|
870
|
-
const text = group.map(entry => entry.text).join("")
|
|
871
|
-
const startTime = group[0].startTime
|
|
872
|
-
const endTime = group[group.length - 1].endTime
|
|
873
|
-
let confidence: number | undefined = undefined
|
|
1060
|
+
const groupText = this.tokensToText(group.map(entry => entry.id!))
|
|
874
1061
|
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
1062
|
+
if (groupText === '') {
|
|
1063
|
+
continue
|
|
1064
|
+
}
|
|
878
1065
|
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
1066
|
+
const startTime = group[0].startTime
|
|
1067
|
+
const endTime = group[group.length - 1].endTime
|
|
1068
|
+
let confidence: number | undefined = undefined
|
|
1069
|
+
|
|
1070
|
+
if (group[0].confidence != null) {
|
|
1071
|
+
confidence = meanOfVector(group.map(entry => entry.confidence!))
|
|
1072
|
+
}
|
|
886
1073
|
|
|
887
|
-
|
|
1074
|
+
const newEntry: TimelineEntry = {
|
|
1075
|
+
type: 'word',
|
|
1076
|
+
text: groupText.trim(),
|
|
1077
|
+
startTime,
|
|
1078
|
+
endTime,
|
|
1079
|
+
confidence,
|
|
1080
|
+
timeline: group,
|
|
888
1081
|
}
|
|
1082
|
+
|
|
1083
|
+
resultTimeline.push(newEntry)
|
|
889
1084
|
}
|
|
890
1085
|
|
|
891
1086
|
return resultTimeline
|
|
892
1087
|
}
|
|
893
1088
|
|
|
894
|
-
async
|
|
1089
|
+
async getTokenTimelineFromAlignmentPath(alignmentPath: AlignmentPath, tokens: number[], startTimeOffset: number, endTimeOffset: number, tokensConfidence?: number[], correctionAmount = 0.0) {
|
|
895
1090
|
if (alignmentPath.length == 0) {
|
|
896
1091
|
return []
|
|
897
1092
|
}
|
|
898
1093
|
|
|
899
|
-
const
|
|
1094
|
+
const tokenTimeline: Timeline = []
|
|
900
1095
|
|
|
901
1096
|
for (let pathIndex = 0; pathIndex < alignmentPath.length; pathIndex++) {
|
|
902
1097
|
if (pathIndex != 0 && alignmentPath[pathIndex].source == alignmentPath[pathIndex - 1].source) {
|
|
@@ -908,41 +1103,37 @@ export class Whisper {
|
|
|
908
1103
|
const tokenIndex = tokenMappingEntry.source
|
|
909
1104
|
const token = tokens[tokenIndex]
|
|
910
1105
|
const tokenConfidence = tokensConfidence ? tokensConfidence[tokenIndex] : undefined
|
|
911
|
-
const tokenText = this.
|
|
912
|
-
|
|
913
|
-
if (token >= this.tokenConfig.eotToken || !tokenText) {
|
|
914
|
-
continue
|
|
915
|
-
}
|
|
1106
|
+
const tokenText = this.tokenToText(token, true)
|
|
916
1107
|
|
|
917
1108
|
let startTime = startTimeOffset + (tokenMappingEntry.dest * 0.02)
|
|
918
1109
|
|
|
919
1110
|
startTime = Math.max(startTime + correctionAmount, startTimeOffset)
|
|
920
1111
|
|
|
921
|
-
if (
|
|
922
|
-
|
|
1112
|
+
if (tokenTimeline.length > 0) {
|
|
1113
|
+
tokenTimeline[tokenTimeline.length - 1].endTime = startTime
|
|
923
1114
|
}
|
|
924
1115
|
|
|
925
|
-
|
|
926
|
-
type:
|
|
1116
|
+
tokenTimeline.push({
|
|
1117
|
+
type: 'token',
|
|
927
1118
|
text: tokenText,
|
|
1119
|
+
id: token,
|
|
928
1120
|
startTime,
|
|
929
1121
|
endTime: -1,
|
|
930
1122
|
confidence: tokenConfidence
|
|
931
1123
|
})
|
|
932
1124
|
}
|
|
933
1125
|
|
|
934
|
-
if (
|
|
935
|
-
|
|
1126
|
+
if (tokenTimeline.length > 0) {
|
|
1127
|
+
tokenTimeline[tokenTimeline.length - 1].endTime = endTimeOffset
|
|
936
1128
|
}
|
|
937
1129
|
|
|
938
|
-
return
|
|
1130
|
+
return tokenTimeline
|
|
939
1131
|
}
|
|
940
1132
|
|
|
941
1133
|
async findAlignmentPathFromQKs(qksTensors: Onnx.Tensor[], tokens: number[], segmentStartFrame: number, segmentEndFrame: number, headIndexes?: number[]) {
|
|
942
1134
|
const segmentFrameCount = segmentEndFrame - segmentStartFrame
|
|
943
1135
|
|
|
944
|
-
if (segmentFrameCount
|
|
945
|
-
//throw new Error("Segment has 0 frames")
|
|
1136
|
+
if (segmentFrameCount === 0 || tokens.length === 0 || qksTensors.length === 0) {
|
|
946
1137
|
return []
|
|
947
1138
|
}
|
|
948
1139
|
|
|
@@ -961,10 +1152,10 @@ export class Whisper {
|
|
|
961
1152
|
}
|
|
962
1153
|
|
|
963
1154
|
// Load attention head weights from tensors
|
|
964
|
-
const attentionHeads: number[][][] = [] // [heads, tokens, frames]
|
|
1155
|
+
const attentionHeads: number[][][] = [] // structure: [heads, tokens, frames]
|
|
965
1156
|
|
|
966
1157
|
for (const headIndex of headIndexes) {
|
|
967
|
-
const attentionHead: number[][] = [] // [tokens, frames]
|
|
1158
|
+
const attentionHead: number[][] = [] // structure: [tokens, frames]
|
|
968
1159
|
|
|
969
1160
|
for (let tokenIndex = 0; tokenIndex < tokenCount; tokenIndex++) {
|
|
970
1161
|
const bufferOffset = headIndex * frameCount
|
|
@@ -1002,7 +1193,7 @@ export class Whisper {
|
|
|
1002
1193
|
const allWeightsForHead = head.flatMap(tokenFrames => tokenFrames)
|
|
1003
1194
|
|
|
1004
1195
|
const meanOfAllWeights = meanOfVector(allWeightsForHead)
|
|
1005
|
-
const stdDeviationOfAllWeights = stdDeviationOfVector(allWeightsForHead)
|
|
1196
|
+
const stdDeviationOfAllWeights = stdDeviationOfVector(allWeightsForHead) + 1e-10
|
|
1006
1197
|
|
|
1007
1198
|
for (const tokenFrames of head) {
|
|
1008
1199
|
for (let frameIndex = 0; frameIndex < tokenFrames.length; frameIndex++) {
|
|
@@ -1045,11 +1236,15 @@ export class Whisper {
|
|
|
1045
1236
|
}
|
|
1046
1237
|
|
|
1047
1238
|
if (fixateTimestampTokens) {
|
|
1239
|
+
// Fixate timestamp tokens to the original ones detected
|
|
1048
1240
|
const timestampTokensStart = this.tokenConfig.timestampTokensStart
|
|
1049
1241
|
|
|
1050
1242
|
for (let tokenIndex = 0; tokenIndex < tokens.length; tokenIndex++) {
|
|
1051
|
-
|
|
1052
|
-
|
|
1243
|
+
const token = tokens[tokenIndex]
|
|
1244
|
+
|
|
1245
|
+
if (this.isTimestampToken(token)) {
|
|
1246
|
+
let timestampFrame = token - timestampTokensStart
|
|
1247
|
+
|
|
1053
1248
|
timestampFrame = clip(timestampFrame, segmentStartFrame, segmentEndFrame - 1)
|
|
1054
1249
|
|
|
1055
1250
|
frameMeansForToken[tokenIndex][timestampFrame] = 100
|
|
@@ -1061,9 +1256,9 @@ export class Whisper {
|
|
|
1061
1256
|
const tokenIndexes = [...Array(tokenCount).keys()]
|
|
1062
1257
|
const frameIndexes = [...Array(segmentFrameCount).keys()]
|
|
1063
1258
|
|
|
1064
|
-
let { path } =
|
|
1259
|
+
let { path } = alignDTWWindowed(tokenIndexes, frameIndexes, (tokenIndex, frameIndex) => {
|
|
1065
1260
|
return -frameMeansForToken[tokenIndex][frameIndex]
|
|
1066
|
-
},
|
|
1261
|
+
}, segmentFrameCount)
|
|
1067
1262
|
|
|
1068
1263
|
path = path.map(entry => ({ source: entry.source, dest: segmentStartFrame + entry.dest }))
|
|
1069
1264
|
|
|
@@ -1073,101 +1268,138 @@ export class Whisper {
|
|
|
1073
1268
|
getKvDimensions(groupCount: number, length: number) {
|
|
1074
1269
|
const modelName = this.modelName
|
|
1075
1270
|
|
|
1076
|
-
if (modelName ==
|
|
1271
|
+
if (modelName == 'tiny' || modelName == 'tiny.en') {
|
|
1077
1272
|
return [8, groupCount, length, 384]
|
|
1078
|
-
} else if (modelName ==
|
|
1273
|
+
} else if (modelName == 'base' || modelName == 'base.en') {
|
|
1079
1274
|
return [12, groupCount, length, 512]
|
|
1080
|
-
} else if (modelName ==
|
|
1275
|
+
} else if (modelName == 'small' || modelName == 'small.en') {
|
|
1081
1276
|
return [24, groupCount, length, 768]
|
|
1082
|
-
} else if (modelName ==
|
|
1277
|
+
} else if (modelName == 'medium' || modelName == 'medium.en') {
|
|
1083
1278
|
return [48, groupCount, length, 1024]
|
|
1084
|
-
} else if (modelName ==
|
|
1279
|
+
} else if (modelName == 'large' || modelName == 'large-v1' || modelName == 'large-v2' || modelName == 'large-v3') {
|
|
1085
1280
|
return [64, groupCount, length, 1280]
|
|
1086
1281
|
} else {
|
|
1087
1282
|
throw new Error(`Unsupported model: ${modelName}`)
|
|
1088
1283
|
}
|
|
1089
1284
|
}
|
|
1090
1285
|
|
|
1091
|
-
|
|
1092
|
-
const
|
|
1286
|
+
getTextStartTokens(language: string, task: WhisperTask, disableTimestamps = false) {
|
|
1287
|
+
const startOfTextToken = this.tokenConfig.startOfTextToken
|
|
1093
1288
|
|
|
1094
|
-
let
|
|
1289
|
+
let tokens: number[]
|
|
1095
1290
|
|
|
1096
1291
|
if (this.isMultiligualModel) {
|
|
1097
|
-
const languageToken =
|
|
1098
|
-
const
|
|
1099
|
-
const transcribeTaskToken = 50359
|
|
1100
|
-
const taskToken = task == "transcribe" ? transcribeTaskToken : translateTaskToken
|
|
1292
|
+
const languageToken = this.tokenConfig.languageTokensStart + languageIdLookup[language]
|
|
1293
|
+
const taskToken = task == 'translate' ? this.tokenConfig.translateTaskToken : this.tokenConfig.transcribeTaskToken
|
|
1101
1294
|
|
|
1102
|
-
|
|
1295
|
+
tokens = [startOfTextToken, languageToken, taskToken]
|
|
1103
1296
|
} else {
|
|
1104
|
-
|
|
1297
|
+
tokens = [startOfTextToken]
|
|
1105
1298
|
}
|
|
1106
1299
|
|
|
1107
1300
|
if (disableTimestamps) {
|
|
1108
|
-
|
|
1301
|
+
tokens.push(this.tokenConfig.noTimestampsToken)
|
|
1109
1302
|
}
|
|
1110
1303
|
|
|
1111
|
-
return
|
|
1304
|
+
return tokens
|
|
1112
1305
|
}
|
|
1113
1306
|
|
|
1114
|
-
|
|
1115
|
-
return
|
|
1307
|
+
tokenToText(token: number, includeMetadataTokens = false) {
|
|
1308
|
+
return this.tokensToText([token], includeMetadataTokens)
|
|
1116
1309
|
}
|
|
1117
1310
|
|
|
1118
|
-
tokensToText(tokens: number[]) {
|
|
1119
|
-
|
|
1311
|
+
tokensToText(tokens: number[], includeMetadataTokens = false) {
|
|
1312
|
+
tokens.forEach(token => this.assertIsValidToken(token))
|
|
1313
|
+
|
|
1314
|
+
if (includeMetadataTokens === false) {
|
|
1315
|
+
tokens = tokens.filter(token => this.isTextToken(token))
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
if (tokens.length === 0) {
|
|
1319
|
+
return ''
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
try {
|
|
1323
|
+
return Buffer.from(this.tiktoken!.decode(new Uint32Array(tokens))).toString('utf8')
|
|
1324
|
+
} catch {
|
|
1325
|
+
return '[TOKENIZER_FAILED]'
|
|
1326
|
+
}
|
|
1120
1327
|
}
|
|
1121
1328
|
|
|
1122
|
-
|
|
1123
|
-
|
|
1329
|
+
textToTokens(text: string) {
|
|
1330
|
+
return Array.from(this.tiktoken!.encode(text))
|
|
1331
|
+
}
|
|
1124
1332
|
|
|
1125
|
-
|
|
1333
|
+
isTextToken(token: number) {
|
|
1334
|
+
return token < this.tokenConfig.endOfTextToken
|
|
1335
|
+
}
|
|
1126
1336
|
|
|
1127
|
-
|
|
1337
|
+
isMetadataToken(token: number) {
|
|
1338
|
+
return token >= this.tokenConfig.endOfTextToken
|
|
1339
|
+
}
|
|
1128
1340
|
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1341
|
+
isLanguageToken(token: number) {
|
|
1342
|
+
return token >= this.tokenConfig.languageTokensStart && token < this.tokenConfig.languageTokensEnd
|
|
1343
|
+
}
|
|
1132
1344
|
|
|
1133
|
-
|
|
1345
|
+
isTimestampToken(token: number) {
|
|
1346
|
+
return token >= this.tokenConfig.timestampTokensStart
|
|
1347
|
+
}
|
|
1134
1348
|
|
|
1135
|
-
|
|
1136
|
-
|
|
1349
|
+
isNonTimestampToken(token: number) {
|
|
1350
|
+
return token < this.tokenConfig.timestampTokensStart
|
|
1351
|
+
}
|
|
1137
1352
|
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
allResultingSubwords.push([word])
|
|
1141
|
-
continue
|
|
1142
|
-
}
|
|
1353
|
+
timestampTokenToSeconds(timestampToken: number) {
|
|
1354
|
+
this.assertIsValidToken(timestampToken)
|
|
1143
1355
|
|
|
1144
|
-
|
|
1356
|
+
if (this.isNonTimestampToken(timestampToken)) {
|
|
1357
|
+
throw new Error(`Invalid timestamp token: ${timestampToken}`)
|
|
1358
|
+
}
|
|
1145
1359
|
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
const currentSubword = subwords[i]
|
|
1149
|
-
const nextSubword = subwords[i + 1]
|
|
1360
|
+
return (timestampToken - this.tokenConfig.timestampTokensStart) * 0.02
|
|
1361
|
+
}
|
|
1150
1362
|
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
}
|
|
1155
|
-
}
|
|
1363
|
+
isValidToken(token: number) {
|
|
1364
|
+
return token < this.tokenConfig.timestampTokensEnd
|
|
1365
|
+
}
|
|
1156
1366
|
|
|
1157
|
-
|
|
1158
|
-
|
|
1367
|
+
assertIsValidToken(token: number) {
|
|
1368
|
+
if (!this.isValidToken(token)) {
|
|
1369
|
+
throw new Error(`Invalid token: ${token}`)
|
|
1370
|
+
}
|
|
1371
|
+
}
|
|
1159
1372
|
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1373
|
+
secondsToFrame(seconds: number) {
|
|
1374
|
+
return Math.floor(seconds / 0.02)
|
|
1375
|
+
}
|
|
1163
1376
|
|
|
1164
|
-
|
|
1165
|
-
|
|
1377
|
+
secondsRangeToFrameCount(startSeconds: number, endSeconds: number) {
|
|
1378
|
+
if (startSeconds > endSeconds) {
|
|
1379
|
+
throw new Error(`Invalid range: ${startSeconds} > ${endSeconds}`)
|
|
1380
|
+
}
|
|
1166
1381
|
|
|
1167
|
-
|
|
1382
|
+
return this.secondsToFrame(endSeconds - startSeconds)
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1385
|
+
languageTokenToLanguageIndex(languageToken: number) {
|
|
1386
|
+
if (!this.isLanguageToken(languageToken)) {
|
|
1387
|
+
throw new Error(`Invalid language token: ${languageToken}`)
|
|
1168
1388
|
}
|
|
1169
1389
|
|
|
1170
|
-
|
|
1390
|
+
let languageIndex = languageToken - this.tokenConfig.languageTokensStart
|
|
1391
|
+
|
|
1392
|
+
if (this.isEnglishOnlyModel) {
|
|
1393
|
+
languageIndex += 1
|
|
1394
|
+
}
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
get isEnglishOnlyModel() {
|
|
1398
|
+
return this.isMultiligualModel === false
|
|
1399
|
+
}
|
|
1400
|
+
|
|
1401
|
+
getAlignmentHeadIndexes() {
|
|
1402
|
+
return alignmentHeadsIndexes[this.modelName]
|
|
1171
1403
|
}
|
|
1172
1404
|
}
|
|
1173
1405
|
|
|
@@ -1334,171 +1566,184 @@ const filterbanks: Filterbank[] = [
|
|
|
1334
1566
|
]
|
|
1335
1567
|
|
|
1336
1568
|
export async function loadPackagesAndGetPaths(modelName: WhisperModelName | undefined, languageCode: string | undefined) {
|
|
1337
|
-
if (
|
|
1569
|
+
if (modelName) {
|
|
1570
|
+
modelName = normalizeWhisperModelName(modelName, languageCode)
|
|
1571
|
+
} else {
|
|
1338
1572
|
if (languageCode) {
|
|
1339
1573
|
const shortLanguageCode = getShortLanguageCode(languageCode)
|
|
1340
1574
|
|
|
1341
|
-
modelName = shortLanguageCode ==
|
|
1575
|
+
modelName = shortLanguageCode == 'en' ? 'tiny.en' : 'tiny'
|
|
1342
1576
|
} else {
|
|
1343
|
-
modelName =
|
|
1577
|
+
modelName = 'tiny'
|
|
1344
1578
|
}
|
|
1345
1579
|
}
|
|
1346
1580
|
|
|
1581
|
+
if (modelName.startsWith('large')) {
|
|
1582
|
+
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.`)
|
|
1583
|
+
}
|
|
1584
|
+
|
|
1347
1585
|
const packageName = modelNameToPackageName[modelName]
|
|
1348
1586
|
|
|
1349
1587
|
const modelDir = await loadPackage(packageName)
|
|
1350
1588
|
|
|
1351
|
-
|
|
1352
|
-
const tokenizerDir = isMultiligualModel(modelName) ? path.join(tokenizerPackagePath, "multilingual") : path.join(tokenizerPackagePath, "gpt2")
|
|
1353
|
-
|
|
1354
|
-
return { modelName, modelDir, tokenizerDir }
|
|
1589
|
+
return { modelName, modelDir }
|
|
1355
1590
|
}
|
|
1356
1591
|
|
|
1357
|
-
export function
|
|
1358
|
-
|
|
1592
|
+
export function normalizeWhisperModelName(modelName: WhisperModelName, languageCode: string | undefined): WhisperModelName {
|
|
1593
|
+
if (languageCode != 'en' && modelName.endsWith('.en')) {
|
|
1594
|
+
const originalModelName = modelName
|
|
1595
|
+
modelName = modelName.slice(0, modelName.length - 3) as WhisperModelName
|
|
1596
|
+
|
|
1597
|
+
const logger = new Logger()
|
|
1598
|
+
logger.logTitledMessage(`Warning`, `The model '${originalModelName}' is English only and cannot be used to transcribe language '${languageCode}'. using '${modelName}' instead.`, chalk.yellowBright)
|
|
1599
|
+
}
|
|
1600
|
+
|
|
1601
|
+
return modelName
|
|
1359
1602
|
}
|
|
1360
1603
|
|
|
1361
|
-
export
|
|
1362
|
-
|
|
1604
|
+
export function isMultilingualModel(modelName: WhisperModelName) {
|
|
1605
|
+
return !isEnglishOnlyModel(modelName)
|
|
1606
|
+
}
|
|
1363
1607
|
|
|
1364
|
-
export
|
|
1365
|
-
|
|
1366
|
-
"tiny.en": "whisper-tiny.en",
|
|
1367
|
-
"base": "whisper-base",
|
|
1368
|
-
"base.en": "whisper-base.en",
|
|
1369
|
-
"small": "whisper-small",
|
|
1370
|
-
"small.en": "whisper-small.en",
|
|
1371
|
-
"medium": "whisper-medium",
|
|
1372
|
-
"medium.en": "whisper-medium.en",
|
|
1373
|
-
"large": "whisper-large-v3",
|
|
1374
|
-
"large-v1": "whisper-large-v1",
|
|
1375
|
-
"large-v2": "whisper-large-v2",
|
|
1376
|
-
"large-v3": "whisper-large-v3"
|
|
1608
|
+
export function isEnglishOnlyModel(modelName: WhisperModelName) {
|
|
1609
|
+
return modelName.endsWith('.en')
|
|
1377
1610
|
}
|
|
1378
1611
|
|
|
1379
|
-
export
|
|
1612
|
+
export type WhisperModelName = 'tiny' | 'tiny.en' | 'base' | 'base.en' | 'small' | 'small.en' | 'medium' | 'medium.en' | 'large' | 'large-v1' | 'large-v2' | 'large-v3'
|
|
1613
|
+
export type WhisperTask = 'transcribe' | 'translate' | 'detect-language'
|
|
1380
1614
|
|
|
1381
|
-
const
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1615
|
+
export const modelNameToPackageName: { [modelName in WhisperModelName]: string } = {
|
|
1616
|
+
'tiny': 'whisper-tiny',
|
|
1617
|
+
'tiny.en': 'whisper-tiny.en',
|
|
1618
|
+
'base': 'whisper-base',
|
|
1619
|
+
'base.en': 'whisper-base.en',
|
|
1620
|
+
'small': 'whisper-small',
|
|
1621
|
+
'small.en': 'whisper-small.en',
|
|
1622
|
+
'medium': 'whisper-medium',
|
|
1623
|
+
'medium.en': 'whisper-medium.en',
|
|
1624
|
+
'large': 'whisper-large-v3',
|
|
1625
|
+
'large-v1': 'whisper-large-v1',
|
|
1626
|
+
'large-v2': 'whisper-large-v2',
|
|
1627
|
+
'large-v3': 'whisper-large-v3'
|
|
1385
1628
|
}
|
|
1386
1629
|
|
|
1630
|
+
export const tokenizerPackageName = 'whisper-tokenizer'
|
|
1631
|
+
|
|
1387
1632
|
const languageIdLookup: { [s: string]: number } = {
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1633
|
+
'en': 0,
|
|
1634
|
+
'zh': 1,
|
|
1635
|
+
'de': 2,
|
|
1636
|
+
'es': 3,
|
|
1637
|
+
'ru': 4,
|
|
1638
|
+
'ko': 5,
|
|
1639
|
+
'fr': 6,
|
|
1640
|
+
'ja': 7,
|
|
1641
|
+
'pt': 8,
|
|
1642
|
+
'tr': 9,
|
|
1643
|
+
'pl': 10,
|
|
1644
|
+
'ca': 11,
|
|
1645
|
+
'nl': 12,
|
|
1646
|
+
'ar': 13,
|
|
1647
|
+
'sv': 14,
|
|
1648
|
+
'it': 15,
|
|
1649
|
+
'id': 16,
|
|
1650
|
+
'hi': 17,
|
|
1651
|
+
'fi': 18,
|
|
1652
|
+
'vi': 19,
|
|
1653
|
+
'iw': 20,
|
|
1654
|
+
'uk': 21,
|
|
1655
|
+
'el': 22,
|
|
1656
|
+
'ms': 23,
|
|
1657
|
+
'cs': 24,
|
|
1658
|
+
'ro': 25,
|
|
1659
|
+
'da': 26,
|
|
1660
|
+
'hu': 27,
|
|
1661
|
+
'ta': 28,
|
|
1662
|
+
'no': 29,
|
|
1663
|
+
'th': 30,
|
|
1664
|
+
'ur': 31,
|
|
1665
|
+
'hr': 32,
|
|
1666
|
+
'bg': 33,
|
|
1667
|
+
'lt': 34,
|
|
1668
|
+
'la': 35,
|
|
1669
|
+
'mi': 36,
|
|
1670
|
+
'ml': 37,
|
|
1671
|
+
'cy': 38,
|
|
1672
|
+
'sk': 39,
|
|
1673
|
+
'te': 40,
|
|
1674
|
+
'fa': 41,
|
|
1675
|
+
'lv': 42,
|
|
1676
|
+
'bn': 43,
|
|
1677
|
+
'sr': 44,
|
|
1678
|
+
'az': 45,
|
|
1679
|
+
'sl': 46,
|
|
1680
|
+
'kn': 47,
|
|
1681
|
+
'et': 48,
|
|
1682
|
+
'mk': 49,
|
|
1683
|
+
'br': 50,
|
|
1684
|
+
'eu': 51,
|
|
1685
|
+
'is': 52,
|
|
1686
|
+
'hy': 53,
|
|
1687
|
+
'ne': 54,
|
|
1688
|
+
'mn': 55,
|
|
1689
|
+
'bs': 56,
|
|
1690
|
+
'kk': 57,
|
|
1691
|
+
'sq': 58,
|
|
1692
|
+
'sw': 59,
|
|
1693
|
+
'gl': 60,
|
|
1694
|
+
'mr': 61,
|
|
1695
|
+
'pa': 62,
|
|
1696
|
+
'si': 63,
|
|
1697
|
+
'km': 64,
|
|
1698
|
+
'sn': 65,
|
|
1699
|
+
'yo': 66,
|
|
1700
|
+
'so': 67,
|
|
1701
|
+
'af': 68,
|
|
1702
|
+
'oc': 69,
|
|
1703
|
+
'ka': 70,
|
|
1704
|
+
'be': 71,
|
|
1705
|
+
'tg': 72,
|
|
1706
|
+
'sd': 73,
|
|
1707
|
+
'gu': 74,
|
|
1708
|
+
'am': 75,
|
|
1709
|
+
'yi': 76,
|
|
1710
|
+
'lo': 77,
|
|
1711
|
+
'uz': 78,
|
|
1712
|
+
'fo': 79,
|
|
1713
|
+
'ht': 80,
|
|
1714
|
+
'ps': 81,
|
|
1715
|
+
'tk': 82,
|
|
1716
|
+
'nn': 83,
|
|
1717
|
+
'mt': 84,
|
|
1718
|
+
'sa': 85,
|
|
1719
|
+
'lb': 86,
|
|
1720
|
+
'my': 87,
|
|
1721
|
+
'bo': 88,
|
|
1722
|
+
'tl': 89,
|
|
1723
|
+
'mg': 90,
|
|
1724
|
+
'as': 91,
|
|
1725
|
+
'tt': 92,
|
|
1726
|
+
'haw': 93,
|
|
1727
|
+
'ln': 94,
|
|
1728
|
+
'ha': 95,
|
|
1729
|
+
'ba': 96,
|
|
1730
|
+
'jw': 97,
|
|
1731
|
+
'su': 98,
|
|
1487
1732
|
}
|
|
1488
1733
|
|
|
1489
1734
|
const alignmentHeadsIndexes: { [name in WhisperModelName]: number[] } = {
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1735
|
+
'tiny.en': [6, 12, 17, 18, 19, 20, 21, 22],
|
|
1736
|
+
'tiny': [14, 18, 20, 21, 22, 23],
|
|
1737
|
+
'base.en': [27, 39, 41, 45, 47],
|
|
1738
|
+
'base': [25, 34, 35, 39, 41, 42, 44, 46],
|
|
1739
|
+
'small.en': [78, 84, 87, 92, 98, 101, 103, 108, 112, 116, 118, 120, 121, 122, 123, 126, 131, 134, 136],
|
|
1740
|
+
'small': [63, 69, 96, 100, 103, 104, 108, 115, 117, 125],
|
|
1741
|
+
'medium.en': [180, 225, 236, 238, 244, 256, 260, 265, 284, 286, 295, 298, 303, 320, 323, 329, 334, 348],
|
|
1742
|
+
'medium': [223, 244, 255, 257, 320, 372],
|
|
1743
|
+
'large-v1': [199, 222, 224, 237, 447, 451, 457, 462, 475],
|
|
1744
|
+
'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],
|
|
1745
|
+
'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)
|
|
1746
|
+
'large': [212, 277, 331, 332, 333, 355, 356, 364, 371, 379, 391, 422, 423, 443, 449, 452, 465, 467, 473, 505, 521, 532, 555],
|
|
1502
1747
|
}
|
|
1503
1748
|
|
|
1504
1749
|
export interface WhisperOptions {
|
|
@@ -1508,13 +1753,21 @@ export interface WhisperOptions {
|
|
|
1508
1753
|
topCandidateCount?: number
|
|
1509
1754
|
punctuationThreshold?: number
|
|
1510
1755
|
autoPromptParts?: boolean
|
|
1756
|
+
maxTokensPerPart?: number
|
|
1757
|
+
suppressRepetition?: boolean
|
|
1758
|
+
seed?: number
|
|
1759
|
+
decodeTimestampTokens?: boolean
|
|
1511
1760
|
}
|
|
1512
1761
|
|
|
1513
|
-
export const
|
|
1762
|
+
export const defaultWhisperOptions: WhisperOptions = {
|
|
1514
1763
|
model: undefined,
|
|
1515
1764
|
temperature: 0.1,
|
|
1516
1765
|
prompt: undefined,
|
|
1517
1766
|
topCandidateCount: 5,
|
|
1518
1767
|
punctuationThreshold: 0.2,
|
|
1519
|
-
autoPromptParts: true
|
|
1768
|
+
autoPromptParts: true,
|
|
1769
|
+
maxTokensPerPart: 250,
|
|
1770
|
+
suppressRepetition: true,
|
|
1771
|
+
seed: undefined,
|
|
1772
|
+
decodeTimestampTokens: false,
|
|
1520
1773
|
}
|