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.
Files changed (153) hide show
  1. package/data/lexicons/heteronyms.en.json +6 -2
  2. package/data/lexicons/words.en.json +379 -19
  3. package/dist/alignment/SemanticTextAlignment.js +6 -7
  4. package/dist/alignment/SemanticTextAlignment.js.map +1 -1
  5. package/dist/api/Alignment.js +2 -2
  6. package/dist/api/Alignment.js.map +1 -1
  7. package/dist/api/Recognition.js +2 -2
  8. package/dist/api/Recognition.js.map +1 -1
  9. package/dist/api/SourceSeparation.js +1 -1
  10. package/dist/api/SpeechTranslation.js +2 -2
  11. package/dist/api/SpeechTranslation.js.map +1 -1
  12. package/dist/api/Synthesis.js +7 -7
  13. package/dist/api/Synthesis.js.map +1 -1
  14. package/dist/api/TimelineTranslationAlignment.js +2 -2
  15. package/dist/api/TimelineTranslationAlignment.js.map +1 -1
  16. package/dist/api/TranslationAlignment.js +2 -2
  17. package/dist/api/TranslationAlignment.js.map +1 -1
  18. package/dist/audio/AudioBufferConversion.d.ts +5 -6
  19. package/dist/audio/AudioBufferConversion.js +16 -248
  20. package/dist/audio/AudioBufferConversion.js.map +1 -1
  21. package/dist/audio/AudioPlayer.js +2 -2
  22. package/dist/audio/AudioPlayer.js.map +1 -1
  23. package/dist/audio/AudioRecorder.js +1 -1
  24. package/dist/audio/AudioRecorder.js.map +1 -1
  25. package/dist/audio/AudioUtilities.d.ts +2 -8
  26. package/dist/audio/AudioUtilities.js +4 -3
  27. package/dist/audio/AudioUtilities.js.map +1 -1
  28. package/dist/cli/CLI.js +4 -4
  29. package/dist/cli/CLI.js.map +1 -1
  30. package/dist/codecs/FFMpegTranscoder.d.ts +1 -4
  31. package/dist/codecs/TIMITCodec.js +1 -2
  32. package/dist/codecs/TIMITCodec.js.map +1 -1
  33. package/dist/denoising/RNNoise.js +5 -5
  34. package/dist/denoising/RNNoise.js.map +1 -1
  35. package/dist/dsp/FFT.js +13 -19
  36. package/dist/dsp/FFT.js.map +1 -1
  37. package/dist/dsp/Rubberband.js +5 -5
  38. package/dist/dsp/Rubberband.js.map +1 -1
  39. package/dist/dsp/Sonic.js +6 -6
  40. package/dist/dsp/Sonic.js.map +1 -1
  41. package/dist/dsp/SpeexResampler.js +11 -11
  42. package/dist/dsp/SpeexResampler.js.map +1 -1
  43. package/dist/encodings/Ascii.js +1 -1
  44. package/dist/encodings/Ascii.js.map +1 -1
  45. package/dist/encodings/Utf16.js +1 -1
  46. package/dist/encodings/Utf16.js.map +1 -1
  47. package/dist/encodings/Utf8.js +1 -1
  48. package/dist/encodings/Utf8.js.map +1 -1
  49. package/dist/nlp/EspeakPhonemizer.js +1 -1
  50. package/dist/nlp/EspeakPhonemizer.js.map +1 -1
  51. package/dist/nlp/Segmentation.d.ts +13 -10
  52. package/dist/nlp/Segmentation.js +129 -106
  53. package/dist/nlp/Segmentation.js.map +1 -1
  54. package/dist/nlp/TextNormalizer.js +74 -42
  55. package/dist/nlp/TextNormalizer.js.map +1 -1
  56. package/dist/recognition/AmazonTranscribeSTT.js +2 -2
  57. package/dist/recognition/AmazonTranscribeSTT.js.map +1 -1
  58. package/dist/recognition/SileroSTT.js +2 -2
  59. package/dist/recognition/SileroSTT.js.map +1 -1
  60. package/dist/recognition/WhisperSTT.js +4 -16
  61. package/dist/recognition/WhisperSTT.js.map +1 -1
  62. package/dist/subtitles/Subtitles.js +52 -7
  63. package/dist/subtitles/Subtitles.js.map +1 -1
  64. package/dist/synthesis/AwsPollyTTS.d.ts +1 -4
  65. package/dist/synthesis/CoquiServerTTS.d.ts +1 -4
  66. package/dist/synthesis/DeepgramTTS.d.ts +1 -4
  67. package/dist/synthesis/ElevenLabsTTS.d.ts +1 -4
  68. package/dist/synthesis/ElevenLabsTTS.js +11 -10
  69. package/dist/synthesis/ElevenLabsTTS.js.map +1 -1
  70. package/dist/synthesis/EspeakTTS.js +8 -8
  71. package/dist/synthesis/EspeakTTS.js.map +1 -1
  72. package/dist/synthesis/FliteTTS.d.ts +1 -4
  73. package/dist/synthesis/GnuSpeechTTS.d.ts +1 -7
  74. package/dist/synthesis/GoogleCloudTTS.d.ts +1 -4
  75. package/dist/synthesis/KokoroTTS.js +21 -10
  76. package/dist/synthesis/KokoroTTS.js.map +1 -1
  77. package/dist/synthesis/SapiTTS.js +1 -2
  78. package/dist/synthesis/SapiTTS.js.map +1 -1
  79. package/dist/synthesis/SvoxPicoTTS.js +25 -25
  80. package/dist/synthesis/SvoxPicoTTS.js.map +1 -1
  81. package/dist/text-translation/DeepLTextTranslation.js +4 -3
  82. package/dist/text-translation/DeepLTextTranslation.js.map +1 -1
  83. package/dist/text-translation/NLLBTextTranslation.js +7 -6
  84. package/dist/text-translation/NLLBTextTranslation.js.map +1 -1
  85. package/dist/utilities/StringUtilities.d.ts +1 -1
  86. package/dist/utilities/StringUtilities.js +5 -5
  87. package/dist/utilities/StringUtilities.js.map +1 -1
  88. package/dist/utilities/Timeline.d.ts +2 -3
  89. package/dist/utilities/Timeline.js +96 -147
  90. package/dist/utilities/Timeline.js.map +1 -1
  91. package/dist/utilities/WikipediaReader.js +3 -3
  92. package/dist/utilities/WikipediaReader.js.map +1 -1
  93. package/dist/voice-activity-detection/WebRtcVAD.js +4 -4
  94. package/dist/voice-activity-detection/WebRtcVAD.js.map +1 -1
  95. package/docs/Tasklist.md +1 -6
  96. package/package.json +13 -11
  97. package/src/alignment/SemanticTextAlignment.ts +6 -7
  98. package/src/api/Alignment.ts +2 -2
  99. package/src/api/Recognition.ts +2 -2
  100. package/src/api/SourceSeparation.ts +1 -1
  101. package/src/api/SpeechTranslation.ts +2 -2
  102. package/src/api/Synthesis.ts +7 -7
  103. package/src/api/TimelineTranslationAlignment.ts +2 -2
  104. package/src/api/TranslationAlignment.ts +2 -2
  105. package/src/audio/AudioBufferConversion.ts +17 -263
  106. package/src/audio/AudioPlayer.ts +3 -3
  107. package/src/audio/AudioRecorder.ts +1 -1
  108. package/src/audio/AudioUtilities.ts +5 -3
  109. package/src/cli/CLI.ts +4 -4
  110. package/src/codecs/TIMITCodec.ts +1 -2
  111. package/src/denoising/RNNoise.ts +5 -5
  112. package/src/dsp/FFT.ts +22 -19
  113. package/src/dsp/Rubberband.ts +5 -5
  114. package/src/dsp/Sonic.ts +6 -6
  115. package/src/dsp/SpeexResampler.ts +11 -11
  116. package/src/encodings/Ascii.ts +1 -1
  117. package/src/encodings/Utf16.ts +1 -1
  118. package/src/encodings/Utf8.ts +1 -1
  119. package/src/nlp/EspeakPhonemizer.ts +1 -1
  120. package/src/nlp/Segmentation.ts +146 -111
  121. package/src/nlp/TextNormalizer.ts +94 -52
  122. package/src/recognition/AmazonTranscribeSTT.ts +2 -2
  123. package/src/recognition/SileroSTT.ts +2 -2
  124. package/src/recognition/WhisperSTT.ts +4 -18
  125. package/src/subtitles/Subtitles.ts +69 -8
  126. package/src/synthesis/ElevenLabsTTS.ts +12 -11
  127. package/src/synthesis/EspeakTTS.ts +9 -8
  128. package/src/synthesis/KokoroTTS.ts +24 -12
  129. package/src/synthesis/SapiTTS.ts +1 -2
  130. package/src/synthesis/SvoxPicoTTS.ts +27 -25
  131. package/src/text-translation/DeepLTextTranslation.ts +4 -3
  132. package/src/text-translation/NLLBTextTranslation.ts +8 -6
  133. package/src/typings/Fillers.d.ts +0 -16
  134. package/src/utilities/StringUtilities.ts +5 -6
  135. package/src/utilities/Timeline.ts +100 -170
  136. package/src/utilities/WikipediaReader.ts +3 -3
  137. package/src/voice-activity-detection/WebRtcVAD.ts +5 -4
  138. package/dist/codecs/WaveCodec.d.ts +0 -19
  139. package/dist/codecs/WaveCodec.js +0 -224
  140. package/dist/codecs/WaveCodec.js.map +0 -1
  141. package/dist/nlp/CompromiseNLP.d.ts +0 -15
  142. package/dist/nlp/CompromiseNLP.js +0 -79
  143. package/dist/nlp/CompromiseNLP.js.map +0 -1
  144. package/dist/utilities/BinaryArrayConversion.d.ts +0 -14
  145. package/dist/utilities/BinaryArrayConversion.js +0 -92
  146. package/dist/utilities/BinaryArrayConversion.js.map +0 -1
  147. package/dist/utilities/WasmMemoryManager.d.ts +0 -153
  148. package/dist/utilities/WasmMemoryManager.js +0 -440
  149. package/dist/utilities/WasmMemoryManager.js.map +0 -1
  150. package/src/codecs/WaveCodec.ts +0 -281
  151. package/src/nlp/CompromiseNLP.ts +0 -119
  152. package/src/utilities/BinaryArrayConversion.ts +0 -117
  153. package/src/utilities/WasmMemoryManager.ts +0 -566
@@ -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 ( // Normalize a four digit year pattern, e.g. 'in 1995'.
60
- wordsPrecedingAYear.includes(lowerCaseWord) &&
30
+ if (wordsPrecedingAYear.includes(lowerCaseWord) &&
61
31
  isFollowedByWhitespace &&
62
- fourDigitYearPattern.test(nextNonWhitespaceWord)) {
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 ( // Normalize a four digit decade pattern, e.g. 'the 1980s'.
45
+ } else if (
74
46
  wordsPrecedingADecade.includes(lowerCaseWord) &&
75
47
  isFollowedByWhitespace &&
76
- fourDigitDecadePattern.test(nextNonWhitespaceWord)) {
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 ( // Normalize a year range pattern, e.g. '1835-1896', ensure there are no spaces between words
88
- fourDigitYearRangePattern.test(words.slice(originalWordIndex, originalWordIndex + 3).join(''))) {
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 ( // Normalize a currency pattern, e.g. '$53.1 million', '€3.53'
101
- symbolsPrecedingACurrency.includes(lowerCaseWord) &&
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
- let currencyWord = symbolsPrecedingACurrencyAsWords[symbolsPrecedingACurrency.indexOf(lowerCaseWord)]
76
+ const currencyWord = currencySymbolsAsWords[currencySymbols.indexOf(lowerCaseWord[0])]
106
77
 
107
- if (wordsSucceedingACurrency.includes(nextNonWhitespaceWords[1]?.toLowerCase())) {
108
- const normalizedString = `${nextNonWhitespaceWord} ${nextNonWhitespaceWords[1]} ${currencyWord}`
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} ${nextNonWhitespaceWords[1]}`
83
+ const referenceString = `${word} ${nextNonWhitespaceWord}`
113
84
  referenceFragments.push(referenceString)
114
85
 
115
- wordIndex += 2
86
+ wordIndex += 1
116
87
  } else {
117
- const normalizedString = `${nextNonWhitespaceWord} ${currencyWord}`
88
+ const normalizedString = `${word.substring(1)} ${currencyWord}`
118
89
 
119
90
  normalizedFragments.push(normalizedString)
120
91
 
121
- const referenceString = `${word}${nextNonWhitespaceWord}`
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 = firstTwoDigitsValue % 10 == 0 && secondTwoDigitsValue == 0
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 { wordCharacterPattern } from '../nlp/Segmentation.js'
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 (!wordCharacterPattern.test(text)) {
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 { wordCharacterPattern } from '../nlp/Segmentation.js'
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 (!wordCharacterPattern.test(text)) {
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 { isPunctuation, isWhitespace, isWord, splitToSentences, splitToWords } from '../nlp/Segmentation.js'
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
- if (shouldSplitToSentences) {
536
- const sentences = splitToSentences(transcript, targetLanguage)
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) || isPunctuation(char)
1242
+ return isWhitespace(char) || includesPunctuation(char)
1257
1243
  }
1258
1244
 
1259
1245
  function startsWithSeparatorCharacter(text: string) {
@@ -6,7 +6,8 @@ import { charactersToWriteAhead } from '../audio/AudioPlayer.js'
6
6
  import { Timeline, TimelineEntry } from '../utilities/Timeline.js'
7
7
  import { readFileAsUtf8 } from '../utilities/FileSystem.js'
8
8
  import { deepClone } from '../utilities/ObjectUtilities.js'
9
- import { formatHMS, formatMS, startsWithAnyOf } from '../utilities/StringUtilities.js'
9
+ import { formatHMS, formatMS } from '../utilities/StringUtilities.js'
10
+ import { anyOf, buildRegExp, capture, digit, inputStart, matches, notUnicodeProperty, oneOrMore, zeroOrMore } from 'regexp-composer'
10
11
 
11
12
  export async function subtitlesFileToText(filename: string) {
12
13
  return subtitlesToText(await readFileAsUtf8(filename))
@@ -257,14 +258,12 @@ function getCuesFromTimeline_IsolateSegmentSentence(timeline: Timeline, config:
257
258
  const lineLengthWithNextWord = nextWordExtendedEndOffset - lineStartOffset
258
259
  const wordsRemaining = wordTimeline.length - wordIndex - 1
259
260
 
260
- const phraseSeparators = [',', ',', '、', ';', ':', '),', '",', '”,', '.', '".', '”.', '."', '.”', '。']
261
-
262
261
  const lineLengthWithNextWordExceedsMaxLineWidth = lineLengthWithNextWord >= maxLineWidth
263
262
  const lineLengthExceedsHalfMaxLineWidth = lineLength >= maxLineWidth / 2
264
263
 
265
264
  const wordsRemainingAreEqualOrLessToMinimumWordsInLine = wordsRemaining <= config.minWordsInLine!
266
265
  const remainingTextExceedsMaxLineWidth = entryText.length - lineStartOffset > maxLineWidth
267
- const followingSubstringIsPhraseSeparator = startsWithAnyOf(entryText.substring(wordEndOffset), phraseSeparators)
266
+ const followingSubstringIsPhraseSeparator = phraseSeparatorRegExp.test(entryText.substring(wordEndOffset))
268
267
 
269
268
  const shouldAddNewLine =
270
269
  isLastWord ||
@@ -434,8 +433,7 @@ function getCuesFromTimeline_IsolateLines(timeline: Timeline, config: SubtitlesC
434
433
  }
435
434
 
436
435
  export function tryParseTimeRangePatternWithHours(line: string) {
437
- const timeRangePatternWithHours = /^(\d+)\:(\d+)\:(\d+)[\.,](\d+)[ ]*-->[ ]*(\d+)\:(\d+)\:(\d+)[\.,](\d+)/
438
- const match = timeRangePatternWithHours.exec(line)
436
+ const match = timeRangeWithHoursRegExp.exec(line)
439
437
 
440
438
  if (!match) {
441
439
  return { startTime: -1, endTime: -1, succeeded: false }
@@ -458,8 +456,7 @@ export function tryParseTimeRangePatternWithHours(line: string) {
458
456
  }
459
457
 
460
458
  export function tryParseTimeRangePatternWithoutHours(line: string) {
461
- const timeRangePatternWithHours = /^(\d+)\:(\d+)[\.,](\d+)[ ]*-->[ ]*(\d+)\:(\d+)[\.,](\d+)/
462
- const match = timeRangePatternWithHours.exec(line)
459
+ const match = timeRangeWithoutHoursRegExp.exec(line)
463
460
 
464
461
  if (!match) {
465
462
  return { startTime: -1, endTime: -1, succeeded: false }
@@ -514,6 +511,70 @@ function cueObjectToText(cue: Cue, cueIndex: number, config: SubtitlesConfig) {
514
511
  return outText
515
512
  }
516
513
 
514
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////
515
+ // Patterns
516
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////
517
+ const timeRangeWithHoursRegExp = buildRegExp([
518
+ inputStart,
519
+ capture(oneOrMore(digit)),
520
+ ':',
521
+ capture(oneOrMore(digit)),
522
+ ':',
523
+ capture(oneOrMore(digit)),
524
+
525
+ anyOf('.', ','),
526
+ capture(oneOrMore(digit)),
527
+
528
+ zeroOrMore(' '),
529
+ '-->',
530
+ zeroOrMore(' '),
531
+
532
+ capture(oneOrMore(digit)),
533
+ ':',
534
+ capture(oneOrMore(digit)),
535
+ ':',
536
+ capture(oneOrMore(digit)),
537
+
538
+ anyOf('.', ','),
539
+ capture(oneOrMore(digit)),
540
+ ])
541
+
542
+ const timeRangeWithoutHoursRegExp = buildRegExp([
543
+ inputStart,
544
+ capture(oneOrMore(digit)),
545
+ ':',
546
+ capture(oneOrMore(digit)),
547
+
548
+ anyOf('.', ','),
549
+ capture(oneOrMore(digit)),
550
+
551
+ zeroOrMore(' '),
552
+ '-->',
553
+ zeroOrMore(' '),
554
+
555
+ capture(oneOrMore(digit)),
556
+ ':',
557
+ capture(oneOrMore(digit)),
558
+
559
+ anyOf('.', ','),
560
+ capture(oneOrMore(digit)),
561
+ ])
562
+
563
+ const phraseSeparatorCharacters = [',', ',', '、', ';', ':', '),', '",', '”,']
564
+
565
+ const phraseSeparatorRegExp = buildRegExp(
566
+ matches([
567
+ inputStart,
568
+ anyOf(...phraseSeparatorCharacters)
569
+ ], {
570
+ ifPrecededBy: notUnicodeProperty('Decimal_Number'),
571
+ ifFollowedBy: notUnicodeProperty('Decimal_Number'),
572
+ })
573
+ )
574
+
575
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////
576
+ // Types
577
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////
517
578
  export type Cue = {
518
579
  lines: string[]
519
580
  startTime: number
@@ -5,7 +5,7 @@ import { Logger } from '../utilities/Logger.js'
5
5
  import { logToStderr } from '../utilities/Utilities.js'
6
6
  import { extendDeep } from '../utilities/ObjectUtilities.js'
7
7
  import { decodeBase64 } from '../encodings/Base64.js'
8
- import { isWordOrSymbolWord, splitToWords } from '../nlp/Segmentation.js'
8
+ import { splitToWords } from '../nlp/Segmentation.js'
9
9
  import { Timeline } from '../utilities/Timeline.js'
10
10
 
11
11
  const log = logToStderr
@@ -79,24 +79,25 @@ export async function synthesize(text: string, voiceId: string, language: string
79
79
  logger.start('Create timeline from returned character timings')
80
80
 
81
81
  const referenceText = characters.join('')
82
- const words = (await splitToWords(referenceText, language)).filter(w => isWordOrSymbolWord(w))
82
+ const wordSequence = await splitToWords(referenceText, language)
83
83
 
84
84
  timeline = []
85
85
 
86
- let offset = 0
86
+ for (const wordEntry of wordSequence.entries) {
87
+ const wordText = wordEntry.text
87
88
 
88
- for (const word of words) {
89
- const wordStartIndex = referenceText.indexOf(word, offset)
90
- const wordEndIndex = wordStartIndex + word.length
89
+ const wordStartOffset = wordEntry.startOffset
90
+ const wordEndOffset = wordEntry.endOffset
91
+
92
+ const startTime = characterStartTimes[wordStartOffset]
93
+ const endTime = characterEndTimes[wordEndOffset] ?? characterEndTimes[wordEndOffset - 1]
91
94
 
92
95
  timeline.push({
93
96
  type: 'word',
94
- text: word,
95
- startTime: characterStartTimes[wordStartIndex],
96
- endTime: characterEndTimes[wordEndIndex] ?? characterEndTimes[wordEndIndex - 1]
97
+ text: wordText,
98
+ startTime,
99
+ endTime,
97
100
  })
98
-
99
- offset = wordEndIndex
100
101
  }
101
102
  }
102
103
 
@@ -1,17 +1,18 @@
1
1
  import { concatFloat32Arrays, logToStderr, formatObjectToString } from '../utilities/Utilities.js'
2
2
  import { int16PcmToFloat32 } from '../audio/AudioBufferConversion.js'
3
3
  import { Logger } from '../utilities/Logger.js'
4
- import { WasmMemoryManager } from '../utilities/WasmMemoryManager.js'
5
4
  import { RawAudio, getEmptyRawAudio } from '../audio/AudioUtilities.js'
6
5
  import { getNormalizedFragmentsForSpeech, simplifyPunctuationCharacters } from '../nlp/TextNormalizer.js'
7
6
  import { ipaPhoneToKirshenbaum } from '../nlp/PhoneConversion.js'
8
- import { splitToWords, wordCharacterPattern } from '../nlp/Segmentation.js'
7
+ import { splitToWords, wordCharacterRegExp } from '../nlp/Segmentation.js'
9
8
  import { Lexicon, tryGetFirstLexiconSubstitution } from '../nlp/Lexicon.js'
10
9
  import { phonemizeSentence } from '../nlp/EspeakPhonemizer.js'
11
10
  import { Timeline, TimelineEntry } from '../utilities/Timeline.js'
12
11
  import { extendDeep } from '../utilities/ObjectUtilities.js'
13
12
  import { escapeHtml } from '../encodings/HtmlEscape.js'
14
13
 
14
+ import { wrapEmscriptenModuleHeap } from 'wasm-heap-manager'
15
+
15
16
  const log = logToStderr
16
17
 
17
18
  let espeakInstance: any
@@ -37,7 +38,7 @@ export async function preprocessAndSynthesize(text: string, language: string, es
37
38
  fragments = []
38
39
  preprocessedFragments = []
39
40
 
40
- let words = await splitToWords(text, language)
41
+ let words = (await splitToWords(text, language)).wordArray
41
42
 
42
43
  // Merge repeating non-words to a single word to work around eSpeak bug
43
44
  {
@@ -51,7 +52,7 @@ export async function preprocessAndSynthesize(text: string, language: string, es
51
52
  i > 0 &&
52
53
  currentWord === previousWord &&
53
54
  !['[', ']'].includes(currentWord) && // Work around eSpeak-NG marker bug with repeating squared brackets
54
- !wordCharacterPattern.test(currentWord)) {
55
+ !wordCharacterRegExp.test(currentWord)) {
55
56
 
56
57
  wordsWithMerges[wordsWithMerges.length - 1] += currentWord
57
58
  } else {
@@ -515,12 +516,12 @@ export async function textToPhonemes(text: string, voice: string, useIPA = true)
515
516
  const { instance, module } = await getEspeakInstance()
516
517
  const textPtr = instance.convert_to_phonemes(text, useIPA)
517
518
 
518
- const wasmMemory = new WasmMemoryManager(module)
519
+ const wasmHeap = wrapEmscriptenModuleHeap(module)
519
520
 
520
- const resultRef = wasmMemory.wrapNullTerminatedUtf8String(textPtr.ptr)
521
- const result = resultRef.getValue()
521
+ const resultRef = wasmHeap.wrapNullTerminatedUtf8String(textPtr.ptr)
522
+ const result = resultRef.value
522
523
 
523
- wasmMemory.freeAll()
524
+ resultRef.free()
524
525
 
525
526
  return result
526
527
  }
@@ -108,9 +108,6 @@ export class KokoroTTS {
108
108
  const {
109
109
  referenceSynthesizedAudio,
110
110
  referenceTimeline,
111
- fragments,
112
- phonemizedFragmentsSubstitutions,
113
- phonemizedSentence
114
111
  } = await Espeak.preprocessAndSynthesize(sentenceText, voiceLanguage, espeakOptions, lexicons)
115
112
 
116
113
  logger.end()
@@ -132,13 +129,28 @@ export class KokoroTTS {
132
129
 
133
130
  const allTokenIds: number[] = []
134
131
 
135
- for (let phraseIndex = 0; phraseIndex < phonemizedSentence.length; phraseIndex++) {
136
- const phrase = phonemizedSentence[phraseIndex]
132
+ for (let clauseIndex = 0; clauseIndex < referenceTimeline.length; clauseIndex++) {
133
+ const clauseEntry = referenceTimeline[clauseIndex]
137
134
 
138
- for (let wordIndex = 0; wordIndex < phrase.length; wordIndex++) {
139
- const word = phrase[wordIndex]
135
+ if (!clauseEntry.timeline) {
136
+ continue
137
+ }
138
+
139
+ const wordTimelineForClause = clauseEntry.timeline
140
+
141
+ for (let wordIndex = 0; wordIndex < wordTimelineForClause.length; wordIndex++) {
142
+ const wordEntry = wordTimelineForClause[wordIndex]
143
+ const wordText = wordEntry.text
144
+
145
+ if (!wordEntry.timeline) {
146
+ continue
147
+ }
148
+
149
+ const tokenTimelineForWord = wordEntry.timeline
150
+ const phonemeTimelineForWord = tokenTimelineForWord.flatMap(tokenEntry => tokenEntry.timeline ?? [])
140
151
 
141
- for (const phoneme of word) {
152
+ for (const phonemeEntry of phonemeTimelineForWord) {
153
+ const phoneme = phonemeEntry.text
142
154
  let processedPhoneme = phoneme
143
155
 
144
156
  if (voicePrimaryLanguageShort === 'en') {
@@ -179,7 +191,7 @@ export class KokoroTTS {
179
191
 
180
192
  if (false) {
181
193
  // Workaround a word having only 'I' not being pronounced at some cases
182
- if (processedPhoneme === 'I' && word.length === 1) {
194
+ if (processedPhoneme === 'I' && wordText.length === 1) {
183
195
  processedPhoneme = 'aɪ'
184
196
  //processedPhoneme = 'ˌI'
185
197
  }
@@ -196,10 +208,10 @@ export class KokoroTTS {
196
208
  }
197
209
  }
198
210
 
199
- if (wordIndex < phrase.length - 1) {
211
+ if (wordIndex < wordTimelineForClause.length - 1) {
200
212
  allTokenIds.push(wordBreakTokenId)
201
213
  } else {
202
- if (phraseIndex < phonemizedSentence.length - 1) {
214
+ if (clauseIndex < referenceTimeline.length - 1) {
203
215
  allTokenIds.push(phraseBreakTokenId)
204
216
  allTokenIds.push(wordBreakTokenId)
205
217
  }
@@ -209,7 +221,7 @@ export class KokoroTTS {
209
221
 
210
222
  allTokenIds.push(sentenceEndTokenId)
211
223
 
212
- const maxPartLength = 509
224
+ const maxPartLength = 500
213
225
 
214
226
  const parts: number[][] = []
215
227
 
@@ -1,7 +1,6 @@
1
1
  import { SynthesisVoice } from '../api/API.js'
2
- import { decodeToChannels } from '../audio/AudioBufferConversion.js'
2
+ import { decodeToChannels, SampleFormat } from '../audio/AudioBufferConversion.js'
3
3
  import { RawAudio } from '../audio/AudioUtilities.js'
4
- import { SampleFormat } from '../codecs/WaveCodec.js'
5
4
  import { getShortLanguageCode, lcidToIsoLanguageCode } from '../utilities/Locale.js'
6
5
  import { Logger } from '../utilities/Logger.js'
7
6
  import { Timeline, TimelineEntry } from '../utilities/Timeline.js'