echogarden 0.12.2 → 1.0.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/README.md +15 -14
- package/data/schemas/options.json +398 -111
- package/dist/alignment/DTWMfccSequenceAlignment.d.ts +1 -1
- package/dist/alignment/DTWMfccSequenceAlignment.js +8 -8
- package/dist/alignment/DTWSequenceAlignment.d.ts +1 -1
- package/dist/alignment/DTWSequenceAlignment.js +1 -1
- package/dist/alignment/DTWSequenceAlignmentWindowed.d.ts +1 -1
- package/dist/alignment/DTWSequenceAlignmentWindowed.js +2 -2
- package/dist/alignment/LevenshteinSequenceAlignment.d.ts +1 -1
- package/dist/alignment/LevenshteinSequenceAlignment.js +1 -1
- package/dist/alignment/SpeechAlignment.d.ts +9 -10
- package/dist/alignment/SpeechAlignment.js +136 -105
- package/dist/alignment/SpeechAlignment.js.map +1 -1
- package/dist/api/API.d.ts +13 -12
- package/dist/api/API.js +14 -13
- package/dist/api/API.js.map +1 -1
- package/dist/api/APIOptions.d.ts +5 -4
- package/dist/api/Alignment.d.ts +15 -9
- package/dist/api/Alignment.js +88 -74
- package/dist/api/Alignment.js.map +1 -1
- package/dist/api/Common.js +1 -1
- package/dist/api/Denoising.d.ts +6 -6
- package/dist/api/Denoising.js +23 -23
- package/dist/api/Denoising.js.map +1 -1
- package/dist/api/LanguageDetection.d.ts +19 -12
- package/dist/api/LanguageDetection.js +88 -38
- package/dist/api/LanguageDetection.js.map +1 -1
- package/dist/api/Recognition.d.ts +16 -6
- package/dist/api/Recognition.js +129 -55
- package/dist/api/Recognition.js.map +1 -1
- package/dist/api/SourceSeparation.d.ts +17 -0
- package/dist/api/SourceSeparation.js +61 -0
- package/dist/api/SourceSeparation.js.map +1 -0
- package/dist/api/Synthesis.d.ts +18 -18
- package/dist/api/Synthesis.js +191 -164
- package/dist/api/Synthesis.js.map +1 -1
- package/dist/api/Translation.d.ts +19 -8
- package/dist/api/Translation.js +132 -35
- package/dist/api/Translation.js.map +1 -1
- package/dist/api/Vad.d.ts +10 -5
- package/dist/api/Vad.js +76 -38
- package/dist/api/Vad.js.map +1 -1
- package/dist/audio/AudioBufferConversion.d.ts +1 -1
- package/dist/audio/AudioBufferConversion.js +4 -4
- package/dist/audio/AudioPlayer.d.ts +1 -1
- package/dist/audio/AudioPlayer.js +26 -26
- package/dist/audio/AudioPlayer.js.map +1 -1
- package/dist/audio/AudioRecorder.d.ts +1 -1
- package/dist/audio/AudioRecorder.js +5 -5
- package/dist/audio/AudioUtilities.d.ts +13 -9
- package/dist/audio/AudioUtilities.js +86 -24
- package/dist/audio/AudioUtilities.js.map +1 -1
- package/dist/cli/CLI.d.ts +3 -3
- package/dist/cli/CLI.js +271 -162
- package/dist/cli/CLI.js.map +1 -1
- package/dist/cli/CLIConfigFile.js +8 -8
- package/dist/cli/CLILauncher.js +6 -6
- package/dist/cli/CLIOptionsSchema.js +2 -2
- package/dist/cli/CLIParser.js +5 -5
- package/dist/cli/CLIStarter.js +4 -4
- package/dist/codecs/FFMpegTranscoder.d.ts +2 -2
- package/dist/codecs/FFMpegTranscoder.js +37 -37
- package/dist/codecs/FFMpegTranscoder.js.map +1 -1
- package/dist/codecs/TIMITCodec.js +5 -5
- package/dist/codecs/WaveCodec.d.ts +1 -1
- package/dist/codecs/WaveCodec.js +22 -22
- package/dist/denoising/RNNoise.d.ts +1 -1
- package/dist/denoising/RNNoise.js +9 -9
- package/dist/dsp/BiquadFilter.d.ts +3 -2
- package/dist/dsp/BiquadFilter.js +18 -11
- package/dist/dsp/BiquadFilter.js.map +1 -1
- package/dist/dsp/DecayingPeakEstimator.d.ts +16 -0
- package/dist/dsp/DecayingPeakEstimator.js +23 -0
- package/dist/dsp/DecayingPeakEstimator.js.map +1 -0
- package/dist/dsp/FFT.d.ts +8 -4
- package/dist/dsp/FFT.js +76 -30
- package/dist/dsp/FFT.js.map +1 -1
- package/dist/dsp/KWeightingFilter.d.ts +9 -0
- package/dist/dsp/KWeightingFilter.js +40 -0
- package/dist/dsp/KWeightingFilter.js.map +1 -0
- package/dist/dsp/LoudnessEstimator.d.ts +21 -0
- package/dist/dsp/LoudnessEstimator.js +47 -0
- package/dist/dsp/LoudnessEstimator.js.map +1 -0
- package/dist/dsp/MFCC.d.ts +2 -2
- package/dist/dsp/MFCC.js +15 -15
- package/dist/dsp/MelSpectogram.d.ts +1 -1
- package/dist/dsp/MelSpectogram.js +6 -6
- package/dist/dsp/Rubberband.d.ts +11 -11
- package/dist/dsp/Rubberband.js +27 -27
- package/dist/dsp/Sonic.d.ts +1 -1
- package/dist/dsp/Sonic.js +3 -3
- package/dist/dsp/SpeexResampler.d.ts +1 -1
- package/dist/dsp/SpeexResampler.js +2 -2
- package/dist/math/VectorMath.d.ts +12 -8
- package/dist/math/VectorMath.js +35 -32
- package/dist/math/VectorMath.js.map +1 -1
- package/dist/nlp/ChineseSegmentation.js +2 -2
- package/dist/nlp/CompromiseNLP.js +3 -3
- package/dist/nlp/EspeakPhonemizer.js +30 -30
- package/dist/nlp/IPA.js +20 -20
- package/dist/nlp/JapaneseSegmentation.js +6 -6
- package/dist/nlp/Lexicon.d.ts +1 -1
- package/dist/nlp/Lexicon.js +7 -7
- package/dist/nlp/Segmentation.d.ts +3 -0
- package/dist/nlp/Segmentation.js +21 -14
- package/dist/nlp/Segmentation.js.map +1 -1
- package/dist/nlp/TextNormalizer.js +16 -16
- package/dist/recognition/AmazonTranscribeSTT.d.ts +2 -2
- package/dist/recognition/AmazonTranscribeSTT.js +13 -14
- package/dist/recognition/AmazonTranscribeSTT.js.map +1 -1
- package/dist/recognition/AzureCognitiveServicesSTT.js +5 -6
- package/dist/recognition/AzureCognitiveServicesSTT.js.map +1 -1
- package/dist/recognition/GoogleCloudSTT.d.ts +3 -3
- package/dist/recognition/GoogleCloudSTT.js +18 -18
- package/dist/recognition/OpenAICloudSTT.d.ts +19 -0
- package/dist/recognition/OpenAICloudSTT.js +81 -0
- package/dist/recognition/OpenAICloudSTT.js.map +1 -0
- package/dist/recognition/SileroSTT.d.ts +2 -2
- package/dist/recognition/SileroSTT.js +25 -25
- package/dist/recognition/VoskSTT.d.ts +2 -2
- package/dist/recognition/VoskSTT.js +8 -8
- package/dist/recognition/WhisperCppSTT.d.ts +88 -0
- package/dist/recognition/WhisperCppSTT.js +332 -0
- package/dist/recognition/WhisperCppSTT.js.map +1 -0
- package/dist/recognition/WhisperSTT.d.ts +49 -25
- package/dist/recognition/WhisperSTT.js +626 -481
- package/dist/recognition/WhisperSTT.js.map +1 -1
- package/dist/server/Client.d.ts +1 -1
- package/dist/server/Client.js +22 -22
- package/dist/server/Server.js +9 -9
- package/dist/server/Server.js.map +1 -1
- package/dist/server/Worker.d.ts +22 -22
- package/dist/server/Worker.js +36 -36
- package/dist/server/Worker.js.map +1 -1
- package/dist/server/WorkerStarter.js +2 -2
- package/dist/source-separation/MDXNetSourceSeparation.d.ts +11 -0
- package/dist/source-separation/MDXNetSourceSeparation.js +161 -0
- package/dist/source-separation/MDXNetSourceSeparation.js.map +1 -0
- package/dist/speech-language-detection/SileroLanguageDetection.d.ts +1 -1
- package/dist/speech-language-detection/SileroLanguageDetection.js +7 -7
- package/dist/subtitles/Subtitles.d.ts +10 -0
- package/dist/subtitles/Subtitles.js +2 -2
- package/dist/subtitles/Subtitles.js.map +1 -1
- package/dist/synthesis/AwsPollyTTS.d.ts +1 -1
- package/dist/synthesis/AwsPollyTTS.js +12 -12
- package/dist/synthesis/AzureCognitiveServicesTTS.js +7 -7
- package/dist/synthesis/CoquiServerTTS.js +10 -10
- package/dist/synthesis/CoquiServerTTS.js.map +1 -1
- package/dist/synthesis/ElevenlabsTTS.d.ts +23 -0
- package/dist/synthesis/ElevenlabsTTS.js +103 -0
- package/dist/synthesis/ElevenlabsTTS.js.map +1 -0
- package/dist/synthesis/EspeakTTS.d.ts +6 -5
- package/dist/synthesis/EspeakTTS.js +81 -69
- package/dist/synthesis/EspeakTTS.js.map +1 -1
- package/dist/synthesis/FliteTTS.d.ts +3 -3
- package/dist/synthesis/FliteTTS.js +154 -154
- package/dist/synthesis/FliteTTS.js.map +1 -1
- package/dist/synthesis/GoogleCloudTTS.d.ts +3 -3
- package/dist/synthesis/GoogleCloudTTS.js +17 -17
- package/dist/synthesis/GoogleCloudTTS.js.map +1 -1
- package/dist/synthesis/GoogleTranslateTTS.d.ts +1 -1
- package/dist/synthesis/GoogleTranslateTTS.js +103 -103
- package/dist/synthesis/MicrosoftEdgeTTS.d.ts +2 -2
- package/dist/synthesis/MicrosoftEdgeTTS.js +74 -74
- package/dist/synthesis/OpenAICloudTTS.d.ts +13 -0
- package/dist/synthesis/OpenAICloudTTS.js +169 -0
- package/dist/synthesis/OpenAICloudTTS.js.map +1 -0
- package/dist/synthesis/SamTTS.js +3 -3
- package/dist/synthesis/SapiTTS.d.ts +3 -3
- package/dist/synthesis/SapiTTS.js +26 -26
- package/dist/synthesis/StreamlabsPollyTTS.d.ts +2 -2
- package/dist/synthesis/StreamlabsPollyTTS.js +27 -27
- package/dist/synthesis/SvoxPicoTTS.d.ts +2 -2
- package/dist/synthesis/SvoxPicoTTS.js +65 -65
- package/dist/synthesis/SvoxPicoTTS.js.map +1 -1
- package/dist/synthesis/VitsTTS.d.ts +3 -3
- package/dist/synthesis/VitsTTS.js +378 -378
- package/dist/synthesis/VitsTTS.js.map +1 -1
- package/dist/tests/Test.js +2 -2
- package/dist/utilities/Compression.d.ts +5 -0
- package/dist/utilities/Compression.js +29 -13
- package/dist/utilities/Compression.js.map +1 -1
- package/dist/utilities/FileDownloader.d.ts +1 -1
- package/dist/utilities/FileDownloader.js +16 -16
- package/dist/utilities/FileSystem.js +7 -7
- package/dist/utilities/Locale.d.ts +7 -7
- package/dist/utilities/Locale.js +15 -15
- package/dist/utilities/Logger.js +3 -3
- package/dist/utilities/ObjectUtilities.js +19 -19
- package/dist/utilities/OpenPromise.js +2 -2
- package/dist/utilities/OpenPromise.js.map +1 -1
- package/dist/utilities/PackageManager.js +31 -0
- package/dist/utilities/PackageManager.js.map +1 -1
- package/dist/utilities/PathUtilities.js +8 -8
- package/dist/utilities/RandomGenerator.js +2 -2
- package/dist/utilities/SmoothEstimator.d.ts +8 -0
- package/dist/utilities/SmoothEstimator.js +25 -0
- package/dist/utilities/SmoothEstimator.js.map +1 -0
- package/dist/utilities/TarballMaker.js +8 -8
- package/dist/utilities/Timeline.d.ts +3 -2
- package/dist/utilities/Timeline.js +11 -11
- package/dist/utilities/Timeline.js.map +1 -1
- package/dist/utilities/Timer.js +4 -4
- package/dist/utilities/Utilities.d.ts +4 -0
- package/dist/utilities/Utilities.js +38 -15
- package/dist/utilities/Utilities.js.map +1 -1
- package/dist/utilities/WasmMemoryManager.js +7 -7
- package/dist/utilities/WebReader.js +23 -23
- package/dist/utilities/WikipediaReader.js +2 -2
- package/dist/voice-activity-detection/AdaptiveGateVAD.d.ts +28 -0
- package/dist/voice-activity-detection/AdaptiveGateVAD.js +138 -0
- package/dist/voice-activity-detection/AdaptiveGateVAD.js.map +1 -0
- package/dist/voice-activity-detection/SileroVAD.d.ts +1 -1
- package/dist/voice-activity-detection/SileroVAD.js +5 -5
- package/dist/voice-activity-detection/SileroVAD.js.map +1 -1
- package/dist/voice-activity-detection/WebRtcVAD.d.ts +1 -1
- package/dist/voice-activity-detection/WebRtcVAD.js +4 -4
- package/docs/API.md +29 -11
- package/docs/CLI.md +31 -7
- package/docs/Contributing.md +38 -0
- package/docs/Development.md +93 -19
- package/docs/Engines.md +28 -16
- package/docs/Licenses.md +4 -1
- package/docs/Options.md +158 -78
- package/docs/Releases.md +262 -0
- package/docs/Server.md +7 -7
- package/docs/Tasklist.md +95 -76
- package/docs/Technical.md +4 -4
- package/package.json +13 -14
- package/src/alignment/DTWMfccSequenceAlignment.ts +9 -9
- package/src/alignment/DTWSequenceAlignment.ts +2 -2
- package/src/alignment/DTWSequenceAlignmentWindowed.ts +3 -3
- package/src/alignment/LevenshteinSequenceAlignment.ts +2 -2
- package/src/alignment/SpeechAlignment.ts +204 -119
- package/src/api/API.ts +14 -13
- package/src/api/APIOptions.ts +12 -11
- package/src/api/Alignment.ts +147 -90
- package/src/api/Common.ts +1 -1
- package/src/api/Denoising.ts +28 -28
- package/src/api/LanguageDetection.ts +135 -48
- package/src/api/Recognition.ts +198 -59
- package/src/api/SourceSeparation.ts +99 -0
- package/src/api/Synthesis.ts +217 -181
- package/src/api/Translation.ts +193 -40
- package/src/api/Vad.ts +110 -41
- package/src/audio/AudioBufferConversion.ts +4 -4
- package/src/audio/AudioPlayer.ts +27 -27
- package/src/audio/AudioRecorder.ts +5 -5
- package/src/audio/AudioUtilities.ts +107 -24
- package/src/cli/CLI.ts +313 -164
- package/src/cli/CLIConfigFile.ts +8 -8
- package/src/cli/CLILauncher.ts +6 -6
- package/src/cli/CLIOptionsSchema.ts +2 -2
- package/src/cli/CLIParser.ts +5 -5
- package/src/cli/CLIStarter.ts +4 -4
- package/src/codecs/FFMpegTranscoder.ts +38 -38
- package/src/codecs/TIMITCodec.ts +5 -5
- package/src/codecs/WaveCodec.ts +22 -22
- package/src/denoising/RNNoise.ts +9 -9
- package/src/dsp/BiquadFilter.ts +19 -11
- package/src/dsp/DecayingPeakEstimator.ts +35 -0
- package/src/dsp/FFT.ts +103 -35
- package/src/dsp/KWeightingFilter.ts +43 -0
- package/src/dsp/LoudnessEstimator.ts +74 -0
- package/src/dsp/MFCC.ts +15 -15
- package/src/dsp/MelSpectogram.ts +7 -7
- package/src/dsp/Rubberband.ts +38 -38
- package/src/dsp/Sonic.ts +4 -4
- package/src/dsp/SpeexResampler.ts +2 -2
- package/src/math/VectorMath.ts +42 -33
- package/src/nlp/ChineseSegmentation.ts +3 -3
- package/src/nlp/CompromiseNLP.ts +3 -3
- package/src/nlp/EspeakPhonemizer.ts +30 -30
- package/src/nlp/IPA.ts +20 -20
- package/src/nlp/JapaneseSegmentation.ts +6 -6
- package/src/nlp/Lexicon.ts +8 -8
- package/src/nlp/Segmentation.ts +23 -14
- package/src/nlp/TextNormalizer.ts +16 -16
- package/src/recognition/AmazonTranscribeSTT.ts +16 -17
- package/src/recognition/AzureCognitiveServicesSTT.ts +8 -6
- package/src/recognition/GoogleCloudSTT.ts +21 -21
- package/src/recognition/OpenAICloudSTT.ts +142 -0
- package/src/recognition/SileroSTT.ts +26 -26
- package/src/recognition/VoskSTT.ts +10 -10
- package/src/recognition/WhisperCppSTT.ts +555 -0
- package/src/recognition/WhisperSTT.ts +760 -507
- package/src/server/Client.ts +23 -23
- package/src/server/Server.ts +9 -9
- package/src/server/Worker.ts +53 -53
- package/src/server/WorkerStarter.ts +2 -2
- package/src/source-separation/MDXNetSourceSeparation.ts +228 -0
- package/src/speech-language-detection/SileroLanguageDetection.ts +8 -8
- package/src/subtitles/Subtitles.ts +3 -3
- package/src/synthesis/AwsPollyTTS.ts +14 -14
- package/src/synthesis/AzureCognitiveServicesTTS.ts +10 -10
- package/src/synthesis/CoquiServerTTS.ts +10 -10
- package/src/synthesis/ElevenlabsTTS.ts +137 -0
- package/src/synthesis/EspeakTTS.ts +90 -71
- package/src/synthesis/FliteTTS.ts +157 -157
- package/src/synthesis/GoogleCloudTTS.ts +19 -19
- package/src/synthesis/GoogleTranslateTTS.ts +104 -104
- package/src/synthesis/MicrosoftEdgeTTS.ts +80 -80
- package/src/synthesis/OpenAICloudTTS.ts +196 -0
- package/src/synthesis/SamTTS.ts +3 -3
- package/src/synthesis/SapiTTS.ts +29 -29
- package/src/synthesis/StreamlabsPollyTTS.ts +29 -29
- package/src/synthesis/SvoxPicoTTS.ts +67 -67
- package/src/synthesis/VitsTTS.ts +380 -380
- package/src/tests/Test.ts +4 -4
- package/src/utilities/Compression.ts +34 -13
- package/src/utilities/FileDownloader.ts +19 -19
- package/src/utilities/FileSystem.ts +7 -7
- package/src/utilities/Locale.ts +22 -22
- package/src/utilities/Logger.ts +4 -4
- package/src/utilities/ObjectUtilities.ts +19 -19
- package/src/utilities/OpenPromise.ts +2 -2
- package/src/utilities/PackageManager.ts +40 -0
- package/src/utilities/PathUtilities.ts +8 -8
- package/src/utilities/RandomGenerator.ts +3 -3
- package/src/utilities/SmoothEstimator.ts +35 -0
- package/src/utilities/TarballMaker.ts +9 -9
- package/src/utilities/Timeline.ts +15 -13
- package/src/utilities/Timer.ts +4 -4
- package/src/utilities/Utilities.ts +49 -15
- package/src/utilities/WasmMemoryManager.ts +7 -7
- package/src/utilities/WebReader.ts +23 -23
- package/src/utilities/WikipediaReader.ts +2 -2
- package/src/voice-activity-detection/AdaptiveGateVAD.ts +202 -0
- package/src/voice-activity-detection/SileroVAD.ts +5 -5
- package/src/voice-activity-detection/WebRtcVAD.ts +5 -5
- package/dist/synthesis/ElevenLabsTTS.d.ts +0 -8
- package/dist/synthesis/ElevenLabsTTS.js +0 -82
- package/dist/synthesis/ElevenLabsTTS.js.map +0 -1
- package/src/synthesis/ElevenLabsTTS.ts +0 -104
package/dist/api/Synthesis.js
CHANGED
|
@@ -1,20 +1,22 @@
|
|
|
1
|
-
import path from
|
|
2
|
-
import { deepClone, extendDeep } from
|
|
3
|
-
import * as FFMpegTranscoder from
|
|
4
|
-
import { clip, convertHtmlToText, sha256AsHex, simplifyPunctuationCharacters, stringifyAndFormatJson, logToStderr, yieldToEventLoop, runOperationWithRetries } from
|
|
5
|
-
import { concatAudioSegments, downmixToMono,
|
|
6
|
-
import { Logger } from
|
|
7
|
-
import { isWordOrSymbolWord, splitToParagraphs, splitToSentences } from
|
|
8
|
-
import { loadLexiconsForLanguage } from
|
|
9
|
-
import * as API from
|
|
10
|
-
import { addTimeOffsetToTimeline, multiplyTimelineByFactor } from
|
|
11
|
-
import { getAppDataDir, ensureDir, existsSync, isFileIsUpToDate, readAndParseJsonFile, writeFileSafe } from
|
|
12
|
-
import { formatLanguageCodeWithName, getShortLanguageCode, normalizeLanguageCode, defaultDialectForLanguageCode } from
|
|
13
|
-
import { loadPackage } from
|
|
14
|
-
import { appName } from
|
|
15
|
-
import { shouldCancelCurrentTask } from
|
|
16
|
-
import chalk from
|
|
17
|
-
import { defaultSubtitlesBaseConfig } from
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { deepClone, extendDeep } from '../utilities/ObjectUtilities.js';
|
|
3
|
+
import * as FFMpegTranscoder from '../codecs/FFMpegTranscoder.js';
|
|
4
|
+
import { clip, convertHtmlToText, sha256AsHex, simplifyPunctuationCharacters, stringifyAndFormatJson, logToStderr, yieldToEventLoop, runOperationWithRetries } from '../utilities/Utilities.js';
|
|
5
|
+
import { attenuateIfClipping, concatAudioSegments, downmixToMono, encodeRawAudioToWave, getSamplePeakDecibels, getEmptyRawAudio, getRawAudioDuration, normalizeAudioLevel, trimAudioEnd, trimAudioStart } from '../audio/AudioUtilities.js';
|
|
6
|
+
import { Logger } from '../utilities/Logger.js';
|
|
7
|
+
import { isWordOrSymbolWord, splitToParagraphs, splitToSentences } from '../nlp/Segmentation.js';
|
|
8
|
+
import { loadLexiconsForLanguage } from '../nlp/Lexicon.js';
|
|
9
|
+
import * as API from './API.js';
|
|
10
|
+
import { addTimeOffsetToTimeline, multiplyTimelineByFactor } from '../utilities/Timeline.js';
|
|
11
|
+
import { getAppDataDir, ensureDir, existsSync, isFileIsUpToDate, readAndParseJsonFile, writeFileSafe } from '../utilities/FileSystem.js';
|
|
12
|
+
import { formatLanguageCodeWithName, getShortLanguageCode, normalizeLanguageCode, defaultDialectForLanguageCode } from '../utilities/Locale.js';
|
|
13
|
+
import { loadPackage } from '../utilities/PackageManager.js';
|
|
14
|
+
import { appName } from './Common.js';
|
|
15
|
+
import { shouldCancelCurrentTask } from '../server/Worker.js';
|
|
16
|
+
import chalk from 'chalk';
|
|
17
|
+
import { defaultSubtitlesBaseConfig } from '../subtitles/Subtitles.js';
|
|
18
|
+
import { defaultOpenAICloudTTSOptions } from '../synthesis/OpenAICloudTTS.js';
|
|
19
|
+
import { defaultElevenlabsTTSOptions } from '../synthesis/ElevenlabsTTS.js';
|
|
18
20
|
const log = logToStderr;
|
|
19
21
|
/////////////////////////////////////////////////////////////////////////////////////////////
|
|
20
22
|
// Synthesis
|
|
@@ -38,7 +40,7 @@ async function synthesizeSegments(segments, options, onSegment, onSentence) {
|
|
|
38
40
|
const logger = new Logger();
|
|
39
41
|
options = extendDeep(defaultSynthesisOptions, options);
|
|
40
42
|
if (!options.language && !options.voice) {
|
|
41
|
-
logger.start(
|
|
43
|
+
logger.start('No language or voice specified. Detecting language');
|
|
42
44
|
let segmentsPlainText = segments;
|
|
43
45
|
if (options.ssml) {
|
|
44
46
|
segmentsPlainText = [];
|
|
@@ -46,7 +48,7 @@ async function synthesizeSegments(segments, options, onSegment, onSentence) {
|
|
|
46
48
|
segmentsPlainText.push(await convertHtmlToText(segment));
|
|
47
49
|
}
|
|
48
50
|
}
|
|
49
|
-
const { detectedLanguage } = await API.detectTextLanguage(segmentsPlainText.join(
|
|
51
|
+
const { detectedLanguage } = await API.detectTextLanguage(segmentsPlainText.join('\n\n'), options.languageDetection || {});
|
|
50
52
|
options.language = detectedLanguage;
|
|
51
53
|
logger.end();
|
|
52
54
|
logger.logTitledMessage('Language detected', formatLanguageCodeWithName(detectedLanguage));
|
|
@@ -61,7 +63,7 @@ async function synthesizeSegments(segments, options, onSegment, onSentence) {
|
|
|
61
63
|
logger.start(`Get voice list for ${options.engine}`);
|
|
62
64
|
const { bestMatchingVoice } = await requestVoiceList(options);
|
|
63
65
|
if (!bestMatchingVoice) {
|
|
64
|
-
throw new Error(
|
|
66
|
+
throw new Error('No matching voice found');
|
|
65
67
|
}
|
|
66
68
|
options.voice = bestMatchingVoice.name;
|
|
67
69
|
if (!options.language) {
|
|
@@ -76,21 +78,21 @@ async function synthesizeSegments(segments, options, onSegment, onSentence) {
|
|
|
76
78
|
let timeOffset = 0;
|
|
77
79
|
for (let segmentIndex = 0; segmentIndex < segments.length; segmentIndex++) {
|
|
78
80
|
const segmentText = segments[segmentIndex].trim();
|
|
79
|
-
logger.log(`\n${chalk.magentaBright(`Synthesizing segment ${segmentIndex + 1}/${segments.length}`)}:
|
|
81
|
+
logger.log(`\n${chalk.magentaBright(`Synthesizing segment ${segmentIndex + 1}/${segments.length}`)}: '${segmentText}'`);
|
|
80
82
|
const segmentStartTime = timeOffset;
|
|
81
83
|
const segmentEntry = {
|
|
82
|
-
type:
|
|
84
|
+
type: 'segment',
|
|
83
85
|
text: segmentText,
|
|
84
86
|
startTime: timeOffset,
|
|
85
87
|
endTime: -1,
|
|
86
88
|
timeline: []
|
|
87
89
|
};
|
|
88
90
|
let sentences;
|
|
89
|
-
if ((options.splitToSentences || options.engine ==
|
|
91
|
+
if ((options.splitToSentences || options.engine == 'vits') && !options.ssml) {
|
|
90
92
|
sentences = splitToSentences(segmentText, options.language);
|
|
91
|
-
sentences = sentences.filter(sentence => sentence.trim() !=
|
|
93
|
+
sentences = sentences.filter(sentence => sentence.trim() != '');
|
|
92
94
|
if (sentences.length == 0) {
|
|
93
|
-
sentences = [
|
|
95
|
+
sentences = [''];
|
|
94
96
|
}
|
|
95
97
|
}
|
|
96
98
|
else {
|
|
@@ -101,8 +103,8 @@ async function synthesizeSegments(segments, options, onSegment, onSentence) {
|
|
|
101
103
|
for (let sentenceIndex = 0; sentenceIndex < sentences.length; sentenceIndex++) {
|
|
102
104
|
await yieldToEventLoop();
|
|
103
105
|
if (shouldCancelCurrentTask()) {
|
|
104
|
-
//log(
|
|
105
|
-
throw new Error(
|
|
106
|
+
//log('\n\n\n\n\nCANCELED\n\n\n\n')
|
|
107
|
+
throw new Error('Canceled');
|
|
106
108
|
}
|
|
107
109
|
const sentenceText = sentences[sentenceIndex].trim();
|
|
108
110
|
logger.log(`\n${chalk.magentaBright(`Synthesizing sentence ${sentenceIndex + 1}/${sentences.length}`)}: "${sentenceText}"`);
|
|
@@ -121,13 +123,13 @@ async function synthesizeSegments(segments, options, onSegment, onSentence) {
|
|
|
121
123
|
const sentenceTimelineWithOffset = addTimeOffsetToTimeline(sentenceTimeline, sentenceStartTime);
|
|
122
124
|
const sentenceEndTime = timeOffset - endPause;
|
|
123
125
|
segmentEntry.timeline.push({
|
|
124
|
-
type:
|
|
126
|
+
type: 'sentence',
|
|
125
127
|
text: sentenceText,
|
|
126
128
|
startTime: sentenceStartTime,
|
|
127
129
|
endTime: sentenceEndTime,
|
|
128
130
|
timeline: sentenceTimelineWithOffset
|
|
129
131
|
});
|
|
130
|
-
peakDecibelsSoFar = Math.max(peakDecibelsSoFar,
|
|
132
|
+
peakDecibelsSoFar = Math.max(peakDecibelsSoFar, getSamplePeakDecibels(sentenceRawAudio.audioChannels));
|
|
131
133
|
const sentenceAudio = await convertToTargetCodecIfNeeded(sentenceRawAudio);
|
|
132
134
|
if (onSentence) {
|
|
133
135
|
await onSentence({
|
|
@@ -176,7 +178,10 @@ async function synthesizeSegments(segments, options, onSegment, onSentence) {
|
|
|
176
178
|
const joinedAudioBuffers = concatAudioSegments(segmentsRawAudio.map(part => part.audioChannels));
|
|
177
179
|
resultRawAudio = { audioChannels: joinedAudioBuffers, sampleRate: segmentsRawAudio[0].sampleRate };
|
|
178
180
|
if (options.postProcessing.normalizeAudio) {
|
|
179
|
-
resultRawAudio = normalizeAudioLevel(resultRawAudio, options.postProcessing.
|
|
181
|
+
resultRawAudio = normalizeAudioLevel(resultRawAudio, options.postProcessing.targetPeak, options.postProcessing.maxGainIncrease);
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
resultRawAudio = attenuateIfClipping(resultRawAudio);
|
|
180
185
|
}
|
|
181
186
|
}
|
|
182
187
|
else {
|
|
@@ -187,8 +192,8 @@ async function synthesizeSegments(segments, options, onSegment, onSentence) {
|
|
|
187
192
|
let output;
|
|
188
193
|
if (targetCodec) {
|
|
189
194
|
logger.start(`Convert to ${targetCodec} codec`);
|
|
190
|
-
if (targetCodec ==
|
|
191
|
-
output =
|
|
195
|
+
if (targetCodec == 'wav') {
|
|
196
|
+
output = encodeRawAudioToWave(rawAudio);
|
|
192
197
|
}
|
|
193
198
|
else {
|
|
194
199
|
const ffmpegOptions = FFMpegTranscoder.getDefaultFFMpegOptionsForSpeech(targetCodec, options.outputAudioFormat?.bitrate);
|
|
@@ -212,13 +217,13 @@ async function synthesizeSegments(segments, options, onSegment, onSentence) {
|
|
|
212
217
|
async function synthesizeSegment(text, options) {
|
|
213
218
|
const logger = new Logger();
|
|
214
219
|
const startTimestamp = logger.getTimestamp();
|
|
215
|
-
logger.start(
|
|
220
|
+
logger.start('Prepare for synthesis');
|
|
216
221
|
const simplifiedText = simplifyPunctuationCharacters(text);
|
|
217
222
|
const engine = options.engine;
|
|
218
223
|
logger.start(`Get voice list for ${engine}`);
|
|
219
224
|
const { bestMatchingVoice } = await requestVoiceList(options);
|
|
220
225
|
if (!bestMatchingVoice) {
|
|
221
|
-
throw new Error(
|
|
226
|
+
throw new Error('No matching voice found');
|
|
222
227
|
}
|
|
223
228
|
const selectedVoice = bestMatchingVoice;
|
|
224
229
|
let voicePackagePath;
|
|
@@ -238,22 +243,22 @@ async function synthesizeSegment(text, options) {
|
|
|
238
243
|
let shouldPostprocessSpeed = false;
|
|
239
244
|
let shouldPostprocessPitch = false;
|
|
240
245
|
switch (engine) {
|
|
241
|
-
case
|
|
246
|
+
case 'vits': {
|
|
242
247
|
if (inputIsSSML) {
|
|
243
248
|
throw new Error(`The VITS engine doesn't currently support SSML inputs`);
|
|
244
249
|
}
|
|
245
250
|
let vitsLanguage = language;
|
|
246
|
-
if (vitsLanguage ==
|
|
247
|
-
vitsLanguage =
|
|
251
|
+
if (vitsLanguage == 'en') {
|
|
252
|
+
vitsLanguage = 'en-us';
|
|
248
253
|
}
|
|
249
|
-
const vitsTTS = await import(
|
|
254
|
+
const vitsTTS = await import('../synthesis/VitsTTS.js');
|
|
250
255
|
const lengthScale = 1 / speed;
|
|
251
256
|
const engineOptions = options.vits;
|
|
252
257
|
const speakerId = engineOptions.speakerId;
|
|
253
258
|
if (speakerId != undefined) {
|
|
254
259
|
if (selectedVoice.speakerCount == undefined) {
|
|
255
260
|
if (speakerId != 0) {
|
|
256
|
-
throw new Error(
|
|
261
|
+
throw new Error('Selected VITS model has only one speaker. Speaker ID must be 0 if specified.');
|
|
257
262
|
}
|
|
258
263
|
}
|
|
259
264
|
else if (speakerId < 0 || speakerId >= selectedVoice.speakerCount) {
|
|
@@ -270,15 +275,15 @@ async function synthesizeSegment(text, options) {
|
|
|
270
275
|
logger.end();
|
|
271
276
|
break;
|
|
272
277
|
}
|
|
273
|
-
case
|
|
278
|
+
case 'pico': {
|
|
274
279
|
if (inputIsSSML) {
|
|
275
280
|
throw new Error(`The SVOX Pico engine doesn't currently support SSML inputs`);
|
|
276
281
|
}
|
|
277
|
-
const SvoxPicoTTS = await import(
|
|
282
|
+
const SvoxPicoTTS = await import('../synthesis/SvoxPicoTTS.js');
|
|
278
283
|
const picoSpeed = Math.round(speed * 1.0 * 100);
|
|
279
284
|
const picoPitch = Math.round(pitch * 1.0 * 100);
|
|
280
285
|
const picoVolume = 35.0;
|
|
281
|
-
const preparedText = `<speed level=
|
|
286
|
+
const preparedText = `<speed level='${picoSpeed}'><pitch level='${picoPitch}'><volume level='${picoVolume}'>${simplifiedText}</volume></pitch></speed>`;
|
|
282
287
|
logger.end();
|
|
283
288
|
const { textAnalysisFilename, signalGenerationFilename } = SvoxPicoTTS.getResourceFilenamesForLanguage(language);
|
|
284
289
|
const resourceFilePath = path.resolve(voicePackagePath, textAnalysisFilename);
|
|
@@ -287,19 +292,19 @@ async function synthesizeSegment(text, options) {
|
|
|
287
292
|
synthesizedAudio = rawAudio;
|
|
288
293
|
break;
|
|
289
294
|
}
|
|
290
|
-
case
|
|
295
|
+
case 'flite': {
|
|
291
296
|
if (inputIsSSML) {
|
|
292
297
|
throw new Error(`The Flite engine doesn't currently support SSML inputs`);
|
|
293
298
|
}
|
|
294
|
-
const FliteTTS = await import(
|
|
299
|
+
const FliteTTS = await import('../synthesis/FliteTTS.js');
|
|
295
300
|
logger.end();
|
|
296
301
|
const { rawAudio, events } = await FliteTTS.synthesize(simplifiedText, voice, voicePackagePath, speed);
|
|
297
302
|
synthesizedAudio = rawAudio;
|
|
298
303
|
shouldPostprocessPitch = true;
|
|
299
304
|
break;
|
|
300
305
|
}
|
|
301
|
-
case
|
|
302
|
-
const EspeakTTS = await import(
|
|
306
|
+
case 'espeak': {
|
|
307
|
+
const EspeakTTS = await import('../synthesis/EspeakTTS.js');
|
|
303
308
|
const engineOptions = options.espeak;
|
|
304
309
|
const espeakVoice = voice;
|
|
305
310
|
const espeakLanguage = selectedVoice.languages[0];
|
|
@@ -307,13 +312,15 @@ async function synthesizeSegment(text, options) {
|
|
|
307
312
|
const espeakPitch = engineOptions.pitch || options.pitch * 50;
|
|
308
313
|
const espeakPitchRange = engineOptions.pitchRange || options.pitchVariation * 50;
|
|
309
314
|
const espeakUseKlatt = engineOptions.useKlatt || false;
|
|
315
|
+
const espeakInsertSeparators = engineOptions.insertSeparators || false;
|
|
310
316
|
const espeakOptions = {
|
|
311
317
|
voice: espeakVoice,
|
|
312
318
|
ssml: inputIsSSML,
|
|
313
319
|
rate: espeakRate,
|
|
314
320
|
pitch: espeakPitch,
|
|
315
321
|
pitchRange: espeakPitchRange,
|
|
316
|
-
useKlatt: espeakUseKlatt
|
|
322
|
+
useKlatt: espeakUseKlatt,
|
|
323
|
+
insertSeparators: espeakInsertSeparators,
|
|
317
324
|
};
|
|
318
325
|
if (inputIsSSML) {
|
|
319
326
|
logger.end();
|
|
@@ -329,11 +336,11 @@ async function synthesizeSegment(text, options) {
|
|
|
329
336
|
}
|
|
330
337
|
break;
|
|
331
338
|
}
|
|
332
|
-
case
|
|
339
|
+
case 'sam': {
|
|
333
340
|
if (inputIsSSML) {
|
|
334
341
|
throw new Error(`The SAM engine doesn't support SSML inputs`);
|
|
335
342
|
}
|
|
336
|
-
const SamTTS = await import(
|
|
343
|
+
const SamTTS = await import('../synthesis/SamTTS.js');
|
|
337
344
|
const engineOptions = options.sam;
|
|
338
345
|
const samPitch = clip(engineOptions.pitch || Math.round((1 / pitch) * 64), 0, 255);
|
|
339
346
|
const samSpeed = clip(engineOptions.speed || Math.round((1 / speed) * 72), 0, 255);
|
|
@@ -344,11 +351,11 @@ async function synthesizeSegment(text, options) {
|
|
|
344
351
|
synthesizedAudio = rawAudio;
|
|
345
352
|
break;
|
|
346
353
|
}
|
|
347
|
-
case
|
|
354
|
+
case 'sapi': {
|
|
348
355
|
if (inputIsSSML) {
|
|
349
356
|
throw new Error(`The SAPI engine doesn't currently support SSML inputs`);
|
|
350
357
|
}
|
|
351
|
-
const SapiTTS = await import(
|
|
358
|
+
const SapiTTS = await import('../synthesis/SapiTTS.js');
|
|
352
359
|
await SapiTTS.AssertSAPIAvailable(false);
|
|
353
360
|
const engineOptions = options.sapi;
|
|
354
361
|
const sapiRate = engineOptions.rate || 0;
|
|
@@ -360,11 +367,11 @@ async function synthesizeSegment(text, options) {
|
|
|
360
367
|
shouldPostprocessPitch = true;
|
|
361
368
|
break;
|
|
362
369
|
}
|
|
363
|
-
case
|
|
370
|
+
case 'msspeech': {
|
|
364
371
|
if (inputIsSSML) {
|
|
365
372
|
throw new Error(`The MSSpeech engine doesn't currently support SSML inputs`);
|
|
366
373
|
}
|
|
367
|
-
const SapiTTS = await import(
|
|
374
|
+
const SapiTTS = await import('../synthesis/SapiTTS.js');
|
|
368
375
|
await SapiTTS.AssertSAPIAvailable(true);
|
|
369
376
|
const engineOptions = options.msspeech;
|
|
370
377
|
const sapiRate = engineOptions.rate || 0;
|
|
@@ -376,11 +383,11 @@ async function synthesizeSegment(text, options) {
|
|
|
376
383
|
shouldPostprocessPitch = true;
|
|
377
384
|
break;
|
|
378
385
|
}
|
|
379
|
-
case
|
|
386
|
+
case 'coqui-server': {
|
|
380
387
|
if (inputIsSSML) {
|
|
381
388
|
throw new Error(`The Coqui Server engine doesn't support SSML inputs`);
|
|
382
389
|
}
|
|
383
|
-
const CoquiServerTTS = await import(
|
|
390
|
+
const CoquiServerTTS = await import('../synthesis/CoquiServerTTS.js');
|
|
384
391
|
const engineOptions = options.coquiServer;
|
|
385
392
|
const speakerId = engineOptions.speakerId;
|
|
386
393
|
const serverUrl = engineOptions.serverUrl;
|
|
@@ -394,12 +401,12 @@ async function synthesizeSegment(text, options) {
|
|
|
394
401
|
shouldPostprocessPitch = true;
|
|
395
402
|
break;
|
|
396
403
|
}
|
|
397
|
-
case
|
|
398
|
-
const GoogleCloudTTS = await import(
|
|
404
|
+
case 'google-cloud': {
|
|
405
|
+
const GoogleCloudTTS = await import('../synthesis/GoogleCloudTTS.js');
|
|
399
406
|
const engineOptions = options.googleCloud;
|
|
400
407
|
const apiKey = engineOptions.apiKey;
|
|
401
408
|
if (!apiKey) {
|
|
402
|
-
throw new Error(`No API key
|
|
409
|
+
throw new Error(`No Google Cloud API key provided`);
|
|
403
410
|
}
|
|
404
411
|
let pitchDeltaSemitones;
|
|
405
412
|
// 1 semitone up = multiply by 1.05946
|
|
@@ -419,16 +426,16 @@ async function synthesizeSegment(text, options) {
|
|
|
419
426
|
synthesizedAudio = rawAudio;
|
|
420
427
|
break;
|
|
421
428
|
}
|
|
422
|
-
case
|
|
423
|
-
const AzureCognitiveServicesTTS = await import(
|
|
429
|
+
case 'microsoft-azure': {
|
|
430
|
+
const AzureCognitiveServicesTTS = await import('../synthesis/AzureCognitiveServicesTTS.js');
|
|
424
431
|
const engineOptions = options.microsoftAzure;
|
|
425
432
|
const subscriptionKey = engineOptions.subscriptionKey;
|
|
426
433
|
if (!subscriptionKey) {
|
|
427
|
-
throw new Error(`No subscription key
|
|
434
|
+
throw new Error(`No Microsoft Azure subscription key provided`);
|
|
428
435
|
}
|
|
429
436
|
const serviceRegion = engineOptions.serviceRegion;
|
|
430
437
|
if (!serviceRegion) {
|
|
431
|
-
throw new Error(`No service region
|
|
438
|
+
throw new Error(`No Microsoft Azure service region provided`);
|
|
432
439
|
}
|
|
433
440
|
let ssmlPitch;
|
|
434
441
|
if (engineOptions.pitchDeltaHz != undefined) {
|
|
@@ -449,20 +456,20 @@ async function synthesizeSegment(text, options) {
|
|
|
449
456
|
timeline = outTimeline;
|
|
450
457
|
break;
|
|
451
458
|
}
|
|
452
|
-
case
|
|
453
|
-
const AwsPollyTTS = await import(
|
|
459
|
+
case 'amazon-polly': {
|
|
460
|
+
const AwsPollyTTS = await import('../synthesis/AwsPollyTTS.js');
|
|
454
461
|
const engineOptions = options.amazonPolly;
|
|
455
462
|
const region = engineOptions.region;
|
|
456
463
|
if (!region) {
|
|
457
|
-
throw new Error(`No region
|
|
464
|
+
throw new Error(`No Amazon Polly region provided`);
|
|
458
465
|
}
|
|
459
466
|
const accessKeyId = engineOptions.accessKeyId;
|
|
460
467
|
if (!accessKeyId) {
|
|
461
|
-
throw new Error(`No access key id
|
|
468
|
+
throw new Error(`No Amazon Polly access key id provided`);
|
|
462
469
|
}
|
|
463
470
|
const secretAccessKey = engineOptions.secretAccessKey;
|
|
464
471
|
if (!secretAccessKey) {
|
|
465
|
-
throw new Error(`No secret access key
|
|
472
|
+
throw new Error(`No Amazon Polly secret access key provided`);
|
|
466
473
|
}
|
|
467
474
|
const pollyEngine = engineOptions.pollyEngine;
|
|
468
475
|
const lexiconNames = engineOptions.lexiconNames;
|
|
@@ -473,32 +480,41 @@ async function synthesizeSegment(text, options) {
|
|
|
473
480
|
shouldPostprocessPitch = true;
|
|
474
481
|
break;
|
|
475
482
|
}
|
|
476
|
-
case
|
|
483
|
+
case 'openai-cloud': {
|
|
484
|
+
const OpenAICloudTTS = await import('../synthesis/OpenAICloudTTS.js');
|
|
485
|
+
const openAICloudTTSOptions = options.openAICloud;
|
|
486
|
+
if (!openAICloudTTSOptions.apiKey) {
|
|
487
|
+
throw new Error(`No API key given`);
|
|
488
|
+
}
|
|
489
|
+
logger.end();
|
|
490
|
+
synthesizedAudio = await OpenAICloudTTS.synthesize(text, voice, speed, openAICloudTTSOptions);
|
|
491
|
+
shouldPostprocessSpeed = false;
|
|
492
|
+
shouldPostprocessPitch = true;
|
|
493
|
+
break;
|
|
494
|
+
}
|
|
495
|
+
case 'elevenlabs': {
|
|
477
496
|
if (inputIsSSML) {
|
|
478
497
|
throw new Error(`The Elevenlabs engine doesn't support SSML inputs`);
|
|
479
498
|
}
|
|
480
|
-
const ElevenLabsTTS = await import(
|
|
499
|
+
const ElevenLabsTTS = await import('../synthesis/ElevenlabsTTS.js');
|
|
481
500
|
const engineOptions = options.elevenlabs;
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
throw new Error(`No ElevenLabs API key given`);
|
|
501
|
+
if (!engineOptions.apiKey) {
|
|
502
|
+
throw new Error(`No ElevenLabs API key provided`);
|
|
485
503
|
}
|
|
486
|
-
const voiceId = selectedVoice[
|
|
487
|
-
const modelId = selectedVoice[
|
|
488
|
-
const stability = engineOptions.stability;
|
|
489
|
-
const similarityBoost = engineOptions.similarityBoost;
|
|
504
|
+
const voiceId = selectedVoice['elevenLabsVoiceId'];
|
|
505
|
+
const modelId = selectedVoice['elevenLabsModelId'];
|
|
490
506
|
logger.end();
|
|
491
|
-
const { rawAudio } = await ElevenLabsTTS.synthesize(text, voiceId,
|
|
507
|
+
const { rawAudio } = await ElevenLabsTTS.synthesize(text, voiceId, modelId, engineOptions);
|
|
492
508
|
synthesizedAudio = rawAudio;
|
|
493
509
|
shouldPostprocessSpeed = true;
|
|
494
510
|
shouldPostprocessPitch = true;
|
|
495
511
|
break;
|
|
496
512
|
}
|
|
497
|
-
case
|
|
513
|
+
case 'google-translate': {
|
|
498
514
|
if (inputIsSSML) {
|
|
499
515
|
throw new Error(`The Google Translate engine doesn't support SSML inputs`);
|
|
500
516
|
}
|
|
501
|
-
const GoogleTranslateTTS = await import(
|
|
517
|
+
const GoogleTranslateTTS = await import('../synthesis/GoogleTranslateTTS.js');
|
|
502
518
|
logger.end();
|
|
503
519
|
const { rawAudio, timeline: segmentTimeline } = await runOperationWithRetries(() => GoogleTranslateTTS.synthesizeLongText(text, language, options.googleTranslate?.tld, options.sentenceEndPause, options.segmentEndPause), logger);
|
|
504
520
|
synthesizedAudio = rawAudio;
|
|
@@ -509,18 +525,18 @@ async function synthesizeSegment(text, options) {
|
|
|
509
525
|
shouldPostprocessPitch = true;
|
|
510
526
|
break;
|
|
511
527
|
}
|
|
512
|
-
case
|
|
528
|
+
case 'microsoft-edge': {
|
|
513
529
|
if (inputIsSSML) {
|
|
514
530
|
throw new Error(`The Microsoft Edge engine doesn't support SSML inputs`);
|
|
515
531
|
}
|
|
516
|
-
const MicrosoftEdgeTTS = await import(
|
|
532
|
+
const MicrosoftEdgeTTS = await import('../synthesis/MicrosoftEdgeTTS.js');
|
|
517
533
|
const engineOptions = options.microsoftEdge;
|
|
518
534
|
const trustedClientToken = engineOptions.trustedClientToken;
|
|
519
535
|
if (!trustedClientToken) {
|
|
520
|
-
throw new Error(
|
|
536
|
+
throw new Error('No Microsoft Edge trusted client token provided');
|
|
521
537
|
}
|
|
522
|
-
if (await sha256AsHex(trustedClientToken) !=
|
|
523
|
-
throw new Error(
|
|
538
|
+
if (await sha256AsHex(trustedClientToken) != '558d7c6a7f7db444895946fe23a54ad172fd6d159f46cb34dd4db21bb27c07d7') {
|
|
539
|
+
throw new Error('Trusted client token is incorrect.');
|
|
524
540
|
}
|
|
525
541
|
let ssmlPitch;
|
|
526
542
|
if (engineOptions.pitchDeltaHz != undefined) {
|
|
@@ -541,11 +557,11 @@ async function synthesizeSegment(text, options) {
|
|
|
541
557
|
timeline = edgeTimeline;
|
|
542
558
|
break;
|
|
543
559
|
}
|
|
544
|
-
case
|
|
560
|
+
case 'streamlabs-polly': {
|
|
545
561
|
if (inputIsSSML) {
|
|
546
562
|
throw new Error(`The Streamlabs Polly Engine engine doesn't support SSML inputs`);
|
|
547
563
|
}
|
|
548
|
-
const StreamlabsPollyTTS = await import(
|
|
564
|
+
const StreamlabsPollyTTS = await import('../synthesis/StreamlabsPollyTTS.js');
|
|
549
565
|
logger.end();
|
|
550
566
|
const { rawAudio, timeline: segmentTimeline } = await StreamlabsPollyTTS.synthesizeLongText(text, voice, language, options.sentenceEndPause, options.segmentEndPause);
|
|
551
567
|
synthesizedAudio = rawAudio;
|
|
@@ -560,10 +576,13 @@ async function synthesizeSegment(text, options) {
|
|
|
560
576
|
throw new Error(`Engine '${options.engine}' is not supported`);
|
|
561
577
|
}
|
|
562
578
|
}
|
|
563
|
-
logger.start(
|
|
579
|
+
logger.start('Postprocess synthesized audio');
|
|
564
580
|
synthesizedAudio = downmixToMono(synthesizedAudio);
|
|
565
581
|
if (options.postProcessing.normalizeAudio) {
|
|
566
|
-
synthesizedAudio = normalizeAudioLevel(synthesizedAudio, options.postProcessing.
|
|
582
|
+
synthesizedAudio = normalizeAudioLevel(synthesizedAudio, options.postProcessing.targetPeak, options.postProcessing.maxGainIncrease);
|
|
583
|
+
}
|
|
584
|
+
else {
|
|
585
|
+
synthesizedAudio = attenuateIfClipping(synthesizedAudio);
|
|
567
586
|
}
|
|
568
587
|
const preTrimSampleCount = synthesizedAudio.audioChannels[0].length;
|
|
569
588
|
synthesizedAudio.audioChannels[0] = trimAudioStart(synthesizedAudio.audioChannels[0]);
|
|
@@ -573,7 +592,7 @@ async function synthesizeSegment(text, options) {
|
|
|
573
592
|
timeline = addTimeOffsetToTimeline(timeline, newDuration - oldDuration);
|
|
574
593
|
}
|
|
575
594
|
if (!timeline) {
|
|
576
|
-
logger.start(
|
|
595
|
+
logger.start('Align synthesized audio with text');
|
|
577
596
|
let plainText = text;
|
|
578
597
|
if (inputIsSSML) {
|
|
579
598
|
plainText = await convertHtmlToText(text);
|
|
@@ -600,15 +619,15 @@ async function synthesizeSegment(text, options) {
|
|
|
600
619
|
pitchShiftFactor = pitch;
|
|
601
620
|
}
|
|
602
621
|
if ((timeStretchFactor != undefined && timeStretchFactor != 1.0) || (pitchShiftFactor != undefined && pitchShiftFactor != 1.0)) {
|
|
603
|
-
logger.start(
|
|
622
|
+
logger.start('Apply time and pitch shifting');
|
|
604
623
|
timeStretchFactor = timeStretchFactor || 1.0;
|
|
605
624
|
pitchShiftFactor = pitchShiftFactor || 1.0;
|
|
606
625
|
const timePitchShiftingMethod = postProcessingOptions.timePitchShiftingMethod;
|
|
607
|
-
if (timePitchShiftingMethod ==
|
|
626
|
+
if (timePitchShiftingMethod == 'sonic') {
|
|
608
627
|
const sonic = await import('../dsp/Sonic.js');
|
|
609
628
|
synthesizedAudio = await sonic.stretchTimePitch(synthesizedAudio, timeStretchFactor, pitchShiftFactor);
|
|
610
629
|
}
|
|
611
|
-
else if (timePitchShiftingMethod ==
|
|
630
|
+
else if (timePitchShiftingMethod == 'rubberband') {
|
|
612
631
|
const rubberband = await import('../dsp/Rubberband.js');
|
|
613
632
|
const rubberbandOptions = extendDeep(rubberband.defaultRubberbandOptions, postProcessingOptions.rubberband || {});
|
|
614
633
|
synthesizedAudio = await rubberband.stretchTimePitch(synthesizedAudio, timeStretchFactor, pitchShiftFactor, rubberbandOptions);
|
|
@@ -639,11 +658,11 @@ function convertSpeedScaleToSSMLValueString(rate) {
|
|
|
639
658
|
}
|
|
640
659
|
function convertPitchScaleToSSMLValueString(pitch, voiceGender) {
|
|
641
660
|
let fundementalFrequency;
|
|
642
|
-
if (voiceGender ==
|
|
661
|
+
if (voiceGender == 'male') {
|
|
643
662
|
// Use an estimate of the average male voice fundemental frequency
|
|
644
663
|
fundementalFrequency = 120;
|
|
645
664
|
}
|
|
646
|
-
else if (voiceGender ==
|
|
665
|
+
else if (voiceGender == 'female') {
|
|
647
666
|
// Use an estimate of the average female voice fundemental frequency
|
|
648
667
|
fundementalFrequency = 210;
|
|
649
668
|
}
|
|
@@ -679,18 +698,18 @@ export const defaultSynthesisOptions = {
|
|
|
679
698
|
whitespace: 'collapse'
|
|
680
699
|
},
|
|
681
700
|
alignment: {
|
|
682
|
-
engine:
|
|
701
|
+
engine: 'dtw',
|
|
683
702
|
dtw: {
|
|
684
703
|
granularity: 'high'
|
|
685
704
|
}
|
|
686
705
|
},
|
|
687
706
|
postProcessing: {
|
|
688
707
|
normalizeAudio: true,
|
|
689
|
-
|
|
690
|
-
|
|
708
|
+
targetPeak: -3,
|
|
709
|
+
maxGainIncrease: 30,
|
|
691
710
|
speed: undefined,
|
|
692
711
|
pitch: undefined,
|
|
693
|
-
timePitchShiftingMethod:
|
|
712
|
+
timePitchShiftingMethod: 'sonic',
|
|
694
713
|
rubberband: {}
|
|
695
714
|
},
|
|
696
715
|
outputAudioFormat: undefined,
|
|
@@ -720,7 +739,7 @@ export const defaultSynthesisOptions = {
|
|
|
720
739
|
rate: 0,
|
|
721
740
|
},
|
|
722
741
|
coquiServer: {
|
|
723
|
-
serverUrl:
|
|
742
|
+
serverUrl: 'http://[::1]:5002',
|
|
724
743
|
speakerId: null
|
|
725
744
|
},
|
|
726
745
|
googleCloud: {
|
|
@@ -740,13 +759,10 @@ export const defaultSynthesisOptions = {
|
|
|
740
759
|
pollyEngine: undefined,
|
|
741
760
|
lexiconNames: undefined,
|
|
742
761
|
},
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
stability: 0.5,
|
|
746
|
-
similarityBoost: 0.5,
|
|
747
|
-
},
|
|
762
|
+
openAICloud: defaultOpenAICloudTTSOptions,
|
|
763
|
+
elevenlabs: defaultElevenlabsTTSOptions,
|
|
748
764
|
googleTranslate: {
|
|
749
|
-
tld:
|
|
765
|
+
tld: 'us'
|
|
750
766
|
},
|
|
751
767
|
microsoftEdge: {
|
|
752
768
|
trustedClientToken: undefined,
|
|
@@ -771,8 +787,8 @@ export async function requestVoiceList(options) {
|
|
|
771
787
|
async function loadVoiceList() {
|
|
772
788
|
let voiceList = [];
|
|
773
789
|
switch (options.engine) {
|
|
774
|
-
case
|
|
775
|
-
const EspeakTTS = await import(
|
|
790
|
+
case 'espeak': {
|
|
791
|
+
const EspeakTTS = await import('../synthesis/EspeakTTS.js');
|
|
776
792
|
const voices = await EspeakTTS.listVoices();
|
|
777
793
|
voiceList = voices.map(voice => {
|
|
778
794
|
const languages = voice.languages.map(lang => normalizeLanguageCode(lang.name));
|
|
@@ -785,61 +801,61 @@ export async function requestVoiceList(options) {
|
|
|
785
801
|
return {
|
|
786
802
|
name: voice.identifier,
|
|
787
803
|
languages,
|
|
788
|
-
gender:
|
|
804
|
+
gender: 'male'
|
|
789
805
|
};
|
|
790
806
|
});
|
|
791
807
|
break;
|
|
792
808
|
}
|
|
793
|
-
case
|
|
794
|
-
const FliteTTS = await import(
|
|
809
|
+
case 'flite': {
|
|
810
|
+
const FliteTTS = await import('../synthesis/FliteTTS.js');
|
|
795
811
|
voiceList = deepClone(FliteTTS.voiceList);
|
|
796
812
|
break;
|
|
797
813
|
}
|
|
798
|
-
case
|
|
799
|
-
const SvoxPicoTTS = await import(
|
|
814
|
+
case 'pico': {
|
|
815
|
+
const SvoxPicoTTS = await import('../synthesis/SvoxPicoTTS.js');
|
|
800
816
|
voiceList = SvoxPicoTTS.voiceList;
|
|
801
817
|
break;
|
|
802
818
|
}
|
|
803
|
-
case
|
|
819
|
+
case 'sam': {
|
|
804
820
|
voiceList.push({
|
|
805
|
-
name:
|
|
806
|
-
languages: [
|
|
807
|
-
gender:
|
|
821
|
+
name: 'sam',
|
|
822
|
+
languages: ['en-US', 'en'],
|
|
823
|
+
gender: 'male'
|
|
808
824
|
});
|
|
809
825
|
break;
|
|
810
826
|
}
|
|
811
|
-
case
|
|
812
|
-
const VitsTTS = await import(
|
|
827
|
+
case 'vits': {
|
|
828
|
+
const VitsTTS = await import('../synthesis/VitsTTS.js');
|
|
813
829
|
voiceList = VitsTTS.voiceList.map(entry => {
|
|
814
830
|
return { ...entry, packageName: `vits-${entry.name}` };
|
|
815
831
|
});
|
|
816
832
|
break;
|
|
817
833
|
}
|
|
818
|
-
case
|
|
819
|
-
const SapiTTS = await import(
|
|
834
|
+
case 'sapi': {
|
|
835
|
+
const SapiTTS = await import('../synthesis/SapiTTS.js');
|
|
820
836
|
await SapiTTS.AssertSAPIAvailable(false);
|
|
821
837
|
voiceList = await SapiTTS.getVoiceList(false);
|
|
822
838
|
break;
|
|
823
839
|
}
|
|
824
|
-
case
|
|
825
|
-
const SapiTTS = await import(
|
|
840
|
+
case 'msspeech': {
|
|
841
|
+
const SapiTTS = await import('../synthesis/SapiTTS.js');
|
|
826
842
|
await SapiTTS.AssertSAPIAvailable(true);
|
|
827
843
|
voiceList = await SapiTTS.getVoiceList(true);
|
|
828
844
|
break;
|
|
829
845
|
}
|
|
830
|
-
case
|
|
846
|
+
case 'coqui-server': {
|
|
831
847
|
voiceList = [{
|
|
832
|
-
name:
|
|
833
|
-
languages: [
|
|
834
|
-
gender:
|
|
848
|
+
name: 'coqui',
|
|
849
|
+
languages: ['en-US'],
|
|
850
|
+
gender: 'unknown'
|
|
835
851
|
}];
|
|
836
852
|
break;
|
|
837
853
|
}
|
|
838
|
-
case
|
|
839
|
-
const GoogleCloudTTS = await import(
|
|
854
|
+
case 'google-cloud': {
|
|
855
|
+
const GoogleCloudTTS = await import('../synthesis/GoogleCloudTTS.js');
|
|
840
856
|
const apiKey = options.googleCloud.apiKey;
|
|
841
857
|
if (!apiKey) {
|
|
842
|
-
throw new Error(`No API key
|
|
858
|
+
throw new Error(`No Google Cloud API key provided`);
|
|
843
859
|
}
|
|
844
860
|
const voices = await GoogleCloudTTS.getVoiceList(apiKey);
|
|
845
861
|
voiceList = voices.map(voice => ({
|
|
@@ -849,39 +865,39 @@ export async function requestVoiceList(options) {
|
|
|
849
865
|
}));
|
|
850
866
|
break;
|
|
851
867
|
}
|
|
852
|
-
case
|
|
853
|
-
const AzureCognitiveServicesTTS = await import(
|
|
868
|
+
case 'microsoft-azure': {
|
|
869
|
+
const AzureCognitiveServicesTTS = await import('../synthesis/AzureCognitiveServicesTTS.js');
|
|
854
870
|
const subscriptionKey = options.microsoftAzure.subscriptionKey;
|
|
855
871
|
if (!subscriptionKey) {
|
|
856
|
-
throw new Error(`No subscription key
|
|
872
|
+
throw new Error(`No Microsoft Azure subscription key provided`);
|
|
857
873
|
}
|
|
858
874
|
const serviceRegion = options.microsoftAzure.serviceRegion;
|
|
859
875
|
if (!serviceRegion) {
|
|
860
|
-
throw new Error(`No service region
|
|
876
|
+
throw new Error(`No Microsoft Azure service region provided`);
|
|
861
877
|
}
|
|
862
878
|
const voices = await AzureCognitiveServicesTTS.getVoiceList(subscriptionKey, serviceRegion);
|
|
863
879
|
for (const voice of voices) {
|
|
864
880
|
voiceList.push({
|
|
865
881
|
name: voice.name,
|
|
866
882
|
languages: [normalizeLanguageCode(voice.locale), getShortLanguageCode(voice.locale)],
|
|
867
|
-
gender: voice.gender == 1 ?
|
|
883
|
+
gender: voice.gender == 1 ? 'female' : 'male'
|
|
868
884
|
});
|
|
869
885
|
}
|
|
870
886
|
break;
|
|
871
887
|
}
|
|
872
|
-
case
|
|
873
|
-
const AwsPollyTTS = await import(
|
|
888
|
+
case 'amazon-polly': {
|
|
889
|
+
const AwsPollyTTS = await import('../synthesis/AwsPollyTTS.js');
|
|
874
890
|
const region = options.amazonPolly.region;
|
|
875
891
|
if (!region) {
|
|
876
|
-
throw new Error(`No region
|
|
892
|
+
throw new Error(`No Amazon Polly region provided`);
|
|
877
893
|
}
|
|
878
894
|
const accessKeyId = options.amazonPolly.accessKeyId;
|
|
879
895
|
if (!accessKeyId) {
|
|
880
|
-
throw new Error(`No access key id
|
|
896
|
+
throw new Error(`No Amazon Polly access key id provided`);
|
|
881
897
|
}
|
|
882
898
|
const secretAccessKey = options.amazonPolly.secretAccessKey;
|
|
883
899
|
if (!secretAccessKey) {
|
|
884
|
-
throw new Error(`No secret access key
|
|
900
|
+
throw new Error(`No Amazon Polly secret access key provided`);
|
|
885
901
|
}
|
|
886
902
|
const voices = await AwsPollyTTS.getVoiceList(region, accessKeyId, secretAccessKey);
|
|
887
903
|
for (const voice of voices) {
|
|
@@ -900,44 +916,49 @@ export async function requestVoiceList(options) {
|
|
|
900
916
|
}
|
|
901
917
|
break;
|
|
902
918
|
}
|
|
903
|
-
case
|
|
904
|
-
const
|
|
919
|
+
case 'openai-cloud': {
|
|
920
|
+
const OpenAICloudTTS = await import('../synthesis/OpenAICloudTTS.js');
|
|
921
|
+
voiceList = OpenAICloudTTS.voiceList;
|
|
922
|
+
break;
|
|
923
|
+
}
|
|
924
|
+
case 'elevenlabs': {
|
|
925
|
+
const ElevenLabsTTS = await import('../synthesis/ElevenlabsTTS.js');
|
|
905
926
|
const engineOptions = options.elevenlabs;
|
|
906
927
|
const apiKey = engineOptions.apiKey;
|
|
907
928
|
if (!apiKey) {
|
|
908
|
-
throw new Error(`No Elevenlabs API key
|
|
929
|
+
throw new Error(`No Elevenlabs API key provided`);
|
|
909
930
|
}
|
|
910
931
|
voiceList = await ElevenLabsTTS.getVoiceList(apiKey);
|
|
911
932
|
break;
|
|
912
933
|
}
|
|
913
|
-
case
|
|
914
|
-
const GoogleTranslateTTS = await import(
|
|
934
|
+
case 'google-translate': {
|
|
935
|
+
const GoogleTranslateTTS = await import('../synthesis/GoogleTranslateTTS.js');
|
|
915
936
|
const langLookup = GoogleTranslateTTS.supportedLanguageLookup;
|
|
916
937
|
for (const langCode in langLookup) {
|
|
917
938
|
voiceList.push({
|
|
918
939
|
name: langLookup[langCode],
|
|
919
|
-
languages: langCode.includes(
|
|
920
|
-
gender:
|
|
940
|
+
languages: langCode.includes('-') ? [normalizeLanguageCode(langCode), getShortLanguageCode(langCode)] : [normalizeLanguageCode(langCode)],
|
|
941
|
+
gender: 'unknown'
|
|
921
942
|
});
|
|
922
943
|
}
|
|
923
944
|
break;
|
|
924
945
|
}
|
|
925
|
-
case
|
|
926
|
-
const MicrosoftEdgeTTS = await import(
|
|
946
|
+
case 'microsoft-edge': {
|
|
947
|
+
const MicrosoftEdgeTTS = await import('../synthesis/MicrosoftEdgeTTS.js');
|
|
927
948
|
const trustedClientToken = options.microsoftEdge?.trustedClientToken;
|
|
928
949
|
if (!trustedClientToken) {
|
|
929
|
-
throw new Error(
|
|
950
|
+
throw new Error('No Microsoft Edge trusted client token provided');
|
|
930
951
|
}
|
|
931
952
|
const voices = await runOperationWithRetries(() => MicrosoftEdgeTTS.getVoiceList(trustedClientToken), logger);
|
|
932
953
|
voiceList = voices.map((voice) => ({
|
|
933
954
|
name: voice.Name,
|
|
934
955
|
languages: [normalizeLanguageCode(voice.Locale), getShortLanguageCode(voice.Locale)],
|
|
935
|
-
gender: voice.Gender ==
|
|
956
|
+
gender: voice.Gender == 'Male' ? 'male' : 'female',
|
|
936
957
|
}));
|
|
937
958
|
break;
|
|
938
959
|
}
|
|
939
|
-
case
|
|
940
|
-
const StreamlabsPollyTTS = await import(
|
|
960
|
+
case 'streamlabs-polly': {
|
|
961
|
+
const StreamlabsPollyTTS = await import('../synthesis/StreamlabsPollyTTS.js');
|
|
941
962
|
voiceList = StreamlabsPollyTTS.voiceList;
|
|
942
963
|
break;
|
|
943
964
|
}
|
|
@@ -954,10 +975,10 @@ export async function requestVoiceList(options) {
|
|
|
954
975
|
else {
|
|
955
976
|
voiceList = await loadVoiceList();
|
|
956
977
|
}
|
|
957
|
-
const languageCode = normalizeLanguageCode(options.language ||
|
|
978
|
+
const languageCode = normalizeLanguageCode(options.language || '');
|
|
958
979
|
if (languageCode) {
|
|
959
980
|
let filteredVoiceList = voiceList.filter(voice => voice.languages.includes(languageCode));
|
|
960
|
-
if (filteredVoiceList.length == 0 && languageCode.includes(
|
|
981
|
+
if (filteredVoiceList.length == 0 && languageCode.includes('-')) {
|
|
961
982
|
const shortLanguageCode = getShortLanguageCode(languageCode);
|
|
962
983
|
filteredVoiceList = voiceList.filter(voice => voice.languages.includes(shortLanguageCode));
|
|
963
984
|
}
|
|
@@ -965,7 +986,7 @@ export async function requestVoiceList(options) {
|
|
|
965
986
|
}
|
|
966
987
|
if (options.voiceGender) {
|
|
967
988
|
const genderLowercase = options.voiceGender.toLowerCase();
|
|
968
|
-
voiceList = voiceList.filter(voice => voice.gender == genderLowercase || voice.gender ==
|
|
989
|
+
voiceList = voiceList.filter(voice => voice.gender == genderLowercase || voice.gender == 'unknown');
|
|
969
990
|
}
|
|
970
991
|
if (options.voice) {
|
|
971
992
|
const namePatternLowerCase = options.voice.toLocaleLowerCase();
|
|
@@ -1000,22 +1021,22 @@ export async function requestVoiceList(options) {
|
|
|
1000
1021
|
}
|
|
1001
1022
|
export async function selectBestOfflineEngineForLanguage(language) {
|
|
1002
1023
|
language = normalizeLanguageCode(language);
|
|
1003
|
-
const VitsTTS = await import(
|
|
1024
|
+
const VitsTTS = await import('../synthesis/VitsTTS.js');
|
|
1004
1025
|
const vitsLanguages = getAllLangCodesFromVoiceList(VitsTTS.voiceList);
|
|
1005
1026
|
if (vitsLanguages.includes(language)) {
|
|
1006
|
-
return
|
|
1027
|
+
return 'vits';
|
|
1007
1028
|
}
|
|
1008
|
-
const FliteTTS = await import(
|
|
1029
|
+
const FliteTTS = await import('../synthesis/FliteTTS.js');
|
|
1009
1030
|
const fliteLanguages = getAllLangCodesFromVoiceList(FliteTTS.voiceList);
|
|
1010
1031
|
if (fliteLanguages.includes(language)) {
|
|
1011
|
-
return
|
|
1032
|
+
return 'flite';
|
|
1012
1033
|
}
|
|
1013
|
-
const SvoxPicoTTS = await import(
|
|
1034
|
+
const SvoxPicoTTS = await import('../synthesis/SvoxPicoTTS.js');
|
|
1014
1035
|
const picoLanguages = getAllLangCodesFromVoiceList(SvoxPicoTTS.voiceList);
|
|
1015
1036
|
if (picoLanguages.includes(language)) {
|
|
1016
|
-
return
|
|
1037
|
+
return 'pico';
|
|
1017
1038
|
}
|
|
1018
|
-
return
|
|
1039
|
+
return 'espeak';
|
|
1019
1040
|
}
|
|
1020
1041
|
export function getAllLangCodesFromVoiceList(voiceList) {
|
|
1021
1042
|
const languageCodes = new Set();
|
|
@@ -1060,13 +1081,13 @@ export const synthesisEngines = [
|
|
|
1060
1081
|
{
|
|
1061
1082
|
id: 'espeak',
|
|
1062
1083
|
name: 'eSpeak NG',
|
|
1063
|
-
description:
|
|
1084
|
+
description: `A lightweight 'robot' sounding formant-based synthesizer.`,
|
|
1064
1085
|
type: 'local'
|
|
1065
1086
|
},
|
|
1066
1087
|
{
|
|
1067
1088
|
id: 'sam',
|
|
1068
1089
|
name: 'SAM (Software Automatic Mouth)',
|
|
1069
|
-
description:
|
|
1090
|
+
description: `A classic 'robot' speech synthesizer from 1982.`,
|
|
1070
1091
|
type: 'local'
|
|
1071
1092
|
},
|
|
1072
1093
|
{
|
|
@@ -1105,6 +1126,12 @@ export const synthesisEngines = [
|
|
|
1105
1126
|
description: 'Amazon Polly (also: AWS Polly) cloud text-to-speech.',
|
|
1106
1127
|
type: 'cloud'
|
|
1107
1128
|
},
|
|
1129
|
+
{
|
|
1130
|
+
id: 'openai-cloud',
|
|
1131
|
+
name: 'OpenAI Cloud',
|
|
1132
|
+
description: 'OpenAI cloud text-to-speech.',
|
|
1133
|
+
type: 'cloud'
|
|
1134
|
+
},
|
|
1108
1135
|
{
|
|
1109
1136
|
id: 'elevenlabs',
|
|
1110
1137
|
name: 'Elevenlabs',
|