echogarden 2.1.2 → 2.2.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 +30 -5
- package/data/lexicons/words.en.json +60 -0
- package/data/schemas/options.json +20 -4
- package/dist/api/Recognition.d.ts +3 -1
- package/dist/api/Recognition.js +17 -0
- package/dist/api/Recognition.js.map +1 -1
- package/dist/api/Synthesis.d.ts +2 -2
- package/dist/api/Synthesis.js +3 -3
- package/dist/api/Synthesis.js.map +1 -1
- package/dist/codecs/FFMpegTranscoder.js +2 -0
- package/dist/codecs/FFMpegTranscoder.js.map +1 -1
- package/dist/nlp/EspeakPhonemizer.d.ts +2 -2
- package/dist/nlp/EspeakPhonemizer.js +36 -36
- package/dist/nlp/PhoneConversion.d.ts +1 -0
- package/dist/nlp/PhoneConversion.js +151 -222
- package/dist/nlp/PhoneConversion.js.map +1 -1
- package/dist/recognition/DeepgramSTT.d.ts +11 -0
- package/dist/recognition/DeepgramSTT.js +64 -0
- package/dist/recognition/DeepgramSTT.js.map +1 -0
- package/dist/recognition/GoogleCloudSTT.js.map +1 -1
- package/dist/synthesis/{ElevenlabsTTS.d.ts → ElevenLabsTTS.d.ts} +3 -3
- package/dist/synthesis/{ElevenlabsTTS.js → ElevenLabsTTS.js} +3 -3
- package/dist/synthesis/{ElevenlabsTTS.js.map → ElevenLabsTTS.js.map} +1 -1
- package/dist/synthesis/EspeakTTS.d.ts +1 -1
- package/dist/synthesis/EspeakTTS.js +8 -7
- package/dist/synthesis/EspeakTTS.js.map +1 -1
- package/dist/synthesis/KokoroTTS.js +14 -14
- package/dist/synthesis/VitsTTS.js +9 -9
- package/dist/utilities/Utilities.d.ts +1 -0
- package/dist/utilities/Utilities.js +5 -0
- package/dist/utilities/Utilities.js.map +1 -1
- package/dist/utilities/WasmMemoryManager.d.ts +1 -1
- package/docs/CUDA.md +6 -6
- package/docs/Development.md +1 -1
- package/docs/Engines.md +1 -0
- package/docs/Options.md +5 -1
- package/package.json +6 -6
- package/src/api/Recognition.ts +29 -1
- package/src/api/Synthesis.ts +7 -7
- package/src/codecs/FFMpegTranscoder.ts +2 -0
- package/src/nlp/EspeakPhonemizer.ts +36 -36
- package/src/nlp/PhoneConversion.ts +176 -225
- package/src/recognition/DeepgramSTT.ts +136 -0
- package/src/recognition/GoogleCloudSTT.ts +0 -1
- package/src/synthesis/{ElevenlabsTTS.ts → ElevenLabsTTS.ts} +4 -4
- package/src/synthesis/EspeakTTS.ts +8 -7
- package/src/synthesis/KokoroTTS.ts +15 -15
- package/src/synthesis/VitsTTS.ts +9 -9
- package/src/utilities/Utilities.ts +6 -0
package/src/api/Recognition.ts
CHANGED
|
@@ -15,6 +15,7 @@ import { type SubtitlesConfig } from '../subtitles/Subtitles.js'
|
|
|
15
15
|
import { type OpenAICloudSTTOptions } from '../recognition/OpenAICloudSTT.js'
|
|
16
16
|
import { type WhisperCppOptions } from '../recognition/WhisperCppSTT.js'
|
|
17
17
|
import { type SileroRecognitionOptions } from '../recognition/SileroSTT.js'
|
|
18
|
+
import { type DeepgramSTTOptions } from '../recognition/DeepgramSTT.js'
|
|
18
19
|
import { OnnxExecutionProvider } from '../utilities/OnnxUtilities.js'
|
|
19
20
|
|
|
20
21
|
const log = logToStderr
|
|
@@ -274,6 +275,22 @@ export async function recognize(input: AudioSourceParam, options: RecognitionOpt
|
|
|
274
275
|
break
|
|
275
276
|
}
|
|
276
277
|
|
|
278
|
+
case 'deepgram': {
|
|
279
|
+
const DeepgramSTT = await import('../recognition/DeepgramSTT.js')
|
|
280
|
+
|
|
281
|
+
const deepgramOptions = options.deepgram!
|
|
282
|
+
|
|
283
|
+
if (!deepgramOptions.apiKey) {
|
|
284
|
+
throw new Error(`No Deepgram API key provided`)
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
logger.end();
|
|
288
|
+
|
|
289
|
+
({ transcript, timeline } = await DeepgramSTT.recognize(sourceRawAudio, options.language ? shortLanguageCode : undefined, deepgramOptions))
|
|
290
|
+
|
|
291
|
+
break
|
|
292
|
+
}
|
|
293
|
+
|
|
277
294
|
default: {
|
|
278
295
|
throw new Error(`Engine '${options.engine}' is not supported`)
|
|
279
296
|
}
|
|
@@ -330,7 +347,7 @@ export interface RecognitionResult {
|
|
|
330
347
|
backgroundRawAudio?: RawAudio
|
|
331
348
|
}
|
|
332
349
|
|
|
333
|
-
export type RecognitionEngine = 'whisper' | 'whisper.cpp' | 'vosk' | 'silero' | 'google-cloud' | 'microsoft-azure' | 'amazon-transcribe' | 'openai-cloud'
|
|
350
|
+
export type RecognitionEngine = 'whisper' | 'whisper.cpp' | 'vosk' | 'silero' | 'google-cloud' | 'microsoft-azure' | 'amazon-transcribe' | 'openai-cloud' | 'deepgram'
|
|
334
351
|
|
|
335
352
|
export interface RecognitionOptions {
|
|
336
353
|
engine?: RecognitionEngine
|
|
@@ -383,6 +400,8 @@ export interface RecognitionOptions {
|
|
|
383
400
|
}
|
|
384
401
|
|
|
385
402
|
openAICloud?: OpenAICloudSTTOptions
|
|
403
|
+
|
|
404
|
+
deepgram?: DeepgramSTTOptions
|
|
386
405
|
}
|
|
387
406
|
|
|
388
407
|
export const defaultRecognitionOptions: RecognitionOptions = {
|
|
@@ -443,6 +462,9 @@ export const defaultRecognitionOptions: RecognitionOptions = {
|
|
|
443
462
|
|
|
444
463
|
openAICloud: {
|
|
445
464
|
},
|
|
465
|
+
|
|
466
|
+
deepgram: {
|
|
467
|
+
}
|
|
446
468
|
}
|
|
447
469
|
|
|
448
470
|
export const recognitionEngines: API.EngineMetadata[] = [
|
|
@@ -494,4 +516,10 @@ export const recognitionEngines: API.EngineMetadata[] = [
|
|
|
494
516
|
description: 'OpenAI cloud speech-to-text service.',
|
|
495
517
|
type: 'cloud'
|
|
496
518
|
},
|
|
519
|
+
{
|
|
520
|
+
id: 'deepgram',
|
|
521
|
+
name: 'Deepgram',
|
|
522
|
+
description: 'Deepgram cloud speech-to-text service.',
|
|
523
|
+
type: 'cloud'
|
|
524
|
+
},
|
|
497
525
|
]
|
package/src/api/Synthesis.ts
CHANGED
|
@@ -20,8 +20,8 @@ import { shouldCancelCurrentTask } from '../server/Worker.js'
|
|
|
20
20
|
import chalk from 'chalk'
|
|
21
21
|
import { type SubtitlesConfig } from '../subtitles/Subtitles.js'
|
|
22
22
|
import { type EspeakOptions } from '../synthesis/EspeakTTS.js'
|
|
23
|
-
import { type OpenAICloudTTSOptions
|
|
24
|
-
import { type
|
|
23
|
+
import { type OpenAICloudTTSOptions } from '../synthesis/OpenAICloudTTS.js'
|
|
24
|
+
import { type ElevenLabsTTSOptions } from '../synthesis/ElevenLabsTTS.js'
|
|
25
25
|
import { OnnxExecutionProvider } from '../utilities/OnnxUtilities.js'
|
|
26
26
|
import { simplifyPunctuationCharacters } from '../nlp/TextNormalizer.js'
|
|
27
27
|
import { convertHtmlToText } from '../utilities/StringUtilities.js'
|
|
@@ -63,7 +63,7 @@ async function synthesizeSegments(segments: string[], options: SynthesisOptions,
|
|
|
63
63
|
let segmentsPlainText = segments
|
|
64
64
|
|
|
65
65
|
if (options.ssml) {
|
|
66
|
-
segmentsPlainText= []
|
|
66
|
+
segmentsPlainText = []
|
|
67
67
|
|
|
68
68
|
for (const segment of segments) {
|
|
69
69
|
segmentsPlainText.push(await convertHtmlToText(segment))
|
|
@@ -811,7 +811,7 @@ async function synthesizeSegment(text: string, options: SynthesisOptions) {
|
|
|
811
811
|
throw new Error(`The Elevenlabs engine doesn't support SSML inputs`)
|
|
812
812
|
}
|
|
813
813
|
|
|
814
|
-
const ElevenLabsTTS = await import('../synthesis/
|
|
814
|
+
const ElevenLabsTTS = await import('../synthesis/ElevenLabsTTS.js')
|
|
815
815
|
|
|
816
816
|
const engineOptions = options.elevenlabs!
|
|
817
817
|
|
|
@@ -1198,7 +1198,7 @@ export interface SynthesisOptions {
|
|
|
1198
1198
|
|
|
1199
1199
|
openAICloud?: OpenAICloudTTSOptions
|
|
1200
1200
|
|
|
1201
|
-
elevenlabs?:
|
|
1201
|
+
elevenlabs?: ElevenLabsTTSOptions,
|
|
1202
1202
|
|
|
1203
1203
|
googleTranslate?: {
|
|
1204
1204
|
tld?: string
|
|
@@ -1593,14 +1593,14 @@ export async function requestVoiceList(options: VoiceListRequestOptions): Promis
|
|
|
1593
1593
|
}
|
|
1594
1594
|
|
|
1595
1595
|
case 'elevenlabs': {
|
|
1596
|
-
const ElevenLabsTTS = await import('../synthesis/
|
|
1596
|
+
const ElevenLabsTTS = await import('../synthesis/ElevenLabsTTS.js')
|
|
1597
1597
|
|
|
1598
1598
|
const engineOptions = options.elevenlabs!
|
|
1599
1599
|
|
|
1600
1600
|
const apiKey = engineOptions.apiKey
|
|
1601
1601
|
|
|
1602
1602
|
if (!apiKey) {
|
|
1603
|
-
throw new Error(`No
|
|
1603
|
+
throw new Error(`No ElevenLabs API key provided`)
|
|
1604
1604
|
}
|
|
1605
1605
|
|
|
1606
1606
|
voiceList = await ElevenLabsTTS.getVoiceList(apiKey)
|
|
@@ -223,6 +223,7 @@ export function getDefaultFFMpegOptionsForSpeech(fileExtension: string, customBi
|
|
|
223
223
|
}
|
|
224
224
|
} else if (fileExtension == 'opus') {
|
|
225
225
|
ffmpegOptions = {
|
|
226
|
+
format: 'ogg',
|
|
226
227
|
codec: 'libopus',
|
|
227
228
|
bitrate: 48,
|
|
228
229
|
customOptions: []
|
|
@@ -236,6 +237,7 @@ export function getDefaultFFMpegOptionsForSpeech(fileExtension: string, customBi
|
|
|
236
237
|
}
|
|
237
238
|
} else if (fileExtension == 'ogg') {
|
|
238
239
|
ffmpegOptions = {
|
|
240
|
+
format: 'ogg',
|
|
239
241
|
codec: 'libvorbis',
|
|
240
242
|
bitrate: 48,
|
|
241
243
|
customOptions: []
|
|
@@ -7,14 +7,14 @@ const log = logToStderr
|
|
|
7
7
|
export async function phonemizeSentence(sentence: string, espeakVoice: string, substitutionMap?: Map<string, string[]>, useIpa = true) {
|
|
8
8
|
const ipaString = await EspeakTTS.textToPhonemes(sentence, espeakVoice, useIpa)
|
|
9
9
|
|
|
10
|
-
const
|
|
10
|
+
const phraseStrings = ipaString.split(' | ')
|
|
11
11
|
|
|
12
|
-
const
|
|
12
|
+
const phrases: string[][][] = []
|
|
13
13
|
|
|
14
|
-
for (let
|
|
15
|
-
const
|
|
14
|
+
for (let phraseIndex = 0; phraseIndex < phraseStrings.length; phraseIndex++) {
|
|
15
|
+
const phraseString = phraseStrings[phraseIndex]
|
|
16
16
|
|
|
17
|
-
const wordStrings =
|
|
17
|
+
const wordStrings = phraseString.trim().split(/ +/g)
|
|
18
18
|
const words: string[][] = []
|
|
19
19
|
|
|
20
20
|
for (let wordIndex = 0; wordIndex < wordStrings.length; wordIndex++) {
|
|
@@ -40,11 +40,11 @@ export async function phonemizeSentence(sentence: string, espeakVoice: string, s
|
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
if (words.length > 0) {
|
|
43
|
-
|
|
43
|
+
phrases.push(words)
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
return
|
|
47
|
+
return phrases
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
export async function phonemizeText(text: string, voice: string, substitutionMap?: Map<string, string[]>) {
|
|
@@ -58,46 +58,46 @@ export async function phonemizeText(text: string, voice: string, substitutionMap
|
|
|
58
58
|
.replaceAll('»', ', ')
|
|
59
59
|
|
|
60
60
|
const segmentedText = await Segmentation.parse(text, voice)
|
|
61
|
-
const
|
|
62
|
-
const
|
|
61
|
+
const preparedPhrases: string[] = []
|
|
62
|
+
const phraseBreakers: string[] = []
|
|
63
63
|
|
|
64
64
|
for (const sentence of segmentedText) {
|
|
65
|
-
for (const
|
|
66
|
-
const words =
|
|
67
|
-
const
|
|
65
|
+
for (const phrase of sentence.phrases) {
|
|
66
|
+
const words = phrase.words.filter(wordObject => Segmentation.isWordOrSymbolWord(wordObject.text))
|
|
67
|
+
const preparedPhraseText = words.map(word => word.text.replace(/\./g, ' ')).join(' ')
|
|
68
68
|
|
|
69
|
-
|
|
69
|
+
preparedPhrases.push(preparedPhraseText)
|
|
70
70
|
|
|
71
|
-
const
|
|
72
|
-
const lastChar =
|
|
71
|
+
const trimmedPhraseText = phrase.text.trim()
|
|
72
|
+
const lastChar = trimmedPhraseText[trimmedPhraseText.length - 1]
|
|
73
73
|
|
|
74
|
-
if (
|
|
75
|
-
if (
|
|
76
|
-
|
|
77
|
-
} else if (
|
|
78
|
-
|
|
74
|
+
if (phrase.isSentenceFinalizer) {
|
|
75
|
+
if (trimmedPhraseText.endsWith('?') || trimmedPhraseText.endsWith(`?"`)) {
|
|
76
|
+
phraseBreakers.push('?')
|
|
77
|
+
} else if (trimmedPhraseText.endsWith('!') || trimmedPhraseText.endsWith(`!"`)) {
|
|
78
|
+
phraseBreakers.push('!')
|
|
79
79
|
} else {
|
|
80
|
-
|
|
80
|
+
phraseBreakers.push('.')
|
|
81
81
|
}
|
|
82
82
|
} else {
|
|
83
83
|
if (lastChar == ':' || lastChar == ';') {
|
|
84
|
-
|
|
84
|
+
phraseBreakers.push(lastChar)
|
|
85
85
|
} else {
|
|
86
|
-
|
|
86
|
+
phraseBreakers.push(',')
|
|
87
87
|
}
|
|
88
88
|
}
|
|
89
89
|
}
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
-
return
|
|
92
|
+
return phonemizePhrases(preparedPhrases, voice, phraseBreakers, substitutionMap)
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
-
export async function
|
|
96
|
-
if (
|
|
95
|
+
export async function phonemizePhrases(phrases: string[], voice: string, phraseBreakers: string[], substitutionMap?: Map<string, string[]>) {
|
|
96
|
+
if (phrases.length == 0) {
|
|
97
97
|
return []
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
-
const preparedText =
|
|
100
|
+
const preparedText = phrases.join('\n\n') // filter(phrase => phrase.trim().length > 0)
|
|
101
101
|
|
|
102
102
|
const ipaString = await EspeakTTS.textToIPA(preparedText, voice)
|
|
103
103
|
|
|
@@ -130,31 +130,31 @@ export async function phonemizeClauses(clauses: string[], voice: string, clauseB
|
|
|
130
130
|
})
|
|
131
131
|
})
|
|
132
132
|
|
|
133
|
-
if (ipaLines.length !=
|
|
134
|
-
log(
|
|
133
|
+
if (ipaLines.length != phraseBreakers.length) {
|
|
134
|
+
log(phrases)
|
|
135
135
|
log(ipaLines)
|
|
136
|
-
log(
|
|
136
|
+
log(phraseBreakers)
|
|
137
137
|
|
|
138
|
-
throw new Error(`Unexpected: IPA lines count (${ipaLines.length}) is not equal to
|
|
138
|
+
throw new Error(`Unexpected: IPA lines count (${ipaLines.length}) is not equal to phrase breakers count (${phraseBreakers.length})`)
|
|
139
139
|
}
|
|
140
140
|
|
|
141
141
|
for (let i = 0; i < phonemeLines.length; i++) {
|
|
142
142
|
const line = phonemeLines[i]
|
|
143
143
|
const lastWordInLine = line[line.length - 1]
|
|
144
144
|
|
|
145
|
-
lastWordInLine.push(
|
|
145
|
+
lastWordInLine.push(phraseBreakers[i])
|
|
146
146
|
}
|
|
147
147
|
|
|
148
148
|
return phonemeLines
|
|
149
149
|
}
|
|
150
150
|
|
|
151
|
-
export function
|
|
151
|
+
export function phonemizedPhrasesToSentences(phonemizedPhrases: string[][][]) {
|
|
152
152
|
let phonemizedSentences: string[][][] = [[]]
|
|
153
153
|
|
|
154
|
-
for (const
|
|
155
|
-
phonemizedSentences[phonemizedSentences.length - 1].push(...
|
|
154
|
+
for (const phonemizedPhrase of phonemizedPhrases) {
|
|
155
|
+
phonemizedSentences[phonemizedSentences.length - 1].push(...phonemizedPhrase)
|
|
156
156
|
|
|
157
|
-
const lastWord =
|
|
157
|
+
const lastWord = phonemizedPhrase[phonemizedPhrase.length - 1]
|
|
158
158
|
const lastPhoneme = lastWord[lastWord.length - 1]
|
|
159
159
|
|
|
160
160
|
if (['.', '?', '!'].includes(lastPhoneme)) {
|