echogarden 2.6.0 → 2.8.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/lexicons/heteronyms.en.json +6 -2
- package/data/lexicons/words.en.json +379 -19
- package/dist/alignment/SemanticTextAlignment.js +6 -7
- package/dist/alignment/SemanticTextAlignment.js.map +1 -1
- package/dist/api/Alignment.js +2 -2
- package/dist/api/Alignment.js.map +1 -1
- package/dist/api/Recognition.js +2 -2
- package/dist/api/Recognition.js.map +1 -1
- package/dist/api/SourceSeparation.js +1 -1
- package/dist/api/SpeechTranslation.js +2 -2
- package/dist/api/SpeechTranslation.js.map +1 -1
- package/dist/api/Synthesis.js +7 -7
- package/dist/api/Synthesis.js.map +1 -1
- package/dist/api/TimelineTranslationAlignment.js +2 -2
- package/dist/api/TimelineTranslationAlignment.js.map +1 -1
- package/dist/api/TranslationAlignment.js +2 -2
- package/dist/api/TranslationAlignment.js.map +1 -1
- package/dist/audio/AudioBufferConversion.d.ts +5 -6
- package/dist/audio/AudioBufferConversion.js +16 -248
- package/dist/audio/AudioBufferConversion.js.map +1 -1
- package/dist/audio/AudioPlayer.js +2 -2
- package/dist/audio/AudioPlayer.js.map +1 -1
- package/dist/audio/AudioRecorder.js +1 -1
- package/dist/audio/AudioRecorder.js.map +1 -1
- package/dist/audio/AudioUtilities.d.ts +2 -8
- package/dist/audio/AudioUtilities.js +4 -3
- package/dist/audio/AudioUtilities.js.map +1 -1
- package/dist/cli/CLI.js +4 -4
- package/dist/cli/CLI.js.map +1 -1
- package/dist/codecs/FFMpegTranscoder.d.ts +1 -4
- package/dist/codecs/TIMITCodec.js +1 -2
- package/dist/codecs/TIMITCodec.js.map +1 -1
- package/dist/denoising/RNNoise.js +5 -5
- package/dist/denoising/RNNoise.js.map +1 -1
- package/dist/dsp/FFT.js +13 -19
- package/dist/dsp/FFT.js.map +1 -1
- package/dist/dsp/Rubberband.js +5 -5
- package/dist/dsp/Rubberband.js.map +1 -1
- package/dist/dsp/Sonic.js +6 -6
- package/dist/dsp/Sonic.js.map +1 -1
- package/dist/dsp/SpeexResampler.js +11 -11
- package/dist/dsp/SpeexResampler.js.map +1 -1
- package/dist/encodings/Ascii.js +1 -1
- package/dist/encodings/Ascii.js.map +1 -1
- package/dist/encodings/Utf16.js +1 -1
- package/dist/encodings/Utf16.js.map +1 -1
- package/dist/encodings/Utf8.js +1 -1
- package/dist/encodings/Utf8.js.map +1 -1
- package/dist/nlp/EspeakPhonemizer.js +1 -1
- package/dist/nlp/EspeakPhonemizer.js.map +1 -1
- package/dist/nlp/Segmentation.d.ts +13 -10
- package/dist/nlp/Segmentation.js +129 -106
- package/dist/nlp/Segmentation.js.map +1 -1
- package/dist/nlp/TextNormalizer.js +74 -42
- package/dist/nlp/TextNormalizer.js.map +1 -1
- package/dist/recognition/AmazonTranscribeSTT.js +2 -2
- package/dist/recognition/AmazonTranscribeSTT.js.map +1 -1
- package/dist/recognition/SileroSTT.js +2 -2
- package/dist/recognition/SileroSTT.js.map +1 -1
- package/dist/recognition/WhisperSTT.js +4 -16
- package/dist/recognition/WhisperSTT.js.map +1 -1
- package/dist/subtitles/Subtitles.js +52 -7
- package/dist/subtitles/Subtitles.js.map +1 -1
- package/dist/synthesis/AwsPollyTTS.d.ts +1 -4
- package/dist/synthesis/CoquiServerTTS.d.ts +1 -4
- package/dist/synthesis/DeepgramTTS.d.ts +1 -4
- package/dist/synthesis/ElevenLabsTTS.d.ts +1 -4
- package/dist/synthesis/ElevenLabsTTS.js +11 -10
- package/dist/synthesis/ElevenLabsTTS.js.map +1 -1
- package/dist/synthesis/EspeakTTS.js +8 -8
- package/dist/synthesis/EspeakTTS.js.map +1 -1
- package/dist/synthesis/FliteTTS.d.ts +1 -4
- package/dist/synthesis/GnuSpeechTTS.d.ts +1 -7
- package/dist/synthesis/GoogleCloudTTS.d.ts +1 -4
- package/dist/synthesis/KokoroTTS.js +21 -10
- package/dist/synthesis/KokoroTTS.js.map +1 -1
- package/dist/synthesis/SapiTTS.js +1 -2
- package/dist/synthesis/SapiTTS.js.map +1 -1
- package/dist/synthesis/SvoxPicoTTS.js +25 -25
- package/dist/synthesis/SvoxPicoTTS.js.map +1 -1
- package/dist/text-translation/DeepLTextTranslation.js +4 -3
- package/dist/text-translation/DeepLTextTranslation.js.map +1 -1
- package/dist/text-translation/NLLBTextTranslation.js +7 -6
- package/dist/text-translation/NLLBTextTranslation.js.map +1 -1
- package/dist/utilities/StringUtilities.d.ts +1 -1
- package/dist/utilities/StringUtilities.js +5 -5
- package/dist/utilities/StringUtilities.js.map +1 -1
- package/dist/utilities/Timeline.d.ts +2 -3
- package/dist/utilities/Timeline.js +96 -147
- package/dist/utilities/Timeline.js.map +1 -1
- package/dist/utilities/WikipediaReader.js +3 -3
- package/dist/utilities/WikipediaReader.js.map +1 -1
- package/dist/voice-activity-detection/WebRtcVAD.js +4 -4
- package/dist/voice-activity-detection/WebRtcVAD.js.map +1 -1
- package/docs/Tasklist.md +1 -6
- package/package.json +13 -11
- package/src/alignment/SemanticTextAlignment.ts +6 -7
- package/src/api/Alignment.ts +2 -2
- package/src/api/Recognition.ts +2 -2
- package/src/api/SourceSeparation.ts +1 -1
- package/src/api/SpeechTranslation.ts +2 -2
- package/src/api/Synthesis.ts +7 -7
- package/src/api/TimelineTranslationAlignment.ts +2 -2
- package/src/api/TranslationAlignment.ts +2 -2
- package/src/audio/AudioBufferConversion.ts +17 -263
- package/src/audio/AudioPlayer.ts +3 -3
- package/src/audio/AudioRecorder.ts +1 -1
- package/src/audio/AudioUtilities.ts +5 -3
- package/src/cli/CLI.ts +4 -4
- package/src/codecs/TIMITCodec.ts +1 -2
- package/src/denoising/RNNoise.ts +5 -5
- package/src/dsp/FFT.ts +22 -19
- package/src/dsp/Rubberband.ts +5 -5
- package/src/dsp/Sonic.ts +6 -6
- package/src/dsp/SpeexResampler.ts +11 -11
- package/src/encodings/Ascii.ts +1 -1
- package/src/encodings/Utf16.ts +1 -1
- package/src/encodings/Utf8.ts +1 -1
- package/src/nlp/EspeakPhonemizer.ts +1 -1
- package/src/nlp/Segmentation.ts +146 -111
- package/src/nlp/TextNormalizer.ts +94 -52
- package/src/recognition/AmazonTranscribeSTT.ts +2 -2
- package/src/recognition/SileroSTT.ts +2 -2
- package/src/recognition/WhisperSTT.ts +4 -18
- package/src/subtitles/Subtitles.ts +69 -8
- package/src/synthesis/ElevenLabsTTS.ts +12 -11
- package/src/synthesis/EspeakTTS.ts +9 -8
- package/src/synthesis/KokoroTTS.ts +24 -12
- package/src/synthesis/SapiTTS.ts +1 -2
- package/src/synthesis/SvoxPicoTTS.ts +27 -25
- package/src/text-translation/DeepLTextTranslation.ts +4 -3
- package/src/text-translation/NLLBTextTranslation.ts +8 -6
- package/src/typings/Fillers.d.ts +0 -16
- package/src/utilities/StringUtilities.ts +5 -6
- package/src/utilities/Timeline.ts +100 -170
- package/src/utilities/WikipediaReader.ts +3 -3
- package/src/voice-activity-detection/WebRtcVAD.ts +5 -4
- package/dist/codecs/WaveCodec.d.ts +0 -19
- package/dist/codecs/WaveCodec.js +0 -224
- package/dist/codecs/WaveCodec.js.map +0 -1
- package/dist/nlp/CompromiseNLP.d.ts +0 -15
- package/dist/nlp/CompromiseNLP.js +0 -79
- package/dist/nlp/CompromiseNLP.js.map +0 -1
- package/dist/utilities/BinaryArrayConversion.d.ts +0 -14
- package/dist/utilities/BinaryArrayConversion.js +0 -92
- package/dist/utilities/BinaryArrayConversion.js.map +0 -1
- package/dist/utilities/WasmMemoryManager.d.ts +0 -153
- package/dist/utilities/WasmMemoryManager.js +0 -440
- package/dist/utilities/WasmMemoryManager.js.map +0 -1
- package/src/codecs/WaveCodec.ts +0 -281
- package/src/nlp/CompromiseNLP.ts +0 -119
- package/src/utilities/BinaryArrayConversion.ts +0 -117
- package/src/utilities/WasmMemoryManager.ts +0 -566
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { SynthesisVoice } from '../api/API.js'
|
|
2
|
-
import { decodeToChannels } from '../audio/AudioBufferConversion.js'
|
|
3
|
-
import { SampleFormat } from '../codecs/WaveCodec.js'
|
|
2
|
+
import { decodeToChannels, SampleFormat } from '../audio/AudioBufferConversion.js'
|
|
4
3
|
import { bandwidthToQFactor } from '../dsp/BiquadFilter.js'
|
|
5
4
|
import { Logger } from '../utilities/Logger.js'
|
|
6
|
-
import { WasmMemoryManager } from '../utilities/WasmMemoryManager.js'
|
|
7
5
|
import { RawAudio } from '../audio/AudioUtilities.js'
|
|
8
6
|
import { readFileAsBinary } from '../utilities/FileSystem.js'
|
|
9
7
|
import { concatUint8Arrays } from '../utilities/Utilities.js'
|
|
10
8
|
|
|
9
|
+
import { wrapEmscriptenModuleHeap } from 'wasm-heap-manager'
|
|
10
|
+
|
|
11
11
|
let svoxPicoInstance: any
|
|
12
12
|
|
|
13
13
|
export async function synthesize(text: string, textAnalysisFilePath: string, signalGenerationFilePath: string, postprocessOutput = true) {
|
|
@@ -18,7 +18,7 @@ export async function synthesize(text: string, textAnalysisFilePath: string, sig
|
|
|
18
18
|
|
|
19
19
|
logger.start('Initialize pico engine')
|
|
20
20
|
|
|
21
|
-
const
|
|
21
|
+
const wasmHeap = wrapEmscriptenModuleHeap(m)
|
|
22
22
|
|
|
23
23
|
const pico_initialize = m._pico_initialize
|
|
24
24
|
const picoext_setTraceLevel = m._picoext_setTraceLevel
|
|
@@ -38,11 +38,11 @@ export async function synthesize(text: string, textAnalysisFilePath: string, sig
|
|
|
38
38
|
const pico_unloadResource = m._pico_unloadResource
|
|
39
39
|
|
|
40
40
|
const picoMemSize = 2500000
|
|
41
|
-
const picoMemAreaRef =
|
|
41
|
+
const picoMemAreaRef = wasmHeap.allocUint8Array(picoMemSize)
|
|
42
42
|
|
|
43
|
-
const systemPtrRef =
|
|
43
|
+
const systemPtrRef = wasmHeap.allocPointer32()
|
|
44
44
|
|
|
45
|
-
let resultCode = pico_initialize(picoMemAreaRef.address, picoMemAreaRef.
|
|
45
|
+
let resultCode = pico_initialize(picoMemAreaRef.address, picoMemAreaRef.allocatedByteCount, systemPtrRef.address)
|
|
46
46
|
const systemPtr = systemPtrRef.value
|
|
47
47
|
|
|
48
48
|
throwErrorIfFailed(resultCode, 'Failed Pico initialization.')
|
|
@@ -55,8 +55,8 @@ export async function synthesize(text: string, textAnalysisFilePath: string, sig
|
|
|
55
55
|
const fileData = await readFileAsBinary(localFilePath)
|
|
56
56
|
m.FS.writeFile(virtualFilePath, fileData)
|
|
57
57
|
|
|
58
|
-
const virtualFilePathRef =
|
|
59
|
-
const resourcePtrRef =
|
|
58
|
+
const virtualFilePathRef = wasmHeap.allocNullTerminatedUtf8String(virtualFilePath)
|
|
59
|
+
const resourcePtrRef = wasmHeap.allocPointer32()
|
|
60
60
|
|
|
61
61
|
resultCode = pico_loadResource(systemPtr, virtualFilePathRef.address, resourcePtrRef.address)
|
|
62
62
|
const resourcePtr = resourcePtrRef.value
|
|
@@ -70,12 +70,12 @@ export async function synthesize(text: string, textAnalysisFilePath: string, sig
|
|
|
70
70
|
const { resourcePtr: signalGenerationResourcePtr, resourcePtrRef: signalGenerationResourcePtrRef } = await loadResource(signalGenerationFilePath)
|
|
71
71
|
|
|
72
72
|
function getResourceName(resourcePtr: number) {
|
|
73
|
-
const resourceNameRef =
|
|
73
|
+
const resourceNameRef = wasmHeap.allocNullTerminatedUtf8String(32)
|
|
74
74
|
resultCode = pico_getResourceName(systemPtr, resourcePtr, resourceNameRef.address)
|
|
75
75
|
|
|
76
76
|
throwErrorIfFailed(resultCode, `Failed getting Pico resource name.`)
|
|
77
77
|
|
|
78
|
-
const resourceName = resourceNameRef.
|
|
78
|
+
const resourceName = resourceNameRef.value
|
|
79
79
|
|
|
80
80
|
return { resourceName, resourceNameRef }
|
|
81
81
|
}
|
|
@@ -83,7 +83,7 @@ export async function synthesize(text: string, textAnalysisFilePath: string, sig
|
|
|
83
83
|
const { resourceName: textAnalysisResourceName, resourceNameRef: textAnalysisResourceNameRef } = getResourceName(textAnalysisResourcePtr)
|
|
84
84
|
const { resourceName: signalGenerationResourceName, resourceNameRef: signalGenerationResourceNameRef } = getResourceName(signalGenerationResourcePtr)
|
|
85
85
|
|
|
86
|
-
const voiceNameRef =
|
|
86
|
+
const voiceNameRef = wasmHeap.allocNullTerminatedUtf8String('PicoVoice')
|
|
87
87
|
|
|
88
88
|
resultCode = pico_createVoiceDefinition(systemPtr, voiceNameRef.address)
|
|
89
89
|
|
|
@@ -96,7 +96,7 @@ export async function synthesize(text: string, textAnalysisFilePath: string, sig
|
|
|
96
96
|
addResourceToVoiceDefinition(textAnalysisResourceNameRef.address)
|
|
97
97
|
addResourceToVoiceDefinition(signalGenerationResourceNameRef.address)
|
|
98
98
|
|
|
99
|
-
const enginePtrRef =
|
|
99
|
+
const enginePtrRef = wasmHeap.allocPointer32()
|
|
100
100
|
resultCode = pico_newEngine(systemPtr, voiceNameRef.address, enginePtrRef.address)
|
|
101
101
|
|
|
102
102
|
throwErrorIfFailed(resultCode, `Failed creating new engine.`)
|
|
@@ -105,15 +105,17 @@ export async function synthesize(text: string, textAnalysisFilePath: string, sig
|
|
|
105
105
|
|
|
106
106
|
logger.start('Synthesize with pico')
|
|
107
107
|
|
|
108
|
-
const textRef =
|
|
108
|
+
const textRef = wasmHeap.allocNullTerminatedUtf8String(text)
|
|
109
109
|
|
|
110
|
-
const bytesWrittenRef =
|
|
110
|
+
const bytesWrittenRef = wasmHeap.allocInt32()
|
|
111
111
|
|
|
112
112
|
const audioParts: Uint8Array[] = []
|
|
113
113
|
|
|
114
|
-
|
|
114
|
+
const textRefEncodedByteCountIncludingTerminator = textRef.encodedByteCount + 1
|
|
115
|
+
|
|
116
|
+
for (let textByteOffset = 0; textByteOffset < textRefEncodedByteCountIncludingTerminator;) {
|
|
115
117
|
bytesWrittenRef.value = 0
|
|
116
|
-
resultCode = pico_putTextUtf8(enginePtr, textRef.address + textByteOffset,
|
|
118
|
+
resultCode = pico_putTextUtf8(enginePtr, textRef.address + textByteOffset, textRefEncodedByteCountIncludingTerminator - textByteOffset, bytesWrittenRef.address)
|
|
117
119
|
const bytesWritten = bytesWrittenRef.value
|
|
118
120
|
|
|
119
121
|
throwErrorIfFailed(resultCode, `Failed writing text to engine.`)
|
|
@@ -130,13 +132,13 @@ export async function synthesize(text: string, textAnalysisFilePath: string, sig
|
|
|
130
132
|
const outBuffers: Uint8Array[] = []
|
|
131
133
|
|
|
132
134
|
const outBufferLength = 16384
|
|
133
|
-
const outBufferRef =
|
|
135
|
+
const outBufferRef = wasmHeap.allocUint8Array(outBufferLength)
|
|
134
136
|
|
|
135
|
-
const outByteCountRef =
|
|
136
|
-
const outDataTypeRef =
|
|
137
|
+
const outByteCountRef = wasmHeap.allocInt16()
|
|
138
|
+
const outDataTypeRef = wasmHeap.allocInt16()
|
|
137
139
|
|
|
138
140
|
while (true) {
|
|
139
|
-
resultCode = pico_getData(enginePtr, outBufferRef.address, outBufferRef.
|
|
141
|
+
resultCode = pico_getData(enginePtr, outBufferRef.address, outBufferRef.allocatedByteCount, outByteCountRef.address, outDataTypeRef.address)
|
|
140
142
|
|
|
141
143
|
throwErrorIfFailed(resultCode, `Failed getting audio data from engine.`, [200, 201])
|
|
142
144
|
|
|
@@ -148,7 +150,7 @@ export async function synthesize(text: string, textAnalysisFilePath: string, sig
|
|
|
148
150
|
}
|
|
149
151
|
|
|
150
152
|
if (outByteCount > 0) {
|
|
151
|
-
outBuffers.push(outBufferRef.slice(0, outByteCount)
|
|
153
|
+
outBuffers.push(outBufferRef.view.slice(0, outByteCount))
|
|
152
154
|
}
|
|
153
155
|
}
|
|
154
156
|
|
|
@@ -180,7 +182,7 @@ export async function synthesize(text: string, textAnalysisFilePath: string, sig
|
|
|
180
182
|
|
|
181
183
|
pico_terminate(systemPtrRef.address)
|
|
182
184
|
|
|
183
|
-
|
|
185
|
+
wasmHeap.freeAll()
|
|
184
186
|
}
|
|
185
187
|
|
|
186
188
|
function throwErrorIfFailed(resultCode: number, title: string, successCodes = [0]) {
|
|
@@ -188,9 +190,9 @@ export async function synthesize(text: string, textAnalysisFilePath: string, sig
|
|
|
188
190
|
return
|
|
189
191
|
}
|
|
190
192
|
|
|
191
|
-
const picoErrorMessageRef =
|
|
193
|
+
const picoErrorMessageRef = wasmHeap.allocNullTerminatedUtf8String(200)
|
|
192
194
|
pico_getSystemStatusMessage(systemPtr, resultCode, picoErrorMessageRef)
|
|
193
|
-
const picoErrorMessage = picoErrorMessageRef.
|
|
195
|
+
const picoErrorMessage = picoErrorMessageRef.value
|
|
194
196
|
|
|
195
197
|
dispose()
|
|
196
198
|
throw new Error(`${title} ${picoErrorMessage}`)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { request } from "gaxios"
|
|
2
2
|
import { Logger } from "../utilities/Logger.js"
|
|
3
|
-
import {
|
|
3
|
+
import { parseText } from "../nlp/Segmentation.js"
|
|
4
4
|
import { TranslationPair } from "../api/TextTranslation.js"
|
|
5
5
|
import { getChromeOnWindowsHeaders } from "../utilities/BrowserRequestHeaders.js"
|
|
6
6
|
|
|
@@ -9,7 +9,8 @@ export async function translateText(text: string, sourceLanguage: string, target
|
|
|
9
9
|
|
|
10
10
|
logger.start(`Prepare request`)
|
|
11
11
|
|
|
12
|
-
const
|
|
12
|
+
const segmentedText = await parseText(text, sourceLanguage)
|
|
13
|
+
const sentences = segmentedText.sentences
|
|
13
14
|
|
|
14
15
|
const requestBodyObject = {
|
|
15
16
|
jsonrpc: '2.0',
|
|
@@ -20,7 +21,7 @@ export async function translateText(text: string, sourceLanguage: string, target
|
|
|
20
21
|
kind: 'default',
|
|
21
22
|
sentences: [
|
|
22
23
|
{
|
|
23
|
-
text:
|
|
24
|
+
text: sentences[0].text,
|
|
24
25
|
id: 1,
|
|
25
26
|
prefix: '',
|
|
26
27
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import chalk from 'chalk'
|
|
2
2
|
import { TranslationPair } from '../api/TextTranslation.js'
|
|
3
|
-
import {
|
|
3
|
+
import { parseText } from '../nlp/Segmentation.js'
|
|
4
4
|
import { Logger } from '../utilities/Logger.js'
|
|
5
5
|
import { loadPackage } from '../utilities/PackageManager.js'
|
|
6
6
|
|
|
@@ -30,19 +30,21 @@ export async function translateText(sourceText: string, sourceLanguage: string,
|
|
|
30
30
|
tgt_lang: 'eng_Latn'
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
const
|
|
33
|
+
const segmentedText = await parseText(sourceText, sourceLanguage)
|
|
34
|
+
|
|
35
|
+
const sentences = segmentedText.sentences
|
|
34
36
|
|
|
35
37
|
logger.end()
|
|
36
38
|
|
|
37
39
|
const translationPairs: TranslationPair[] = []
|
|
38
40
|
|
|
39
41
|
for (let i = 0; i < sentences.length; i++) {
|
|
40
|
-
const
|
|
42
|
+
const sentenceText = sentences[i].text
|
|
41
43
|
|
|
42
|
-
logger.logTitledMessage(`Translate sentence ${i + 1}/${sentences.length}`, `"${
|
|
44
|
+
logger.logTitledMessage(`Translate sentence ${i + 1}/${sentences.length}`, `"${sentenceText.trim()}"`, chalk.magentaBright)
|
|
43
45
|
|
|
44
46
|
logger.start(`Tokenize sentence`)
|
|
45
|
-
const inputs = (tokenizer as any)._build_translation_inputs(
|
|
47
|
+
const inputs = (tokenizer as any)._build_translation_inputs(sentenceText, {
|
|
46
48
|
padding: true,
|
|
47
49
|
truncation: true,
|
|
48
50
|
}, config)
|
|
@@ -67,7 +69,7 @@ export async function translateText(sourceText: string, sourceLanguage: string,
|
|
|
67
69
|
.trim()
|
|
68
70
|
|
|
69
71
|
translationPairs.push({
|
|
70
|
-
sourceText:
|
|
72
|
+
sourceText: sentenceText,
|
|
71
73
|
translatedText
|
|
72
74
|
})
|
|
73
75
|
|
package/src/typings/Fillers.d.ts
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
declare module 'moving-median'
|
|
2
|
-
|
|
3
1
|
declare module 'html-to-text' {
|
|
4
2
|
export function htmlToText(html: string, options: {
|
|
5
3
|
wordwrap: boolean | number
|
|
@@ -7,20 +5,6 @@ declare module 'html-to-text' {
|
|
|
7
5
|
})
|
|
8
6
|
}
|
|
9
7
|
|
|
10
|
-
declare module 'cldr-segmentation' {
|
|
11
|
-
export function sentenceSplit(text: string, suppressions: any)
|
|
12
|
-
export function wordSplit(text: string, suppressions: any)
|
|
13
|
-
export const suppressions: Record<string, Suppressions>
|
|
14
|
-
|
|
15
|
-
export class Suppressions {
|
|
16
|
-
constructor(forwardTrie: any, backwardTrie: any, list: string)
|
|
17
|
-
|
|
18
|
-
merge(other: Suppressions)
|
|
19
|
-
|
|
20
|
-
static create(list: string[]): Suppressions
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
8
|
declare module 'winax'
|
|
25
9
|
|
|
26
10
|
declare module 'tinyld' {
|
|
@@ -52,7 +52,7 @@ export function formatListWithQuotedElements(strings: string[], quoteSymbol = `'
|
|
|
52
52
|
|
|
53
53
|
export function getUTF32Chars(str: string) {
|
|
54
54
|
const utf32chars: string[] = []
|
|
55
|
-
const
|
|
55
|
+
const utf16To32Mapping: number[] = []
|
|
56
56
|
|
|
57
57
|
let utf32Index = 0
|
|
58
58
|
|
|
@@ -60,15 +60,15 @@ export function getUTF32Chars(str: string) {
|
|
|
60
60
|
utf32chars.push(utf32char)
|
|
61
61
|
|
|
62
62
|
for (let i = 0; i < utf32char.length; i++) {
|
|
63
|
-
|
|
63
|
+
utf16To32Mapping.push(utf32Index)
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
utf32Index += 1
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
|
|
69
|
+
utf16To32Mapping.push(utf32Index)
|
|
70
70
|
|
|
71
|
-
return { utf32chars,
|
|
71
|
+
return { utf32chars, utf16To32Mapping }
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
export function containsInvalidCodepoint(str: string) {
|
|
@@ -81,10 +81,9 @@ export function containsInvalidCodepoint(str: string) {
|
|
|
81
81
|
return false
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
-
|
|
85
84
|
export function splitAndPreserveSeparators(text: string, separatorRegex: RegExp): string[] {
|
|
86
85
|
if (!separatorRegex.flags.includes('g')) {
|
|
87
|
-
throw new Error('
|
|
86
|
+
throw new Error('Separator regular expression must have a global flag')
|
|
88
87
|
}
|
|
89
88
|
|
|
90
89
|
// Use the match method to find all matches for the separators
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
import { addMissingPunctuationWordsToWordSequence, segmentWordSequence, WordSequence } from '@echogarden/text-segmentation'
|
|
1
2
|
import { ParagraphBreakType, WhitespaceProcessing } from '../api/Common.js'
|
|
2
|
-
import {
|
|
3
|
+
import { isWord, isWordOrSymbolWord, splitToParagraphs, splitToWords } from '../nlp/Segmentation.js'
|
|
3
4
|
import { deepClone } from './ObjectUtilities.js'
|
|
4
|
-
import { getUTF32Chars
|
|
5
|
+
import { getUTF32Chars } from './StringUtilities.js'
|
|
5
6
|
import { roundToDigits } from './Utilities.js'
|
|
6
7
|
|
|
7
8
|
export function addTimeOffsetToTimeline(targetTimeline: Timeline, timeOffset: number) {
|
|
@@ -62,199 +63,155 @@ export function roundTimelineProperties(targetTimeline: Timeline, decimalDigits
|
|
|
62
63
|
return roundedTimeline
|
|
63
64
|
}
|
|
64
65
|
|
|
65
|
-
export async function wordTimelineToSegmentSentenceTimeline(
|
|
66
|
-
|
|
66
|
+
export async function wordTimelineToSegmentSentenceTimeline(wordTimelineWithOffsets: Timeline, transcript: string, language: string, paragraphBreaks: ParagraphBreakType = 'double', whitespace: WhitespaceProcessing = 'collapse') {
|
|
67
|
+
const wordSequence = new WordSequence()
|
|
67
68
|
|
|
68
|
-
{
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
// to identify them as sentence breaks.
|
|
72
|
-
const maskedTranscript = replaceSentenceEndersWithinWordsWithMaskingCharacter(transcript, wordTimeline, '_')
|
|
73
|
-
|
|
74
|
-
// Split to segments and sentences, based on the masked transcript,
|
|
75
|
-
// don't apply any whitespace processing yet.
|
|
76
|
-
let paragraphs: string[]
|
|
77
|
-
|
|
78
|
-
if (paragraphBreaks === 'single') {
|
|
79
|
-
paragraphs = splitAndPreserveSeparators(maskedTranscript, /(\r?\n)+/g)
|
|
80
|
-
} else if (paragraphBreaks === 'double') {
|
|
81
|
-
paragraphs = splitAndPreserveSeparators(maskedTranscript, /(\r?\n)(\r?\n)+/g)
|
|
82
|
-
} else {
|
|
83
|
-
throw new Error(`Invalid paragraph break type: '${paragraphBreaks}'`)
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
const maskedSegments = paragraphs.map(paragraph => splitToSentences(paragraph, language))
|
|
87
|
-
|
|
88
|
-
// Restore the sentence text the original text, using the original transcript,
|
|
89
|
-
// and apply whitespace processing to each sentence.
|
|
90
|
-
let offset = 0
|
|
69
|
+
for (const wordEntry of wordTimelineWithOffsets) {
|
|
70
|
+
const wordStartOffset = wordEntry.startOffsetUtf16!
|
|
71
|
+
const wordEndOffset = wordEntry.endOffsetUtf16!
|
|
91
72
|
|
|
92
|
-
|
|
93
|
-
const newSegment: string[] = []
|
|
73
|
+
const isPunctuation = !isWordOrSymbolWord(wordEntry.text)
|
|
94
74
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
const sentenceLength = sentence.length
|
|
98
|
-
|
|
99
|
-
const restoredSentence = transcript.substring(offset, offset + sentenceLength)
|
|
100
|
-
const restoredAndProcessedSentence = applyWhitespaceProcessing(restoredSentence, whitespace).trim()
|
|
75
|
+
wordSequence.addWord(wordEntry.text, wordStartOffset, isPunctuation)
|
|
76
|
+
}
|
|
101
77
|
|
|
102
|
-
|
|
103
|
-
newSegment.push(restoredAndProcessedSentence)
|
|
104
|
-
}
|
|
78
|
+
const { wordSequenceWithPunctuation, originalWordsReverseMapping } = addMissingPunctuationWordsToWordSequence(wordSequence, transcript)
|
|
105
79
|
|
|
106
|
-
|
|
107
|
-
}
|
|
80
|
+
const segmentedWordSequence = await segmentWordSequence(wordSequenceWithPunctuation)
|
|
108
81
|
|
|
109
|
-
|
|
110
|
-
|
|
82
|
+
const paragraphs = splitToParagraphs(transcript, paragraphBreaks, whitespace)
|
|
83
|
+
const sentenceIndexesForParagraph = paragraphs.map(_ => [] as number[])
|
|
111
84
|
|
|
112
|
-
|
|
113
|
-
|
|
85
|
+
{
|
|
86
|
+
let sentenceIndex = 0
|
|
87
|
+
let charOffset = 0
|
|
114
88
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
let text = ''
|
|
118
|
-
const charIndexToSentenceEntryMapping: TimelineEntry[] = []
|
|
89
|
+
for (let paragraphIndex = 0; paragraphIndex < paragraphs.length; paragraphIndex++) {
|
|
90
|
+
const paragraph = paragraphs[paragraphIndex]
|
|
119
91
|
|
|
120
|
-
|
|
92
|
+
const paragraphStartOffset = charOffset
|
|
93
|
+
const paragraphEndOffset = paragraphStartOffset + paragraph.length
|
|
121
94
|
|
|
122
|
-
|
|
123
|
-
|
|
95
|
+
while (sentenceIndex < segmentedWordSequence.sentences.length) {
|
|
96
|
+
const sentenceEntry = segmentedWordSequence.sentences[sentenceIndex]
|
|
97
|
+
const sentenceStartOffset = sentenceEntry.charRange.start
|
|
98
|
+
const sentenceEndOffset = sentenceEntry.charRange.end
|
|
124
99
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
for (const sentence of segment) {
|
|
134
|
-
const sentenceEntry: TimelineEntry = {
|
|
135
|
-
type: 'sentence',
|
|
136
|
-
text: sentence,
|
|
137
|
-
startTime: -1,
|
|
138
|
-
endTime: -1,
|
|
139
|
-
timeline: []
|
|
100
|
+
if (sentenceStartOffset < paragraphEndOffset) {
|
|
101
|
+
sentenceIndexesForParagraph[paragraphIndex].push(sentenceIndex)
|
|
102
|
+
sentenceIndex++
|
|
103
|
+
} else {
|
|
104
|
+
break
|
|
105
|
+
}
|
|
140
106
|
}
|
|
141
107
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
charIndexToSentenceEntryMapping.push(sentenceEntry)
|
|
108
|
+
if (sentenceIndex === segmentedWordSequence.sentences.length) {
|
|
109
|
+
break
|
|
145
110
|
}
|
|
146
111
|
|
|
147
|
-
|
|
112
|
+
charOffset += paragraph.length
|
|
148
113
|
}
|
|
149
|
-
|
|
150
|
-
segmentTimeline.push(segmentEntry)
|
|
151
114
|
}
|
|
152
115
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
let wordSearchStartOffset = 0
|
|
156
|
-
|
|
157
|
-
for (let wordIndex = 0; wordIndex < wordTimeline.length; wordIndex++) {
|
|
158
|
-
const wordEntry = wordTimeline[wordIndex]
|
|
159
|
-
const wordText = wordEntry.text
|
|
160
|
-
|
|
161
|
-
if (!isWordOrSymbolWord(wordText)) {
|
|
162
|
-
continue
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
const indexOfWordInText = text.indexOf(wordText, wordSearchStartOffset)
|
|
166
|
-
|
|
167
|
-
if (indexOfWordInText == -1) {
|
|
168
|
-
throw new Error(`Couldn't find the word '${wordText}' in the text at start position ${wordSearchStartOffset}`)
|
|
169
|
-
}
|
|
116
|
+
const segmentTimeline: Timeline = []
|
|
117
|
+
let wordIndex = 0
|
|
170
118
|
|
|
171
|
-
|
|
172
|
-
|
|
119
|
+
for (let paragraphIndex = 0; paragraphIndex < paragraphs.length; paragraphIndex++) {
|
|
120
|
+
const paragraph = paragraphs[paragraphIndex]
|
|
121
|
+
const sentencesIndexes = sentenceIndexesForParagraph[paragraphIndex]
|
|
122
|
+
const sentenceEntries = sentencesIndexes.map(index => segmentedWordSequence.sentences[index])
|
|
173
123
|
|
|
174
|
-
|
|
175
|
-
}
|
|
176
|
-
}
|
|
124
|
+
const sentenceTimeline: Timeline = []
|
|
177
125
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
const newSegmentTimeline: Timeline = []
|
|
126
|
+
for (const sentenceEntry of sentenceEntries) {
|
|
127
|
+
const wordTimeline: Timeline = []
|
|
181
128
|
|
|
182
|
-
|
|
183
|
-
|
|
129
|
+
for (const _ of sentenceEntry.words.entries) {
|
|
130
|
+
const originalWordIndex = originalWordsReverseMapping.get(wordIndex)
|
|
184
131
|
|
|
185
|
-
|
|
132
|
+
if (originalWordIndex !== undefined) {
|
|
133
|
+
const wordTimelineEntry = wordTimelineWithOffsets[originalWordIndex]
|
|
186
134
|
|
|
187
|
-
|
|
188
|
-
|
|
135
|
+
wordTimeline.push(wordTimelineEntry)
|
|
136
|
+
}
|
|
189
137
|
|
|
190
|
-
|
|
191
|
-
continue
|
|
138
|
+
wordIndex += 1
|
|
192
139
|
}
|
|
193
140
|
|
|
194
|
-
|
|
195
|
-
|
|
141
|
+
const sentenceTimelineEntry: TimelineEntry = {
|
|
142
|
+
type: 'sentence',
|
|
143
|
+
text: sentenceEntry.text,
|
|
144
|
+
startTime: wordTimeline[0].startTime,
|
|
145
|
+
endTime: wordTimeline[wordTimeline.length - 1].endTime,
|
|
196
146
|
|
|
197
|
-
|
|
198
|
-
|
|
147
|
+
timeline: wordTimeline,
|
|
148
|
+
}
|
|
199
149
|
|
|
200
|
-
|
|
201
|
-
continue
|
|
150
|
+
sentenceTimeline.push(sentenceTimelineEntry)
|
|
202
151
|
}
|
|
203
152
|
|
|
204
|
-
|
|
153
|
+
const segmentTimelineEntry: TimelineEntry = {
|
|
154
|
+
type: 'segment',
|
|
155
|
+
text: paragraph,
|
|
156
|
+
startTime: sentenceTimeline[0].startTime,
|
|
157
|
+
endTime: sentenceTimeline[sentenceTimeline.length - 1].endTime,
|
|
205
158
|
|
|
206
|
-
|
|
207
|
-
|
|
159
|
+
timeline: sentenceTimeline,
|
|
160
|
+
}
|
|
208
161
|
|
|
209
|
-
|
|
162
|
+
segmentTimeline.push(segmentTimelineEntry)
|
|
210
163
|
}
|
|
211
164
|
|
|
212
|
-
return { segmentTimeline
|
|
165
|
+
return { segmentTimeline }
|
|
213
166
|
}
|
|
214
167
|
|
|
215
|
-
export function
|
|
216
|
-
const {
|
|
168
|
+
export function addWordTextOffsetsToTimelineInPlace(timeline: Timeline, text: string) {
|
|
169
|
+
const { utf16To32Mapping } = getUTF32Chars(text)
|
|
217
170
|
|
|
218
|
-
|
|
219
|
-
if (entry.type == 'word') {
|
|
220
|
-
let word = entry.text
|
|
171
|
+
let currentOffset = 0
|
|
221
172
|
|
|
222
|
-
|
|
173
|
+
function processTimeline(timeline: Timeline) {
|
|
174
|
+
for (const entry of timeline) {
|
|
175
|
+
if (entry.type === 'word') {
|
|
176
|
+
let word = entry.text
|
|
223
177
|
|
|
224
|
-
|
|
178
|
+
word = word.trim().replaceAll(/\s+/g, ' ')
|
|
225
179
|
|
|
226
|
-
|
|
227
|
-
let endOffset: number | undefined
|
|
180
|
+
const wordParts = word.split(' ')
|
|
228
181
|
|
|
229
|
-
|
|
230
|
-
let
|
|
182
|
+
let startOffset: number | undefined
|
|
183
|
+
let endOffset: number | undefined
|
|
231
184
|
|
|
232
|
-
let
|
|
185
|
+
for (let i = 0; i < wordParts.length; i++) {
|
|
186
|
+
const wordPart = wordParts[i]
|
|
233
187
|
|
|
234
|
-
|
|
235
|
-
continue
|
|
236
|
-
}
|
|
188
|
+
const wordPartOffset = text.indexOf(wordPart, currentOffset)
|
|
237
189
|
|
|
238
|
-
|
|
190
|
+
if (wordPartOffset === -1) {
|
|
191
|
+
continue
|
|
192
|
+
}
|
|
239
193
|
|
|
240
|
-
|
|
241
|
-
startOffset = wordPartOffset
|
|
242
|
-
}
|
|
194
|
+
currentOffset = wordPartOffset + wordParts[i].length
|
|
243
195
|
|
|
244
|
-
|
|
245
|
-
|
|
196
|
+
if (i === 0) {
|
|
197
|
+
startOffset = wordPartOffset
|
|
198
|
+
}
|
|
246
199
|
|
|
247
|
-
|
|
248
|
-
|
|
200
|
+
endOffset = currentOffset
|
|
201
|
+
}
|
|
249
202
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
203
|
+
entry.startOffsetUtf16 = startOffset
|
|
204
|
+
entry.endOffsetUtf16 = endOffset
|
|
205
|
+
|
|
206
|
+
entry.startOffsetUtf32 = startOffset !== undefined ? utf16To32Mapping[startOffset] : undefined
|
|
207
|
+
entry.endOffsetUtf32 = endOffset !== undefined ? utf16To32Mapping[endOffset] : undefined
|
|
208
|
+
} else if (entry.timeline) {
|
|
209
|
+
processTimeline(entry.timeline)
|
|
210
|
+
}
|
|
254
211
|
}
|
|
255
212
|
}
|
|
256
213
|
|
|
257
|
-
return
|
|
214
|
+
return processTimeline(timeline)
|
|
258
215
|
}
|
|
259
216
|
|
|
260
217
|
function replaceSentenceEndersWithinWordsWithMaskingCharacter(transcript: string, wordTimeline: Timeline, maskingCharacter: string) {
|
|
@@ -344,30 +301,3 @@ export type TimelineEntry = {
|
|
|
344
301
|
}
|
|
345
302
|
|
|
346
303
|
export type Timeline = TimelineEntry[]
|
|
347
|
-
|
|
348
|
-
////
|
|
349
|
-
|
|
350
|
-
export async function testTimelineFix() {
|
|
351
|
-
let transcript = 'Hello world how are you? Do you want to play chess?'
|
|
352
|
-
const words = (await splitToWords(transcript, 'en')).filter(word => word.trim() !== '')
|
|
353
|
-
|
|
354
|
-
let wordTimeline: Timeline = words.map(wordText => ({
|
|
355
|
-
type: 'word',
|
|
356
|
-
|
|
357
|
-
text: wordText,
|
|
358
|
-
|
|
359
|
-
startTime: 0,
|
|
360
|
-
endTime: 0,
|
|
361
|
-
}))
|
|
362
|
-
|
|
363
|
-
addWordTextOffsetsToTimeline(wordTimeline, transcript)
|
|
364
|
-
|
|
365
|
-
wordTimeline[1].text = 'wor.d'
|
|
366
|
-
wordTimeline[8].text = 'wa.t'
|
|
367
|
-
|
|
368
|
-
transcript = transcript.replace('world', 'wor.d').replace('want', 'wa.t')
|
|
369
|
-
|
|
370
|
-
const result = await wordTimelineToSegmentSentenceTimeline(wordTimeline, transcript, 'en')
|
|
371
|
-
|
|
372
|
-
const x = 1
|
|
373
|
-
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { splitToParagraphs,
|
|
1
|
+
import { splitToParagraphs, wordCharacterRegExp } from '../nlp/Segmentation.js'
|
|
2
2
|
import { Logger } from './Logger.js'
|
|
3
3
|
|
|
4
4
|
export async function parseWikipediaArticle(articleName: string, language: string) {
|
|
@@ -20,7 +20,7 @@ export async function parseWikipediaArticle(articleName: string, language: strin
|
|
|
20
20
|
for (const section of sections) {
|
|
21
21
|
const sectionTitle = section.title()
|
|
22
22
|
|
|
23
|
-
if (
|
|
23
|
+
if (wordCharacterRegExp.test(sectionTitle)) {
|
|
24
24
|
sectionsText.push(sectionTitle)
|
|
25
25
|
}
|
|
26
26
|
|
|
@@ -29,7 +29,7 @@ export async function parseWikipediaArticle(articleName: string, language: strin
|
|
|
29
29
|
for (const paragraph of sectionParagraphs) {
|
|
30
30
|
const paragraphText = paragraph
|
|
31
31
|
|
|
32
|
-
if (
|
|
32
|
+
if (wordCharacterRegExp.test(paragraphText)) {
|
|
33
33
|
sectionsText.push(paragraphText)
|
|
34
34
|
}
|
|
35
35
|
}
|