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
|
@@ -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
|
|
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 =
|
|
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
|
|
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
|
|
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 {
|
|
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
|
|
82
|
+
const wordSequence = await splitToWords(referenceText, language)
|
|
83
83
|
|
|
84
84
|
timeline = []
|
|
85
85
|
|
|
86
|
-
|
|
86
|
+
for (const wordEntry of wordSequence.entries) {
|
|
87
|
+
const wordText = wordEntry.text
|
|
87
88
|
|
|
88
|
-
|
|
89
|
-
const
|
|
90
|
-
|
|
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:
|
|
95
|
-
startTime
|
|
96
|
-
endTime
|
|
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,
|
|
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
|
-
!
|
|
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
|
|
519
|
+
const wasmHeap = wrapEmscriptenModuleHeap(module)
|
|
519
520
|
|
|
520
|
-
const resultRef =
|
|
521
|
-
const result = resultRef.
|
|
521
|
+
const resultRef = wasmHeap.wrapNullTerminatedUtf8String(textPtr.ptr)
|
|
522
|
+
const result = resultRef.value
|
|
522
523
|
|
|
523
|
-
|
|
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
|
|
136
|
-
const
|
|
132
|
+
for (let clauseIndex = 0; clauseIndex < referenceTimeline.length; clauseIndex++) {
|
|
133
|
+
const clauseEntry = referenceTimeline[clauseIndex]
|
|
137
134
|
|
|
138
|
-
|
|
139
|
-
|
|
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
|
|
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' &&
|
|
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 <
|
|
211
|
+
if (wordIndex < wordTimelineForClause.length - 1) {
|
|
200
212
|
allTokenIds.push(wordBreakTokenId)
|
|
201
213
|
} else {
|
|
202
|
-
if (
|
|
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 =
|
|
224
|
+
const maxPartLength = 500
|
|
213
225
|
|
|
214
226
|
const parts: number[][] = []
|
|
215
227
|
|
|
@@ -2,11 +2,12 @@ import { SynthesisVoice } from '../api/API.js'
|
|
|
2
2
|
import { decodeToChannels, SampleFormat } from '../audio/AudioBufferConversion.js'
|
|
3
3
|
import { bandwidthToQFactor } from '../dsp/BiquadFilter.js'
|
|
4
4
|
import { Logger } from '../utilities/Logger.js'
|
|
5
|
-
import { WasmMemoryManager } from '../utilities/WasmMemoryManager.js'
|
|
6
5
|
import { RawAudio } from '../audio/AudioUtilities.js'
|
|
7
6
|
import { readFileAsBinary } from '../utilities/FileSystem.js'
|
|
8
7
|
import { concatUint8Arrays } from '../utilities/Utilities.js'
|
|
9
8
|
|
|
9
|
+
import { wrapEmscriptenModuleHeap } from 'wasm-heap-manager'
|
|
10
|
+
|
|
10
11
|
let svoxPicoInstance: any
|
|
11
12
|
|
|
12
13
|
export async function synthesize(text: string, textAnalysisFilePath: string, signalGenerationFilePath: string, postprocessOutput = true) {
|
|
@@ -17,7 +18,7 @@ export async function synthesize(text: string, textAnalysisFilePath: string, sig
|
|
|
17
18
|
|
|
18
19
|
logger.start('Initialize pico engine')
|
|
19
20
|
|
|
20
|
-
const
|
|
21
|
+
const wasmHeap = wrapEmscriptenModuleHeap(m)
|
|
21
22
|
|
|
22
23
|
const pico_initialize = m._pico_initialize
|
|
23
24
|
const picoext_setTraceLevel = m._picoext_setTraceLevel
|
|
@@ -37,11 +38,11 @@ export async function synthesize(text: string, textAnalysisFilePath: string, sig
|
|
|
37
38
|
const pico_unloadResource = m._pico_unloadResource
|
|
38
39
|
|
|
39
40
|
const picoMemSize = 2500000
|
|
40
|
-
const picoMemAreaRef =
|
|
41
|
+
const picoMemAreaRef = wasmHeap.allocUint8Array(picoMemSize)
|
|
41
42
|
|
|
42
|
-
const systemPtrRef =
|
|
43
|
+
const systemPtrRef = wasmHeap.allocPointer32()
|
|
43
44
|
|
|
44
|
-
let resultCode = pico_initialize(picoMemAreaRef.address, picoMemAreaRef.
|
|
45
|
+
let resultCode = pico_initialize(picoMemAreaRef.address, picoMemAreaRef.allocatedByteCount, systemPtrRef.address)
|
|
45
46
|
const systemPtr = systemPtrRef.value
|
|
46
47
|
|
|
47
48
|
throwErrorIfFailed(resultCode, 'Failed Pico initialization.')
|
|
@@ -54,8 +55,8 @@ export async function synthesize(text: string, textAnalysisFilePath: string, sig
|
|
|
54
55
|
const fileData = await readFileAsBinary(localFilePath)
|
|
55
56
|
m.FS.writeFile(virtualFilePath, fileData)
|
|
56
57
|
|
|
57
|
-
const virtualFilePathRef =
|
|
58
|
-
const resourcePtrRef =
|
|
58
|
+
const virtualFilePathRef = wasmHeap.allocNullTerminatedUtf8String(virtualFilePath)
|
|
59
|
+
const resourcePtrRef = wasmHeap.allocPointer32()
|
|
59
60
|
|
|
60
61
|
resultCode = pico_loadResource(systemPtr, virtualFilePathRef.address, resourcePtrRef.address)
|
|
61
62
|
const resourcePtr = resourcePtrRef.value
|
|
@@ -69,12 +70,12 @@ export async function synthesize(text: string, textAnalysisFilePath: string, sig
|
|
|
69
70
|
const { resourcePtr: signalGenerationResourcePtr, resourcePtrRef: signalGenerationResourcePtrRef } = await loadResource(signalGenerationFilePath)
|
|
70
71
|
|
|
71
72
|
function getResourceName(resourcePtr: number) {
|
|
72
|
-
const resourceNameRef =
|
|
73
|
+
const resourceNameRef = wasmHeap.allocNullTerminatedUtf8String(32)
|
|
73
74
|
resultCode = pico_getResourceName(systemPtr, resourcePtr, resourceNameRef.address)
|
|
74
75
|
|
|
75
76
|
throwErrorIfFailed(resultCode, `Failed getting Pico resource name.`)
|
|
76
77
|
|
|
77
|
-
const resourceName = resourceNameRef.
|
|
78
|
+
const resourceName = resourceNameRef.value
|
|
78
79
|
|
|
79
80
|
return { resourceName, resourceNameRef }
|
|
80
81
|
}
|
|
@@ -82,7 +83,7 @@ export async function synthesize(text: string, textAnalysisFilePath: string, sig
|
|
|
82
83
|
const { resourceName: textAnalysisResourceName, resourceNameRef: textAnalysisResourceNameRef } = getResourceName(textAnalysisResourcePtr)
|
|
83
84
|
const { resourceName: signalGenerationResourceName, resourceNameRef: signalGenerationResourceNameRef } = getResourceName(signalGenerationResourcePtr)
|
|
84
85
|
|
|
85
|
-
const voiceNameRef =
|
|
86
|
+
const voiceNameRef = wasmHeap.allocNullTerminatedUtf8String('PicoVoice')
|
|
86
87
|
|
|
87
88
|
resultCode = pico_createVoiceDefinition(systemPtr, voiceNameRef.address)
|
|
88
89
|
|
|
@@ -95,7 +96,7 @@ export async function synthesize(text: string, textAnalysisFilePath: string, sig
|
|
|
95
96
|
addResourceToVoiceDefinition(textAnalysisResourceNameRef.address)
|
|
96
97
|
addResourceToVoiceDefinition(signalGenerationResourceNameRef.address)
|
|
97
98
|
|
|
98
|
-
const enginePtrRef =
|
|
99
|
+
const enginePtrRef = wasmHeap.allocPointer32()
|
|
99
100
|
resultCode = pico_newEngine(systemPtr, voiceNameRef.address, enginePtrRef.address)
|
|
100
101
|
|
|
101
102
|
throwErrorIfFailed(resultCode, `Failed creating new engine.`)
|
|
@@ -104,15 +105,17 @@ export async function synthesize(text: string, textAnalysisFilePath: string, sig
|
|
|
104
105
|
|
|
105
106
|
logger.start('Synthesize with pico')
|
|
106
107
|
|
|
107
|
-
const textRef =
|
|
108
|
+
const textRef = wasmHeap.allocNullTerminatedUtf8String(text)
|
|
108
109
|
|
|
109
|
-
const bytesWrittenRef =
|
|
110
|
+
const bytesWrittenRef = wasmHeap.allocInt32()
|
|
110
111
|
|
|
111
112
|
const audioParts: Uint8Array[] = []
|
|
112
113
|
|
|
113
|
-
|
|
114
|
+
const textRefEncodedByteCountIncludingTerminator = textRef.encodedByteCount + 1
|
|
115
|
+
|
|
116
|
+
for (let textByteOffset = 0; textByteOffset < textRefEncodedByteCountIncludingTerminator;) {
|
|
114
117
|
bytesWrittenRef.value = 0
|
|
115
|
-
resultCode = pico_putTextUtf8(enginePtr, textRef.address + textByteOffset,
|
|
118
|
+
resultCode = pico_putTextUtf8(enginePtr, textRef.address + textByteOffset, textRefEncodedByteCountIncludingTerminator - textByteOffset, bytesWrittenRef.address)
|
|
116
119
|
const bytesWritten = bytesWrittenRef.value
|
|
117
120
|
|
|
118
121
|
throwErrorIfFailed(resultCode, `Failed writing text to engine.`)
|
|
@@ -129,13 +132,13 @@ export async function synthesize(text: string, textAnalysisFilePath: string, sig
|
|
|
129
132
|
const outBuffers: Uint8Array[] = []
|
|
130
133
|
|
|
131
134
|
const outBufferLength = 16384
|
|
132
|
-
const outBufferRef =
|
|
135
|
+
const outBufferRef = wasmHeap.allocUint8Array(outBufferLength)
|
|
133
136
|
|
|
134
|
-
const outByteCountRef =
|
|
135
|
-
const outDataTypeRef =
|
|
137
|
+
const outByteCountRef = wasmHeap.allocInt16()
|
|
138
|
+
const outDataTypeRef = wasmHeap.allocInt16()
|
|
136
139
|
|
|
137
140
|
while (true) {
|
|
138
|
-
resultCode = pico_getData(enginePtr, outBufferRef.address, outBufferRef.
|
|
141
|
+
resultCode = pico_getData(enginePtr, outBufferRef.address, outBufferRef.allocatedByteCount, outByteCountRef.address, outDataTypeRef.address)
|
|
139
142
|
|
|
140
143
|
throwErrorIfFailed(resultCode, `Failed getting audio data from engine.`, [200, 201])
|
|
141
144
|
|
|
@@ -147,7 +150,7 @@ export async function synthesize(text: string, textAnalysisFilePath: string, sig
|
|
|
147
150
|
}
|
|
148
151
|
|
|
149
152
|
if (outByteCount > 0) {
|
|
150
|
-
outBuffers.push(outBufferRef.slice(0, outByteCount)
|
|
153
|
+
outBuffers.push(outBufferRef.view.slice(0, outByteCount))
|
|
151
154
|
}
|
|
152
155
|
}
|
|
153
156
|
|
|
@@ -179,7 +182,7 @@ export async function synthesize(text: string, textAnalysisFilePath: string, sig
|
|
|
179
182
|
|
|
180
183
|
pico_terminate(systemPtrRef.address)
|
|
181
184
|
|
|
182
|
-
|
|
185
|
+
wasmHeap.freeAll()
|
|
183
186
|
}
|
|
184
187
|
|
|
185
188
|
function throwErrorIfFailed(resultCode: number, title: string, successCodes = [0]) {
|
|
@@ -187,9 +190,9 @@ export async function synthesize(text: string, textAnalysisFilePath: string, sig
|
|
|
187
190
|
return
|
|
188
191
|
}
|
|
189
192
|
|
|
190
|
-
const picoErrorMessageRef =
|
|
193
|
+
const picoErrorMessageRef = wasmHeap.allocNullTerminatedUtf8String(200)
|
|
191
194
|
pico_getSystemStatusMessage(systemPtr, resultCode, picoErrorMessageRef)
|
|
192
|
-
const picoErrorMessage = picoErrorMessageRef.
|
|
195
|
+
const picoErrorMessage = picoErrorMessageRef.value
|
|
193
196
|
|
|
194
197
|
dispose()
|
|
195
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
|