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,6 +1,6 @@
|
|
|
1
1
|
import { RawAudio, cloneRawAudio } from '../audio/AudioUtilities.js'
|
|
2
2
|
import { concatFloat32Arrays, isWasmSimdSupported } from '../utilities/Utilities.js'
|
|
3
|
-
import {
|
|
3
|
+
import { wrapEmscriptenModuleHeap } from 'wasm-heap-manager'
|
|
4
4
|
|
|
5
5
|
let speexResamplerInstance: any
|
|
6
6
|
|
|
@@ -23,17 +23,17 @@ export async function resampleAudioSpeex(rawAudio: RawAudio, outSampleRate: numb
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
const m = await getSpeexResamplerInstance()
|
|
26
|
-
const
|
|
26
|
+
const wasmHeap = wrapEmscriptenModuleHeap(m)
|
|
27
27
|
|
|
28
28
|
function speexResultCodeToString(resultCode: number) {
|
|
29
29
|
const errorStrPtr = m._speex_resampler_strerror(resultCode)
|
|
30
|
-
const
|
|
31
|
-
const message =
|
|
30
|
+
const messageRef = wasmHeap.wrapNullTerminatedUtf8String(errorStrPtr)
|
|
31
|
+
const message = messageRef.value
|
|
32
32
|
|
|
33
33
|
return message
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
const initErrRef =
|
|
36
|
+
const initErrRef = wasmHeap.allocInt32()
|
|
37
37
|
const resamplerStateAddress = m._speex_resampler_init(channelCount, inSampleRate, outSampleRate, quality, initErrRef.address)
|
|
38
38
|
let resultCode = initErrRef.value
|
|
39
39
|
|
|
@@ -46,11 +46,11 @@ export async function resampleAudioSpeex(rawAudio: RawAudio, outSampleRate: numb
|
|
|
46
46
|
|
|
47
47
|
const maxChunkSize = 2 ** 20
|
|
48
48
|
|
|
49
|
-
const inputChunkSampleCountRef =
|
|
50
|
-
const outputChunkSampleCountRef =
|
|
49
|
+
const inputChunkSampleCountRef = wasmHeap.allocInt32()
|
|
50
|
+
const outputChunkSampleCountRef = wasmHeap.allocInt32()
|
|
51
51
|
|
|
52
|
-
const inputChunkSamplesRef =
|
|
53
|
-
const outputChunkSamplesRef =
|
|
52
|
+
const inputChunkSamplesRef = wasmHeap.allocFloat32Array(maxChunkSize * 2)
|
|
53
|
+
const outputChunkSamplesRef = wasmHeap.allocFloat32Array(Math.floor(maxChunkSize * sampleRateRatio) * 2)
|
|
54
54
|
|
|
55
55
|
const resampledAudioChunksForChannels: Float32Array[][] = []
|
|
56
56
|
|
|
@@ -65,7 +65,7 @@ export async function resampleAudioSpeex(rawAudio: RawAudio, outSampleRate: numb
|
|
|
65
65
|
const inputPaddingSize = isLastChunk ? inputLatency : 0
|
|
66
66
|
const maxSamplesToRead = Math.min(maxChunkSize, totalSampleCount - readOffset) + inputPaddingSize
|
|
67
67
|
|
|
68
|
-
const maxSamplesToWrite = outputChunkSamplesRef.
|
|
68
|
+
const maxSamplesToWrite = outputChunkSamplesRef.elementCount
|
|
69
69
|
|
|
70
70
|
const inputChunkSamplesForChannel = rawAudio.audioChannels[channelIndex].slice(readOffset, readOffset + maxSamplesToRead)
|
|
71
71
|
|
|
@@ -91,7 +91,7 @@ export async function resampleAudioSpeex(rawAudio: RawAudio, outSampleRate: numb
|
|
|
91
91
|
}
|
|
92
92
|
|
|
93
93
|
m._speex_resampler_destroy(resamplerStateAddress)
|
|
94
|
-
|
|
94
|
+
wasmHeap.freeAll()
|
|
95
95
|
|
|
96
96
|
const resampledAudio: RawAudio = {
|
|
97
97
|
audioChannels: [],
|
package/src/encodings/Ascii.ts
CHANGED
|
@@ -49,7 +49,7 @@ export class ChunkedAsciiDecoder {
|
|
|
49
49
|
private readonly textDecoder = new TextDecoder('windows-1252')
|
|
50
50
|
|
|
51
51
|
writeChunk(chunk: Uint8Array) {
|
|
52
|
-
const decodedChunk = this.textDecoder.decode(chunk)
|
|
52
|
+
const decodedChunk = this.textDecoder.decode(chunk, { stream: true })
|
|
53
53
|
|
|
54
54
|
this.str += decodedChunk
|
|
55
55
|
}
|
package/src/encodings/Utf16.ts
CHANGED
|
@@ -41,7 +41,7 @@ export class ChunkedUtf16Decoder {
|
|
|
41
41
|
private readonly textDecoder = new TextDecoder('utf-16le')
|
|
42
42
|
|
|
43
43
|
writeChunk(chunk: Uint16Array) {
|
|
44
|
-
const decodedChunk = this.textDecoder.decode(chunk)
|
|
44
|
+
const decodedChunk = this.textDecoder.decode(chunk, { stream: true })
|
|
45
45
|
|
|
46
46
|
this.str += decodedChunk
|
|
47
47
|
}
|
package/src/encodings/Utf8.ts
CHANGED
|
@@ -33,7 +33,7 @@ export class ChunkedUtf8Decoder {
|
|
|
33
33
|
private readonly textDecoder = new TextDecoder('utf-8')
|
|
34
34
|
|
|
35
35
|
writeChunk(chunk: Uint8Array) {
|
|
36
|
-
const decodedChunk = this.textDecoder.decode(chunk)
|
|
36
|
+
const decodedChunk = this.textDecoder.decode(chunk, { stream: true })
|
|
37
37
|
|
|
38
38
|
this.str += decodedChunk
|
|
39
39
|
}
|
|
@@ -57,7 +57,7 @@ export async function phonemizeText(text: string, voice: string, substitutionMap
|
|
|
57
57
|
.replaceAll('«', ', ')
|
|
58
58
|
.replaceAll('»', ', ')
|
|
59
59
|
|
|
60
|
-
const segmentedText = await Segmentation.
|
|
60
|
+
const segmentedText = await Segmentation.parseTextAndConvertToFragmentObjects(text, voice)
|
|
61
61
|
const preparedPhrases: string[] = []
|
|
62
62
|
const phraseBreakers: string[] = []
|
|
63
63
|
|
package/src/nlp/Segmentation.ts
CHANGED
|
@@ -1,22 +1,26 @@
|
|
|
1
|
-
import * as CldrSegmentation from 'cldr-segmentation'
|
|
2
|
-
import { splitChineseTextToWords_Jieba } from './ChineseSegmentation.js'
|
|
3
|
-
|
|
4
1
|
import { sumArray, logToStderr } from '../utilities/Utilities.js'
|
|
5
2
|
import { getShortLanguageCode } from '../utilities/Locale.js'
|
|
6
|
-
import { splitJapaneseTextToWords_Kuromoji } from './JapaneseSegmentation.js'
|
|
7
3
|
import { ParagraphBreakType, WhitespaceProcessing } from '../api/Common.js'
|
|
8
|
-
import { includesAnyOf,
|
|
4
|
+
import { includesAnyOf, splitAndPreserveSeparators } from '../utilities/StringUtilities.js'
|
|
5
|
+
|
|
6
|
+
import * as TextSegmentation from '@echogarden/text-segmentation'
|
|
7
|
+
import { splitChineseTextToWords_Jieba } from './ChineseSegmentation.js'
|
|
8
|
+
import { splitJapaneseTextToWords_Kuromoji } from './JapaneseSegmentation.js'
|
|
9
9
|
|
|
10
10
|
const log = logToStderr
|
|
11
11
|
|
|
12
|
-
export const
|
|
13
|
-
export const
|
|
12
|
+
export const wordCharacterRegExp = /[\p{Letter}\p{Number}]/u
|
|
13
|
+
export const emojiCharacterRegExp = /[\p{Emoji}]/u
|
|
14
|
+
export const punctuationRegExp = /[\p{Punctuation}]/u
|
|
14
15
|
|
|
15
|
-
export const phraseSeparators = [',', ';', ':', '
|
|
16
|
+
export const phraseSeparators = [',', '、', ',', '،', ';', ';', ':', ':', '—']
|
|
16
17
|
export const symbolWords = ['$', '€', '¢', '£', '¥', '©', '®', '™', '%', '&', '#', '~', '@', '+', '±', '÷', '/', '*', '=', '¼', '½', '¾']
|
|
17
18
|
|
|
19
|
+
///////////////////////////////////////////////////////////////////////////////////////////////
|
|
20
|
+
// Predicates
|
|
21
|
+
///////////////////////////////////////////////////////////////////////////////////////////////
|
|
18
22
|
export function isWordOrSymbolWord(str: string) {
|
|
19
|
-
return isWord(str) || symbolWords.includes(str)
|
|
23
|
+
return isWord(str) || includesEmoji(str) || symbolWords.includes(str)
|
|
20
24
|
}
|
|
21
25
|
|
|
22
26
|
export function isSymbolWord(str: string) {
|
|
@@ -24,87 +28,106 @@ export function isSymbolWord(str: string) {
|
|
|
24
28
|
}
|
|
25
29
|
|
|
26
30
|
export function isWord(str: string) {
|
|
27
|
-
return
|
|
31
|
+
return wordCharacterRegExp.test(str.trim())
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function includesPunctuation(str: string) {
|
|
35
|
+
return punctuationRegExp.test(str.trim())
|
|
28
36
|
}
|
|
29
37
|
|
|
30
|
-
export function
|
|
31
|
-
return
|
|
38
|
+
export function includesEmoji(str: string) {
|
|
39
|
+
return emojiCharacterRegExp.test(str.trim())
|
|
32
40
|
}
|
|
33
41
|
|
|
34
42
|
export function isWhitespace(str: string) {
|
|
35
43
|
return str.trim().length === 0
|
|
36
44
|
}
|
|
37
45
|
|
|
38
|
-
|
|
39
|
-
|
|
46
|
+
///////////////////////////////////////////////////////////////////////////////////////////////
|
|
47
|
+
// Paragraph, line, sentence, phrase, and word segmentation
|
|
48
|
+
///////////////////////////////////////////////////////////////////////////////////////////////
|
|
49
|
+
export function splitToParagraphs(text: string, paragraphBreakType: ParagraphBreakType, whitespaceProcessingMethod: WhitespaceProcessing) {
|
|
50
|
+
let paragraphs: string[] = []
|
|
40
51
|
|
|
41
|
-
|
|
52
|
+
if (paragraphBreakType === 'single') {
|
|
53
|
+
paragraphs = splitAndPreserveSeparators(text, /(\r?\n)+/g)
|
|
54
|
+
} else if (paragraphBreakType === 'double') {
|
|
55
|
+
paragraphs = splitAndPreserveSeparators(text, /(\r?\n)(\r?\n)+/g)
|
|
56
|
+
} else {
|
|
57
|
+
throw new Error(`Invalid paragraph break type: '${paragraphBreakType}'`)
|
|
58
|
+
}
|
|
42
59
|
|
|
43
|
-
|
|
60
|
+
paragraphs = paragraphs.map(p => applyWhitespaceProcessing(p, whitespaceProcessingMethod))
|
|
61
|
+
paragraphs = paragraphs.filter(p => p.length > 0)
|
|
44
62
|
|
|
45
|
-
|
|
63
|
+
return paragraphs
|
|
46
64
|
}
|
|
47
65
|
|
|
48
|
-
export
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
get length() { return sumArray(this.words, (word) => word.length) }
|
|
52
|
-
|
|
53
|
-
get text() { return this.words.reduce<string>((result, word) => result + word.text, '') }
|
|
54
|
-
|
|
55
|
-
get lastWord() {
|
|
56
|
-
if (this.words.length == 0) {
|
|
57
|
-
return undefined
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
return this.words[this.words.length - 1]
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
get isSentenceFinalizer() { return this.lastWord != null ? this.lastWord.isSentenceFinalizer : false }
|
|
66
|
+
export function splitToLines(text: string) {
|
|
67
|
+
return splitAndPreserveSeparators(text, /\r?\n/g)
|
|
64
68
|
}
|
|
65
69
|
|
|
66
|
-
export
|
|
67
|
-
|
|
68
|
-
isSentenceFinalizer: boolean
|
|
69
|
-
|
|
70
|
-
constructor(text: string, isSentenceFinalizer: boolean) {
|
|
71
|
-
this.text = text
|
|
72
|
-
this.isSentenceFinalizer = isSentenceFinalizer
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
get containsOnlyPunctuation() { return !wordCharacterPattern.test(this.text) && !this.isSymbolWord }
|
|
70
|
+
export async function parseText(text: string, langCode: string) {
|
|
71
|
+
const shortLangCode = getShortLanguageCode(langCode || '')
|
|
76
72
|
|
|
77
|
-
|
|
73
|
+
const wordSequence = await splitToWords(text, shortLangCode)
|
|
78
74
|
|
|
79
|
-
|
|
75
|
+
const parsedText = await TextSegmentation.segmentWordSequence(wordSequence)
|
|
80
76
|
|
|
81
|
-
|
|
77
|
+
return parsedText
|
|
82
78
|
}
|
|
83
79
|
|
|
84
|
-
export
|
|
80
|
+
export async function splitToWords(text: string, langCode: string): Promise<TextSegmentation.WordSequence> {
|
|
81
|
+
const shortLangCode = getShortLanguageCode(langCode || '')
|
|
85
82
|
|
|
86
|
-
|
|
87
|
-
|
|
83
|
+
if (shortLangCode === 'zh' || shortLangCode === 'cmn' || shortLangCode === 'ja') {
|
|
84
|
+
let wordArray: string[] = []
|
|
88
85
|
|
|
89
|
-
|
|
86
|
+
if (shortLangCode === 'zh' || shortLangCode === 'cmn') {
|
|
87
|
+
wordArray = await splitChineseTextToWords_Jieba(text)
|
|
88
|
+
} else {
|
|
89
|
+
wordArray = await splitJapaneseTextToWords_Kuromoji(text)
|
|
90
|
+
}
|
|
90
91
|
|
|
91
|
-
|
|
92
|
+
const wordSequence = new TextSegmentation.WordSequence()
|
|
92
93
|
|
|
93
|
-
|
|
94
|
+
let offset = 0
|
|
94
95
|
|
|
95
|
-
|
|
96
|
+
for (const wordText of wordArray) {
|
|
97
|
+
const startOffset = offset
|
|
98
|
+
const endOffset = startOffset + wordText.length
|
|
99
|
+
const isNonPunctuation = isWordOrSymbolWord(wordText)
|
|
96
100
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
101
|
+
wordSequence.addWord(wordText, startOffset, isNonPunctuation)
|
|
102
|
+
|
|
103
|
+
offset = endOffset
|
|
100
104
|
}
|
|
101
105
|
|
|
102
|
-
return
|
|
106
|
+
return wordSequence
|
|
107
|
+
} else {
|
|
108
|
+
return TextSegmentation.splitToWords(text, { language: langCode })
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function applyWhitespaceProcessing(text: string, whitespaceProcessingMethod: WhitespaceProcessing) {
|
|
113
|
+
if (whitespaceProcessingMethod === 'removeLineBreaks') {
|
|
114
|
+
return text.trim().replaceAll(/(\r?\n)+/g, ' ')
|
|
115
|
+
} else if (whitespaceProcessingMethod === 'collapse') {
|
|
116
|
+
return text.trim().replaceAll(/\s+/g, ' ')
|
|
117
|
+
} else if (whitespaceProcessingMethod === 'preserve') {
|
|
118
|
+
return text
|
|
119
|
+
} else {
|
|
120
|
+
throw new Error(`Invalid whitespace processing method: '${whitespaceProcessingMethod}'`)
|
|
103
121
|
}
|
|
104
122
|
}
|
|
105
123
|
|
|
124
|
+
///////////////////////////////////////////////////////////////////////////////////////////////
|
|
125
|
+
// Fragment segmentation
|
|
126
|
+
//
|
|
127
|
+
// Used to split text to fragments, to fit particular size constraints.
|
|
128
|
+
///////////////////////////////////////////////////////////////////////////////////////////////
|
|
106
129
|
export async function splitToFragments(text: string, maxFragmentLength: number, langCode: string, preserveSentences = true, preservePhrases = true) {
|
|
107
|
-
const parsedText = await
|
|
130
|
+
const parsedText = await parseTextAndConvertToFragmentObjects(text, langCode)
|
|
108
131
|
|
|
109
132
|
const fragments: Fragment[] = []
|
|
110
133
|
let currentFragment = new Fragment()
|
|
@@ -173,33 +196,28 @@ export async function splitToFragments(text: string, maxFragmentLength: number,
|
|
|
173
196
|
return fragments
|
|
174
197
|
}
|
|
175
198
|
|
|
176
|
-
export async function
|
|
177
|
-
const
|
|
199
|
+
export async function parseTextAndConvertToFragmentObjects(text: string, langCode: string) {
|
|
200
|
+
const segmentedText = await parseText(text, langCode)
|
|
201
|
+
|
|
178
202
|
const sentences: Sentence[] = []
|
|
179
203
|
|
|
180
|
-
for (const
|
|
204
|
+
for (const sentenceEntry of segmentedText.sentences) {
|
|
181
205
|
const sentence = new Sentence()
|
|
182
206
|
|
|
183
|
-
|
|
184
|
-
|
|
207
|
+
for (const phraseEntry of sentenceEntry.phrases) {
|
|
208
|
+
const phrase = new Phrase()
|
|
185
209
|
|
|
186
|
-
|
|
187
|
-
|
|
210
|
+
for (const wordEntry of phraseEntry.words.entries) {
|
|
211
|
+
const isSentenceFinalizer = wordEntry === sentenceEntry.words.lastEntry
|
|
188
212
|
|
|
189
|
-
|
|
190
|
-
const separatorIndex = indexOfAnyOf(word.text, phraseSeparators)
|
|
191
|
-
currentPhrase.words.push(new Word(word.text.substring(0, separatorIndex + 1), word.isSentenceFinalizer))
|
|
192
|
-
sentence.phrases.push(currentPhrase)
|
|
213
|
+
const word = new Word(wordEntry.text, isSentenceFinalizer)
|
|
193
214
|
|
|
194
|
-
|
|
195
|
-
currentPhrase.words.push(new Word(word.text.substring(separatorIndex + 1), false))
|
|
196
|
-
} else {
|
|
197
|
-
currentPhrase.words.push(word)
|
|
215
|
+
phrase.words.push(word)
|
|
198
216
|
}
|
|
199
|
-
}
|
|
200
217
|
|
|
201
|
-
|
|
202
|
-
|
|
218
|
+
if (phrase.words.length > 0) {
|
|
219
|
+
sentence.phrases.push(phrase)
|
|
220
|
+
}
|
|
203
221
|
}
|
|
204
222
|
|
|
205
223
|
sentences.push(sentence)
|
|
@@ -208,53 +226,70 @@ export async function parse(text: string, langCode: string) {
|
|
|
208
226
|
return sentences
|
|
209
227
|
}
|
|
210
228
|
|
|
211
|
-
export
|
|
212
|
-
|
|
229
|
+
export class Sentence {
|
|
230
|
+
phrases: Phrase[] = []
|
|
231
|
+
|
|
232
|
+
readonly isSentenceFinalizer = true
|
|
213
233
|
|
|
214
|
-
return
|
|
234
|
+
get length() { return sumArray(this.phrases, (phrase) => phrase.length) }
|
|
235
|
+
|
|
236
|
+
get text() { return this.phrases.reduce<string>((result, phrase) => result + phrase.text, '') }
|
|
215
237
|
}
|
|
216
238
|
|
|
217
|
-
export
|
|
218
|
-
|
|
239
|
+
export class Phrase {
|
|
240
|
+
words: Word[] = []
|
|
219
241
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
242
|
+
get length() { return sumArray(this.words, (word) => word.length) }
|
|
243
|
+
|
|
244
|
+
get text() { return this.words.reduce<string>((result, word) => result + word.text, '') }
|
|
245
|
+
|
|
246
|
+
get lastWord() {
|
|
247
|
+
if (this.words.length == 0) {
|
|
248
|
+
return undefined
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return this.words[this.words.length - 1]
|
|
226
252
|
}
|
|
253
|
+
|
|
254
|
+
get isSentenceFinalizer() { return this.lastWord != null ? this.lastWord.isSentenceFinalizer : false }
|
|
227
255
|
}
|
|
228
256
|
|
|
229
|
-
export
|
|
230
|
-
|
|
257
|
+
export class Word {
|
|
258
|
+
readonly text: string
|
|
259
|
+
isSentenceFinalizer: boolean
|
|
231
260
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
paragraphs = text.split(/(\r?\n)(\r?\n)+/g)
|
|
236
|
-
} else {
|
|
237
|
-
throw new Error(`Invalid paragraph break type: '${paragraphBreaks}'`)
|
|
261
|
+
constructor(text: string, isSentenceFinalizer: boolean) {
|
|
262
|
+
this.text = text
|
|
263
|
+
this.isSentenceFinalizer = isSentenceFinalizer
|
|
238
264
|
}
|
|
239
265
|
|
|
240
|
-
|
|
241
|
-
paragraphs = paragraphs.filter(p => p.length > 0)
|
|
266
|
+
get containsOnlyPunctuation() { return !wordCharacterRegExp.test(this.text) && !this.isSymbolWord }
|
|
242
267
|
|
|
243
|
-
return
|
|
244
|
-
}
|
|
268
|
+
get isSymbolWord() { return symbolWords.includes(this.text) }
|
|
245
269
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
} else if (whitespaceProcessingMethod === 'collapse') {
|
|
250
|
-
return text.replaceAll(/\s+/g, ' ')
|
|
251
|
-
} else if (whitespaceProcessingMethod === 'preserve') {
|
|
252
|
-
return text
|
|
253
|
-
} else {
|
|
254
|
-
throw new Error(`Invalid whitespace processing method: '${whitespaceProcessingMethod}'`)
|
|
255
|
-
}
|
|
270
|
+
get isPhraseSeperator() { return this.containsOnlyPunctuation && includesAnyOf(this.text, phraseSeparators) }
|
|
271
|
+
|
|
272
|
+
get length() { return this.text.length }
|
|
256
273
|
}
|
|
257
274
|
|
|
258
|
-
export
|
|
259
|
-
|
|
275
|
+
export type Segment = Sentence | Phrase | Word
|
|
276
|
+
|
|
277
|
+
export class Fragment {
|
|
278
|
+
segments: Segment[] = []
|
|
279
|
+
|
|
280
|
+
get length() { return sumArray(this.segments, (phrase) => phrase.length) }
|
|
281
|
+
|
|
282
|
+
get text() { return this.segments.reduce<string>((result, segment) => result + segment.text, '') }
|
|
283
|
+
|
|
284
|
+
get isEmpty() { return this.length == 0 }
|
|
285
|
+
|
|
286
|
+
get isNonempty() { return !this.isEmpty }
|
|
287
|
+
|
|
288
|
+
get lastSegment() {
|
|
289
|
+
if (this.isEmpty) {
|
|
290
|
+
return undefined
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
return this.segments[this.segments.length - 1]
|
|
294
|
+
}
|
|
260
295
|
}
|