echogarden 1.4.4 → 1.6.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/data/schemas/options.json +310 -25
- package/dist/alignment/DTWMfccSequenceAlignment.d.ts +1 -1
- package/dist/alignment/DTWMfccSequenceAlignment.js +5 -5
- package/dist/alignment/DTWSequenceAlignmentWindowed.js +1 -3
- package/dist/alignment/DTWSequenceAlignmentWindowed.js.map +1 -1
- package/dist/alignment/{TextAlignment.d.ts → SemanticTextAlignment.d.ts} +4 -2
- package/dist/alignment/SemanticTextAlignment.js +336 -0
- package/dist/alignment/SemanticTextAlignment.js.map +1 -0
- package/dist/alignment/SpeechAlignment.d.ts +4 -3
- package/dist/alignment/SpeechAlignment.js +130 -39
- package/dist/alignment/SpeechAlignment.js.map +1 -1
- package/dist/api/API.d.ts +7 -3
- package/dist/api/API.js +7 -2
- package/dist/api/API.js.map +1 -1
- package/dist/api/APIOptions.d.ts +4 -1
- package/dist/api/Alignment.d.ts +1 -1
- package/dist/api/Alignment.js +13 -5
- package/dist/api/Alignment.js.map +1 -1
- package/dist/api/LanguageDetectionCommon.d.ts +6 -0
- package/dist/api/LanguageDetectionCommon.js +2 -0
- package/dist/api/LanguageDetectionCommon.js.map +1 -0
- package/dist/api/Recognition.js.map +1 -1
- package/dist/api/{LanguageDetection.d.ts → SpeechLanguageDetection.d.ts} +1 -25
- package/dist/api/{LanguageDetection.js → SpeechLanguageDetection.js} +1 -68
- package/dist/api/SpeechLanguageDetection.js.map +1 -0
- package/dist/api/{Translation.js → SpeechTranslation.js} +3 -3
- package/dist/api/SpeechTranslation.js.map +1 -0
- package/dist/api/Synthesis.d.ts +0 -1
- package/dist/api/Synthesis.js +4 -4
- package/dist/api/TextLanguageDetection.d.ts +21 -0
- package/dist/api/TextLanguageDetection.js +67 -0
- package/dist/api/TextLanguageDetection.js.map +1 -0
- package/dist/api/TextTranslation.d.ts +25 -0
- package/dist/api/TextTranslation.js +101 -0
- package/dist/api/TextTranslation.js.map +1 -0
- package/dist/api/TimelineTranslationAlignment.d.ts +23 -0
- package/dist/api/TimelineTranslationAlignment.js +92 -0
- package/dist/api/TimelineTranslationAlignment.js.map +1 -0
- package/dist/api/TranscriptAndTranslationAlignment.d.ts +35 -0
- package/dist/api/TranscriptAndTranslationAlignment.js +78 -0
- package/dist/api/TranscriptAndTranslationAlignment.js.map +1 -0
- package/dist/api/TranslationAlignment.d.ts +4 -3
- package/dist/api/TranslationAlignment.js +9 -8
- package/dist/api/TranslationAlignment.js.map +1 -1
- package/dist/api/VoiceActivityDetection.js +16 -1
- package/dist/api/VoiceActivityDetection.js.map +1 -1
- package/dist/audio/AudioBufferConversion.d.ts +0 -1
- package/dist/audio/AudioPlayer.d.ts +0 -1
- package/dist/audio/AudioPlayer.js +62 -41
- package/dist/audio/AudioPlayer.js.map +1 -1
- package/dist/audio/AudioUtilities.d.ts +0 -1
- package/dist/cli/CLI.d.ts +28 -7
- package/dist/cli/CLI.js +265 -37
- package/dist/cli/CLI.js.map +1 -1
- package/dist/codecs/FFMpegTranscoder.d.ts +0 -1
- package/dist/codecs/FFMpegTranscoder.js +7 -0
- package/dist/codecs/FFMpegTranscoder.js.map +1 -1
- package/dist/codecs/TIMITCodec.d.ts +0 -1
- package/dist/codecs/WaveCodec.d.ts +0 -1
- package/dist/dsp/FFT.d.ts +1 -1
- package/dist/dsp/FFT.js +6 -0
- package/dist/dsp/FFT.js.map +1 -1
- package/dist/dsp/KWeightingFilter.js +1 -1
- package/dist/dsp/KWeightingFilter.js.map +1 -1
- package/dist/dsp/MelSpectogram.d.ts +3 -2
- package/dist/dsp/MelSpectogram.js +14 -8
- package/dist/dsp/MelSpectogram.js.map +1 -1
- package/dist/math/VectorMath.d.ts +9 -9
- package/dist/math/VectorMath.js +10 -10
- package/dist/math/VectorMath.js.map +1 -1
- package/dist/nlp/ChineseSegmentation.js +4 -4
- package/dist/nlp/ChineseSegmentation.js.map +1 -1
- package/dist/nlp/Segmentation.d.ts +2 -2
- package/dist/nlp/Segmentation.js +20 -13
- package/dist/nlp/Segmentation.js.map +1 -1
- package/dist/recognition/OpenAICloudSTT.d.ts +2 -1
- package/dist/recognition/OpenAICloudSTT.js +30 -19
- package/dist/recognition/OpenAICloudSTT.js.map +1 -1
- package/dist/recognition/SileroSTT.d.ts +0 -1
- package/dist/recognition/WhisperCppSTT.d.ts +3 -3
- package/dist/recognition/WhisperCppSTT.js +21 -9
- package/dist/recognition/WhisperCppSTT.js.map +1 -1
- package/dist/recognition/WhisperSTT.d.ts +9 -6
- package/dist/recognition/WhisperSTT.js +227 -46
- package/dist/recognition/WhisperSTT.js.map +1 -1
- package/dist/server/Client.d.ts +3 -4
- package/dist/server/Client.js.map +1 -1
- package/dist/server/Worker.d.ts +3 -3
- package/dist/server/Worker.js +3 -2
- package/dist/server/Worker.js.map +1 -1
- package/dist/source-separation/MDXNetSourceSeparation.d.ts +0 -1
- package/dist/source-separation/MDXNetSourceSeparation.js +1 -1
- package/dist/source-separation/MDXNetSourceSeparation.js.map +1 -1
- package/dist/speech-embeddings/WavToVec2BertFeatureEmbeddings.d.ts +12 -0
- package/dist/speech-embeddings/WavToVec2BertFeatureEmbeddings.js +68 -0
- package/dist/speech-embeddings/WavToVec2BertFeatureEmbeddings.js.map +1 -0
- package/dist/speech-language-detection/SileroLanguageDetection.d.ts +1 -2
- package/dist/speech-language-detection/SileroLanguageDetection.js +1 -1
- package/dist/speech-language-detection/SileroLanguageDetection.js.map +1 -1
- package/dist/subtitles/Subtitles.js +2 -2
- package/dist/subtitles/Subtitles.js.map +1 -1
- package/dist/synthesis/GoogleCloudTTS.d.ts +0 -1
- package/dist/synthesis/GoogleTranslateTTS.d.ts +0 -1
- package/dist/synthesis/GoogleTranslateTTS.js +6 -21
- package/dist/synthesis/GoogleTranslateTTS.js.map +1 -1
- package/dist/synthesis/StreamlabsPollyTTS.d.ts +0 -1
- package/dist/synthesis/VitsTTS.d.ts +0 -1
- package/dist/synthesis/VitsTTS.js +30 -0
- package/dist/synthesis/VitsTTS.js.map +1 -1
- package/dist/tests/Test.js +0 -31
- package/dist/tests/Test.js.map +1 -1
- package/dist/text-language-detection/FastTextLanguageDetection.d.ts +1 -1
- package/dist/text-language-detection/FastTextLanguageDetection.js.map +1 -1
- package/dist/text-language-detection/TinyLDLanguageDetection.d.ts +1 -1
- package/dist/text-language-detection/TinyLDLanguageDetection.js.map +1 -1
- package/dist/text-translation/DeepLTextTranslation.d.ts +2 -0
- package/dist/text-translation/DeepLTextTranslation.js +67 -0
- package/dist/text-translation/DeepLTextTranslation.js.map +1 -0
- package/dist/text-translation/GoogleTranslateTextTranslation.d.ts +10 -0
- package/dist/text-translation/GoogleTranslateTextTranslation.js +554 -0
- package/dist/text-translation/GoogleTranslateTextTranslation.js.map +1 -0
- package/dist/text-translation/NLLBTextTranslation.d.ts +2 -1
- package/dist/text-translation/NLLBTextTranslation.js +249 -19
- package/dist/text-translation/NLLBTextTranslation.js.map +1 -1
- package/dist/utilities/BinaryArrayConversion.d.ts +0 -1
- package/dist/utilities/BrowserRequestHeaders.d.ts +6 -0
- package/dist/utilities/BrowserRequestHeaders.js +52 -0
- package/dist/utilities/BrowserRequestHeaders.js.map +1 -0
- package/dist/utilities/BufferFileReadStream.d.ts +20 -0
- package/dist/utilities/BufferFileReadStream.js +81 -0
- package/dist/utilities/BufferFileReadStream.js.map +1 -0
- package/dist/utilities/DynamicUint8Array.d.ts +9 -0
- package/dist/utilities/DynamicUint8Array.js +31 -0
- package/dist/utilities/DynamicUint8Array.js.map +1 -0
- package/dist/utilities/FileSystem.d.ts +0 -2
- package/dist/utilities/Hashing.d.ts +3 -10
- package/dist/utilities/Hashing.js +10 -127
- package/dist/utilities/Hashing.js.map +1 -1
- package/dist/utilities/LEB128.d.ts +15 -5
- package/dist/utilities/LEB128.js +199 -119
- package/dist/utilities/LEB128.js.map +1 -1
- package/dist/utilities/LPVarInt.d.ts +11 -0
- package/dist/utilities/LPVarInt.js +187 -0
- package/dist/utilities/LPVarInt.js.map +1 -0
- package/dist/utilities/Locale.d.ts +1 -1
- package/dist/utilities/Locale.js +1 -1
- package/dist/utilities/OnnxUtilities.d.ts +1 -2
- package/dist/utilities/PVarInt.d.ts +4 -0
- package/dist/utilities/PVarInt.js +166 -0
- package/dist/utilities/PVarInt.js.map +1 -0
- package/dist/utilities/PackageManager.js +48 -25
- package/dist/utilities/PackageManager.js.map +1 -1
- package/dist/utilities/RandomGenerator.d.ts +3 -17
- package/dist/utilities/RandomGenerator.js +12 -81
- package/dist/utilities/RandomGenerator.js.map +1 -1
- package/dist/utilities/Timeline.d.ts +2 -0
- package/dist/utilities/Timeline.js +129 -20
- package/dist/utilities/Timeline.js.map +1 -1
- package/dist/utilities/Utilities.d.ts +1 -3
- package/dist/utilities/Utilities.js +30 -3
- package/dist/utilities/Utilities.js.map +1 -1
- package/dist/utilities/VarInt.d.ts +4 -0
- package/dist/utilities/VarInt.js +166 -0
- package/dist/utilities/VarInt.js.map +1 -0
- package/dist/utilities/VirtualFileReadStream.d.ts +20 -0
- package/dist/utilities/VirtualFileReadStream.js +79 -0
- package/dist/utilities/VirtualFileReadStream.js.map +1 -0
- package/dist/utilities/WebReader.js +7 -23
- package/dist/utilities/WebReader.js.map +1 -1
- package/dist/voice-activity-detection/SileroVAD.d.ts +0 -1
- package/docs/API.md +105 -3
- package/docs/CLI.md +51 -1
- package/docs/Engines.md +32 -3
- package/docs/Options.md +53 -12
- package/docs/Tasklist.md +1 -13
- package/package.json +20 -24
- package/src/alignment/DTWMfccSequenceAlignment.ts +5 -5
- package/src/alignment/DTWSequenceAlignmentWindowed.ts +1 -3
- package/src/alignment/SemanticTextAlignment.ts +467 -0
- package/src/alignment/SpeechAlignment.ts +214 -56
- package/src/api/API.ts +18 -2
- package/src/api/APIOptions.ts +14 -1
- package/src/api/Alignment.ts +31 -9
- package/src/api/LanguageDetectionCommon.ts +7 -0
- package/src/api/Recognition.ts +2 -0
- package/src/api/{LanguageDetection.ts → SpeechLanguageDetection.ts} +1 -119
- package/src/api/{Translation.ts → SpeechTranslation.ts} +2 -2
- package/src/api/Synthesis.ts +4 -4
- package/src/api/TextLanguageDetection.ts +116 -0
- package/src/api/TextTranslation.ts +177 -0
- package/src/api/TimelineTranslationAlignment.ts +162 -0
- package/src/api/TranscriptAndTranslationAlignment.ts +164 -0
- package/src/api/TranslationAlignment.ts +12 -10
- package/src/api/VoiceActivityDetection.ts +24 -3
- package/src/audio/AudioPlayer.ts +2 -0
- package/src/cli/CLI.ts +376 -40
- package/src/codecs/FFMpegTranscoder.ts +6 -0
- package/src/dsp/FFT.ts +8 -2
- package/src/dsp/KWeightingFilter.ts +1 -1
- package/src/dsp/MelSpectogram.ts +17 -8
- package/src/math/VectorMath.ts +15 -15
- package/src/nlp/ChineseSegmentation.ts +6 -4
- package/src/nlp/Segmentation.ts +18 -13
- package/src/recognition/OpenAICloudSTT.ts +47 -29
- package/src/recognition/WhisperCppSTT.ts +26 -11
- package/src/recognition/WhisperSTT.ts +364 -49
- package/src/server/Client.ts +3 -2
- package/src/server/Worker.ts +3 -2
- package/src/source-separation/MDXNetSourceSeparation.ts +1 -1
- package/src/speech-embeddings/WavToVec2BertFeatureEmbeddings.ts +107 -0
- package/src/speech-language-detection/SileroLanguageDetection.ts +2 -1
- package/src/subtitles/Subtitles.ts +2 -2
- package/src/synthesis/GoogleTranslateTTS.ts +7 -21
- package/src/synthesis/VitsTTS.ts +31 -3
- package/src/tests/Test.ts +1 -38
- package/src/text-language-detection/FastTextLanguageDetection.ts +1 -1
- package/src/text-language-detection/TinyLDLanguageDetection.ts +1 -1
- package/src/text-translation/DeepLTextTranslation.ts +88 -0
- package/src/text-translation/GoogleTranslateTextTranslation.ts +667 -0
- package/src/text-translation/NLLBTextTranslation.ts +261 -21
- package/src/typings/Fillers.d.ts +25 -2
- package/src/utilities/BrowserRequestHeaders.ts +59 -0
- package/src/utilities/DynamicUint8Array.ts +39 -0
- package/src/utilities/Hashing.ts +14 -167
- package/src/utilities/LEB128.ts +273 -148
- package/src/utilities/LPVarInt.ts +292 -0
- package/src/utilities/Locale.ts +1 -1
- package/src/utilities/OnnxUtilities.ts +1 -1
- package/src/utilities/PackageManager.ts +51 -30
- package/src/utilities/RandomGenerator.ts +12 -113
- package/src/utilities/Timeline.ts +162 -23
- package/src/utilities/Utilities.ts +40 -3
- package/src/utilities/VirtualFileReadStream.ts +109 -0
- package/src/utilities/WebReader.ts +9 -23
- package/dist/alignment/TextAlignment.js +0 -156
- package/dist/alignment/TextAlignment.js.map +0 -1
- package/dist/api/LanguageDetection.js.map +0 -1
- package/dist/api/Translation.js.map +0 -1
- package/src/alignment/TextAlignment.ts +0 -234
- /package/dist/api/{Translation.d.ts → SpeechTranslation.d.ts} +0 -0
|
@@ -0,0 +1,467 @@
|
|
|
1
|
+
import { type PreTrainedModel, type PreTrainedTokenizer } from '@echogarden/transformers-nodejs-lite'
|
|
2
|
+
import { Logger } from '../utilities/Logger.js'
|
|
3
|
+
import { loadPackage } from '../utilities/PackageManager.js'
|
|
4
|
+
import { alignDTWWindowed } from './DTWSequenceAlignmentWindowed.js'
|
|
5
|
+
import { cosineDistance } from '../math/VectorMath.js'
|
|
6
|
+
import { isPunctuation, isWord, splitToSentences, splitToWords } from '../nlp/Segmentation.js'
|
|
7
|
+
import { Timeline, extractEntries } from '../utilities/Timeline.js'
|
|
8
|
+
|
|
9
|
+
export async function alignTimelineToTextSemantically(timeline: Timeline, text: string, textLangCode: string) {
|
|
10
|
+
const logger = new Logger()
|
|
11
|
+
|
|
12
|
+
logger.start(`Prepare text for semantic alignment`)
|
|
13
|
+
|
|
14
|
+
const timelineSentenceEntries = extractEntries(timeline, entry => entry.type === 'sentence')
|
|
15
|
+
|
|
16
|
+
const timelineWordEntryGroups: Timeline[] = []
|
|
17
|
+
const timelineWordGroups: string[][] = []
|
|
18
|
+
|
|
19
|
+
for (const sentenceEntry of timelineSentenceEntries) {
|
|
20
|
+
const wordEntryGroup = sentenceEntry.timeline!
|
|
21
|
+
.filter(wordEntry => isWord(wordEntry.text))
|
|
22
|
+
|
|
23
|
+
timelineWordEntryGroups.push(wordEntryGroup)
|
|
24
|
+
timelineWordGroups.push(wordEntryGroup.map(wordEntry => wordEntry.text))
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const timelineWordEntriesFiltered = timelineWordEntryGroups.flat()
|
|
28
|
+
|
|
29
|
+
const textSentences = splitToSentences(text, textLangCode)
|
|
30
|
+
|
|
31
|
+
const textWordGroups: string[][] = []
|
|
32
|
+
|
|
33
|
+
for (const sentenceText of textSentences) {
|
|
34
|
+
let wordGroup = await splitToWords(sentenceText, textLangCode)
|
|
35
|
+
wordGroup = wordGroup.filter(word => isWord(word))
|
|
36
|
+
|
|
37
|
+
textWordGroups.push(wordGroup)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const textWords = textWordGroups.flat()
|
|
41
|
+
|
|
42
|
+
logger.end()
|
|
43
|
+
|
|
44
|
+
const wordMappingEntries = await alignWordsToWordsSemantically(timelineWordGroups, textWordGroups)
|
|
45
|
+
|
|
46
|
+
logger.start(`Build timeline for translation`)
|
|
47
|
+
|
|
48
|
+
const mappingGroups = new Map<number, number[]>()
|
|
49
|
+
|
|
50
|
+
for (const wordMappingEntry of wordMappingEntries) {
|
|
51
|
+
const wordIndex1 = wordMappingEntry.wordIndex1
|
|
52
|
+
const wordIndex2 = wordMappingEntry.wordIndex2
|
|
53
|
+
|
|
54
|
+
let group = mappingGroups.get(wordIndex1)
|
|
55
|
+
|
|
56
|
+
if (!group) {
|
|
57
|
+
group = []
|
|
58
|
+
mappingGroups.set(wordIndex1, group)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (!group.includes(wordIndex2)) {
|
|
62
|
+
group.push(wordIndex2)
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
type TimeSlice = { startTime: number, endTime: number }
|
|
67
|
+
|
|
68
|
+
const timeSlicesLookup = new Map<number, TimeSlice[]>()
|
|
69
|
+
|
|
70
|
+
for (const [wordIndex1, mappedWordIndexes] of mappingGroups) {
|
|
71
|
+
if (mappedWordIndexes.length === 0) {
|
|
72
|
+
continue
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const startTime = timelineWordEntriesFiltered[wordIndex1].startTime
|
|
76
|
+
const endTime = timelineWordEntriesFiltered[wordIndex1].endTime
|
|
77
|
+
|
|
78
|
+
const splitCount = mappedWordIndexes.length
|
|
79
|
+
|
|
80
|
+
const sliceDuration = (endTime - startTime) / splitCount
|
|
81
|
+
|
|
82
|
+
let timeOffset = 0
|
|
83
|
+
|
|
84
|
+
for (let i = 0; i < splitCount; i++) {
|
|
85
|
+
const timeSlice: TimeSlice = {
|
|
86
|
+
startTime: startTime + timeOffset,
|
|
87
|
+
endTime: startTime + timeOffset + sliceDuration
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const wordIndex2 = mappedWordIndexes[i]
|
|
91
|
+
|
|
92
|
+
let timeSlicesForTargetWord = timeSlicesLookup.get(wordIndex2)
|
|
93
|
+
|
|
94
|
+
if (!timeSlicesForTargetWord) {
|
|
95
|
+
timeSlicesForTargetWord = []
|
|
96
|
+
timeSlicesLookup.set(wordIndex2, timeSlicesForTargetWord)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
timeSlicesForTargetWord.push(timeSlice)
|
|
100
|
+
|
|
101
|
+
timeOffset += sliceDuration
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const resultTimeline: Timeline = []
|
|
106
|
+
|
|
107
|
+
for (const [key, value] of timeSlicesLookup) {
|
|
108
|
+
resultTimeline.push({
|
|
109
|
+
type: 'word',
|
|
110
|
+
text: textWords[key],
|
|
111
|
+
startTime: value[0].startTime,
|
|
112
|
+
endTime: value[value.length - 1].endTime
|
|
113
|
+
})
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
logger.end()
|
|
117
|
+
|
|
118
|
+
return resultTimeline
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export async function alignWordsToWordsSemantically(wordsGroups1: string[][], wordsGroups2: string[][], windowTokenCount = 20000) {
|
|
122
|
+
const logger = new Logger()
|
|
123
|
+
|
|
124
|
+
// Load embedding model
|
|
125
|
+
const modelPath = await loadPackage(`xenova-multilingual-e5-small-fp16`)
|
|
126
|
+
|
|
127
|
+
const embeddingModel = new E5TextEmbedding(modelPath)
|
|
128
|
+
|
|
129
|
+
logger.start(`Initialize E5 embedding model`)
|
|
130
|
+
await embeddingModel.initializeIfNeeded()
|
|
131
|
+
|
|
132
|
+
async function extractEmbeddingsFromWordGroups(wordGroups: string[][]) {
|
|
133
|
+
const logger = new Logger()
|
|
134
|
+
|
|
135
|
+
const maxTokensPerFragment = 512
|
|
136
|
+
const { Tensor } = await import('@echogarden/transformers-nodejs-lite')
|
|
137
|
+
|
|
138
|
+
const words: string[] = []
|
|
139
|
+
|
|
140
|
+
const embeddings: TokenEmbeddingData[] = []
|
|
141
|
+
const tokenToWordIndexMapping: number[] = []
|
|
142
|
+
|
|
143
|
+
for (const wordGroup of wordGroups) {
|
|
144
|
+
const { joinedText: joinedTextForGroup, offsets: offsetsForGroup } = joinAndGetOffsets(wordGroup)
|
|
145
|
+
|
|
146
|
+
logger.start(`Tokenize text`)
|
|
147
|
+
const inputsForGroup = await embeddingModel.tokenizeToModelInputs(joinedTextForGroup)
|
|
148
|
+
|
|
149
|
+
logger.start(`Infer embeddings for text`)
|
|
150
|
+
|
|
151
|
+
const allTokenIds = inputsForGroup['input_ids'].data
|
|
152
|
+
const allAttentionMask = inputsForGroup['attention_mask'].data
|
|
153
|
+
|
|
154
|
+
let embeddingsForGroup: TokenEmbeddingData[] = []
|
|
155
|
+
|
|
156
|
+
for (let tokenStart = 0; tokenStart < allTokenIds.length; tokenStart += maxTokensPerFragment) {
|
|
157
|
+
const tokenEnd = Math.min(tokenStart + maxTokensPerFragment, allTokenIds.length)
|
|
158
|
+
const fragmentTokenCount = tokenEnd - tokenStart
|
|
159
|
+
|
|
160
|
+
const fragmentInputIdsTensor = new Tensor('int64', allTokenIds.slice(tokenStart, tokenEnd), [1, fragmentTokenCount])
|
|
161
|
+
const fragmentAttentionMaskTensor = new Tensor('int64', allAttentionMask.slice(tokenStart, tokenEnd), [1, fragmentTokenCount])
|
|
162
|
+
|
|
163
|
+
const inputsForFragment = { input_ids: fragmentInputIdsTensor, attention_mask: fragmentAttentionMaskTensor }
|
|
164
|
+
|
|
165
|
+
const embeddingsForFragment = await embeddingModel.inferTokenEmbeddings(inputsForFragment)
|
|
166
|
+
|
|
167
|
+
embeddingsForGroup.push(...embeddingsForFragment)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
logger.start(`Compute token to word mapping for text`)
|
|
171
|
+
const filteredEmbeddingsForGroup = embeddingsForGroup.filter((embedding) => embedding.text !== '▁' && embedding.text !== '<s>' && embedding.text !== '</s>')
|
|
172
|
+
const tokenToWordIndexMappingForGroup = mapTokenEmbeddingsToWordIndexes(filteredEmbeddingsForGroup, joinedTextForGroup, offsetsForGroup)
|
|
173
|
+
const tokenToWordIndexMappingForGroupWithOffset = tokenToWordIndexMappingForGroup.map(value => words.length + value)
|
|
174
|
+
|
|
175
|
+
embeddings.push(...filteredEmbeddingsForGroup)
|
|
176
|
+
tokenToWordIndexMapping.push(...tokenToWordIndexMappingForGroupWithOffset)
|
|
177
|
+
|
|
178
|
+
words.push(...wordGroup)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return { words, embeddings, tokenToWordIndexMapping }
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
logger.start(`Extract embeddings from source 1`)
|
|
185
|
+
const {
|
|
186
|
+
words: words1,
|
|
187
|
+
embeddings: embeddings1,
|
|
188
|
+
tokenToWordIndexMapping: tokenToWordIndexMapping1
|
|
189
|
+
} = await extractEmbeddingsFromWordGroups(wordsGroups1)
|
|
190
|
+
|
|
191
|
+
logger.start(`Extract embeddings from source 2`)
|
|
192
|
+
const {
|
|
193
|
+
words: words2,
|
|
194
|
+
embeddings: embeddings2,
|
|
195
|
+
tokenToWordIndexMapping: tokenToWordIndexMapping2
|
|
196
|
+
} = await extractEmbeddingsFromWordGroups(wordsGroups2)
|
|
197
|
+
|
|
198
|
+
// Align
|
|
199
|
+
function costFunction(a: TokenEmbeddingData, b: TokenEmbeddingData) {
|
|
200
|
+
const aIsPunctuation = isPunctuation(a.text)
|
|
201
|
+
const bIsPunctuation = isPunctuation(b.text)
|
|
202
|
+
|
|
203
|
+
if (aIsPunctuation === bIsPunctuation) {
|
|
204
|
+
return cosineDistance(a.embeddingVector, b.embeddingVector)
|
|
205
|
+
} else {
|
|
206
|
+
return 1.0
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
logger.start(`Align token embedding vectors using DTW`)
|
|
211
|
+
|
|
212
|
+
const { path } = alignDTWWindowed(embeddings1, embeddings2, costFunction, windowTokenCount)
|
|
213
|
+
|
|
214
|
+
// Use alignment path to words to words
|
|
215
|
+
logger.start(`Map tokens to words`)
|
|
216
|
+
|
|
217
|
+
const wordMapping: WordMapping[] = []
|
|
218
|
+
|
|
219
|
+
for (let i = 0; i < path.length; i++) {
|
|
220
|
+
const pathEntry = path[i]
|
|
221
|
+
|
|
222
|
+
const sourceTokenIndex = pathEntry.source
|
|
223
|
+
const destTokenIndex = pathEntry.dest
|
|
224
|
+
|
|
225
|
+
const mappedWordIndex1 = tokenToWordIndexMapping1[sourceTokenIndex]
|
|
226
|
+
const mappedWordIndex2 = tokenToWordIndexMapping2[destTokenIndex]
|
|
227
|
+
|
|
228
|
+
wordMapping.push({
|
|
229
|
+
wordIndex1: mappedWordIndex1,
|
|
230
|
+
word1: words1[mappedWordIndex1],
|
|
231
|
+
wordIndex2: mappedWordIndex2,
|
|
232
|
+
word2: words2[mappedWordIndex2],
|
|
233
|
+
})
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
logger.end()
|
|
237
|
+
|
|
238
|
+
return wordMapping
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function mapTokenEmbeddingsToWordIndexes(embeddings: TokenEmbeddingData[], text: string, textWordOffsets: number[]) {
|
|
242
|
+
const tokenToWordIndex: number[] = []
|
|
243
|
+
|
|
244
|
+
let currentTextOffset = 0
|
|
245
|
+
|
|
246
|
+
for (let i = 0; i < embeddings.length; i++) {
|
|
247
|
+
const embedding = embeddings[i]
|
|
248
|
+
let tokenText = embedding.text
|
|
249
|
+
|
|
250
|
+
if (tokenText === '<s>' || tokenText === '</s>') {
|
|
251
|
+
tokenToWordIndex.push(-1)
|
|
252
|
+
|
|
253
|
+
continue
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (tokenText.startsWith('▁')) {
|
|
257
|
+
tokenText = tokenText.substring(1)
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const matchPosition = text.indexOf(tokenText, currentTextOffset)
|
|
261
|
+
|
|
262
|
+
if (matchPosition === -1) {
|
|
263
|
+
throw new Error(`Token '${tokenText}' not found in text`)
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
currentTextOffset = matchPosition + tokenText.length
|
|
267
|
+
|
|
268
|
+
let tokenMatchingWordIndex = textWordOffsets.findIndex((index) => index > matchPosition)
|
|
269
|
+
|
|
270
|
+
if (tokenMatchingWordIndex === -1) {
|
|
271
|
+
throw new Error(`Token '${tokenText}' not found in text`)
|
|
272
|
+
} else {
|
|
273
|
+
tokenMatchingWordIndex = Math.max(tokenMatchingWordIndex - 1, 0)
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
tokenToWordIndex.push(tokenMatchingWordIndex)
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
return tokenToWordIndex
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function joinAndGetOffsets(words: string[]) {
|
|
283
|
+
let joinedText = ''
|
|
284
|
+
const offsets: number[] = []
|
|
285
|
+
|
|
286
|
+
let offset = 0
|
|
287
|
+
|
|
288
|
+
for (const word of words) {
|
|
289
|
+
const extendedWord = `${word} `
|
|
290
|
+
joinedText += extendedWord
|
|
291
|
+
|
|
292
|
+
offsets.push(offset)
|
|
293
|
+
|
|
294
|
+
offset += extendedWord.length
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
offsets.push(joinedText.length)
|
|
298
|
+
|
|
299
|
+
return { joinedText, offsets }
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
export class E5TextEmbedding {
|
|
303
|
+
tokenizer?: PreTrainedTokenizer
|
|
304
|
+
model?: PreTrainedModel
|
|
305
|
+
|
|
306
|
+
constructor(public readonly modelPath: string) {
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
async tokenizeToModelInputs(text: string) {
|
|
310
|
+
await this.initializeIfNeeded()
|
|
311
|
+
|
|
312
|
+
const inputs = await this.tokenizer!(text)
|
|
313
|
+
|
|
314
|
+
return inputs
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
async inferTokenEmbeddings(inputs: any) {
|
|
318
|
+
await this.initializeIfNeeded()
|
|
319
|
+
|
|
320
|
+
const tokensText = this.tokenizer!.model.convert_ids_to_tokens(Array.from(inputs.input_ids.data))
|
|
321
|
+
|
|
322
|
+
const result = await this.model!(inputs)
|
|
323
|
+
|
|
324
|
+
const lastHiddenState = result.last_hidden_state
|
|
325
|
+
|
|
326
|
+
const tokenCount = lastHiddenState.dims[1]
|
|
327
|
+
const embeddingSize = lastHiddenState.dims[2]
|
|
328
|
+
|
|
329
|
+
const tokenEmbeddings: TokenEmbeddingData[] = []
|
|
330
|
+
|
|
331
|
+
for (let i = 0; i < tokenCount; i++) {
|
|
332
|
+
const tokenEmbeddingVector = lastHiddenState.data.slice(i * embeddingSize, (i + 1) * embeddingSize)
|
|
333
|
+
|
|
334
|
+
const tokenId = Number(inputs.input_ids.data[i])
|
|
335
|
+
const tokenText = tokensText[i]
|
|
336
|
+
|
|
337
|
+
tokenEmbeddings.push({
|
|
338
|
+
id: tokenId,
|
|
339
|
+
text: tokenText,
|
|
340
|
+
embeddingVector: tokenEmbeddingVector
|
|
341
|
+
})
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
return tokenEmbeddings
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
async initializeIfNeeded() {
|
|
348
|
+
if (this.tokenizer && this.model) {
|
|
349
|
+
return
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
const { AutoTokenizer, AutoModel } = await import('@echogarden/transformers-nodejs-lite')
|
|
353
|
+
|
|
354
|
+
this.tokenizer = await AutoTokenizer.from_pretrained(this.modelPath)
|
|
355
|
+
this.model = await AutoModel.from_pretrained(this.modelPath)
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
export interface TokenEmbeddingData {
|
|
360
|
+
id: number
|
|
361
|
+
text: string
|
|
362
|
+
embeddingVector: Float32Array
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
export interface WordMapping {
|
|
366
|
+
wordIndex1: number
|
|
367
|
+
word1: string
|
|
368
|
+
|
|
369
|
+
wordIndex2: number
|
|
370
|
+
word2: string
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
export const e5SupportedLanguages: string[] = [
|
|
374
|
+
'af', // Afrikaans
|
|
375
|
+
'am', // Amharic
|
|
376
|
+
'ar', // Arabic
|
|
377
|
+
'as', // Assamese
|
|
378
|
+
'az', // Azerbaijani
|
|
379
|
+
'be', // Belarusian
|
|
380
|
+
'bg', // Bulgarian
|
|
381
|
+
'bn', // Bengali
|
|
382
|
+
'br', // Breton
|
|
383
|
+
'bs', // Bosnian
|
|
384
|
+
'ca', // Catalan
|
|
385
|
+
'cs', // Czech
|
|
386
|
+
'cy', // Welsh
|
|
387
|
+
'da', // Danish
|
|
388
|
+
'de', // German
|
|
389
|
+
'el', // Greek
|
|
390
|
+
'en', // English
|
|
391
|
+
'eo', // Esperanto
|
|
392
|
+
'es', // Spanish
|
|
393
|
+
'et', // Estonian
|
|
394
|
+
'eu', // Basque
|
|
395
|
+
'fa', // Persian
|
|
396
|
+
'fi', // Finnish
|
|
397
|
+
'fr', // French
|
|
398
|
+
'fy', // Western Frisian
|
|
399
|
+
'ga', // Irish
|
|
400
|
+
'gd', // Scottish Gaelic
|
|
401
|
+
'gl', // Galician
|
|
402
|
+
'gu', // Gujarati
|
|
403
|
+
'ha', // Hausa
|
|
404
|
+
'he', // Hebrew
|
|
405
|
+
'hi', // Hindi
|
|
406
|
+
'hr', // Croatian
|
|
407
|
+
'hu', // Hungarian
|
|
408
|
+
'hy', // Armenian
|
|
409
|
+
'id', // Indonesian
|
|
410
|
+
'is', // Icelandic
|
|
411
|
+
'it', // Italian
|
|
412
|
+
'ja', // Japanese
|
|
413
|
+
'jv', // Javanese
|
|
414
|
+
'ka', // Georgian
|
|
415
|
+
'kk', // Kazakh
|
|
416
|
+
'km', // Khmer
|
|
417
|
+
'kn', // Kannada
|
|
418
|
+
'ko', // Korean
|
|
419
|
+
'ku', // Kurdish
|
|
420
|
+
'ky', // Kyrgyz
|
|
421
|
+
'la', // Latin
|
|
422
|
+
'lo', // Lao
|
|
423
|
+
'lt', // Lithuanian
|
|
424
|
+
'lv', // Latvian
|
|
425
|
+
'mg', // Malagasy
|
|
426
|
+
'mk', // Macedonian
|
|
427
|
+
'ml', // Malayalam
|
|
428
|
+
'mn', // Mongolian
|
|
429
|
+
'mr', // Marathi
|
|
430
|
+
'ms', // Malay
|
|
431
|
+
'my', // Burmese
|
|
432
|
+
'ne', // Nepali
|
|
433
|
+
'nl', // Dutch
|
|
434
|
+
'no', // Norwegian
|
|
435
|
+
'om', // Oromo
|
|
436
|
+
'or', // Oriya
|
|
437
|
+
'pa', // Panjabi
|
|
438
|
+
'pl', // Polish
|
|
439
|
+
'ps', // Pashto
|
|
440
|
+
'pt', // Portuguese
|
|
441
|
+
'ro', // Romanian
|
|
442
|
+
'ru', // Russian
|
|
443
|
+
'sa', // Sanskrit
|
|
444
|
+
'sd', // Sindhi
|
|
445
|
+
'si', // Sinhala
|
|
446
|
+
'sk', // Slovak
|
|
447
|
+
'sl', // Slovenian
|
|
448
|
+
'so', // Somali
|
|
449
|
+
'sq', // Albanian
|
|
450
|
+
'sr', // Serbian
|
|
451
|
+
'su', // Sundanese
|
|
452
|
+
'sv', // Swedish
|
|
453
|
+
'sw', // Swahili
|
|
454
|
+
'ta', // Tamil
|
|
455
|
+
'te', // Telugu
|
|
456
|
+
'th', // Thai
|
|
457
|
+
'tl', // Tagalog
|
|
458
|
+
'tr', // Turkish
|
|
459
|
+
'ug', // Uyghur
|
|
460
|
+
'uk', // Ukrainian
|
|
461
|
+
'ur', // Urdu
|
|
462
|
+
'uz', // Uzbek
|
|
463
|
+
'vi', // Vietnamese
|
|
464
|
+
'xh', // Xhosa
|
|
465
|
+
'yi', // Yiddish
|
|
466
|
+
'zh', // Chinese
|
|
467
|
+
]
|