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.
Files changed (334) hide show
  1. package/README.md +15 -14
  2. package/data/schemas/options.json +398 -111
  3. package/dist/alignment/DTWMfccSequenceAlignment.d.ts +1 -1
  4. package/dist/alignment/DTWMfccSequenceAlignment.js +8 -8
  5. package/dist/alignment/DTWSequenceAlignment.d.ts +1 -1
  6. package/dist/alignment/DTWSequenceAlignment.js +1 -1
  7. package/dist/alignment/DTWSequenceAlignmentWindowed.d.ts +1 -1
  8. package/dist/alignment/DTWSequenceAlignmentWindowed.js +2 -2
  9. package/dist/alignment/LevenshteinSequenceAlignment.d.ts +1 -1
  10. package/dist/alignment/LevenshteinSequenceAlignment.js +1 -1
  11. package/dist/alignment/SpeechAlignment.d.ts +9 -10
  12. package/dist/alignment/SpeechAlignment.js +136 -105
  13. package/dist/alignment/SpeechAlignment.js.map +1 -1
  14. package/dist/api/API.d.ts +13 -12
  15. package/dist/api/API.js +14 -13
  16. package/dist/api/API.js.map +1 -1
  17. package/dist/api/APIOptions.d.ts +5 -4
  18. package/dist/api/Alignment.d.ts +15 -9
  19. package/dist/api/Alignment.js +88 -74
  20. package/dist/api/Alignment.js.map +1 -1
  21. package/dist/api/Common.js +1 -1
  22. package/dist/api/Denoising.d.ts +6 -6
  23. package/dist/api/Denoising.js +23 -23
  24. package/dist/api/Denoising.js.map +1 -1
  25. package/dist/api/LanguageDetection.d.ts +19 -12
  26. package/dist/api/LanguageDetection.js +88 -38
  27. package/dist/api/LanguageDetection.js.map +1 -1
  28. package/dist/api/Recognition.d.ts +16 -6
  29. package/dist/api/Recognition.js +129 -55
  30. package/dist/api/Recognition.js.map +1 -1
  31. package/dist/api/SourceSeparation.d.ts +17 -0
  32. package/dist/api/SourceSeparation.js +61 -0
  33. package/dist/api/SourceSeparation.js.map +1 -0
  34. package/dist/api/Synthesis.d.ts +18 -18
  35. package/dist/api/Synthesis.js +191 -164
  36. package/dist/api/Synthesis.js.map +1 -1
  37. package/dist/api/Translation.d.ts +19 -8
  38. package/dist/api/Translation.js +132 -35
  39. package/dist/api/Translation.js.map +1 -1
  40. package/dist/api/Vad.d.ts +10 -5
  41. package/dist/api/Vad.js +76 -38
  42. package/dist/api/Vad.js.map +1 -1
  43. package/dist/audio/AudioBufferConversion.d.ts +1 -1
  44. package/dist/audio/AudioBufferConversion.js +4 -4
  45. package/dist/audio/AudioPlayer.d.ts +1 -1
  46. package/dist/audio/AudioPlayer.js +26 -26
  47. package/dist/audio/AudioPlayer.js.map +1 -1
  48. package/dist/audio/AudioRecorder.d.ts +1 -1
  49. package/dist/audio/AudioRecorder.js +5 -5
  50. package/dist/audio/AudioUtilities.d.ts +13 -9
  51. package/dist/audio/AudioUtilities.js +86 -24
  52. package/dist/audio/AudioUtilities.js.map +1 -1
  53. package/dist/cli/CLI.d.ts +3 -3
  54. package/dist/cli/CLI.js +271 -162
  55. package/dist/cli/CLI.js.map +1 -1
  56. package/dist/cli/CLIConfigFile.js +8 -8
  57. package/dist/cli/CLILauncher.js +6 -6
  58. package/dist/cli/CLIOptionsSchema.js +2 -2
  59. package/dist/cli/CLIParser.js +5 -5
  60. package/dist/cli/CLIStarter.js +4 -4
  61. package/dist/codecs/FFMpegTranscoder.d.ts +2 -2
  62. package/dist/codecs/FFMpegTranscoder.js +37 -37
  63. package/dist/codecs/FFMpegTranscoder.js.map +1 -1
  64. package/dist/codecs/TIMITCodec.js +5 -5
  65. package/dist/codecs/WaveCodec.d.ts +1 -1
  66. package/dist/codecs/WaveCodec.js +22 -22
  67. package/dist/denoising/RNNoise.d.ts +1 -1
  68. package/dist/denoising/RNNoise.js +9 -9
  69. package/dist/dsp/BiquadFilter.d.ts +3 -2
  70. package/dist/dsp/BiquadFilter.js +18 -11
  71. package/dist/dsp/BiquadFilter.js.map +1 -1
  72. package/dist/dsp/DecayingPeakEstimator.d.ts +16 -0
  73. package/dist/dsp/DecayingPeakEstimator.js +23 -0
  74. package/dist/dsp/DecayingPeakEstimator.js.map +1 -0
  75. package/dist/dsp/FFT.d.ts +8 -4
  76. package/dist/dsp/FFT.js +76 -30
  77. package/dist/dsp/FFT.js.map +1 -1
  78. package/dist/dsp/KWeightingFilter.d.ts +9 -0
  79. package/dist/dsp/KWeightingFilter.js +40 -0
  80. package/dist/dsp/KWeightingFilter.js.map +1 -0
  81. package/dist/dsp/LoudnessEstimator.d.ts +21 -0
  82. package/dist/dsp/LoudnessEstimator.js +47 -0
  83. package/dist/dsp/LoudnessEstimator.js.map +1 -0
  84. package/dist/dsp/MFCC.d.ts +2 -2
  85. package/dist/dsp/MFCC.js +15 -15
  86. package/dist/dsp/MelSpectogram.d.ts +1 -1
  87. package/dist/dsp/MelSpectogram.js +6 -6
  88. package/dist/dsp/Rubberband.d.ts +11 -11
  89. package/dist/dsp/Rubberband.js +27 -27
  90. package/dist/dsp/Sonic.d.ts +1 -1
  91. package/dist/dsp/Sonic.js +3 -3
  92. package/dist/dsp/SpeexResampler.d.ts +1 -1
  93. package/dist/dsp/SpeexResampler.js +2 -2
  94. package/dist/math/VectorMath.d.ts +12 -8
  95. package/dist/math/VectorMath.js +35 -32
  96. package/dist/math/VectorMath.js.map +1 -1
  97. package/dist/nlp/ChineseSegmentation.js +2 -2
  98. package/dist/nlp/CompromiseNLP.js +3 -3
  99. package/dist/nlp/EspeakPhonemizer.js +30 -30
  100. package/dist/nlp/IPA.js +20 -20
  101. package/dist/nlp/JapaneseSegmentation.js +6 -6
  102. package/dist/nlp/Lexicon.d.ts +1 -1
  103. package/dist/nlp/Lexicon.js +7 -7
  104. package/dist/nlp/Segmentation.d.ts +3 -0
  105. package/dist/nlp/Segmentation.js +21 -14
  106. package/dist/nlp/Segmentation.js.map +1 -1
  107. package/dist/nlp/TextNormalizer.js +16 -16
  108. package/dist/recognition/AmazonTranscribeSTT.d.ts +2 -2
  109. package/dist/recognition/AmazonTranscribeSTT.js +13 -14
  110. package/dist/recognition/AmazonTranscribeSTT.js.map +1 -1
  111. package/dist/recognition/AzureCognitiveServicesSTT.js +5 -6
  112. package/dist/recognition/AzureCognitiveServicesSTT.js.map +1 -1
  113. package/dist/recognition/GoogleCloudSTT.d.ts +3 -3
  114. package/dist/recognition/GoogleCloudSTT.js +18 -18
  115. package/dist/recognition/OpenAICloudSTT.d.ts +19 -0
  116. package/dist/recognition/OpenAICloudSTT.js +81 -0
  117. package/dist/recognition/OpenAICloudSTT.js.map +1 -0
  118. package/dist/recognition/SileroSTT.d.ts +2 -2
  119. package/dist/recognition/SileroSTT.js +25 -25
  120. package/dist/recognition/VoskSTT.d.ts +2 -2
  121. package/dist/recognition/VoskSTT.js +8 -8
  122. package/dist/recognition/WhisperCppSTT.d.ts +88 -0
  123. package/dist/recognition/WhisperCppSTT.js +332 -0
  124. package/dist/recognition/WhisperCppSTT.js.map +1 -0
  125. package/dist/recognition/WhisperSTT.d.ts +49 -25
  126. package/dist/recognition/WhisperSTT.js +626 -481
  127. package/dist/recognition/WhisperSTT.js.map +1 -1
  128. package/dist/server/Client.d.ts +1 -1
  129. package/dist/server/Client.js +22 -22
  130. package/dist/server/Server.js +9 -9
  131. package/dist/server/Server.js.map +1 -1
  132. package/dist/server/Worker.d.ts +22 -22
  133. package/dist/server/Worker.js +36 -36
  134. package/dist/server/Worker.js.map +1 -1
  135. package/dist/server/WorkerStarter.js +2 -2
  136. package/dist/source-separation/MDXNetSourceSeparation.d.ts +11 -0
  137. package/dist/source-separation/MDXNetSourceSeparation.js +161 -0
  138. package/dist/source-separation/MDXNetSourceSeparation.js.map +1 -0
  139. package/dist/speech-language-detection/SileroLanguageDetection.d.ts +1 -1
  140. package/dist/speech-language-detection/SileroLanguageDetection.js +7 -7
  141. package/dist/subtitles/Subtitles.d.ts +10 -0
  142. package/dist/subtitles/Subtitles.js +2 -2
  143. package/dist/subtitles/Subtitles.js.map +1 -1
  144. package/dist/synthesis/AwsPollyTTS.d.ts +1 -1
  145. package/dist/synthesis/AwsPollyTTS.js +12 -12
  146. package/dist/synthesis/AzureCognitiveServicesTTS.js +7 -7
  147. package/dist/synthesis/CoquiServerTTS.js +10 -10
  148. package/dist/synthesis/CoquiServerTTS.js.map +1 -1
  149. package/dist/synthesis/ElevenlabsTTS.d.ts +23 -0
  150. package/dist/synthesis/ElevenlabsTTS.js +103 -0
  151. package/dist/synthesis/ElevenlabsTTS.js.map +1 -0
  152. package/dist/synthesis/EspeakTTS.d.ts +6 -5
  153. package/dist/synthesis/EspeakTTS.js +81 -69
  154. package/dist/synthesis/EspeakTTS.js.map +1 -1
  155. package/dist/synthesis/FliteTTS.d.ts +3 -3
  156. package/dist/synthesis/FliteTTS.js +154 -154
  157. package/dist/synthesis/FliteTTS.js.map +1 -1
  158. package/dist/synthesis/GoogleCloudTTS.d.ts +3 -3
  159. package/dist/synthesis/GoogleCloudTTS.js +17 -17
  160. package/dist/synthesis/GoogleCloudTTS.js.map +1 -1
  161. package/dist/synthesis/GoogleTranslateTTS.d.ts +1 -1
  162. package/dist/synthesis/GoogleTranslateTTS.js +103 -103
  163. package/dist/synthesis/MicrosoftEdgeTTS.d.ts +2 -2
  164. package/dist/synthesis/MicrosoftEdgeTTS.js +74 -74
  165. package/dist/synthesis/OpenAICloudTTS.d.ts +13 -0
  166. package/dist/synthesis/OpenAICloudTTS.js +169 -0
  167. package/dist/synthesis/OpenAICloudTTS.js.map +1 -0
  168. package/dist/synthesis/SamTTS.js +3 -3
  169. package/dist/synthesis/SapiTTS.d.ts +3 -3
  170. package/dist/synthesis/SapiTTS.js +26 -26
  171. package/dist/synthesis/StreamlabsPollyTTS.d.ts +2 -2
  172. package/dist/synthesis/StreamlabsPollyTTS.js +27 -27
  173. package/dist/synthesis/SvoxPicoTTS.d.ts +2 -2
  174. package/dist/synthesis/SvoxPicoTTS.js +65 -65
  175. package/dist/synthesis/SvoxPicoTTS.js.map +1 -1
  176. package/dist/synthesis/VitsTTS.d.ts +3 -3
  177. package/dist/synthesis/VitsTTS.js +378 -378
  178. package/dist/synthesis/VitsTTS.js.map +1 -1
  179. package/dist/tests/Test.js +2 -2
  180. package/dist/utilities/Compression.d.ts +5 -0
  181. package/dist/utilities/Compression.js +29 -13
  182. package/dist/utilities/Compression.js.map +1 -1
  183. package/dist/utilities/FileDownloader.d.ts +1 -1
  184. package/dist/utilities/FileDownloader.js +16 -16
  185. package/dist/utilities/FileSystem.js +7 -7
  186. package/dist/utilities/Locale.d.ts +7 -7
  187. package/dist/utilities/Locale.js +15 -15
  188. package/dist/utilities/Logger.js +3 -3
  189. package/dist/utilities/ObjectUtilities.js +19 -19
  190. package/dist/utilities/OpenPromise.js +2 -2
  191. package/dist/utilities/OpenPromise.js.map +1 -1
  192. package/dist/utilities/PackageManager.js +31 -0
  193. package/dist/utilities/PackageManager.js.map +1 -1
  194. package/dist/utilities/PathUtilities.js +8 -8
  195. package/dist/utilities/RandomGenerator.js +2 -2
  196. package/dist/utilities/SmoothEstimator.d.ts +8 -0
  197. package/dist/utilities/SmoothEstimator.js +25 -0
  198. package/dist/utilities/SmoothEstimator.js.map +1 -0
  199. package/dist/utilities/TarballMaker.js +8 -8
  200. package/dist/utilities/Timeline.d.ts +3 -2
  201. package/dist/utilities/Timeline.js +11 -11
  202. package/dist/utilities/Timeline.js.map +1 -1
  203. package/dist/utilities/Timer.js +4 -4
  204. package/dist/utilities/Utilities.d.ts +4 -0
  205. package/dist/utilities/Utilities.js +38 -15
  206. package/dist/utilities/Utilities.js.map +1 -1
  207. package/dist/utilities/WasmMemoryManager.js +7 -7
  208. package/dist/utilities/WebReader.js +23 -23
  209. package/dist/utilities/WikipediaReader.js +2 -2
  210. package/dist/voice-activity-detection/AdaptiveGateVAD.d.ts +28 -0
  211. package/dist/voice-activity-detection/AdaptiveGateVAD.js +138 -0
  212. package/dist/voice-activity-detection/AdaptiveGateVAD.js.map +1 -0
  213. package/dist/voice-activity-detection/SileroVAD.d.ts +1 -1
  214. package/dist/voice-activity-detection/SileroVAD.js +5 -5
  215. package/dist/voice-activity-detection/SileroVAD.js.map +1 -1
  216. package/dist/voice-activity-detection/WebRtcVAD.d.ts +1 -1
  217. package/dist/voice-activity-detection/WebRtcVAD.js +4 -4
  218. package/docs/API.md +29 -11
  219. package/docs/CLI.md +31 -7
  220. package/docs/Contributing.md +38 -0
  221. package/docs/Development.md +93 -19
  222. package/docs/Engines.md +28 -16
  223. package/docs/Licenses.md +4 -1
  224. package/docs/Options.md +158 -78
  225. package/docs/Releases.md +262 -0
  226. package/docs/Server.md +7 -7
  227. package/docs/Tasklist.md +95 -76
  228. package/docs/Technical.md +4 -4
  229. package/package.json +13 -14
  230. package/src/alignment/DTWMfccSequenceAlignment.ts +9 -9
  231. package/src/alignment/DTWSequenceAlignment.ts +2 -2
  232. package/src/alignment/DTWSequenceAlignmentWindowed.ts +3 -3
  233. package/src/alignment/LevenshteinSequenceAlignment.ts +2 -2
  234. package/src/alignment/SpeechAlignment.ts +204 -119
  235. package/src/api/API.ts +14 -13
  236. package/src/api/APIOptions.ts +12 -11
  237. package/src/api/Alignment.ts +147 -90
  238. package/src/api/Common.ts +1 -1
  239. package/src/api/Denoising.ts +28 -28
  240. package/src/api/LanguageDetection.ts +135 -48
  241. package/src/api/Recognition.ts +198 -59
  242. package/src/api/SourceSeparation.ts +99 -0
  243. package/src/api/Synthesis.ts +217 -181
  244. package/src/api/Translation.ts +193 -40
  245. package/src/api/Vad.ts +110 -41
  246. package/src/audio/AudioBufferConversion.ts +4 -4
  247. package/src/audio/AudioPlayer.ts +27 -27
  248. package/src/audio/AudioRecorder.ts +5 -5
  249. package/src/audio/AudioUtilities.ts +107 -24
  250. package/src/cli/CLI.ts +313 -164
  251. package/src/cli/CLIConfigFile.ts +8 -8
  252. package/src/cli/CLILauncher.ts +6 -6
  253. package/src/cli/CLIOptionsSchema.ts +2 -2
  254. package/src/cli/CLIParser.ts +5 -5
  255. package/src/cli/CLIStarter.ts +4 -4
  256. package/src/codecs/FFMpegTranscoder.ts +38 -38
  257. package/src/codecs/TIMITCodec.ts +5 -5
  258. package/src/codecs/WaveCodec.ts +22 -22
  259. package/src/denoising/RNNoise.ts +9 -9
  260. package/src/dsp/BiquadFilter.ts +19 -11
  261. package/src/dsp/DecayingPeakEstimator.ts +35 -0
  262. package/src/dsp/FFT.ts +103 -35
  263. package/src/dsp/KWeightingFilter.ts +43 -0
  264. package/src/dsp/LoudnessEstimator.ts +74 -0
  265. package/src/dsp/MFCC.ts +15 -15
  266. package/src/dsp/MelSpectogram.ts +7 -7
  267. package/src/dsp/Rubberband.ts +38 -38
  268. package/src/dsp/Sonic.ts +4 -4
  269. package/src/dsp/SpeexResampler.ts +2 -2
  270. package/src/math/VectorMath.ts +42 -33
  271. package/src/nlp/ChineseSegmentation.ts +3 -3
  272. package/src/nlp/CompromiseNLP.ts +3 -3
  273. package/src/nlp/EspeakPhonemizer.ts +30 -30
  274. package/src/nlp/IPA.ts +20 -20
  275. package/src/nlp/JapaneseSegmentation.ts +6 -6
  276. package/src/nlp/Lexicon.ts +8 -8
  277. package/src/nlp/Segmentation.ts +23 -14
  278. package/src/nlp/TextNormalizer.ts +16 -16
  279. package/src/recognition/AmazonTranscribeSTT.ts +16 -17
  280. package/src/recognition/AzureCognitiveServicesSTT.ts +8 -6
  281. package/src/recognition/GoogleCloudSTT.ts +21 -21
  282. package/src/recognition/OpenAICloudSTT.ts +142 -0
  283. package/src/recognition/SileroSTT.ts +26 -26
  284. package/src/recognition/VoskSTT.ts +10 -10
  285. package/src/recognition/WhisperCppSTT.ts +555 -0
  286. package/src/recognition/WhisperSTT.ts +760 -507
  287. package/src/server/Client.ts +23 -23
  288. package/src/server/Server.ts +9 -9
  289. package/src/server/Worker.ts +53 -53
  290. package/src/server/WorkerStarter.ts +2 -2
  291. package/src/source-separation/MDXNetSourceSeparation.ts +228 -0
  292. package/src/speech-language-detection/SileroLanguageDetection.ts +8 -8
  293. package/src/subtitles/Subtitles.ts +3 -3
  294. package/src/synthesis/AwsPollyTTS.ts +14 -14
  295. package/src/synthesis/AzureCognitiveServicesTTS.ts +10 -10
  296. package/src/synthesis/CoquiServerTTS.ts +10 -10
  297. package/src/synthesis/ElevenlabsTTS.ts +137 -0
  298. package/src/synthesis/EspeakTTS.ts +90 -71
  299. package/src/synthesis/FliteTTS.ts +157 -157
  300. package/src/synthesis/GoogleCloudTTS.ts +19 -19
  301. package/src/synthesis/GoogleTranslateTTS.ts +104 -104
  302. package/src/synthesis/MicrosoftEdgeTTS.ts +80 -80
  303. package/src/synthesis/OpenAICloudTTS.ts +196 -0
  304. package/src/synthesis/SamTTS.ts +3 -3
  305. package/src/synthesis/SapiTTS.ts +29 -29
  306. package/src/synthesis/StreamlabsPollyTTS.ts +29 -29
  307. package/src/synthesis/SvoxPicoTTS.ts +67 -67
  308. package/src/synthesis/VitsTTS.ts +380 -380
  309. package/src/tests/Test.ts +4 -4
  310. package/src/utilities/Compression.ts +34 -13
  311. package/src/utilities/FileDownloader.ts +19 -19
  312. package/src/utilities/FileSystem.ts +7 -7
  313. package/src/utilities/Locale.ts +22 -22
  314. package/src/utilities/Logger.ts +4 -4
  315. package/src/utilities/ObjectUtilities.ts +19 -19
  316. package/src/utilities/OpenPromise.ts +2 -2
  317. package/src/utilities/PackageManager.ts +40 -0
  318. package/src/utilities/PathUtilities.ts +8 -8
  319. package/src/utilities/RandomGenerator.ts +3 -3
  320. package/src/utilities/SmoothEstimator.ts +35 -0
  321. package/src/utilities/TarballMaker.ts +9 -9
  322. package/src/utilities/Timeline.ts +15 -13
  323. package/src/utilities/Timer.ts +4 -4
  324. package/src/utilities/Utilities.ts +49 -15
  325. package/src/utilities/WasmMemoryManager.ts +7 -7
  326. package/src/utilities/WebReader.ts +23 -23
  327. package/src/utilities/WikipediaReader.ts +2 -2
  328. package/src/voice-activity-detection/AdaptiveGateVAD.ts +202 -0
  329. package/src/voice-activity-detection/SileroVAD.ts +5 -5
  330. package/src/voice-activity-detection/WebRtcVAD.ts +5 -5
  331. package/dist/synthesis/ElevenLabsTTS.d.ts +0 -8
  332. package/dist/synthesis/ElevenLabsTTS.js +0 -82
  333. package/dist/synthesis/ElevenLabsTTS.js.map +0 -1
  334. package/src/synthesis/ElevenLabsTTS.ts +0 -104
@@ -1,20 +1,22 @@
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 { concatAudioSegments, downmixToMono, encodeWaveBuffer, getAudioPeakDecibels, 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";
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("No language or voice specified. Detecting language");
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("\n\n"), options.languageDetection || {});
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("No matching voice found");
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}`)}: "${segmentText}"`);
81
+ logger.log(`\n${chalk.magentaBright(`Synthesizing segment ${segmentIndex + 1}/${segments.length}`)}: '${segmentText}'`);
80
82
  const segmentStartTime = timeOffset;
81
83
  const segmentEntry = {
82
- type: "segment",
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 == "vits") && !options.ssml) {
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("\n\n\n\n\nCANCELED\n\n\n\n")
105
- throw new Error("Canceled");
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: "sentence",
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, getAudioPeakDecibels(sentenceRawAudio.audioChannels));
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.targetPeakDb, options.postProcessing.maxIncreaseDb);
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 == "wav") {
191
- output = encodeWaveBuffer(rawAudio);
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("Prepare for synthesis");
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("No matching voice found");
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 "vits": {
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 == "en") {
247
- vitsLanguage = "en-us";
251
+ if (vitsLanguage == 'en') {
252
+ vitsLanguage = 'en-us';
248
253
  }
249
- const vitsTTS = await import("../synthesis/VitsTTS.js");
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("Selected VITS model has only one speaker. Speaker ID must be 0 if specified.");
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 "pico": {
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("../synthesis/SvoxPicoTTS.js");
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="${picoSpeed}"><pitch level="${picoPitch}"><volume level="${picoVolume}">${simplifiedText}</volume></pitch></speed>`;
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 "flite": {
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("../synthesis/FliteTTS.js");
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 "espeak": {
302
- const EspeakTTS = await import("../synthesis/EspeakTTS.js");
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 "sam": {
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("../synthesis/SamTTS.js");
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 "sapi": {
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("../synthesis/SapiTTS.js");
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 "msspeech": {
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("../synthesis/SapiTTS.js");
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 "coqui-server": {
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("../synthesis/CoquiServerTTS.js");
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 "google-cloud": {
398
- const GoogleCloudTTS = await import("../synthesis/GoogleCloudTTS.js");
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 given`);
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 "microsoft-azure": {
423
- const AzureCognitiveServicesTTS = await import("../synthesis/AzureCognitiveServicesTTS.js");
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 given`);
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 given`);
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 "amazon-polly": {
453
- const AwsPollyTTS = await import("../synthesis/AwsPollyTTS.js");
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 given`);
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 given`);
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 given`);
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 "elevenlabs": {
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("../synthesis/ElevenLabsTTS.js");
499
+ const ElevenLabsTTS = await import('../synthesis/ElevenlabsTTS.js');
481
500
  const engineOptions = options.elevenlabs;
482
- const apiKey = engineOptions.apiKey;
483
- if (!apiKey) {
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["elevenLabsVoiceId"];
487
- const modelId = selectedVoice["elevenLabsModelId"];
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, apiKey, modelId, stability, similarityBoost);
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 "google-translate": {
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("../synthesis/GoogleTranslateTTS.js");
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 "microsoft-edge": {
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("../synthesis/MicrosoftEdgeTTS.js");
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("No trusted client token provided.");
536
+ throw new Error('No Microsoft Edge trusted client token provided');
521
537
  }
522
- if (await sha256AsHex(trustedClientToken) != "558d7c6a7f7db444895946fe23a54ad172fd6d159f46cb34dd4db21bb27c07d7") {
523
- throw new Error("Trusted client token is incorrect.");
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 "streamlabs-polly": {
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("../synthesis/StreamlabsPollyTTS.js");
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("Postprocess synthesized audio");
579
+ logger.start('Postprocess synthesized audio');
564
580
  synthesizedAudio = downmixToMono(synthesizedAudio);
565
581
  if (options.postProcessing.normalizeAudio) {
566
- synthesizedAudio = normalizeAudioLevel(synthesizedAudio, options.postProcessing.targetPeakDb, options.postProcessing.maxIncreaseDb);
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("Align synthesized audio with text");
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("Apply time and pitch shifting");
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 == "sonic") {
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 == "rubberband") {
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 == "male") {
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 == "female") {
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: "dtw",
701
+ engine: 'dtw',
683
702
  dtw: {
684
703
  granularity: 'high'
685
704
  }
686
705
  },
687
706
  postProcessing: {
688
707
  normalizeAudio: true,
689
- targetPeakDb: -3,
690
- maxIncreaseDb: 30,
708
+ targetPeak: -3,
709
+ maxGainIncrease: 30,
691
710
  speed: undefined,
692
711
  pitch: undefined,
693
- timePitchShiftingMethod: "sonic",
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: "http://[::1]:5002",
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
- elevenlabs: {
744
- apiKey: undefined,
745
- stability: 0.5,
746
- similarityBoost: 0.5,
747
- },
762
+ openAICloud: defaultOpenAICloudTTSOptions,
763
+ elevenlabs: defaultElevenlabsTTSOptions,
748
764
  googleTranslate: {
749
- tld: "us"
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 "espeak": {
775
- const EspeakTTS = await import("../synthesis/EspeakTTS.js");
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: "male"
804
+ gender: 'male'
789
805
  };
790
806
  });
791
807
  break;
792
808
  }
793
- case "flite": {
794
- const FliteTTS = await import("../synthesis/FliteTTS.js");
809
+ case 'flite': {
810
+ const FliteTTS = await import('../synthesis/FliteTTS.js');
795
811
  voiceList = deepClone(FliteTTS.voiceList);
796
812
  break;
797
813
  }
798
- case "pico": {
799
- const SvoxPicoTTS = await import("../synthesis/SvoxPicoTTS.js");
814
+ case 'pico': {
815
+ const SvoxPicoTTS = await import('../synthesis/SvoxPicoTTS.js');
800
816
  voiceList = SvoxPicoTTS.voiceList;
801
817
  break;
802
818
  }
803
- case "sam": {
819
+ case 'sam': {
804
820
  voiceList.push({
805
- name: "sam",
806
- languages: ["en-US", "en"],
807
- gender: "male"
821
+ name: 'sam',
822
+ languages: ['en-US', 'en'],
823
+ gender: 'male'
808
824
  });
809
825
  break;
810
826
  }
811
- case "vits": {
812
- const VitsTTS = await import("../synthesis/VitsTTS.js");
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 "sapi": {
819
- const SapiTTS = await import("../synthesis/SapiTTS.js");
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 "msspeech": {
825
- const SapiTTS = await import("../synthesis/SapiTTS.js");
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 "coqui-server": {
846
+ case 'coqui-server': {
831
847
  voiceList = [{
832
- name: "coqui",
833
- languages: ["en-US"],
834
- gender: "unknown"
848
+ name: 'coqui',
849
+ languages: ['en-US'],
850
+ gender: 'unknown'
835
851
  }];
836
852
  break;
837
853
  }
838
- case "google-cloud": {
839
- const GoogleCloudTTS = await import("../synthesis/GoogleCloudTTS.js");
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 given`);
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 "microsoft-azure": {
853
- const AzureCognitiveServicesTTS = await import("../synthesis/AzureCognitiveServicesTTS.js");
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 given`);
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 given`);
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 ? "female" : "male"
883
+ gender: voice.gender == 1 ? 'female' : 'male'
868
884
  });
869
885
  }
870
886
  break;
871
887
  }
872
- case "amazon-polly": {
873
- const AwsPollyTTS = await import("../synthesis/AwsPollyTTS.js");
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 given`);
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 given`);
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 given`);
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 "elevenlabs": {
904
- const ElevenLabsTTS = await import("../synthesis/ElevenLabsTTS.js");
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 given`);
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 "google-translate": {
914
- const GoogleTranslateTTS = await import("../synthesis/GoogleTranslateTTS.js");
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("-") ? [normalizeLanguageCode(langCode), getShortLanguageCode(langCode)] : [normalizeLanguageCode(langCode)],
920
- gender: "unknown"
940
+ languages: langCode.includes('-') ? [normalizeLanguageCode(langCode), getShortLanguageCode(langCode)] : [normalizeLanguageCode(langCode)],
941
+ gender: 'unknown'
921
942
  });
922
943
  }
923
944
  break;
924
945
  }
925
- case "microsoft-edge": {
926
- const MicrosoftEdgeTTS = await import("../synthesis/MicrosoftEdgeTTS.js");
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("No trusted client token provided");
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 == "Male" ? "male" : "female",
956
+ gender: voice.Gender == 'Male' ? 'male' : 'female',
936
957
  }));
937
958
  break;
938
959
  }
939
- case "streamlabs-polly": {
940
- const StreamlabsPollyTTS = await import("../synthesis/StreamlabsPollyTTS.js");
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 == "unknown");
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("../synthesis/VitsTTS.js");
1024
+ const VitsTTS = await import('../synthesis/VitsTTS.js');
1004
1025
  const vitsLanguages = getAllLangCodesFromVoiceList(VitsTTS.voiceList);
1005
1026
  if (vitsLanguages.includes(language)) {
1006
- return "vits";
1027
+ return 'vits';
1007
1028
  }
1008
- const FliteTTS = await import("../synthesis/FliteTTS.js");
1029
+ const FliteTTS = await import('../synthesis/FliteTTS.js');
1009
1030
  const fliteLanguages = getAllLangCodesFromVoiceList(FliteTTS.voiceList);
1010
1031
  if (fliteLanguages.includes(language)) {
1011
- return "flite";
1032
+ return 'flite';
1012
1033
  }
1013
- const SvoxPicoTTS = await import("../synthesis/SvoxPicoTTS.js");
1034
+ const SvoxPicoTTS = await import('../synthesis/SvoxPicoTTS.js');
1014
1035
  const picoLanguages = getAllLangCodesFromVoiceList(SvoxPicoTTS.voiceList);
1015
1036
  if (picoLanguages.includes(language)) {
1016
- return "pico";
1037
+ return 'pico';
1017
1038
  }
1018
- return "espeak";
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: 'A lightweight "robot" sounding formant-based synthesizer.',
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: 'A classic "robot" speech synthesizer from 1982.',
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',