echogarden 0.12.2 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -14
- package/data/schemas/options.json +398 -111
- package/dist/alignment/DTWMfccSequenceAlignment.d.ts +1 -1
- package/dist/alignment/DTWMfccSequenceAlignment.js +8 -8
- package/dist/alignment/DTWSequenceAlignment.d.ts +1 -1
- package/dist/alignment/DTWSequenceAlignment.js +1 -1
- package/dist/alignment/DTWSequenceAlignmentWindowed.d.ts +1 -1
- package/dist/alignment/DTWSequenceAlignmentWindowed.js +2 -2
- package/dist/alignment/LevenshteinSequenceAlignment.d.ts +1 -1
- package/dist/alignment/LevenshteinSequenceAlignment.js +1 -1
- package/dist/alignment/SpeechAlignment.d.ts +9 -10
- package/dist/alignment/SpeechAlignment.js +136 -105
- package/dist/alignment/SpeechAlignment.js.map +1 -1
- package/dist/api/API.d.ts +13 -12
- package/dist/api/API.js +14 -13
- package/dist/api/API.js.map +1 -1
- package/dist/api/APIOptions.d.ts +5 -4
- package/dist/api/Alignment.d.ts +15 -9
- package/dist/api/Alignment.js +88 -74
- package/dist/api/Alignment.js.map +1 -1
- package/dist/api/Common.js +1 -1
- package/dist/api/Denoising.d.ts +6 -6
- package/dist/api/Denoising.js +23 -23
- package/dist/api/Denoising.js.map +1 -1
- package/dist/api/LanguageDetection.d.ts +19 -12
- package/dist/api/LanguageDetection.js +88 -38
- package/dist/api/LanguageDetection.js.map +1 -1
- package/dist/api/Recognition.d.ts +16 -6
- package/dist/api/Recognition.js +129 -55
- package/dist/api/Recognition.js.map +1 -1
- package/dist/api/SourceSeparation.d.ts +17 -0
- package/dist/api/SourceSeparation.js +61 -0
- package/dist/api/SourceSeparation.js.map +1 -0
- package/dist/api/Synthesis.d.ts +18 -18
- package/dist/api/Synthesis.js +191 -164
- package/dist/api/Synthesis.js.map +1 -1
- package/dist/api/Translation.d.ts +19 -8
- package/dist/api/Translation.js +132 -35
- package/dist/api/Translation.js.map +1 -1
- package/dist/api/Vad.d.ts +10 -5
- package/dist/api/Vad.js +76 -38
- package/dist/api/Vad.js.map +1 -1
- package/dist/audio/AudioBufferConversion.d.ts +1 -1
- package/dist/audio/AudioBufferConversion.js +4 -4
- package/dist/audio/AudioPlayer.d.ts +1 -1
- package/dist/audio/AudioPlayer.js +26 -26
- package/dist/audio/AudioPlayer.js.map +1 -1
- package/dist/audio/AudioRecorder.d.ts +1 -1
- package/dist/audio/AudioRecorder.js +5 -5
- package/dist/audio/AudioUtilities.d.ts +13 -9
- package/dist/audio/AudioUtilities.js +86 -24
- package/dist/audio/AudioUtilities.js.map +1 -1
- package/dist/cli/CLI.d.ts +3 -3
- package/dist/cli/CLI.js +271 -162
- package/dist/cli/CLI.js.map +1 -1
- package/dist/cli/CLIConfigFile.js +8 -8
- package/dist/cli/CLILauncher.js +6 -6
- package/dist/cli/CLIOptionsSchema.js +2 -2
- package/dist/cli/CLIParser.js +5 -5
- package/dist/cli/CLIStarter.js +4 -4
- package/dist/codecs/FFMpegTranscoder.d.ts +2 -2
- package/dist/codecs/FFMpegTranscoder.js +37 -37
- package/dist/codecs/FFMpegTranscoder.js.map +1 -1
- package/dist/codecs/TIMITCodec.js +5 -5
- package/dist/codecs/WaveCodec.d.ts +1 -1
- package/dist/codecs/WaveCodec.js +22 -22
- package/dist/denoising/RNNoise.d.ts +1 -1
- package/dist/denoising/RNNoise.js +9 -9
- package/dist/dsp/BiquadFilter.d.ts +3 -2
- package/dist/dsp/BiquadFilter.js +18 -11
- package/dist/dsp/BiquadFilter.js.map +1 -1
- package/dist/dsp/DecayingPeakEstimator.d.ts +16 -0
- package/dist/dsp/DecayingPeakEstimator.js +23 -0
- package/dist/dsp/DecayingPeakEstimator.js.map +1 -0
- package/dist/dsp/FFT.d.ts +8 -4
- package/dist/dsp/FFT.js +76 -30
- package/dist/dsp/FFT.js.map +1 -1
- package/dist/dsp/KWeightingFilter.d.ts +9 -0
- package/dist/dsp/KWeightingFilter.js +40 -0
- package/dist/dsp/KWeightingFilter.js.map +1 -0
- package/dist/dsp/LoudnessEstimator.d.ts +21 -0
- package/dist/dsp/LoudnessEstimator.js +47 -0
- package/dist/dsp/LoudnessEstimator.js.map +1 -0
- package/dist/dsp/MFCC.d.ts +2 -2
- package/dist/dsp/MFCC.js +15 -15
- package/dist/dsp/MelSpectogram.d.ts +1 -1
- package/dist/dsp/MelSpectogram.js +6 -6
- package/dist/dsp/Rubberband.d.ts +11 -11
- package/dist/dsp/Rubberband.js +27 -27
- package/dist/dsp/Sonic.d.ts +1 -1
- package/dist/dsp/Sonic.js +3 -3
- package/dist/dsp/SpeexResampler.d.ts +1 -1
- package/dist/dsp/SpeexResampler.js +2 -2
- package/dist/math/VectorMath.d.ts +12 -8
- package/dist/math/VectorMath.js +35 -32
- package/dist/math/VectorMath.js.map +1 -1
- package/dist/nlp/ChineseSegmentation.js +2 -2
- package/dist/nlp/CompromiseNLP.js +3 -3
- package/dist/nlp/EspeakPhonemizer.js +30 -30
- package/dist/nlp/IPA.js +20 -20
- package/dist/nlp/JapaneseSegmentation.js +6 -6
- package/dist/nlp/Lexicon.d.ts +1 -1
- package/dist/nlp/Lexicon.js +7 -7
- package/dist/nlp/Segmentation.d.ts +3 -0
- package/dist/nlp/Segmentation.js +21 -14
- package/dist/nlp/Segmentation.js.map +1 -1
- package/dist/nlp/TextNormalizer.js +16 -16
- package/dist/recognition/AmazonTranscribeSTT.d.ts +2 -2
- package/dist/recognition/AmazonTranscribeSTT.js +13 -14
- package/dist/recognition/AmazonTranscribeSTT.js.map +1 -1
- package/dist/recognition/AzureCognitiveServicesSTT.js +5 -6
- package/dist/recognition/AzureCognitiveServicesSTT.js.map +1 -1
- package/dist/recognition/GoogleCloudSTT.d.ts +3 -3
- package/dist/recognition/GoogleCloudSTT.js +18 -18
- package/dist/recognition/OpenAICloudSTT.d.ts +19 -0
- package/dist/recognition/OpenAICloudSTT.js +81 -0
- package/dist/recognition/OpenAICloudSTT.js.map +1 -0
- package/dist/recognition/SileroSTT.d.ts +2 -2
- package/dist/recognition/SileroSTT.js +25 -25
- package/dist/recognition/VoskSTT.d.ts +2 -2
- package/dist/recognition/VoskSTT.js +8 -8
- package/dist/recognition/WhisperCppSTT.d.ts +88 -0
- package/dist/recognition/WhisperCppSTT.js +332 -0
- package/dist/recognition/WhisperCppSTT.js.map +1 -0
- package/dist/recognition/WhisperSTT.d.ts +49 -25
- package/dist/recognition/WhisperSTT.js +626 -481
- package/dist/recognition/WhisperSTT.js.map +1 -1
- package/dist/server/Client.d.ts +1 -1
- package/dist/server/Client.js +22 -22
- package/dist/server/Server.js +9 -9
- package/dist/server/Server.js.map +1 -1
- package/dist/server/Worker.d.ts +22 -22
- package/dist/server/Worker.js +36 -36
- package/dist/server/Worker.js.map +1 -1
- package/dist/server/WorkerStarter.js +2 -2
- package/dist/source-separation/MDXNetSourceSeparation.d.ts +11 -0
- package/dist/source-separation/MDXNetSourceSeparation.js +161 -0
- package/dist/source-separation/MDXNetSourceSeparation.js.map +1 -0
- package/dist/speech-language-detection/SileroLanguageDetection.d.ts +1 -1
- package/dist/speech-language-detection/SileroLanguageDetection.js +7 -7
- package/dist/subtitles/Subtitles.d.ts +10 -0
- package/dist/subtitles/Subtitles.js +2 -2
- package/dist/subtitles/Subtitles.js.map +1 -1
- package/dist/synthesis/AwsPollyTTS.d.ts +1 -1
- package/dist/synthesis/AwsPollyTTS.js +12 -12
- package/dist/synthesis/AzureCognitiveServicesTTS.js +7 -7
- package/dist/synthesis/CoquiServerTTS.js +10 -10
- package/dist/synthesis/CoquiServerTTS.js.map +1 -1
- package/dist/synthesis/ElevenlabsTTS.d.ts +23 -0
- package/dist/synthesis/ElevenlabsTTS.js +103 -0
- package/dist/synthesis/ElevenlabsTTS.js.map +1 -0
- package/dist/synthesis/EspeakTTS.d.ts +6 -5
- package/dist/synthesis/EspeakTTS.js +81 -69
- package/dist/synthesis/EspeakTTS.js.map +1 -1
- package/dist/synthesis/FliteTTS.d.ts +3 -3
- package/dist/synthesis/FliteTTS.js +154 -154
- package/dist/synthesis/FliteTTS.js.map +1 -1
- package/dist/synthesis/GoogleCloudTTS.d.ts +3 -3
- package/dist/synthesis/GoogleCloudTTS.js +17 -17
- package/dist/synthesis/GoogleCloudTTS.js.map +1 -1
- package/dist/synthesis/GoogleTranslateTTS.d.ts +1 -1
- package/dist/synthesis/GoogleTranslateTTS.js +103 -103
- package/dist/synthesis/MicrosoftEdgeTTS.d.ts +2 -2
- package/dist/synthesis/MicrosoftEdgeTTS.js +74 -74
- package/dist/synthesis/OpenAICloudTTS.d.ts +13 -0
- package/dist/synthesis/OpenAICloudTTS.js +169 -0
- package/dist/synthesis/OpenAICloudTTS.js.map +1 -0
- package/dist/synthesis/SamTTS.js +3 -3
- package/dist/synthesis/SapiTTS.d.ts +3 -3
- package/dist/synthesis/SapiTTS.js +26 -26
- package/dist/synthesis/StreamlabsPollyTTS.d.ts +2 -2
- package/dist/synthesis/StreamlabsPollyTTS.js +27 -27
- package/dist/synthesis/SvoxPicoTTS.d.ts +2 -2
- package/dist/synthesis/SvoxPicoTTS.js +65 -65
- package/dist/synthesis/SvoxPicoTTS.js.map +1 -1
- package/dist/synthesis/VitsTTS.d.ts +3 -3
- package/dist/synthesis/VitsTTS.js +378 -378
- package/dist/synthesis/VitsTTS.js.map +1 -1
- package/dist/tests/Test.js +2 -2
- package/dist/utilities/Compression.d.ts +5 -0
- package/dist/utilities/Compression.js +29 -13
- package/dist/utilities/Compression.js.map +1 -1
- package/dist/utilities/FileDownloader.d.ts +1 -1
- package/dist/utilities/FileDownloader.js +16 -16
- package/dist/utilities/FileSystem.js +7 -7
- package/dist/utilities/Locale.d.ts +7 -7
- package/dist/utilities/Locale.js +15 -15
- package/dist/utilities/Logger.js +3 -3
- package/dist/utilities/ObjectUtilities.js +19 -19
- package/dist/utilities/OpenPromise.js +2 -2
- package/dist/utilities/OpenPromise.js.map +1 -1
- package/dist/utilities/PackageManager.js +31 -0
- package/dist/utilities/PackageManager.js.map +1 -1
- package/dist/utilities/PathUtilities.js +8 -8
- package/dist/utilities/RandomGenerator.js +2 -2
- package/dist/utilities/SmoothEstimator.d.ts +8 -0
- package/dist/utilities/SmoothEstimator.js +25 -0
- package/dist/utilities/SmoothEstimator.js.map +1 -0
- package/dist/utilities/TarballMaker.js +8 -8
- package/dist/utilities/Timeline.d.ts +3 -2
- package/dist/utilities/Timeline.js +11 -11
- package/dist/utilities/Timeline.js.map +1 -1
- package/dist/utilities/Timer.js +4 -4
- package/dist/utilities/Utilities.d.ts +4 -0
- package/dist/utilities/Utilities.js +38 -15
- package/dist/utilities/Utilities.js.map +1 -1
- package/dist/utilities/WasmMemoryManager.js +7 -7
- package/dist/utilities/WebReader.js +23 -23
- package/dist/utilities/WikipediaReader.js +2 -2
- package/dist/voice-activity-detection/AdaptiveGateVAD.d.ts +28 -0
- package/dist/voice-activity-detection/AdaptiveGateVAD.js +138 -0
- package/dist/voice-activity-detection/AdaptiveGateVAD.js.map +1 -0
- package/dist/voice-activity-detection/SileroVAD.d.ts +1 -1
- package/dist/voice-activity-detection/SileroVAD.js +5 -5
- package/dist/voice-activity-detection/SileroVAD.js.map +1 -1
- package/dist/voice-activity-detection/WebRtcVAD.d.ts +1 -1
- package/dist/voice-activity-detection/WebRtcVAD.js +4 -4
- package/docs/API.md +29 -11
- package/docs/CLI.md +31 -7
- package/docs/Contributing.md +38 -0
- package/docs/Development.md +93 -19
- package/docs/Engines.md +28 -16
- package/docs/Licenses.md +4 -1
- package/docs/Options.md +158 -78
- package/docs/Releases.md +262 -0
- package/docs/Server.md +7 -7
- package/docs/Tasklist.md +95 -76
- package/docs/Technical.md +4 -4
- package/package.json +13 -14
- package/src/alignment/DTWMfccSequenceAlignment.ts +9 -9
- package/src/alignment/DTWSequenceAlignment.ts +2 -2
- package/src/alignment/DTWSequenceAlignmentWindowed.ts +3 -3
- package/src/alignment/LevenshteinSequenceAlignment.ts +2 -2
- package/src/alignment/SpeechAlignment.ts +204 -119
- package/src/api/API.ts +14 -13
- package/src/api/APIOptions.ts +12 -11
- package/src/api/Alignment.ts +147 -90
- package/src/api/Common.ts +1 -1
- package/src/api/Denoising.ts +28 -28
- package/src/api/LanguageDetection.ts +135 -48
- package/src/api/Recognition.ts +198 -59
- package/src/api/SourceSeparation.ts +99 -0
- package/src/api/Synthesis.ts +217 -181
- package/src/api/Translation.ts +193 -40
- package/src/api/Vad.ts +110 -41
- package/src/audio/AudioBufferConversion.ts +4 -4
- package/src/audio/AudioPlayer.ts +27 -27
- package/src/audio/AudioRecorder.ts +5 -5
- package/src/audio/AudioUtilities.ts +107 -24
- package/src/cli/CLI.ts +313 -164
- package/src/cli/CLIConfigFile.ts +8 -8
- package/src/cli/CLILauncher.ts +6 -6
- package/src/cli/CLIOptionsSchema.ts +2 -2
- package/src/cli/CLIParser.ts +5 -5
- package/src/cli/CLIStarter.ts +4 -4
- package/src/codecs/FFMpegTranscoder.ts +38 -38
- package/src/codecs/TIMITCodec.ts +5 -5
- package/src/codecs/WaveCodec.ts +22 -22
- package/src/denoising/RNNoise.ts +9 -9
- package/src/dsp/BiquadFilter.ts +19 -11
- package/src/dsp/DecayingPeakEstimator.ts +35 -0
- package/src/dsp/FFT.ts +103 -35
- package/src/dsp/KWeightingFilter.ts +43 -0
- package/src/dsp/LoudnessEstimator.ts +74 -0
- package/src/dsp/MFCC.ts +15 -15
- package/src/dsp/MelSpectogram.ts +7 -7
- package/src/dsp/Rubberband.ts +38 -38
- package/src/dsp/Sonic.ts +4 -4
- package/src/dsp/SpeexResampler.ts +2 -2
- package/src/math/VectorMath.ts +42 -33
- package/src/nlp/ChineseSegmentation.ts +3 -3
- package/src/nlp/CompromiseNLP.ts +3 -3
- package/src/nlp/EspeakPhonemizer.ts +30 -30
- package/src/nlp/IPA.ts +20 -20
- package/src/nlp/JapaneseSegmentation.ts +6 -6
- package/src/nlp/Lexicon.ts +8 -8
- package/src/nlp/Segmentation.ts +23 -14
- package/src/nlp/TextNormalizer.ts +16 -16
- package/src/recognition/AmazonTranscribeSTT.ts +16 -17
- package/src/recognition/AzureCognitiveServicesSTT.ts +8 -6
- package/src/recognition/GoogleCloudSTT.ts +21 -21
- package/src/recognition/OpenAICloudSTT.ts +142 -0
- package/src/recognition/SileroSTT.ts +26 -26
- package/src/recognition/VoskSTT.ts +10 -10
- package/src/recognition/WhisperCppSTT.ts +555 -0
- package/src/recognition/WhisperSTT.ts +760 -507
- package/src/server/Client.ts +23 -23
- package/src/server/Server.ts +9 -9
- package/src/server/Worker.ts +53 -53
- package/src/server/WorkerStarter.ts +2 -2
- package/src/source-separation/MDXNetSourceSeparation.ts +228 -0
- package/src/speech-language-detection/SileroLanguageDetection.ts +8 -8
- package/src/subtitles/Subtitles.ts +3 -3
- package/src/synthesis/AwsPollyTTS.ts +14 -14
- package/src/synthesis/AzureCognitiveServicesTTS.ts +10 -10
- package/src/synthesis/CoquiServerTTS.ts +10 -10
- package/src/synthesis/ElevenlabsTTS.ts +137 -0
- package/src/synthesis/EspeakTTS.ts +90 -71
- package/src/synthesis/FliteTTS.ts +157 -157
- package/src/synthesis/GoogleCloudTTS.ts +19 -19
- package/src/synthesis/GoogleTranslateTTS.ts +104 -104
- package/src/synthesis/MicrosoftEdgeTTS.ts +80 -80
- package/src/synthesis/OpenAICloudTTS.ts +196 -0
- package/src/synthesis/SamTTS.ts +3 -3
- package/src/synthesis/SapiTTS.ts +29 -29
- package/src/synthesis/StreamlabsPollyTTS.ts +29 -29
- package/src/synthesis/SvoxPicoTTS.ts +67 -67
- package/src/synthesis/VitsTTS.ts +380 -380
- package/src/tests/Test.ts +4 -4
- package/src/utilities/Compression.ts +34 -13
- package/src/utilities/FileDownloader.ts +19 -19
- package/src/utilities/FileSystem.ts +7 -7
- package/src/utilities/Locale.ts +22 -22
- package/src/utilities/Logger.ts +4 -4
- package/src/utilities/ObjectUtilities.ts +19 -19
- package/src/utilities/OpenPromise.ts +2 -2
- package/src/utilities/PackageManager.ts +40 -0
- package/src/utilities/PathUtilities.ts +8 -8
- package/src/utilities/RandomGenerator.ts +3 -3
- package/src/utilities/SmoothEstimator.ts +35 -0
- package/src/utilities/TarballMaker.ts +9 -9
- package/src/utilities/Timeline.ts +15 -13
- package/src/utilities/Timer.ts +4 -4
- package/src/utilities/Utilities.ts +49 -15
- package/src/utilities/WasmMemoryManager.ts +7 -7
- package/src/utilities/WebReader.ts +23 -23
- package/src/utilities/WikipediaReader.ts +2 -2
- package/src/voice-activity-detection/AdaptiveGateVAD.ts +202 -0
- package/src/voice-activity-detection/SileroVAD.ts +5 -5
- package/src/voice-activity-detection/WebRtcVAD.ts +5 -5
- package/dist/synthesis/ElevenLabsTTS.d.ts +0 -8
- package/dist/synthesis/ElevenLabsTTS.js +0 -82
- package/dist/synthesis/ElevenLabsTTS.js.map +0 -1
- package/src/synthesis/ElevenLabsTTS.ts +0 -104
package/dist/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
|
|
4
|
-
import { getOptionTypeFromSchema } from
|
|
5
|
-
import { parseConfigFile, parseJSONConfigFile } from
|
|
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,
|
|
8
|
-
import { subtitlesToText, subtitlesToTimeline, timelineToSubtitles } from
|
|
9
|
-
import { Logger, resetActiveLogger } from
|
|
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
|
|
12
|
-
import path, { parse as parsePath } from
|
|
13
|
-
import { splitToParagraphs, splitToWords, wordCharacterPattern } from
|
|
14
|
-
import { playAudioSamples, playAudioWithWordTimeline } from
|
|
15
|
-
import { extendDeep } from
|
|
16
|
-
import { addTimeOffsetToTimeline, addWordTextOffsetsToTimeline, roundTimelineProperties } from
|
|
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(
|
|
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(
|
|
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 ==
|
|
55
|
-
logger.log(`Supported operations:\n\n${commandHelp.join(
|
|
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 ==
|
|
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(
|
|
63
|
+
if (!args.options.has('config')) {
|
|
64
64
|
const defaultConfigFile = `./${appName}.config`;
|
|
65
|
-
const defaultJsonConfigFile = defaultConfigFile +
|
|
65
|
+
const defaultJsonConfigFile = defaultConfigFile + '.json';
|
|
66
66
|
if (existsSync(defaultConfigFile)) {
|
|
67
|
-
args.options.set(
|
|
67
|
+
args.options.set('config', defaultConfigFile);
|
|
68
68
|
}
|
|
69
69
|
else if (existsSync(defaultJsonConfigFile)) {
|
|
70
|
-
args.options.set(
|
|
70
|
+
args.options.set('config', defaultJsonConfigFile);
|
|
71
71
|
}
|
|
72
72
|
}
|
|
73
|
-
if (args.options.has(
|
|
74
|
-
const configFilePath = args.options.get(
|
|
75
|
-
args.options.delete(
|
|
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(
|
|
77
|
+
if (configFilePath.endsWith('.config')) {
|
|
78
78
|
parsedOptionFile = await parseConfigFile(configFilePath);
|
|
79
79
|
}
|
|
80
|
-
else if (configFilePath.endsWith(
|
|
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(
|
|
88
|
-
sectionName =
|
|
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
|
-
`
|
|
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,
|
|
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,
|
|
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,
|
|
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 ==
|
|
244
|
+
if (command == 'speak') {
|
|
239
245
|
throw new Error(`'speak' requires an argument containing the text to speak.`);
|
|
240
246
|
}
|
|
241
|
-
else if (command ==
|
|
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 ==
|
|
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 ==
|
|
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,
|
|
264
|
+
const options = await cliOptionsMapToOptionsObject(cliOptions, 'SynthesisOptions', additionalOptionsSchema);
|
|
259
265
|
const allowOverwrite = getWithDefault(options.overwrite, overwriteByDefault);
|
|
260
|
-
const {
|
|
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 ==
|
|
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 ==
|
|
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 !=
|
|
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 ==
|
|
290
|
+
if (sourceFileExtension == 'txt') {
|
|
285
291
|
textSegments = splitToParagraphs(fileContent, plainTextParagraphBreaks, plainTextWhitespace);
|
|
286
292
|
plainText = fileContent;
|
|
287
293
|
}
|
|
288
|
-
else if (sourceFileExtension ==
|
|
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 ==
|
|
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 ==
|
|
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 ==
|
|
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(
|
|
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(
|
|
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 ==
|
|
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(
|
|
326
|
+
const { parseWikipediaArticle } = await import('../utilities/WikipediaReader.js');
|
|
321
327
|
if (!options.language) {
|
|
322
|
-
options.language =
|
|
328
|
+
options.language = 'en';
|
|
323
329
|
}
|
|
324
330
|
textSegments = await parseWikipediaArticle(mainArg, getShortLanguageCode(options.language));
|
|
325
331
|
}
|
|
326
332
|
else {
|
|
327
|
-
throw new Error(
|
|
333
|
+
throw new Error('Invalid command');
|
|
328
334
|
}
|
|
329
335
|
async function onSegment(segmentData) {
|
|
330
|
-
if (
|
|
331
|
-
logger.start(
|
|
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(
|
|
357
|
+
logger.start('\nWrite output files');
|
|
352
358
|
}
|
|
353
|
-
for (const
|
|
354
|
-
const
|
|
355
|
-
if (
|
|
359
|
+
for (const outputFilename of outputFilenames) {
|
|
360
|
+
const placeholderPatternMatch = outputFilename.match(filenamePlaceholderPattern);
|
|
361
|
+
if (placeholderPatternMatch) {
|
|
356
362
|
continue;
|
|
357
363
|
}
|
|
358
|
-
const fileSaver = getFileSaver(
|
|
359
|
-
await fileSaver(synthesizedAudio, timeline, textSegments.join(
|
|
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,
|
|
385
|
+
const options = await cliOptionsMapToOptionsObject(cliOptions, 'RecognitionOptions', additionalOptionsSchema);
|
|
380
386
|
const allowOverwrite = getWithDefault(options.overwrite, overwriteByDefault);
|
|
381
|
-
const {
|
|
382
|
-
const { transcript, timeline, wordTimeline, inputRawAudio,
|
|
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(
|
|
390
|
+
logger.start('\nWrite output files');
|
|
385
391
|
}
|
|
386
|
-
for (const
|
|
387
|
-
const partPatternMatch =
|
|
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(
|
|
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
|
-
|
|
397
|
-
|
|
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 ==
|
|
434
|
+
if (referenceFileExtension == 'txt') {
|
|
421
435
|
text = fileContent;
|
|
422
436
|
}
|
|
423
|
-
else if (referenceFileExtension ==
|
|
437
|
+
else if (referenceFileExtension == 'html' || referenceFileExtension == 'htm') {
|
|
424
438
|
text = await convertHtmlToText(fileContent);
|
|
425
439
|
}
|
|
426
|
-
else if (referenceFileExtension ==
|
|
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,
|
|
452
|
+
const options = await cliOptionsMapToOptionsObject(cliOptions, 'AlignmentOptions', additionalOptionsSchema);
|
|
439
453
|
const allowOverwrite = getWithDefault(options.overwrite, overwriteByDefault);
|
|
440
|
-
const {
|
|
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(
|
|
457
|
+
logger.start('\nWrite output files');
|
|
444
458
|
}
|
|
445
|
-
if (
|
|
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
|
|
454
|
-
const partPatternMatch =
|
|
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(
|
|
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
|
-
|
|
464
|
-
|
|
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,
|
|
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(
|
|
510
|
+
logger.start('\nWrite output files');
|
|
489
511
|
}
|
|
490
|
-
for (const
|
|
491
|
-
const partPatternMatch =
|
|
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(
|
|
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
|
-
|
|
501
|
-
|
|
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 = [
|
|
564
|
+
const supportedInputTextFormats = ['txt', 'srt', 'vtt'];
|
|
515
565
|
let results;
|
|
516
566
|
let allowOverwrite;
|
|
517
|
-
if (mode ==
|
|
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,
|
|
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:
|
|
528
|
-
if (inputFileExtension ==
|
|
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,
|
|
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(
|
|
546
|
-
const resultsAsText = results.map(result => `${formatLanguageCodeWithName(result.language)}: ${result.probability.toFixed(5)}`).join(
|
|
547
|
-
for (const
|
|
548
|
-
const fileSaver = getFileSaver(
|
|
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(
|
|
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,
|
|
625
|
+
const options = await cliOptionsMapToOptionsObject(cliOptions, 'VADOptions', additionalOptionsSchema);
|
|
576
626
|
const allowOverwrite = getWithDefault(options.overwrite, overwriteByDefault);
|
|
577
|
-
await checkOutputFilenames(outputFilenames, true, true,
|
|
578
|
-
|
|
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(
|
|
630
|
+
logger.start('\nWrite output files');
|
|
581
631
|
}
|
|
582
|
-
for (const
|
|
583
|
-
const partPatternMatch =
|
|
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(
|
|
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 =
|
|
594
|
-
return { ...entry, type:
|
|
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,
|
|
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(
|
|
677
|
+
logger.start('\nWrite output files');
|
|
621
678
|
}
|
|
622
|
-
for (const
|
|
623
|
-
const fileSaver = getFileSaver(
|
|
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 '
|
|
679
|
-
engines = API.
|
|
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([
|
|
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,
|
|
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(
|
|
820
|
+
}).join('\n\n');
|
|
729
821
|
if (outputFilenames.length > 0) {
|
|
730
|
-
logger.start(
|
|
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(
|
|
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(
|
|
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(
|
|
900
|
+
logger.log(installedPackageNamesFormatted.join('\n'));
|
|
809
901
|
}
|
|
810
902
|
async function serve(commandArgs, cliOptions) {
|
|
811
|
-
const options = await cliOptionsMapToOptionsObject(cliOptions,
|
|
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(
|
|
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(
|
|
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
|
|
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 (
|
|
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
|
|
954
|
-
if (
|
|
955
|
-
if (
|
|
956
|
-
throw new Error(`Invalid
|
|
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
|
-
|
|
1067
|
+
includesPlaceholderPattern = true;
|
|
959
1068
|
}
|
|
960
1069
|
}
|
|
961
|
-
return {
|
|
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(
|
|
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
|
|
977
|
-
if (!
|
|
1085
|
+
const placeholderPatternMatch = outputFilename.match(filenamePlaceholderPattern);
|
|
1086
|
+
if (!placeholderPatternMatch) {
|
|
978
1087
|
continue;
|
|
979
1088
|
}
|
|
980
|
-
const segmentFilename = outputFilename.replace(
|
|
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]}
|
|
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 ==
|
|
1108
|
+
if (fileExtension == 'txt') {
|
|
1000
1109
|
fileSaver = async (audio, timeline, text) => {
|
|
1001
1110
|
await ensureDir(fileDir);
|
|
1002
|
-
return writeFileSafe(outputFilePath, text, { encoding:
|
|
1111
|
+
return writeFileSafe(outputFilePath, text, { encoding: 'utf-8' });
|
|
1003
1112
|
};
|
|
1004
1113
|
}
|
|
1005
|
-
else if (fileExtension ==
|
|
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 ==
|
|
1121
|
+
else if (fileExtension == 'srt') {
|
|
1013
1122
|
fileSaver = async (audio, timeline, text, subtitlesConfig) => {
|
|
1014
1123
|
await ensureDir(fileDir);
|
|
1015
1124
|
const extraSubtitleConfigOptions = {
|
|
1016
|
-
format:
|
|
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 ==
|
|
1134
|
+
else if (fileExtension == 'vtt') {
|
|
1026
1135
|
fileSaver = async (audio, timeline, text, subtitlesConfig) => {
|
|
1027
1136
|
await ensureDir(fileDir);
|
|
1028
1137
|
const extraSubtitleConfigOptions = {
|
|
1029
|
-
format:
|
|
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 ==
|
|
1147
|
+
else if (fileExtension == 'wav') {
|
|
1039
1148
|
fileSaver = async (audio) => {
|
|
1040
1149
|
await ensureDir(fileDir);
|
|
1041
|
-
return writeFileSafe(outputFilePath,
|
|
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(
|
|
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 = [
|
|
1061
|
-
const
|
|
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
|