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
package/dist/cli/CLI.js CHANGED
@@ -1,19 +1,19 @@
1
1
  import * as API from '../api/API.js';
2
2
  import { parseCLIArguments } from './CLIParser.js';
3
- import { convertHtmlToText, formatIntegerWithLeadingZeros, formatListWithQuotedElements, getWithDefault, setupUnhandledExceptionListeners, splitFilenameOnExtendedExtension, stringifyAndFormatJson } from "../utilities/Utilities.js";
4
- import { getOptionTypeFromSchema } from "./CLIOptionsSchema.js";
5
- import { parseConfigFile, parseJSONConfigFile } from "./CLIConfigFile.js";
3
+ import { convertHtmlToText, formatIntegerWithLeadingZeros, formatListWithQuotedElements, getWithDefault, setupUnhandledExceptionListeners, splitFilenameOnExtendedExtension, stringifyAndFormatJson } from '../utilities/Utilities.js';
4
+ import { getOptionTypeFromSchema } from './CLIOptionsSchema.js';
5
+ import { parseConfigFile, parseJSONConfigFile } from './CLIConfigFile.js';
6
6
  import chalk from 'chalk';
7
- import { applyGainDecibels, encodeWaveBuffer, getEmptyRawAudio, getRawAudioDuration, normalizeAudioLevel, sliceRawAudioByTime } from "../audio/AudioUtilities.js";
8
- import { subtitlesToText, subtitlesToTimeline, timelineToSubtitles } from "../subtitles/Subtitles.js";
9
- import { Logger, resetActiveLogger } from "../utilities/Logger.js";
7
+ import { applyGainDecibels, encodeRawAudioToWave, getEmptyRawAudio, getRawAudioDuration, normalizeAudioLevel, sliceRawAudioByTime } from '../audio/AudioUtilities.js';
8
+ import { subtitlesToText, subtitlesToTimeline, timelineToSubtitles } from '../subtitles/Subtitles.js';
9
+ import { Logger, resetActiveLogger } from '../utilities/Logger.js';
10
10
  import { isMainThread, parentPort } from 'node:worker_threads';
11
- import { encodeFromChannels, getDefaultFFMpegOptionsForSpeech } from "../codecs/FFMpegTranscoder.js";
12
- import path, { parse as parsePath } from "node:path";
13
- import { splitToParagraphs, splitToWords, wordCharacterPattern } from "../nlp/Segmentation.js";
14
- import { playAudioSamples, playAudioWithWordTimeline } from "../audio/AudioPlayer.js";
15
- import { extendDeep } from "../utilities/ObjectUtilities.js";
16
- import { addTimeOffsetToTimeline, addWordTextOffsetsToTimeline, roundTimelineProperties } from "../utilities/Timeline.js";
11
+ import { encodeFromChannels, getDefaultFFMpegOptionsForSpeech } from '../codecs/FFMpegTranscoder.js';
12
+ import path, { parse as parsePath } from 'node:path';
13
+ import { splitToParagraphs, splitToWords, wordCharacterPattern } from '../nlp/Segmentation.js';
14
+ import { playAudioSamples, playAudioWithWordTimeline } from '../audio/AudioPlayer.js';
15
+ import { extendDeep } from '../utilities/ObjectUtilities.js';
16
+ import { addTimeOffsetToTimeline, addWordTextOffsetsToTimeline, roundTimelineProperties } from '../utilities/Timeline.js';
17
17
  import { ensureDir, existsSync, readAndParseJsonFile, readFile, readdir, writeFileSafe } from '../utilities/FileSystem.js';
18
18
  import { formatLanguageCodeWithName, getShortLanguageCode } from '../utilities/Locale.js';
19
19
  import { ensureAndGetPackagesDir, getVersionTagFromPackageName, loadPackage, resolveVersionTagForUnversionedPackageName } from '../utilities/PackageManager.js';
@@ -30,7 +30,7 @@ async function startIfInWorkerThread() {
30
30
  }
31
31
  setupUnhandledExceptionListeners();
32
32
  const initOpenPromise = new OpenPromise();
33
- parentPort.once("message", (message) => {
33
+ parentPort.once('message', (message) => {
34
34
  if (message.name == 'init') {
35
35
  process.stderr.isTTY = message.stdErrIsTTY;
36
36
  process.stderr.hasColors = () => message.hasColors;
@@ -48,44 +48,44 @@ export async function start(processArgs) {
48
48
  const logger = new Logger();
49
49
  let args;
50
50
  try {
51
- const packageData = await readAndParseJsonFile(resolveToModuleRootDir("package.json"));
51
+ const packageData = await readAndParseJsonFile(resolveToModuleRootDir('package.json'));
52
52
  logger.log(chalk.magentaBright(`Echogarden v${packageData.version}\n`));
53
53
  const command = processArgs[0];
54
- if (!command || command == "help") {
55
- logger.log(`Supported operations:\n\n${commandHelp.join("\n")}`);
54
+ if (!command || command == 'help') {
55
+ logger.log(`Supported operations:\n\n${commandHelp.join('\n')}`);
56
56
  process.exit(0);
57
57
  }
58
- if (command == "--help") {
58
+ if (command == '--help') {
59
59
  logger.log(`There's no command called '--help'. Did you mean to run 'echogarden help'?`);
60
60
  process.exit(0);
61
61
  }
62
62
  args = parseCLIArguments(command, processArgs.slice(1));
63
- if (!args.options.has("config")) {
63
+ if (!args.options.has('config')) {
64
64
  const defaultConfigFile = `./${appName}.config`;
65
- const defaultJsonConfigFile = defaultConfigFile + ".json";
65
+ const defaultJsonConfigFile = defaultConfigFile + '.json';
66
66
  if (existsSync(defaultConfigFile)) {
67
- args.options.set("config", defaultConfigFile);
67
+ args.options.set('config', defaultConfigFile);
68
68
  }
69
69
  else if (existsSync(defaultJsonConfigFile)) {
70
- args.options.set("config", defaultJsonConfigFile);
70
+ args.options.set('config', defaultJsonConfigFile);
71
71
  }
72
72
  }
73
- if (args.options.has("config")) {
74
- const configFilePath = args.options.get("config");
75
- args.options.delete("config");
73
+ if (args.options.has('config')) {
74
+ const configFilePath = args.options.get('config');
75
+ args.options.delete('config');
76
76
  let parsedOptionFile;
77
- if (configFilePath.endsWith(".config")) {
77
+ if (configFilePath.endsWith('.config')) {
78
78
  parsedOptionFile = await parseConfigFile(configFilePath);
79
79
  }
80
- else if (configFilePath.endsWith(".config.json")) {
80
+ else if (configFilePath.endsWith('.config.json')) {
81
81
  parsedOptionFile = await parseJSONConfigFile(configFilePath);
82
82
  }
83
83
  else {
84
84
  throw new Error(`Specified config file '${configFilePath}' doesn't have a supported extension. Should be either '.config' or '.config.json'`);
85
85
  }
86
86
  let sectionName = args.command;
87
- if (sectionName.startsWith("speak-")) {
88
- sectionName = "speak";
87
+ if (sectionName.startsWith('speak-')) {
88
+ sectionName = 'speak';
89
89
  }
90
90
  const newOptions = parsedOptionFile.get(sectionName) || new Map();
91
91
  for (const [key, value] of args.options) {
@@ -142,7 +142,9 @@ const commandHelp = [
142
142
  `${executableName} ${chalk.magentaBright('detect-voice-activity')} audioFile [output files...] [options...]`,
143
143
  ` Detect voice activity in audio file\n`,
144
144
  `${executableName} ${chalk.magentaBright('denoise')} audioFile [output files...] [options...]`,
145
- ` Denoise audio file\n`,
145
+ ` Apply speech denoising to audio file\n`,
146
+ `${executableName} ${chalk.magentaBright('isolate')} audioFile [output files...] [options...]`,
147
+ ` Extract isolated voice track from an audio file\n`,
146
148
  `${executableName} ${chalk.magentaBright('list-engines')} operation`,
147
149
  ` List available engines for the specified operation\n`,
148
150
  `${executableName} ${chalk.magentaBright('list-voices')} tts-engine [output files...] [options...]`,
@@ -181,15 +183,15 @@ async function startWithArgs(parsedArgs) {
181
183
  break;
182
184
  }
183
185
  case 'detect-language': {
184
- await detectLanguage(parsedArgs.commandArgs, parsedArgs.options, "auto");
186
+ await detectLanguage(parsedArgs.commandArgs, parsedArgs.options, 'auto');
185
187
  break;
186
188
  }
187
189
  case 'detect-speech-language': {
188
- await detectLanguage(parsedArgs.commandArgs, parsedArgs.options, "speech");
190
+ await detectLanguage(parsedArgs.commandArgs, parsedArgs.options, 'speech');
189
191
  break;
190
192
  }
191
193
  case 'detect-text-language': {
192
- await detectLanguage(parsedArgs.commandArgs, parsedArgs.options, "text");
194
+ await detectLanguage(parsedArgs.commandArgs, parsedArgs.options, 'text');
193
195
  break;
194
196
  }
195
197
  case 'detect-voice-activity': {
@@ -200,6 +202,10 @@ async function startWithArgs(parsedArgs) {
200
202
  await denoise(parsedArgs.commandArgs, parsedArgs.options);
201
203
  break;
202
204
  }
205
+ case 'isolate': {
206
+ await isolate(parsedArgs.commandArgs, parsedArgs.options);
207
+ break;
208
+ }
203
209
  case 'list-engines': {
204
210
  await listEngines(parsedArgs.commandArgs, parsedArgs.options);
205
211
  break;
@@ -235,16 +241,16 @@ async function speak(command, commandArgs, cliOptions) {
235
241
  const mainArg = commandArgs[0];
236
242
  const outputFilenames = commandArgs.slice(1);
237
243
  if (mainArg == undefined) {
238
- if (command == "speak") {
244
+ if (command == 'speak') {
239
245
  throw new Error(`'speak' requires an argument containing the text to speak.`);
240
246
  }
241
- else if (command == "speak-file") {
247
+ else if (command == 'speak-file') {
242
248
  throw new Error(`'speak-file' requires an argument containing the file to speak.`);
243
249
  }
244
- else if (command == "speak-url") {
250
+ else if (command == 'speak-url') {
245
251
  throw new Error(`'speak-url' requires an argument containing the url to speak.`);
246
252
  }
247
- else if (command == "speak-wikipedia") {
253
+ else if (command == 'speak-wikipedia') {
248
254
  throw new Error(`'speak-wikipedia' requires an argument containing the name of the Wikipedia article to speak.`);
249
255
  }
250
256
  return;
@@ -255,14 +261,14 @@ async function speak(command, commandArgs, cliOptions) {
255
261
  if (!cliOptions.has('play') && !cliOptions.has('no-play')) {
256
262
  cliOptions.set('play', `${outputFilenames.length == 0}`);
257
263
  }
258
- const options = await cliOptionsMapToOptionsObject(cliOptions, "SynthesisOptions", additionalOptionsSchema);
264
+ const options = await cliOptionsMapToOptionsObject(cliOptions, 'SynthesisOptions', additionalOptionsSchema);
259
265
  const allowOverwrite = getWithDefault(options.overwrite, overwriteByDefault);
260
- const { includesPartPattern } = await checkOutputFilenames(outputFilenames, true, true, true);
266
+ const { includesPlaceholderPattern } = await checkOutputFilenames(outputFilenames, true, true, true);
261
267
  let plainText = undefined;
262
268
  let textSegments;
263
269
  const plainTextParagraphBreaks = options.plainText?.paragraphBreaks || API.defaultSynthesisOptions.plainText.paragraphBreaks;
264
270
  const plainTextWhitespace = options.plainText?.whitespace || API.defaultSynthesisOptions.plainText.whitespace;
265
- if (command == "speak") {
271
+ if (command == 'speak') {
266
272
  if (options.ssml) {
267
273
  textSegments = [mainArg];
268
274
  }
@@ -271,29 +277,29 @@ async function speak(command, commandArgs, cliOptions) {
271
277
  }
272
278
  plainText = mainArg;
273
279
  }
274
- else if (command == "speak-file") {
280
+ else if (command == 'speak-file') {
275
281
  const sourceFile = mainArg;
276
282
  if (!existsSync(sourceFile)) {
277
283
  throw new Error(`The given source file '${sourceFile}' was not found.`);
278
284
  }
279
285
  const sourceFileExtension = getLowercaseFileExtension(sourceFile);
280
286
  const fileContent = await readFile(sourceFile, { encoding: 'utf-8' });
281
- if (options.ssml && sourceFileExtension != "xml" && sourceFileExtension != "ssml") {
287
+ if (options.ssml && sourceFileExtension != 'xml' && sourceFileExtension != 'ssml') {
282
288
  throw new Error(`SSML option is set, but source file doesn't have an 'xml' or 'ssml' extension.`);
283
289
  }
284
- if (sourceFileExtension == "txt") {
290
+ if (sourceFileExtension == 'txt') {
285
291
  textSegments = splitToParagraphs(fileContent, plainTextParagraphBreaks, plainTextWhitespace);
286
292
  plainText = fileContent;
287
293
  }
288
- else if (sourceFileExtension == "html" || sourceFileExtension == "htm") {
294
+ else if (sourceFileExtension == 'html' || sourceFileExtension == 'htm') {
289
295
  const textContent = await convertHtmlToText(fileContent);
290
296
  textSegments = splitToParagraphs(textContent, 'single', 'preserve');
291
297
  }
292
- else if (sourceFileExtension == "srt" || sourceFileExtension == "vtt") {
298
+ else if (sourceFileExtension == 'srt' || sourceFileExtension == 'vtt') {
293
299
  const fileContent = await readFile(sourceFile, { encoding: 'utf-8' });
294
300
  textSegments = subtitlesToTimeline(fileContent).map(entry => entry.text);
295
301
  }
296
- else if (sourceFileExtension == "xml" || sourceFileExtension == "ssml") {
302
+ else if (sourceFileExtension == 'xml' || sourceFileExtension == 'ssml') {
297
303
  options.ssml = true;
298
304
  textSegments = [fileContent];
299
305
  }
@@ -301,34 +307,34 @@ async function speak(command, commandArgs, cliOptions) {
301
307
  throw new Error(`'speak-file' only supports inputs with extensions 'txt', 'html', 'htm', 'xml', 'ssml', 'srt', 'vtt'`);
302
308
  }
303
309
  }
304
- else if (command == "speak-url") {
310
+ else if (command == 'speak-url') {
305
311
  if (options.ssml) {
306
312
  throw new Error(`speak-url doesn't provide SSML inputs`);
307
313
  }
308
314
  const url = mainArg;
309
- if (!url.startsWith("http://") && !url.startsWith("https://")) {
315
+ if (!url.startsWith('http://') && !url.startsWith('https://')) {
310
316
  throw new Error(`'${url}' is not a valid URL. Only 'http://' and 'https://' protocols are supported`);
311
317
  }
312
- const { fetchDocumentText } = await import("../utilities/WebReader.js");
318
+ const { fetchDocumentText } = await import('../utilities/WebReader.js');
313
319
  const textContent = await fetchDocumentText(url);
314
320
  textSegments = splitToParagraphs(textContent, 'single', 'preserve');
315
321
  }
316
- else if (command == "speak-wikipedia") {
322
+ else if (command == 'speak-wikipedia') {
317
323
  if (options.ssml) {
318
324
  throw new Error(`speak-wikipedia doesn't provide SSML inputs`);
319
325
  }
320
- const { parseWikipediaArticle } = await import("../utilities/WikipediaReader.js");
326
+ const { parseWikipediaArticle } = await import('../utilities/WikipediaReader.js');
321
327
  if (!options.language) {
322
- options.language = "en";
328
+ options.language = 'en';
323
329
  }
324
330
  textSegments = await parseWikipediaArticle(mainArg, getShortLanguageCode(options.language));
325
331
  }
326
332
  else {
327
- throw new Error("Invalid command");
333
+ throw new Error('Invalid command');
328
334
  }
329
335
  async function onSegment(segmentData) {
330
- if (includesPartPattern) {
331
- logger.start("Writing output files for segment");
336
+ if (includesPlaceholderPattern) {
337
+ logger.start('Write output files for segment');
332
338
  }
333
339
  await writeOutputFilesForSegment(outputFilenames, segmentData.index, segmentData.total, segmentData.audio, segmentData.timeline, segmentData.transcript, segmentData.language, allowOverwrite);
334
340
  logger.end();
@@ -348,15 +354,15 @@ async function speak(command, commandArgs, cliOptions) {
348
354
  addWordTextOffsetsToTimeline(timeline, plainText);
349
355
  }
350
356
  if (outputFilenames.length > 0) {
351
- logger.start("\nWriting output files");
357
+ logger.start('\nWrite output files');
352
358
  }
353
- for (const outputFile of outputFilenames) {
354
- const partPatternMatch = outputFile.match(segmentFilenamePattern);
355
- if (partPatternMatch) {
359
+ for (const outputFilename of outputFilenames) {
360
+ const placeholderPatternMatch = outputFilename.match(filenamePlaceholderPattern);
361
+ if (placeholderPatternMatch) {
356
362
  continue;
357
363
  }
358
- const fileSaver = getFileSaver(outputFile, allowOverwrite);
359
- await fileSaver(synthesizedAudio, timeline, textSegments.join("\n\n"), options.subtitles);
364
+ const fileSaver = getFileSaver(outputFilename, allowOverwrite);
365
+ await fileSaver(synthesizedAudio, timeline, textSegments.join('\n\n'), options.subtitles);
360
366
  }
361
367
  logger.end();
362
368
  }
@@ -376,25 +382,33 @@ async function transcribe(commandArgs, cliOptions) {
376
382
  if (!cliOptions.has('play') && !cliOptions.has('no-play')) {
377
383
  cliOptions.set('play', `${outputFilenames.length == 0}`);
378
384
  }
379
- const options = await cliOptionsMapToOptionsObject(cliOptions, "RecognitionOptions", additionalOptionsSchema);
385
+ const options = await cliOptionsMapToOptionsObject(cliOptions, 'RecognitionOptions', additionalOptionsSchema);
380
386
  const allowOverwrite = getWithDefault(options.overwrite, overwriteByDefault);
381
- const { includesPartPattern } = await checkOutputFilenames(outputFilenames, true, true, true);
382
- const { transcript, timeline, wordTimeline, inputRawAudio, language } = await API.recognize(sourceFilename, options);
387
+ const { includesPlaceholderPattern } = await checkOutputFilenames(outputFilenames, true, true, true);
388
+ const { transcript, timeline, wordTimeline, language, inputRawAudio, isolatedRawAudio, backgroundRawAudio } = await API.recognize(sourceFilename, options);
383
389
  if (outputFilenames.length > 0) {
384
- logger.start("\nWriting output files");
390
+ logger.start('\nWrite output files');
385
391
  }
386
- for (const outputFile of outputFilenames) {
387
- const partPatternMatch = outputFile.match(segmentFilenamePattern);
392
+ for (const outputFilename of outputFilenames) {
393
+ const partPatternMatch = outputFilename.match(filenamePlaceholderPattern);
388
394
  if (partPatternMatch) {
389
395
  continue;
390
396
  }
391
- const fileSaver = getFileSaver(outputFile, allowOverwrite);
397
+ const fileSaver = getFileSaver(outputFilename, allowOverwrite);
392
398
  await fileSaver(inputRawAudio, timeline, transcript, options.subtitles);
399
+ await writeSourceSeparationOutputIfNeeded(outputFilename, isolatedRawAudio, backgroundRawAudio, allowOverwrite, true);
393
400
  }
394
401
  logger.end();
395
402
  if (options.play) {
396
- const normalizedAudio = normalizeAudioLevel(inputRawAudio);
397
- await playAudioWithWordTimeline(normalizedAudio, wordTimeline, transcript);
403
+ let audioToPlay;
404
+ if (isolatedRawAudio) {
405
+ audioToPlay = isolatedRawAudio;
406
+ }
407
+ else {
408
+ audioToPlay = inputRawAudio;
409
+ }
410
+ const normalizedAudioToPlay = normalizeAudioLevel(audioToPlay);
411
+ await playAudioWithWordTimeline(normalizedAudioToPlay, wordTimeline, transcript);
398
412
  }
399
413
  }
400
414
  async function align(commandArgs, cliOptions) {
@@ -417,13 +431,13 @@ async function align(commandArgs, cliOptions) {
417
431
  const referenceFileExtension = getLowercaseFileExtension(alignmentReferenceFile);
418
432
  const fileContent = await readFile(alignmentReferenceFile, { encoding: 'utf-8' });
419
433
  let text;
420
- if (referenceFileExtension == "txt") {
434
+ if (referenceFileExtension == 'txt') {
421
435
  text = fileContent;
422
436
  }
423
- else if (referenceFileExtension == "html" || referenceFileExtension == "htm") {
437
+ else if (referenceFileExtension == 'html' || referenceFileExtension == 'htm') {
424
438
  text = await convertHtmlToText(fileContent);
425
439
  }
426
- else if (referenceFileExtension == "srt" || referenceFileExtension == "vtt") {
440
+ else if (referenceFileExtension == 'srt' || referenceFileExtension == 'vtt') {
427
441
  text = subtitlesToText(fileContent);
428
442
  }
429
443
  else {
@@ -435,14 +449,14 @@ async function align(commandArgs, cliOptions) {
435
449
  if (!cliOptions.has('play') && !cliOptions.has('no-play')) {
436
450
  cliOptions.set('play', `${outputFilenames.length == 0}`);
437
451
  }
438
- const options = await cliOptionsMapToOptionsObject(cliOptions, "AlignmentOptions", additionalOptionsSchema);
452
+ const options = await cliOptionsMapToOptionsObject(cliOptions, 'AlignmentOptions', additionalOptionsSchema);
439
453
  const allowOverwrite = getWithDefault(options.overwrite, overwriteByDefault);
440
- const { includesPartPattern } = await checkOutputFilenames(outputFilenames, true, true, true);
441
- const { timeline, wordTimeline, transcript, language, inputRawAudio } = await API.align(audioFilename, text, options);
454
+ const { includesPlaceholderPattern } = await checkOutputFilenames(outputFilenames, true, true, true);
455
+ const { timeline, wordTimeline, transcript, language, inputRawAudio, isolatedRawAudio, backgroundRawAudio } = await API.align(audioFilename, text, options);
442
456
  if (outputFilenames.length > 0) {
443
- logger.start("\nWriting output files");
457
+ logger.start('\nWrite output files');
444
458
  }
445
- if (includesPartPattern) {
459
+ if (includesPlaceholderPattern) {
446
460
  for (let segmentIndex = 0; segmentIndex < timeline.length; segmentIndex++) {
447
461
  const segmentEntry = timeline[segmentIndex];
448
462
  const segmentAudio = sliceRawAudioByTime(inputRawAudio, segmentEntry.startTime, segmentEntry.endTime);
@@ -450,18 +464,26 @@ async function align(commandArgs, cliOptions) {
450
464
  await writeOutputFilesForSegment(outputFilenames, segmentIndex, timeline.length, segmentAudio, sentenceTimeline, segmentEntry.text, language, allowOverwrite);
451
465
  }
452
466
  }
453
- for (const outputFile of outputFilenames) {
454
- const partPatternMatch = outputFile.match(segmentFilenamePattern);
467
+ for (const outputFilename of outputFilenames) {
468
+ const partPatternMatch = outputFilename.match(filenamePlaceholderPattern);
455
469
  if (partPatternMatch) {
456
470
  continue;
457
471
  }
458
- const fileSaver = getFileSaver(outputFile, allowOverwrite);
472
+ const fileSaver = getFileSaver(outputFilename, allowOverwrite);
459
473
  await fileSaver(inputRawAudio, timeline, transcript, options.subtitles);
474
+ await writeSourceSeparationOutputIfNeeded(outputFilename, isolatedRawAudio, backgroundRawAudio, allowOverwrite, true);
460
475
  }
461
476
  logger.end();
462
477
  if (options.play) {
463
- const normalizedAudio = normalizeAudioLevel(inputRawAudio);
464
- await playAudioWithWordTimeline(normalizedAudio, wordTimeline, transcript);
478
+ let audioToPlay;
479
+ if (isolatedRawAudio) {
480
+ audioToPlay = isolatedRawAudio;
481
+ }
482
+ else {
483
+ audioToPlay = inputRawAudio;
484
+ }
485
+ const normalizedAudioToPlay = normalizeAudioLevel(audioToPlay);
486
+ await playAudioWithWordTimeline(normalizedAudioToPlay, wordTimeline, transcript);
465
487
  }
466
488
  }
467
489
  async function translateSpeech(commandArgs, cliOptions) {
@@ -480,25 +502,53 @@ async function translateSpeech(commandArgs, cliOptions) {
480
502
  if (!cliOptions.has('play') && !cliOptions.has('no-play')) {
481
503
  cliOptions.set('play', `${outputFilenames.length == 0}`);
482
504
  }
483
- const options = await cliOptionsMapToOptionsObject(cliOptions, "SpeechTranslationOptions", additionalOptionsSchema);
505
+ const options = await cliOptionsMapToOptionsObject(cliOptions, 'SpeechTranslationOptions', additionalOptionsSchema);
484
506
  const allowOverwrite = getWithDefault(options.overwrite, overwriteByDefault);
485
507
  await checkOutputFilenames(outputFilenames, true, true, true);
486
- const { transcript, timeline, wordTimeline, sourceLanguage, targetLanguage, inputRawAudio } = await API.translateSpeech(inputFilename, options);
508
+ const { transcript, timeline, wordTimeline, sourceLanguage, targetLanguage, inputRawAudio, isolatedRawAudio, backgroundRawAudio } = await API.translateSpeech(inputFilename, options);
487
509
  if (outputFilenames.length > 0) {
488
- logger.start("\nWriting output files");
510
+ logger.start('\nWrite output files');
489
511
  }
490
- for (const outputFile of outputFilenames) {
491
- const partPatternMatch = outputFile.match(segmentFilenamePattern);
512
+ for (const outputFilename of outputFilenames) {
513
+ const partPatternMatch = outputFilename.match(filenamePlaceholderPattern);
492
514
  if (partPatternMatch) {
493
515
  continue;
494
516
  }
495
- const fileSaver = getFileSaver(outputFile, allowOverwrite);
517
+ const fileSaver = getFileSaver(outputFilename, allowOverwrite);
496
518
  await fileSaver(inputRawAudio, timeline, transcript, options.subtitles);
519
+ await writeSourceSeparationOutputIfNeeded(outputFilename, isolatedRawAudio, backgroundRawAudio, allowOverwrite, true);
497
520
  }
498
521
  logger.end();
499
522
  if (options.play) {
500
- const normalizedAudio = normalizeAudioLevel(inputRawAudio);
501
- await playAudioWithWordTimeline(normalizedAudio, wordTimeline, transcript);
523
+ let audioToPlay;
524
+ if (isolatedRawAudio) {
525
+ audioToPlay = isolatedRawAudio;
526
+ }
527
+ else {
528
+ audioToPlay = inputRawAudio;
529
+ }
530
+ const normalizedAudioToPlay = normalizeAudioLevel(audioToPlay);
531
+ let transcriptToPlay;
532
+ let timelineToPlay;
533
+ if (wordTimeline) {
534
+ transcriptToPlay = transcript;
535
+ timelineToPlay = wordTimeline;
536
+ }
537
+ else {
538
+ timelineToPlay = timeline.map(entry => ({
539
+ type: 'word',
540
+ text: entry.text.trim(),
541
+ startTime: entry.startTime,
542
+ endTime: entry.endTime
543
+ }));
544
+ transcriptToPlay = '';
545
+ for (const entry of timelineToPlay) {
546
+ transcriptToPlay += entry.text;
547
+ transcriptToPlay += ' ';
548
+ }
549
+ transcriptToPlay = transcriptToPlay.trim();
550
+ }
551
+ await playAudioWithWordTimeline(normalizedAudioToPlay, timelineToPlay, transcriptToPlay);
502
552
  }
503
553
  }
504
554
  async function detectLanguage(commandArgs, cliOptions, mode) {
@@ -511,21 +561,21 @@ async function detectLanguage(commandArgs, cliOptions, mode) {
511
561
  const additionalOptionsSchema = new Map();
512
562
  additionalOptionsSchema.set('overwrite', { type: 'boolean' });
513
563
  const inputFileExtension = getLowercaseFileExtension(inputFilePath);
514
- const supportedInputTextFormats = ["txt", "srt", "vtt"];
564
+ const supportedInputTextFormats = ['txt', 'srt', 'vtt'];
515
565
  let results;
516
566
  let allowOverwrite;
517
- if (mode == "text" || (mode == "auto" && supportedInputTextFormats.includes(inputFileExtension))) {
567
+ if (mode == 'text' || (mode == 'auto' && supportedInputTextFormats.includes(inputFileExtension))) {
518
568
  if (inputFilePath == undefined) {
519
569
  throw new Error(`detect-text-language requires an argument containing the input file path.`);
520
570
  }
521
571
  if (!supportedInputTextFormats.includes(inputFileExtension)) {
522
572
  throw new Error(`'detect-text-language' doesn't support input file extension '${inputFileExtension}'`);
523
573
  }
524
- const options = await cliOptionsMapToOptionsObject(cliOptions, "TextLanguageDetectionOptions", additionalOptionsSchema);
574
+ const options = await cliOptionsMapToOptionsObject(cliOptions, 'TextLanguageDetectionOptions', additionalOptionsSchema);
525
575
  allowOverwrite = getWithDefault(options.overwrite, overwriteByDefault);
526
576
  await checkOutputFilenames(outputFilenames, false, true, false);
527
- let text = await readFile(inputFilePath, { encoding: "utf-8" });
528
- if (inputFileExtension == "srt" || inputFileExtension == "vtt") {
577
+ let text = await readFile(inputFilePath, { encoding: 'utf-8' });
578
+ if (inputFileExtension == 'srt' || inputFileExtension == 'vtt') {
529
579
  text = subtitlesToText(text);
530
580
  }
531
581
  const { detectedLanguage, detectedLanguageProbabilities } = await API.detectTextLanguage(text, options);
@@ -535,23 +585,23 @@ async function detectLanguage(commandArgs, cliOptions, mode) {
535
585
  if (inputFilePath == undefined) {
536
586
  throw new Error(`detect-speech-language requires an argument containing the input audio file path.`);
537
587
  }
538
- const options = await cliOptionsMapToOptionsObject(cliOptions, "SpeechLanguageDetectionOptions", additionalOptionsSchema);
588
+ const options = await cliOptionsMapToOptionsObject(cliOptions, 'SpeechLanguageDetectionOptions', additionalOptionsSchema);
539
589
  allowOverwrite = getWithDefault(options.overwrite, overwriteByDefault);
540
590
  await checkOutputFilenames(outputFilenames, false, true, false);
541
591
  const { detectedLanguage, detectedLanguageProbabilities } = await API.detectSpeechLanguage(inputFilePath, options);
542
592
  results = detectedLanguageProbabilities;
543
593
  }
544
594
  if (outputFilenames.length > 0) {
545
- logger.start("\nWriting output files");
546
- const resultsAsText = results.map(result => `${formatLanguageCodeWithName(result.language)}: ${result.probability.toFixed(5)}`).join("\n");
547
- for (const outputFile of outputFilenames) {
548
- const fileSaver = getFileSaver(outputFile, allowOverwrite);
595
+ logger.start('\nWrite output files');
596
+ const resultsAsText = results.map(result => `${formatLanguageCodeWithName(result.language)}: ${result.probability.toFixed(5)}`).join('\n');
597
+ for (const outputFilename of outputFilenames) {
598
+ const fileSaver = getFileSaver(outputFilename, allowOverwrite);
549
599
  await fileSaver(getEmptyRawAudio(0, 0), results, resultsAsText);
550
600
  }
551
601
  }
552
602
  else {
553
- const resultsAsText = results.slice(0, 10).map(result => `${formatLanguageCodeWithName(result.language)}: ${result.probability.toFixed(5)}`).join("\n");
554
- logger.log("");
603
+ const resultsAsText = results.slice(0, 10).map(result => `${formatLanguageCodeWithName(result.language)}: ${result.probability.toFixed(5)}`).join('\n');
604
+ logger.log('');
555
605
  logger.log(resultsAsText);
556
606
  }
557
607
  logger.end();
@@ -572,26 +622,33 @@ async function detectVoiceActivity(commandArgs, cliOptions) {
572
622
  if (!cliOptions.has('play') && !cliOptions.has('no-play')) {
573
623
  cliOptions.set('play', `${outputFilenames.length == 0}`);
574
624
  }
575
- const options = await cliOptionsMapToOptionsObject(cliOptions, "VADOptions", additionalOptionsSchema);
625
+ const options = await cliOptionsMapToOptionsObject(cliOptions, 'VADOptions', additionalOptionsSchema);
576
626
  const allowOverwrite = getWithDefault(options.overwrite, overwriteByDefault);
577
- await checkOutputFilenames(outputFilenames, true, true, false);
578
- const { timeline, inputRawAudio } = await API.detectVoiceActivity(audioFilename, options);
627
+ await checkOutputFilenames(outputFilenames, true, true, true);
628
+ let { timeline, verboseTimeline, inputRawAudio, croppedRawAudio } = await API.detectVoiceActivity(audioFilename, options);
579
629
  if (outputFilenames.length > 0) {
580
- logger.start("\nWriting output files");
630
+ logger.start('\nWrite output files');
581
631
  }
582
- for (const outputFile of outputFilenames) {
583
- const partPatternMatch = outputFile.match(segmentFilenamePattern);
632
+ for (const outputFilename of outputFilenames) {
633
+ const partPatternMatch = outputFilename.match(filenamePlaceholderPattern);
584
634
  if (partPatternMatch) {
585
635
  continue;
586
636
  }
587
- const fileSaver = getFileSaver(outputFile, allowOverwrite);
588
- await fileSaver(inputRawAudio, timeline, "");
637
+ const fileSaver = getFileSaver(outputFilename, allowOverwrite);
638
+ await fileSaver(inputRawAudio, timeline, '', { maxAddedDuration: 0 });
639
+ const fileExtension = getLowercaseFileExtension(outputFilename);
640
+ if (supportedOutputMediaFileExtensions.includes(fileExtension)) {
641
+ const pathWithoutExtension = outputFilename.substring(0, outputFilename.lastIndexOf('.'));
642
+ const isolatedOutputFilePath = `${pathWithoutExtension}.cropped.${fileExtension}`;
643
+ const fileSaver = getFileSaver(isolatedOutputFilePath, allowOverwrite);
644
+ await fileSaver(croppedRawAudio, [], '');
645
+ }
589
646
  }
590
647
  logger.end();
591
648
  if (options.play) {
592
649
  const normalizedAudio = normalizeAudioLevel(inputRawAudio);
593
- const timelineToPlay = timeline.map(entry => {
594
- return { ...entry, type: "word" };
650
+ const timelineToPlay = verboseTimeline.map(entry => {
651
+ return { ...entry, type: 'word' };
595
652
  });
596
653
  await playAudioWithWordTimeline(normalizedAudio, timelineToPlay);
597
654
  }
@@ -612,22 +669,53 @@ async function denoise(commandArgs, cliOptions) {
612
669
  if (!cliOptions.has('play') && !cliOptions.has('no-play')) {
613
670
  cliOptions.set('play', `${outputFilenames.length == 0}`);
614
671
  }
615
- const options = await cliOptionsMapToOptionsObject(cliOptions, "DenoisingOptions", additionalOptionsSchema);
672
+ const options = await cliOptionsMapToOptionsObject(cliOptions, 'DenoisingOptions', additionalOptionsSchema);
616
673
  const allowOverwrite = getWithDefault(options.overwrite, overwriteByDefault);
617
674
  await checkOutputFilenames(outputFilenames, true, false, false);
618
675
  const { denoisedAudio } = await API.denoise(audioFilename, options);
619
676
  if (outputFilenames.length > 0) {
620
- logger.start("\nWriting output files");
677
+ logger.start('\nWrite output files');
621
678
  }
622
- for (const filename of outputFilenames) {
623
- const fileSaver = getFileSaver(filename, allowOverwrite);
624
- await fileSaver(denoisedAudio, [], "");
679
+ for (const outputFilename of outputFilenames) {
680
+ const fileSaver = getFileSaver(outputFilename, allowOverwrite);
681
+ await fileSaver(denoisedAudio, [], '');
625
682
  }
626
683
  logger.end();
627
684
  if (options.play) {
628
685
  await playAudioSamples(denoisedAudio);
629
686
  }
630
687
  }
688
+ async function isolate(commandArgs, cliOptions) {
689
+ const logger = new Logger();
690
+ const audioFilename = commandArgs[0];
691
+ const outputFilenames = commandArgs.slice(1);
692
+ if (audioFilename == undefined) {
693
+ throw new Error(`'isolate' requires an argument containing the audio file path.`);
694
+ }
695
+ if (!existsSync(audioFilename)) {
696
+ throw new Error(`The given source audio file '${audioFilename}' was not found.`);
697
+ }
698
+ const additionalOptionsSchema = new Map();
699
+ additionalOptionsSchema.set('play', { type: 'boolean' });
700
+ additionalOptionsSchema.set('overwrite', { type: 'boolean' });
701
+ if (!cliOptions.has('play') && !cliOptions.has('no-play')) {
702
+ cliOptions.set('play', `${outputFilenames.length == 0}`);
703
+ }
704
+ const options = await cliOptionsMapToOptionsObject(cliOptions, 'SourceSeparationOptions', additionalOptionsSchema);
705
+ const allowOverwrite = getWithDefault(options.overwrite, overwriteByDefault);
706
+ await checkOutputFilenames(outputFilenames, true, false, false);
707
+ const { inputRawAudio, isolatedRawAudio, backgroundRawAudio } = await API.isolate(audioFilename, options);
708
+ if (outputFilenames.length > 0) {
709
+ logger.start('\nWrite output files');
710
+ }
711
+ for (let outputFilename of outputFilenames) {
712
+ await writeSourceSeparationOutputIfNeeded(outputFilename, isolatedRawAudio, backgroundRawAudio, allowOverwrite, false);
713
+ }
714
+ logger.end();
715
+ if (options.play) {
716
+ await playAudioSamples(isolatedRawAudio);
717
+ }
718
+ }
631
719
  async function listEngines(commandArgs, cliOptions) {
632
720
  const logger = new Logger();
633
721
  const targetOperation = commandArgs[0];
@@ -643,6 +731,10 @@ async function listEngines(commandArgs, cliOptions) {
643
731
  engines = API.synthesisEngines;
644
732
  break;
645
733
  }
734
+ case 'list-voices': {
735
+ engines = API.synthesisEngines;
736
+ break;
737
+ }
646
738
  case 'transcribe': {
647
739
  engines = API.recognitionEngines;
648
740
  break;
@@ -675,8 +767,8 @@ async function listEngines(commandArgs, cliOptions) {
675
767
  engines = API.denoisingEngines;
676
768
  break;
677
769
  }
678
- case 'list-voices': {
679
- engines = API.synthesisEngines;
770
+ case 'isolate': {
771
+ engines = API.sourceSeparationEngines;
680
772
  break;
681
773
  }
682
774
  case 'list-engines':
@@ -695,7 +787,7 @@ async function listEngines(commandArgs, cliOptions) {
695
787
  logger.logTitledMessage('Description', engine.description);
696
788
  logger.logTitledMessage('Type', engine.type);
697
789
  if (index < engines.length - 1) {
698
- logger.log("");
790
+ logger.log('');
699
791
  }
700
792
  }
701
793
  }
@@ -705,19 +797,19 @@ async function listTTSVoices(commandArgs, cliOptions) {
705
797
  const outputFilenames = commandArgs.slice(1);
706
798
  if (!targetEngine) {
707
799
  const optionsSchema = await getOptionsSchema();
708
- const { enum: ttsEnginesEnum } = getOptionTypeFromSchema(["VoiceListRequestOptions", "engine"], optionsSchema);
709
- throw new Error(`list-voices requires an argument specifying one of these supported engines:\n${ttsEnginesEnum.join(", ")}`);
800
+ const { enum: ttsEnginesEnum } = getOptionTypeFromSchema(['VoiceListRequestOptions', 'engine'], optionsSchema);
801
+ throw new Error(`list-voices requires an argument specifying one of these supported engines:\n${ttsEnginesEnum.join(', ')}`);
710
802
  }
711
803
  const additionalOptionsSchema = new Map();
712
804
  additionalOptionsSchema.set('overwrite', { type: 'boolean' });
713
805
  cliOptions.set('engine', targetEngine);
714
- const options = await cliOptionsMapToOptionsObject(cliOptions, "VoiceListRequestOptions", additionalOptionsSchema);
806
+ const options = await cliOptionsMapToOptionsObject(cliOptions, 'VoiceListRequestOptions', additionalOptionsSchema);
715
807
  const allowOverwrite = getWithDefault(options.overwrite, overwriteByDefault);
716
808
  await checkOutputFilenames(outputFilenames, false, true, false);
717
809
  const { voiceList } = await API.requestVoiceList(options);
718
810
  const voiceListText = voiceList.map(entry => {
719
811
  const nameText = entry.name;
720
- const languagesNamesText = entry.languages.map(language => formatLanguageCodeWithName(language)).join(", ");
812
+ const languagesNamesText = entry.languages.map(language => formatLanguageCodeWithName(language)).join(', ');
721
813
  const genderText = entry.gender;
722
814
  let entryText = `${chalk.cyanBright('Identifier')}: ${chalk.magentaBright(nameText)}\n${chalk.cyanBright('Languages')}: ${languagesNamesText}\n${chalk.cyanBright('Gender')}: ${genderText}`;
723
815
  const speakerCount = entry.speakerCount;
@@ -725,9 +817,9 @@ async function listTTSVoices(commandArgs, cliOptions) {
725
817
  entryText += `\n${chalk.cyanBright('Speaker count')}: ${speakerCount}`;
726
818
  }
727
819
  return entryText;
728
- }).join("\n\n");
820
+ }).join('\n\n');
729
821
  if (outputFilenames.length > 0) {
730
- logger.start("\nWriting output files");
822
+ logger.start('\nWrite output files');
731
823
  for (const filename of outputFilenames) {
732
824
  const fileSaver = getFileSaver(filename, allowOverwrite);
733
825
  const { default: stripAnsi } = await import('strip-ansi');
@@ -743,7 +835,7 @@ async function listTTSVoices(commandArgs, cliOptions) {
743
835
  async function installPackages(commandArgs, cliOptions) {
744
836
  const logger = new Logger();
745
837
  if (commandArgs.length == 0) {
746
- throw new Error("No package names specified");
838
+ throw new Error('No package names specified');
747
839
  }
748
840
  const failedPackageNames = [];
749
841
  for (const packageName of commandArgs) {
@@ -768,7 +860,7 @@ async function installPackages(commandArgs, cliOptions) {
768
860
  async function uninstallPackages(commandArgs, cliOptions) {
769
861
  const logger = new Logger();
770
862
  if (commandArgs.length == 0) {
771
- throw new Error("No package names specified");
863
+ throw new Error('No package names specified');
772
864
  }
773
865
  const failedPackageNames = [];
774
866
  for (const packageName of commandArgs) {
@@ -805,16 +897,33 @@ async function listPackages(commandArgs, cliOptions) {
805
897
  });
806
898
  installedPackageNamesFormatted.sort();
807
899
  logger.log(`Total of ${installedPackageNamesFormatted.length} packages installed in '${packagesDir}'\n`);
808
- logger.log(installedPackageNamesFormatted.join("\n"));
900
+ logger.log(installedPackageNamesFormatted.join('\n'));
809
901
  }
810
902
  async function serve(commandArgs, cliOptions) {
811
- const options = await cliOptionsMapToOptionsObject(cliOptions, "ServerOptions");
903
+ const options = await cliOptionsMapToOptionsObject(cliOptions, 'ServerOptions');
812
904
  async function onServerStarted(serverOptions) {
813
905
  // Run a test routine (early development)
814
906
  //await runClientWebSocketTest(serverOptions.port!, serverOptions.secure!)
815
907
  }
816
908
  await startServer(options, onServerStarted);
817
909
  }
910
+ async function writeSourceSeparationOutputIfNeeded(outputFilename, isolatedRawAudio, backgroundRawAudio, allowOverwrite, prefixIsolated) {
911
+ // Write source separation output if needed
912
+ const fileExtension = getLowercaseFileExtension(outputFilename);
913
+ if (isolatedRawAudio && backgroundRawAudio && supportedOutputMediaFileExtensions.includes(fileExtension)) {
914
+ const pathWithoutExtension = outputFilename.substring(0, outputFilename.lastIndexOf('.'));
915
+ {
916
+ const isolatedOutputFilePath = prefixIsolated ? `${pathWithoutExtension}.isolated.${fileExtension}` : outputFilename;
917
+ const fileSaver = getFileSaver(isolatedOutputFilePath, allowOverwrite);
918
+ await fileSaver(isolatedRawAudio, [], '');
919
+ }
920
+ {
921
+ const backgroundOutputFilePath = `${pathWithoutExtension}.background.${fileExtension}`;
922
+ const fileSaver = getFileSaver(backgroundOutputFilePath, allowOverwrite);
923
+ await fileSaver(backgroundRawAudio, [], '');
924
+ }
925
+ }
926
+ }
818
927
  async function cliOptionsMapToOptionsObject(cliOptionsMap, optionsRoot, additionalOptionsSchema) {
819
928
  const optionsSchema = await getOptionsSchema();
820
929
  const resultingObj = {};
@@ -835,14 +944,14 @@ async function cliOptionsMapToOptionsObject(cliOptionsMap, optionsRoot, addition
835
944
  }
836
945
  for (let [key, value] of cliOptionsMap) {
837
946
  let isNegated = false;
838
- if (key.startsWith("no-")) {
947
+ if (key.startsWith('no-')) {
839
948
  isNegated = true;
840
949
  key = key.slice(3);
841
950
  if (value) {
842
951
  throw new Error(`The negated property '${key}' cannot have a value.`);
843
952
  }
844
953
  }
845
- const path = key.split(".");
954
+ const path = key.split('.');
846
955
  let optionType;
847
956
  let optionEnum;
848
957
  let optionIsUnion;
@@ -908,7 +1017,7 @@ async function cliOptionsMapToOptionsObject(cliOptionsMap, optionsRoot, addition
908
1017
  parsedValue = value;
909
1018
  }
910
1019
  if (optionEnum && !optionEnum.includes(parsedValue)) {
911
- throw new Error(`The property '${key}' must be one of ${optionEnum.join(", ")}`);
1020
+ throw new Error(`The property '${key}' must be one of ${optionEnum.join(', ')}`);
912
1021
  }
913
1022
  setValueAtPath(path, parsedValue);
914
1023
  }
@@ -917,7 +1026,7 @@ async function cliOptionsMapToOptionsObject(cliOptionsMap, optionsRoot, addition
917
1026
  let cachedOptionsSchema;
918
1027
  export async function getOptionsSchema() {
919
1028
  if (!cachedOptionsSchema) {
920
- cachedOptionsSchema = await readAndParseJsonFile(resolveToModuleRootDir("data/schemas/options.json"));
1029
+ cachedOptionsSchema = await readAndParseJsonFile(resolveToModuleRootDir('data/schemas/options.json'));
921
1030
  }
922
1031
  return cachedOptionsSchema;
923
1032
  }
@@ -932,11 +1041,11 @@ export async function checkOutputFilenames(outputFilenames, acceptMediaOutputs,
932
1041
  if (acceptSubtitleOutputs) {
933
1042
  supportedFileExtensions.push(...supportedSubtitleFileExtensions);
934
1043
  }
935
- let includesPartPattern = false;
1044
+ let includesPlaceholderPattern = false;
936
1045
  for (const outputFilename of outputFilenames) {
937
1046
  const fileExtension = getLowercaseFileExtension(outputFilename);
938
1047
  if (!supportedFileExtensions.includes(fileExtension)) {
939
- let errorText = "";
1048
+ let errorText = '';
940
1049
  errorText += `\nThe specified output path '${outputFilename}' doesn't have a supported file extension.\n`;
941
1050
  errorText += `\nSupported extensions are:\n`;
942
1051
  if (acceptMediaOutputs) {
@@ -945,50 +1054,50 @@ export async function checkOutputFilenames(outputFilenames, acceptMediaOutputs,
945
1054
  if (acceptMetadataOutputs) {
946
1055
  errorText += `${formatListWithQuotedElements(supportedMetadataFileExtensions)} for metadata output files.\n`;
947
1056
  }
948
- if (acceptMetadataOutputs) {
1057
+ if (acceptSubtitleOutputs) {
949
1058
  errorText += `${formatListWithQuotedElements(supportedSubtitleFileExtensions)} for subtitle output files.\n`;
950
1059
  }
951
1060
  throw new Error(errorText);
952
1061
  }
953
- const partPatternMatch = outputFilename.match(segmentFilenamePattern);
954
- if (partPatternMatch) {
955
- if (partPatternMatch[1] != "segment") {
956
- throw new Error(`Invalid square bracket pattern: '${partPatternMatch[1]}'. Square bracket output filename pattern currently only supports the value 'segment'. For example: '/segment/[segment].wav'`);
1062
+ const placeholderPatternMatch = outputFilename.match(filenamePlaceholderPattern);
1063
+ if (placeholderPatternMatch) {
1064
+ if (placeholderPatternMatch[1] != 'segment') {
1065
+ throw new Error(`Invalid placeholder pattern: '${placeholderPatternMatch[1]}'. Placeholder output filename pattern currently only supports 'segment'. For example: '/out/[segment].wav'`);
957
1066
  }
958
- includesPartPattern = true;
1067
+ includesPlaceholderPattern = true;
959
1068
  }
960
1069
  }
961
- return { includesPartPattern };
1070
+ return { includesPlaceholderPattern };
962
1071
  }
963
1072
  export async function writeOutputFilesForSegment(outputFilenames, index, total, audio, timeline, text, language, allowOverwrite) {
964
1073
  const digitCount = Math.max((total + 1).toString().length, 2);
965
1074
  const segmentWords = (await splitToWords(text, language)).filter(text => wordCharacterPattern.test(text));
966
- const segmentJoinedWords = segmentWords.join(" ").trim();
1075
+ const segmentJoinedWords = segmentWords.join(' ').trim();
967
1076
  let initialText;
968
1077
  const maxLength = 50;
969
1078
  if (segmentJoinedWords.length < maxLength) {
970
1079
  initialText = segmentJoinedWords.substring(0, maxLength).trim();
971
1080
  }
972
1081
  else {
973
- initialText = segmentJoinedWords.substring(0, maxLength - 4).trim() + ".. ";
1082
+ initialText = segmentJoinedWords.substring(0, maxLength - 4).trim() + '.. ';
974
1083
  }
975
1084
  for (const outputFilename of outputFilenames) {
976
- const partPatternMatch = outputFilename.match(segmentFilenamePattern);
977
- if (!partPatternMatch) {
1085
+ const placeholderPatternMatch = outputFilename.match(filenamePlaceholderPattern);
1086
+ if (!placeholderPatternMatch) {
978
1087
  continue;
979
1088
  }
980
- const segmentFilename = outputFilename.replace(segmentFilenamePattern, `${formatIntegerWithLeadingZeros(index + 1, digitCount)} - ${initialText}.$2`);
1089
+ const segmentFilename = outputFilename.replace(filenamePlaceholderPattern, `${formatIntegerWithLeadingZeros(index + 1, digitCount)} - ${initialText}.$2`);
981
1090
  const fileSaver = getFileSaver(segmentFilename, allowOverwrite);
982
1091
  await fileSaver(audio, timeline, text);
983
1092
  }
984
1093
  }
985
1094
  function getFileSaver(outputFilePath, allowOverwrite) {
986
1095
  const parsedPath = parsePath(outputFilePath);
987
- const fileDir = parsedPath.dir || "./";
1096
+ const fileDir = parsedPath.dir || './';
988
1097
  if (!allowOverwrite) {
989
1098
  const filenameParts = splitFilenameOnExtendedExtension(parsedPath.base);
990
1099
  for (let i = 1; existsSync(outputFilePath); i++) {
991
- outputFilePath = path.join(parsedPath.dir, `${filenameParts[0]} (${i})`);
1100
+ outputFilePath = path.join(parsedPath.dir, `${filenameParts[0]}_${formatIntegerWithLeadingZeros(i, 3)}`);
992
1101
  if (filenameParts[1]) {
993
1102
  outputFilePath += `.${filenameParts[1]}`;
994
1103
  }
@@ -996,24 +1105,24 @@ function getFileSaver(outputFilePath, allowOverwrite) {
996
1105
  }
997
1106
  const fileExtension = getLowercaseFileExtension(outputFilePath);
998
1107
  let fileSaver;
999
- if (fileExtension == "txt") {
1108
+ if (fileExtension == 'txt') {
1000
1109
  fileSaver = async (audio, timeline, text) => {
1001
1110
  await ensureDir(fileDir);
1002
- return writeFileSafe(outputFilePath, text, { encoding: "utf-8" });
1111
+ return writeFileSafe(outputFilePath, text, { encoding: 'utf-8' });
1003
1112
  };
1004
1113
  }
1005
- else if (fileExtension == "json") {
1114
+ else if (fileExtension == 'json') {
1006
1115
  fileSaver = async (audio, timeline, text) => {
1007
1116
  await ensureDir(fileDir);
1008
1117
  const roundedTimeline = roundTimelineProperties(timeline);
1009
1118
  return writeFileSafe(outputFilePath, stringifyAndFormatJson(roundedTimeline));
1010
1119
  };
1011
1120
  }
1012
- else if (fileExtension == "srt") {
1121
+ else if (fileExtension == 'srt') {
1013
1122
  fileSaver = async (audio, timeline, text, subtitlesConfig) => {
1014
1123
  await ensureDir(fileDir);
1015
1124
  const extraSubtitleConfigOptions = {
1016
- format: "srt",
1125
+ format: 'srt',
1017
1126
  originalText: text,
1018
1127
  totalDuration: getRawAudioDuration(audio)
1019
1128
  };
@@ -1022,11 +1131,11 @@ function getFileSaver(outputFilePath, allowOverwrite) {
1022
1131
  return writeFileSafe(outputFilePath, subtitles);
1023
1132
  };
1024
1133
  }
1025
- else if (fileExtension == "vtt") {
1134
+ else if (fileExtension == 'vtt') {
1026
1135
  fileSaver = async (audio, timeline, text, subtitlesConfig) => {
1027
1136
  await ensureDir(fileDir);
1028
1137
  const extraSubtitleConfigOptions = {
1029
- format: "webvtt",
1138
+ format: 'webvtt',
1030
1139
  originalText: text,
1031
1140
  totalDuration: getRawAudioDuration(audio)
1032
1141
  };
@@ -1035,10 +1144,10 @@ function getFileSaver(outputFilePath, allowOverwrite) {
1035
1144
  return writeFileSafe(outputFilePath, subtitles);
1036
1145
  };
1037
1146
  }
1038
- else if (fileExtension == "wav") {
1147
+ else if (fileExtension == 'wav') {
1039
1148
  fileSaver = async (audio) => {
1040
1149
  await ensureDir(fileDir);
1041
- return writeFileSafe(outputFilePath, encodeWaveBuffer(audio));
1150
+ return writeFileSafe(outputFilePath, encodeRawAudioToWave(audio));
1042
1151
  };
1043
1152
  }
1044
1153
  else if (supportedOutputMediaFileExtensions.includes(fileExtension)) {
@@ -1051,14 +1160,14 @@ function getFileSaver(outputFilePath, allowOverwrite) {
1051
1160
  };
1052
1161
  }
1053
1162
  else {
1054
- throw new Error("Unsupported file extension");
1163
+ throw new Error('Unsupported output file extension');
1055
1164
  }
1056
1165
  return fileSaver;
1057
1166
  }
1058
1167
  const supportedMetadataFileExtensions = ['txt', 'json'];
1059
1168
  const supportedSubtitleFileExtensions = ['srt', 'vtt'];
1060
- const supportedOutputMediaFileExtensions = ["wav", "mp3", "opus", "m4a", "ogg", "flac"];
1061
- const segmentFilenamePattern = /\[(.*)\]\.(.*)$/;
1169
+ const supportedOutputMediaFileExtensions = ['wav', 'mp3', 'opus', 'm4a', 'ogg', 'flac'];
1170
+ const filenamePlaceholderPattern = /\[(.*)\]\.(.*)$/;
1062
1171
  const overwriteByDefault = false;
1063
1172
  startIfInWorkerThread();
1064
1173
  //# sourceMappingURL=CLI.js.map