echogarden 2.7.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/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/AudioPlayer.js +2 -2
- package/dist/audio/AudioPlayer.js.map +1 -1
- package/dist/audio/AudioUtilities.d.ts +1 -1
- package/dist/cli/CLI.js +4 -4
- package/dist/cli/CLI.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/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/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/KokoroTTS.js +21 -10
- package/dist/synthesis/KokoroTTS.js.map +1 -1
- package/dist/synthesis/SvoxPicoTTS.js +24 -23
- 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/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/AudioPlayer.ts +2 -2
- package/src/cli/CLI.ts +4 -4
- 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/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/SvoxPicoTTS.ts +26 -23
- 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/nlp/CompromiseNLP.d.ts +0 -15
- package/dist/nlp/CompromiseNLP.js +0 -79
- package/dist/nlp/CompromiseNLP.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/nlp/CompromiseNLP.ts +0 -119
- package/src/utilities/WasmMemoryManager.ts +0 -566
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
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { getShortLanguageCode } from '../utilities/Locale.js'
|
|
2
2
|
import { substituteCharactersUsingLookup } from '../utilities/StringUtilities.js'
|
|
3
|
+
import { anyOf, buildRegExp, charRange, inputEnd, inputStart, repeated, zeroOrMore } from 'regexp-composer'
|
|
3
4
|
|
|
4
5
|
export function getNormalizedFragmentsForSpeech(
|
|
5
6
|
words: string[],
|
|
@@ -13,36 +14,6 @@ export function getNormalizedFragmentsForSpeech(
|
|
|
13
14
|
return { normalizedFragments: [...nonWhitespaceWords], referenceFragments: [...nonWhitespaceWords] }
|
|
14
15
|
}
|
|
15
16
|
|
|
16
|
-
const numberPattern = /^[0-9][0-9\,\.]*$/
|
|
17
|
-
|
|
18
|
-
const fourDigitYearPattern = /^[0-9][0-9][0-9][0-9]$/
|
|
19
|
-
const fourDigitDecadePattern = /^[0-9][0-9][0-9]0s$/
|
|
20
|
-
|
|
21
|
-
const fourDigitYearRangePattern = /^[0-9][0-9][0-9][0-9][\-\–][0-9][0-9][0-9][0-9]$/
|
|
22
|
-
|
|
23
|
-
const wordsPrecedingAYear = [
|
|
24
|
-
'in', 'the', 'a', 'to', 'of', 'since', 'from', 'between', 'by', 'until', 'around', 'before', 'after',
|
|
25
|
-
'his', 'her', 'year', 'years', 'during', 'copyright', '©', 'early', 'mid', 'late',
|
|
26
|
-
'january', 'february', 'march', 'april', 'may', 'june', 'july', 'august', 'september', 'october', 'november', 'december',
|
|
27
|
-
'jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug', 'sep', 'oct', 'nov', 'dec'
|
|
28
|
-
]
|
|
29
|
-
|
|
30
|
-
const wordsPrecedingADecade = [
|
|
31
|
-
'the', 'in', 'early', 'mid', 'late', 'a'
|
|
32
|
-
]
|
|
33
|
-
|
|
34
|
-
const symbolsPrecedingACurrency = [
|
|
35
|
-
'$', '€', '£', '¥'
|
|
36
|
-
]
|
|
37
|
-
|
|
38
|
-
const symbolsPrecedingACurrencyAsWords = [
|
|
39
|
-
'dollars', 'euros', 'pounds', 'yen'
|
|
40
|
-
]
|
|
41
|
-
|
|
42
|
-
const wordsSucceedingACurrency = [
|
|
43
|
-
'million', 'billion', 'trillion'
|
|
44
|
-
]
|
|
45
|
-
|
|
46
17
|
const normalizedFragments: string[] = []
|
|
47
18
|
const referenceFragments: string[] = []
|
|
48
19
|
|
|
@@ -56,10 +27,11 @@ export function getNormalizedFragmentsForSpeech(
|
|
|
56
27
|
const originalWordIndex = nonWhitespaceWordOriginalIndex[wordIndex]
|
|
57
28
|
const isFollowedByWhitespace = words[originalWordIndex + 1]?.trim().length === 0
|
|
58
29
|
|
|
59
|
-
if (
|
|
60
|
-
wordsPrecedingAYear.includes(lowerCaseWord) &&
|
|
30
|
+
if (wordsPrecedingAYear.includes(lowerCaseWord) &&
|
|
61
31
|
isFollowedByWhitespace &&
|
|
62
|
-
|
|
32
|
+
fourDigitYearPatternRegExp.test(nextNonWhitespaceWord)) {
|
|
33
|
+
|
|
34
|
+
// Normalize a four digit year pattern, e.g. 'in 1995'.
|
|
63
35
|
|
|
64
36
|
const normalizedString = normalizeFourDigitYearString(nextNonWhitespaceWord)
|
|
65
37
|
|
|
@@ -70,10 +42,12 @@ export function getNormalizedFragmentsForSpeech(
|
|
|
70
42
|
referenceFragments.push(nextNonWhitespaceWord)
|
|
71
43
|
|
|
72
44
|
wordIndex += 1
|
|
73
|
-
} else if (
|
|
45
|
+
} else if (
|
|
74
46
|
wordsPrecedingADecade.includes(lowerCaseWord) &&
|
|
75
47
|
isFollowedByWhitespace &&
|
|
76
|
-
|
|
48
|
+
fourDigitDecadePatternRegExp.test(nextNonWhitespaceWord)) {
|
|
49
|
+
|
|
50
|
+
// Normalize a four digit decade pattern, e.g. 'the 1980s'.
|
|
77
51
|
|
|
78
52
|
const normalizedString = normalizeFourDigitDecadeString(nextNonWhitespaceWord)
|
|
79
53
|
|
|
@@ -84,9 +58,8 @@ export function getNormalizedFragmentsForSpeech(
|
|
|
84
58
|
referenceFragments.push(nextNonWhitespaceWord)
|
|
85
59
|
|
|
86
60
|
wordIndex += 1
|
|
87
|
-
} else if (
|
|
88
|
-
|
|
89
|
-
|
|
61
|
+
} else if (fourDigitYearRangePatternRegExp.test(words.slice(originalWordIndex, originalWordIndex + 3).join(''))) {
|
|
62
|
+
// Normalize a year range pattern, e.g. '1835-1896', ensure there are no spaces between words
|
|
90
63
|
normalizedFragments.push(normalizeFourDigitYearString(nonWhitespaceWords[wordIndex]))
|
|
91
64
|
referenceFragments.push(nonWhitespaceWords[wordIndex])
|
|
92
65
|
|
|
@@ -97,32 +70,37 @@ export function getNormalizedFragmentsForSpeech(
|
|
|
97
70
|
referenceFragments.push(nonWhitespaceWords[wordIndex + 2])
|
|
98
71
|
|
|
99
72
|
wordIndex += 2
|
|
100
|
-
} else if (
|
|
101
|
-
|
|
102
|
-
!isFollowedByWhitespace &&
|
|
103
|
-
numberPattern.test(nextNonWhitespaceWord)) {
|
|
73
|
+
} else if (precedingCurrencyPatternRegExp.test(lowerCaseWord)) {
|
|
74
|
+
// Normalize a currency pattern with preceding currency symbol, e.g. '€3.53', '$53.1 million',
|
|
104
75
|
|
|
105
|
-
|
|
76
|
+
const currencyWord = currencySymbolsAsWords[currencySymbols.indexOf(lowerCaseWord[0])]
|
|
106
77
|
|
|
107
|
-
if (
|
|
108
|
-
const normalizedString = `${
|
|
78
|
+
if (wordsFollowingACurrency.includes(nextNonWhitespaceWord?.toLowerCase())) {
|
|
79
|
+
const normalizedString = `${word.substring(1)} ${nextNonWhitespaceWord} ${currencyWord}`
|
|
109
80
|
|
|
110
81
|
normalizedFragments.push(normalizedString)
|
|
111
82
|
|
|
112
|
-
const referenceString = `${word}${nextNonWhitespaceWord}
|
|
83
|
+
const referenceString = `${word} ${nextNonWhitespaceWord}`
|
|
113
84
|
referenceFragments.push(referenceString)
|
|
114
85
|
|
|
115
|
-
wordIndex +=
|
|
86
|
+
wordIndex += 1
|
|
116
87
|
} else {
|
|
117
|
-
const normalizedString = `${
|
|
88
|
+
const normalizedString = `${word.substring(1)} ${currencyWord}`
|
|
118
89
|
|
|
119
90
|
normalizedFragments.push(normalizedString)
|
|
120
91
|
|
|
121
|
-
const referenceString =
|
|
92
|
+
const referenceString = word
|
|
122
93
|
referenceFragments.push(referenceString)
|
|
123
|
-
|
|
124
|
-
wordIndex += 1
|
|
125
94
|
}
|
|
95
|
+
} else if (followingCurrencyPatternRegExp.test(lowerCaseWord)) {
|
|
96
|
+
const currencyWord = currencySymbolsAsWords[currencySymbols.indexOf(lowerCaseWord[lowerCaseWord.length - 1])]
|
|
97
|
+
|
|
98
|
+
const normalizedString = `${word.substring(0, word.length - 1)} ${currencyWord}`
|
|
99
|
+
|
|
100
|
+
normalizedFragments.push(normalizedString)
|
|
101
|
+
|
|
102
|
+
const referenceString = word
|
|
103
|
+
referenceFragments.push(referenceString)
|
|
126
104
|
} else {
|
|
127
105
|
normalizedFragments.push(word)
|
|
128
106
|
referenceFragments.push(word)
|
|
@@ -156,7 +134,7 @@ export function normalizeFourDigitDecadeString(decadeString: string) {
|
|
|
156
134
|
let normalizedString: string
|
|
157
135
|
|
|
158
136
|
const isBeforeSecondMillenium = firstTwoDigitsValue < 10
|
|
159
|
-
const isMilleniumDecade =
|
|
137
|
+
const isMilleniumDecade = firstTwoDigitsValue % 10 == 0 && secondTwoDigitsValue == 0
|
|
160
138
|
|
|
161
139
|
if (!isBeforeSecondMillenium && !isMilleniumDecade) {
|
|
162
140
|
if (secondTwoDigitsValue != 0) {
|
|
@@ -229,3 +207,67 @@ export const punctuationSubstitutionLookup: Record<string, string> = {
|
|
|
229
207
|
'!': `!`,
|
|
230
208
|
'¡': `!`,
|
|
231
209
|
}
|
|
210
|
+
|
|
211
|
+
const wordsPrecedingAYear = [
|
|
212
|
+
'in', 'the', 'a', 'to', 'of', 'since', 'from', 'between', 'by', 'until', 'around', 'before', 'after',
|
|
213
|
+
'his', 'her', 'year', 'years', 'during', 'copyright', '©', 'early', 'mid', 'late',
|
|
214
|
+
'january', 'february', 'march', 'april', 'may', 'june', 'july', 'august', 'september', 'october', 'november', 'december',
|
|
215
|
+
'jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug', 'sep', 'oct', 'nov', 'dec',
|
|
216
|
+
'winter', 'spring', 'summer', 'fall', 'autumn'
|
|
217
|
+
]
|
|
218
|
+
|
|
219
|
+
const wordsPrecedingADecade = [
|
|
220
|
+
'the', 'in', 'early', 'mid', 'late', 'a'
|
|
221
|
+
]
|
|
222
|
+
|
|
223
|
+
const currencySymbols = [
|
|
224
|
+
'$', '¥', '€', '£', '₩', '₭', '₽', '₫', '฿', '¢', '₮', '؋', '₦', '₱', '₴', '₪'
|
|
225
|
+
]
|
|
226
|
+
|
|
227
|
+
const currencySymbolsAsWords = [
|
|
228
|
+
'dollars', 'yen', 'euros', 'pounds', 'won', 'kip', 'rubles', 'dong', 'baht', 'cents', 'tögrög', 'afghanis', 'naira', 'pesos', 'hryvnia', 'shekels'
|
|
229
|
+
]
|
|
230
|
+
|
|
231
|
+
const wordsFollowingACurrency = [
|
|
232
|
+
'million', 'billion', 'trillion'
|
|
233
|
+
]
|
|
234
|
+
|
|
235
|
+
const arabicNumeralPattern = charRange('0', '9')
|
|
236
|
+
|
|
237
|
+
const numberPattern = [
|
|
238
|
+
inputStart,
|
|
239
|
+
arabicNumeralPattern,
|
|
240
|
+
zeroOrMore(anyOf(arabicNumeralPattern, ',', '.')),
|
|
241
|
+
inputEnd
|
|
242
|
+
]
|
|
243
|
+
|
|
244
|
+
const numberPatternRegExp = buildRegExp(numberPattern)
|
|
245
|
+
|
|
246
|
+
const precedingCurrencyPattern = [
|
|
247
|
+
inputStart,
|
|
248
|
+
anyOf(...currencySymbols),
|
|
249
|
+
arabicNumeralPattern,
|
|
250
|
+
zeroOrMore(anyOf(arabicNumeralPattern, ',', '.')),
|
|
251
|
+
inputEnd
|
|
252
|
+
]
|
|
253
|
+
|
|
254
|
+
const precedingCurrencyPatternRegExp = buildRegExp(precedingCurrencyPattern)
|
|
255
|
+
|
|
256
|
+
const followingCurrencyPattern = [
|
|
257
|
+
inputStart,
|
|
258
|
+
arabicNumeralPattern,
|
|
259
|
+
zeroOrMore(anyOf(arabicNumeralPattern, ',', '.')),
|
|
260
|
+
anyOf(...currencySymbols),
|
|
261
|
+
inputEnd
|
|
262
|
+
]
|
|
263
|
+
|
|
264
|
+
const followingCurrencyPatternRegExp = buildRegExp(followingCurrencyPattern)
|
|
265
|
+
|
|
266
|
+
const fourDigitYearPattern = [inputStart, repeated(4, arabicNumeralPattern), inputEnd]
|
|
267
|
+
const fourDigitYearPatternRegExp = buildRegExp(fourDigitYearPattern)
|
|
268
|
+
|
|
269
|
+
const fourDigitDecadePattern = [inputStart, repeated(3, arabicNumeralPattern), '0s', inputEnd]
|
|
270
|
+
const fourDigitDecadePatternRegExp = buildRegExp(fourDigitDecadePattern)
|
|
271
|
+
|
|
272
|
+
const fourDigitYearRangePattern = [inputStart, repeated(4, arabicNumeralPattern), anyOf('-', '–'), repeated(4, arabicNumeralPattern), inputEnd]
|
|
273
|
+
const fourDigitYearRangePatternRegExp = buildRegExp(fourDigitYearRangePattern)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Item, LanguageCode, StartStreamTranscriptionCommandInput } from '@aws-sdk/client-transcribe-streaming'
|
|
2
|
-
import {
|
|
2
|
+
import { wordCharacterRegExp } from '../nlp/Segmentation.js'
|
|
3
3
|
import * as FFMpegTranscoder from '../codecs/FFMpegTranscoder.js'
|
|
4
4
|
import { Logger } from '../utilities/Logger.js'
|
|
5
5
|
import { Timeline } from '../utilities/Timeline.js'
|
|
@@ -102,7 +102,7 @@ export async function recgonize(rawAudio: RawAudio, languageCode: string, region
|
|
|
102
102
|
for (const event of events) {
|
|
103
103
|
const text = event.Content!
|
|
104
104
|
|
|
105
|
-
if (!
|
|
105
|
+
if (!wordCharacterRegExp.test(text)) {
|
|
106
106
|
continue
|
|
107
107
|
}
|
|
108
108
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { indexOfMax } from '../math/VectorMath.js'
|
|
2
|
-
import {
|
|
2
|
+
import { wordCharacterRegExp } from '../nlp/Segmentation.js'
|
|
3
3
|
import { Logger } from '../utilities/Logger.js'
|
|
4
4
|
import { logToStderr } from '../utilities/Utilities.js'
|
|
5
5
|
import { Timeline } from '../utilities/Timeline.js'
|
|
@@ -182,7 +182,7 @@ export class SileroSTT {
|
|
|
182
182
|
for (let i = 0; i < words.length; i++) {
|
|
183
183
|
const text = words[i]
|
|
184
184
|
|
|
185
|
-
if (!
|
|
185
|
+
if (!wordCharacterRegExp.test(text)) {
|
|
186
186
|
continue
|
|
187
187
|
}
|
|
188
188
|
|
|
@@ -18,7 +18,7 @@ import chalk from 'chalk'
|
|
|
18
18
|
import { XorShift32PRNG } from '../utilities/RandomGenerator.js'
|
|
19
19
|
import { detectSpeechLanguageByParts } from '../api/SpeechLanguageDetection.js'
|
|
20
20
|
import { type Tiktoken } from 'tiktoken/lite'
|
|
21
|
-
import {
|
|
21
|
+
import { includesPunctuation, isWhitespace, splitToWords } from '../nlp/Segmentation.js'
|
|
22
22
|
import { medianOf5Filter } from '../math/MedianFilter.js'
|
|
23
23
|
import { getDeflateCompressionMetricsForString } from '../utilities/Compression.js'
|
|
24
24
|
import { dmlProviderAvailable, getOnnxSessionOptions, makeOnnxLikeFloat32Tensor, OnnxExecutionProvider, OnnxLikeFloat32Tensor } from '../utilities/OnnxUtilities.js'
|
|
@@ -528,25 +528,11 @@ export class Whisper {
|
|
|
528
528
|
|
|
529
529
|
const targetLanguage = task === 'transcribe' ? sourceLanguage : 'en'
|
|
530
530
|
|
|
531
|
-
const shouldSplitToSentences = false
|
|
532
531
|
|
|
533
532
|
let simplifiedTranscript = ''
|
|
534
533
|
|
|
535
|
-
|
|
536
|
-
const
|
|
537
|
-
|
|
538
|
-
for (const sentence of sentences) {
|
|
539
|
-
let sentenceWords = await splitToWords(sentence, targetLanguage)
|
|
540
|
-
sentenceWords = sentenceWords.filter(word => isWord(word))
|
|
541
|
-
|
|
542
|
-
simplifiedTranscript += sentenceWords.join(' ')
|
|
543
|
-
simplifiedTranscript += ' '
|
|
544
|
-
}
|
|
545
|
-
} else {
|
|
546
|
-
let words = await splitToWords(transcript, targetLanguage)
|
|
547
|
-
|
|
548
|
-
words = words.map(word => word.trim())
|
|
549
|
-
words = words.filter(word => isWord(word))
|
|
534
|
+
{
|
|
535
|
+
const words = (await splitToWords(transcript, targetLanguage)).nonPunctuationWords
|
|
550
536
|
|
|
551
537
|
simplifiedTranscript = words.join(' ')
|
|
552
538
|
}
|
|
@@ -1253,7 +1239,7 @@ export class Whisper {
|
|
|
1253
1239
|
return false
|
|
1254
1240
|
}
|
|
1255
1241
|
|
|
1256
|
-
return isWhitespace(char) ||
|
|
1242
|
+
return isWhitespace(char) || includesPunctuation(char)
|
|
1257
1243
|
}
|
|
1258
1244
|
|
|
1259
1245
|
function startsWithSeparatorCharacter(text: string) {
|