echogarden 0.12.2 → 1.0.1
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 +11 -6
- package/dist/alignment/SpeechAlignment.js +113 -71
- 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 +92 -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 +91 -19
- package/docs/Engines.md +28 -16
- package/docs/Licenses.md +4 -1
- package/docs/Options.md +159 -79
- package/docs/Releases.md +262 -0
- package/docs/Server.md +7 -7
- package/docs/Tasklist.md +96 -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 +195 -86
- package/src/api/API.ts +14 -13
- package/src/api/APIOptions.ts +12 -11
- package/src/api/Alignment.ts +148 -92
- 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/src/cli/CLI.ts
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
import * as API from '../api/API.js'
|
|
2
2
|
import { CLIArguments, parseCLIArguments } from './CLIParser.js'
|
|
3
|
-
import { convertHtmlToText, formatIntegerWithLeadingZeros, formatListWithQuotedElements, getWithDefault, logToStderr, setupUnhandledExceptionListeners, splitFilenameOnExtendedExtension, stringifyAndFormatJson } from
|
|
4
|
-
import { getOptionTypeFromSchema, SchemaTypeDefinition } from
|
|
5
|
-
import { ParsedConfigFile, parseConfigFile, parseJSONConfigFile } from
|
|
3
|
+
import { convertHtmlToText, formatIntegerWithLeadingZeros, formatListWithQuotedElements, getWithDefault, logToStderr, setupUnhandledExceptionListeners, splitFilenameOnExtendedExtension, stringifyAndFormatJson } from '../utilities/Utilities.js'
|
|
4
|
+
import { getOptionTypeFromSchema, SchemaTypeDefinition } from './CLIOptionsSchema.js'
|
|
5
|
+
import { ParsedConfigFile, parseConfigFile, parseJSONConfigFile } from './CLIConfigFile.js'
|
|
6
6
|
|
|
7
7
|
import chalk from 'chalk'
|
|
8
|
-
import { RawAudio, applyGainDecibels,
|
|
9
|
-
import { SubtitlesConfig, subtitlesToText, subtitlesToTimeline, timelineToSubtitles } from
|
|
10
|
-
import { Logger, resetActiveLogger } from
|
|
8
|
+
import { RawAudio, applyGainDecibels, encodeRawAudioToWave, getEmptyRawAudio, getRawAudioDuration, normalizeAudioLevel, sliceRawAudioByTime } from '../audio/AudioUtilities.js'
|
|
9
|
+
import { SubtitlesConfig, subtitlesToText, subtitlesToTimeline, timelineToSubtitles } from '../subtitles/Subtitles.js'
|
|
10
|
+
import { Logger, resetActiveLogger } from '../utilities/Logger.js'
|
|
11
11
|
import { isMainThread, parentPort } from 'node:worker_threads'
|
|
12
|
-
import { encodeFromChannels, getDefaultFFMpegOptionsForSpeech } from
|
|
13
|
-
import path, { parse as parsePath } from
|
|
14
|
-
import { splitToParagraphs, splitToWords, wordCharacterPattern } from
|
|
15
|
-
import { playAudioSamples, playAudioWithWordTimeline } from
|
|
16
|
-
import { extendDeep } from
|
|
17
|
-
import { Timeline, TimelineEntry, addTimeOffsetToTimeline, addWordTextOffsetsToTimeline, roundTimelineProperties, wordTimelineToSegmentSentenceTimeline } from
|
|
12
|
+
import { encodeFromChannels, getDefaultFFMpegOptionsForSpeech } from '../codecs/FFMpegTranscoder.js'
|
|
13
|
+
import path, { parse as parsePath } from 'node:path'
|
|
14
|
+
import { splitToParagraphs, splitToWords, wordCharacterPattern } from '../nlp/Segmentation.js'
|
|
15
|
+
import { playAudioSamples, playAudioWithWordTimeline } from '../audio/AudioPlayer.js'
|
|
16
|
+
import { deepClone, extendDeep } from '../utilities/ObjectUtilities.js'
|
|
17
|
+
import { Timeline, TimelineEntry, addTimeOffsetToTimeline, addWordTextOffsetsToTimeline, roundTimelineProperties, wordTimelineToSegmentSentenceTimeline } from '../utilities/Timeline.js'
|
|
18
18
|
import { ensureDir, existsSync, readAndParseJsonFile, readFile, readdir, writeFileSafe } from '../utilities/FileSystem.js'
|
|
19
19
|
import { formatLanguageCodeWithName, getShortLanguageCode } from '../utilities/Locale.js'
|
|
20
20
|
import { APIOptions } from '../api/APIOptions.js'
|
|
@@ -37,7 +37,7 @@ async function startIfInWorkerThread() {
|
|
|
37
37
|
|
|
38
38
|
const initOpenPromise = new OpenPromise<void>()
|
|
39
39
|
|
|
40
|
-
parentPort.once(
|
|
40
|
+
parentPort.once('message', (message) => {
|
|
41
41
|
if (message.name == 'init') {
|
|
42
42
|
process.stderr.isTTY = message.stdErrIsTTY
|
|
43
43
|
process.stderr.hasColors = () => message.hasColors
|
|
@@ -62,44 +62,44 @@ export async function start(processArgs: string[]) {
|
|
|
62
62
|
let args: CLIArguments
|
|
63
63
|
|
|
64
64
|
try {
|
|
65
|
-
const packageData = await readAndParseJsonFile(resolveToModuleRootDir(
|
|
65
|
+
const packageData = await readAndParseJsonFile(resolveToModuleRootDir('package.json'))
|
|
66
66
|
|
|
67
67
|
logger.log(chalk.magentaBright(`Echogarden v${packageData.version}\n`))
|
|
68
68
|
|
|
69
69
|
const command = processArgs[0]
|
|
70
70
|
|
|
71
|
-
if (!command || command ==
|
|
72
|
-
logger.log(`Supported operations:\n\n${commandHelp.join(
|
|
71
|
+
if (!command || command == 'help') {
|
|
72
|
+
logger.log(`Supported operations:\n\n${commandHelp.join('\n')}`)
|
|
73
73
|
process.exit(0)
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
-
if (command ==
|
|
76
|
+
if (command == '--help') {
|
|
77
77
|
logger.log(`There's no command called '--help'. Did you mean to run 'echogarden help'?`)
|
|
78
78
|
process.exit(0)
|
|
79
79
|
}
|
|
80
80
|
|
|
81
81
|
args = parseCLIArguments(command, processArgs.slice(1))
|
|
82
82
|
|
|
83
|
-
if (!args.options.has(
|
|
83
|
+
if (!args.options.has('config')) {
|
|
84
84
|
const defaultConfigFile = `./${appName}.config`
|
|
85
|
-
const defaultJsonConfigFile = defaultConfigFile +
|
|
85
|
+
const defaultJsonConfigFile = defaultConfigFile + '.json'
|
|
86
86
|
|
|
87
87
|
if (existsSync(defaultConfigFile)) {
|
|
88
|
-
args.options.set(
|
|
88
|
+
args.options.set('config', defaultConfigFile)
|
|
89
89
|
} else if (existsSync(defaultJsonConfigFile)) {
|
|
90
|
-
args.options.set(
|
|
90
|
+
args.options.set('config', defaultJsonConfigFile)
|
|
91
91
|
}
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
-
if (args.options.has(
|
|
95
|
-
const configFilePath = args.options.get(
|
|
96
|
-
args.options.delete(
|
|
94
|
+
if (args.options.has('config')) {
|
|
95
|
+
const configFilePath = args.options.get('config')!
|
|
96
|
+
args.options.delete('config')
|
|
97
97
|
|
|
98
98
|
let parsedOptionFile: ParsedConfigFile
|
|
99
99
|
|
|
100
|
-
if (configFilePath.endsWith(
|
|
100
|
+
if (configFilePath.endsWith('.config')) {
|
|
101
101
|
parsedOptionFile = await parseConfigFile(configFilePath)
|
|
102
|
-
} else if (configFilePath.endsWith(
|
|
102
|
+
} else if (configFilePath.endsWith('.config.json')) {
|
|
103
103
|
parsedOptionFile = await parseJSONConfigFile(configFilePath)
|
|
104
104
|
} else {
|
|
105
105
|
throw new Error(`Specified config file '${configFilePath}' doesn't have a supported extension. Should be either '.config' or '.config.json'`)
|
|
@@ -107,8 +107,8 @@ export async function start(processArgs: string[]) {
|
|
|
107
107
|
|
|
108
108
|
let sectionName = args.command
|
|
109
109
|
|
|
110
|
-
if (sectionName.startsWith(
|
|
111
|
-
sectionName =
|
|
110
|
+
if (sectionName.startsWith('speak-')) {
|
|
111
|
+
sectionName = 'speak'
|
|
112
112
|
}
|
|
113
113
|
|
|
114
114
|
const newOptions = parsedOptionFile.get(sectionName) || new Map<string, string>()
|
|
@@ -174,7 +174,9 @@ const commandHelp = [
|
|
|
174
174
|
`${executableName} ${chalk.magentaBright('detect-voice-activity')} audioFile [output files...] [options...]`,
|
|
175
175
|
` Detect voice activity in audio file\n`,
|
|
176
176
|
`${executableName} ${chalk.magentaBright('denoise')} audioFile [output files...] [options...]`,
|
|
177
|
-
`
|
|
177
|
+
` Apply speech denoising to audio file\n`,
|
|
178
|
+
`${executableName} ${chalk.magentaBright('isolate')} audioFile [output files...] [options...]`,
|
|
179
|
+
` Extract isolated voice track from an audio file\n`,
|
|
178
180
|
`${executableName} ${chalk.magentaBright('list-engines')} operation`,
|
|
179
181
|
` List available engines for the specified operation\n`,
|
|
180
182
|
`${executableName} ${chalk.magentaBright('list-voices')} tts-engine [output files...] [options...]`,
|
|
@@ -219,17 +221,17 @@ async function startWithArgs(parsedArgs: CLIArguments) {
|
|
|
219
221
|
}
|
|
220
222
|
|
|
221
223
|
case 'detect-language': {
|
|
222
|
-
await detectLanguage(parsedArgs.commandArgs, parsedArgs.options,
|
|
224
|
+
await detectLanguage(parsedArgs.commandArgs, parsedArgs.options, 'auto')
|
|
223
225
|
break
|
|
224
226
|
}
|
|
225
227
|
|
|
226
228
|
case 'detect-speech-language': {
|
|
227
|
-
await detectLanguage(parsedArgs.commandArgs, parsedArgs.options,
|
|
229
|
+
await detectLanguage(parsedArgs.commandArgs, parsedArgs.options, 'speech')
|
|
228
230
|
break
|
|
229
231
|
}
|
|
230
232
|
|
|
231
233
|
case 'detect-text-language': {
|
|
232
|
-
await detectLanguage(parsedArgs.commandArgs, parsedArgs.options,
|
|
234
|
+
await detectLanguage(parsedArgs.commandArgs, parsedArgs.options, 'text')
|
|
233
235
|
break
|
|
234
236
|
}
|
|
235
237
|
|
|
@@ -243,6 +245,11 @@ async function startWithArgs(parsedArgs: CLIArguments) {
|
|
|
243
245
|
break
|
|
244
246
|
}
|
|
245
247
|
|
|
248
|
+
case 'isolate': {
|
|
249
|
+
await isolate(parsedArgs.commandArgs, parsedArgs.options)
|
|
250
|
+
break
|
|
251
|
+
}
|
|
252
|
+
|
|
246
253
|
case 'list-engines': {
|
|
247
254
|
await listEngines(parsedArgs.commandArgs, parsedArgs.options)
|
|
248
255
|
break
|
|
@@ -280,7 +287,7 @@ async function startWithArgs(parsedArgs: CLIArguments) {
|
|
|
280
287
|
}
|
|
281
288
|
}
|
|
282
289
|
|
|
283
|
-
type SpeakCommand =
|
|
290
|
+
type SpeakCommand = 'speak' | 'speak-file' | 'speak-url' | 'speak-wikipedia'
|
|
284
291
|
|
|
285
292
|
async function speak(command: SpeakCommand, commandArgs: string[], cliOptions: Map<string, string>) {
|
|
286
293
|
const logger = new Logger()
|
|
@@ -289,13 +296,13 @@ async function speak(command: SpeakCommand, commandArgs: string[], cliOptions: M
|
|
|
289
296
|
const outputFilenames = commandArgs.slice(1)
|
|
290
297
|
|
|
291
298
|
if (mainArg == undefined) {
|
|
292
|
-
if (command ==
|
|
299
|
+
if (command == 'speak') {
|
|
293
300
|
throw new Error(`'speak' requires an argument containing the text to speak.`)
|
|
294
|
-
} else if (command ==
|
|
301
|
+
} else if (command == 'speak-file') {
|
|
295
302
|
throw new Error(`'speak-file' requires an argument containing the file to speak.`)
|
|
296
|
-
} else if (command ==
|
|
303
|
+
} else if (command == 'speak-url') {
|
|
297
304
|
throw new Error(`'speak-url' requires an argument containing the url to speak.`)
|
|
298
|
-
} else if (command ==
|
|
305
|
+
} else if (command == 'speak-wikipedia') {
|
|
299
306
|
throw new Error(`'speak-wikipedia' requires an argument containing the name of the Wikipedia article to speak.`)
|
|
300
307
|
}
|
|
301
308
|
|
|
@@ -310,10 +317,10 @@ async function speak(command: SpeakCommand, commandArgs: string[], cliOptions: M
|
|
|
310
317
|
cliOptions.set('play', `${outputFilenames.length == 0}`)
|
|
311
318
|
}
|
|
312
319
|
|
|
313
|
-
const options: API.SynthesisOptions = await cliOptionsMapToOptionsObject(cliOptions,
|
|
320
|
+
const options: API.SynthesisOptions = await cliOptionsMapToOptionsObject(cliOptions, 'SynthesisOptions', additionalOptionsSchema)
|
|
314
321
|
|
|
315
322
|
const allowOverwrite = getWithDefault((options as any).overwrite, overwriteByDefault)
|
|
316
|
-
const {
|
|
323
|
+
const { includesPlaceholderPattern } = await checkOutputFilenames(outputFilenames, true, true, true)
|
|
317
324
|
|
|
318
325
|
let plainText: string | undefined = undefined
|
|
319
326
|
let textSegments: string[]
|
|
@@ -321,7 +328,7 @@ async function speak(command: SpeakCommand, commandArgs: string[], cliOptions: M
|
|
|
321
328
|
const plainTextParagraphBreaks = options.plainText?.paragraphBreaks || API.defaultSynthesisOptions.plainText!.paragraphBreaks!
|
|
322
329
|
const plainTextWhitespace = options.plainText?.whitespace || API.defaultSynthesisOptions.plainText!.whitespace!
|
|
323
330
|
|
|
324
|
-
if (command ==
|
|
331
|
+
if (command == 'speak') {
|
|
325
332
|
if (options.ssml) {
|
|
326
333
|
textSegments = [mainArg]
|
|
327
334
|
} else {
|
|
@@ -329,7 +336,7 @@ async function speak(command: SpeakCommand, commandArgs: string[], cliOptions: M
|
|
|
329
336
|
}
|
|
330
337
|
|
|
331
338
|
plainText = mainArg
|
|
332
|
-
} else if (command ==
|
|
339
|
+
} else if (command == 'speak-file') {
|
|
333
340
|
const sourceFile = mainArg
|
|
334
341
|
|
|
335
342
|
if (!existsSync(sourceFile)) {
|
|
@@ -339,59 +346,59 @@ async function speak(command: SpeakCommand, commandArgs: string[], cliOptions: M
|
|
|
339
346
|
const sourceFileExtension = getLowercaseFileExtension(sourceFile)
|
|
340
347
|
const fileContent = await readFile(sourceFile, { encoding: 'utf-8' })
|
|
341
348
|
|
|
342
|
-
if (options.ssml && sourceFileExtension !=
|
|
349
|
+
if (options.ssml && sourceFileExtension != 'xml' && sourceFileExtension != 'ssml') {
|
|
343
350
|
throw new Error(`SSML option is set, but source file doesn't have an 'xml' or 'ssml' extension.`)
|
|
344
351
|
}
|
|
345
352
|
|
|
346
|
-
if (sourceFileExtension ==
|
|
353
|
+
if (sourceFileExtension == 'txt') {
|
|
347
354
|
textSegments = splitToParagraphs(fileContent, plainTextParagraphBreaks, plainTextWhitespace)
|
|
348
355
|
|
|
349
356
|
plainText = fileContent
|
|
350
|
-
} else if (sourceFileExtension ==
|
|
357
|
+
} else if (sourceFileExtension == 'html' || sourceFileExtension == 'htm') {
|
|
351
358
|
const textContent = await convertHtmlToText(fileContent)
|
|
352
359
|
textSegments = splitToParagraphs(textContent, 'single', 'preserve')
|
|
353
|
-
} else if (sourceFileExtension ==
|
|
360
|
+
} else if (sourceFileExtension == 'srt' || sourceFileExtension == 'vtt') {
|
|
354
361
|
const fileContent = await readFile(sourceFile, { encoding: 'utf-8' })
|
|
355
362
|
textSegments = subtitlesToTimeline(fileContent).map(entry => entry.text)
|
|
356
|
-
} else if (sourceFileExtension ==
|
|
363
|
+
} else if (sourceFileExtension == 'xml' || sourceFileExtension == 'ssml') {
|
|
357
364
|
options.ssml = true
|
|
358
365
|
textSegments = [fileContent]
|
|
359
366
|
} else {
|
|
360
367
|
throw new Error(`'speak-file' only supports inputs with extensions 'txt', 'html', 'htm', 'xml', 'ssml', 'srt', 'vtt'`)
|
|
361
368
|
}
|
|
362
|
-
} else if (command ==
|
|
369
|
+
} else if (command == 'speak-url') {
|
|
363
370
|
if (options.ssml) {
|
|
364
371
|
throw new Error(`speak-url doesn't provide SSML inputs`)
|
|
365
372
|
}
|
|
366
373
|
|
|
367
374
|
const url = mainArg
|
|
368
375
|
|
|
369
|
-
if (!url.startsWith(
|
|
376
|
+
if (!url.startsWith('http://') && !url.startsWith('https://')) {
|
|
370
377
|
throw new Error(`'${url}' is not a valid URL. Only 'http://' and 'https://' protocols are supported`)
|
|
371
378
|
}
|
|
372
379
|
|
|
373
|
-
const { fetchDocumentText } = await import(
|
|
380
|
+
const { fetchDocumentText } = await import('../utilities/WebReader.js')
|
|
374
381
|
const textContent = await fetchDocumentText(url)
|
|
375
382
|
|
|
376
383
|
textSegments = splitToParagraphs(textContent, 'single', 'preserve')
|
|
377
|
-
} else if (command ==
|
|
384
|
+
} else if (command == 'speak-wikipedia') {
|
|
378
385
|
if (options.ssml) {
|
|
379
386
|
throw new Error(`speak-wikipedia doesn't provide SSML inputs`)
|
|
380
387
|
}
|
|
381
388
|
|
|
382
|
-
const { parseWikipediaArticle } = await import(
|
|
389
|
+
const { parseWikipediaArticle } = await import('../utilities/WikipediaReader.js')
|
|
383
390
|
if (!options.language) {
|
|
384
|
-
options.language =
|
|
391
|
+
options.language = 'en'
|
|
385
392
|
}
|
|
386
393
|
|
|
387
394
|
textSegments = await parseWikipediaArticle(mainArg, getShortLanguageCode(options.language))
|
|
388
395
|
} else {
|
|
389
|
-
throw new Error(
|
|
396
|
+
throw new Error('Invalid command')
|
|
390
397
|
}
|
|
391
398
|
|
|
392
399
|
async function onSegment(segmentData: API.SynthesisSegmentEventData) {
|
|
393
|
-
if (
|
|
394
|
-
logger.start(
|
|
400
|
+
if (includesPlaceholderPattern) {
|
|
401
|
+
logger.start('Write output files for segment')
|
|
395
402
|
}
|
|
396
403
|
|
|
397
404
|
await writeOutputFilesForSegment(outputFilenames, segmentData.index, segmentData.total, segmentData.audio as RawAudio, segmentData.timeline, segmentData.transcript, segmentData.language, allowOverwrite)
|
|
@@ -420,18 +427,18 @@ async function speak(command: SpeakCommand, commandArgs: string[], cliOptions: M
|
|
|
420
427
|
}
|
|
421
428
|
|
|
422
429
|
if (outputFilenames.length > 0) {
|
|
423
|
-
logger.start(
|
|
430
|
+
logger.start('\nWrite output files')
|
|
424
431
|
}
|
|
425
432
|
|
|
426
|
-
for (const
|
|
427
|
-
const
|
|
433
|
+
for (const outputFilename of outputFilenames) {
|
|
434
|
+
const placeholderPatternMatch = outputFilename.match(filenamePlaceholderPattern)
|
|
428
435
|
|
|
429
|
-
if (
|
|
436
|
+
if (placeholderPatternMatch) {
|
|
430
437
|
continue
|
|
431
438
|
}
|
|
432
439
|
|
|
433
|
-
const fileSaver = getFileSaver(
|
|
434
|
-
await fileSaver(synthesizedAudio as RawAudio, timeline, textSegments.join(
|
|
440
|
+
const fileSaver = getFileSaver(outputFilename, allowOverwrite)
|
|
441
|
+
await fileSaver(synthesizedAudio as RawAudio, timeline, textSegments.join('\n\n'), options.subtitles)
|
|
435
442
|
}
|
|
436
443
|
|
|
437
444
|
logger.end()
|
|
@@ -459,35 +466,45 @@ async function transcribe(commandArgs: string[], cliOptions: Map<string, string>
|
|
|
459
466
|
cliOptions.set('play', `${outputFilenames.length == 0}`)
|
|
460
467
|
}
|
|
461
468
|
|
|
462
|
-
const options: API.RecognitionOptions = await cliOptionsMapToOptionsObject(cliOptions,
|
|
469
|
+
const options: API.RecognitionOptions = await cliOptionsMapToOptionsObject(cliOptions, 'RecognitionOptions', additionalOptionsSchema)
|
|
463
470
|
|
|
464
471
|
const allowOverwrite = getWithDefault((options as any).overwrite, overwriteByDefault)
|
|
465
|
-
const {
|
|
472
|
+
const { includesPlaceholderPattern } = await checkOutputFilenames(outputFilenames, true, true, true)
|
|
466
473
|
|
|
467
|
-
const { transcript, timeline, wordTimeline, inputRawAudio,
|
|
474
|
+
const { transcript, timeline, wordTimeline, language, inputRawAudio, isolatedRawAudio, backgroundRawAudio } = await API.recognize(sourceFilename, options)
|
|
468
475
|
|
|
469
476
|
if (outputFilenames.length > 0) {
|
|
470
|
-
logger.start(
|
|
477
|
+
logger.start('\nWrite output files')
|
|
471
478
|
}
|
|
472
479
|
|
|
473
|
-
for (const
|
|
474
|
-
const partPatternMatch =
|
|
480
|
+
for (const outputFilename of outputFilenames) {
|
|
481
|
+
const partPatternMatch = outputFilename.match(filenamePlaceholderPattern)
|
|
475
482
|
|
|
476
483
|
if (partPatternMatch) {
|
|
477
484
|
continue
|
|
478
485
|
}
|
|
479
486
|
|
|
480
|
-
const fileSaver = getFileSaver(
|
|
487
|
+
const fileSaver = getFileSaver(outputFilename, allowOverwrite)
|
|
481
488
|
|
|
482
489
|
await fileSaver(inputRawAudio, timeline, transcript, options.subtitles)
|
|
490
|
+
|
|
491
|
+
await writeSourceSeparationOutputIfNeeded(outputFilename, isolatedRawAudio, backgroundRawAudio, allowOverwrite, true)
|
|
483
492
|
}
|
|
484
493
|
|
|
485
494
|
logger.end()
|
|
486
495
|
|
|
487
496
|
if ((options as any).play) {
|
|
488
|
-
|
|
497
|
+
let audioToPlay: RawAudio
|
|
498
|
+
|
|
499
|
+
if (isolatedRawAudio) {
|
|
500
|
+
audioToPlay = isolatedRawAudio
|
|
501
|
+
} else {
|
|
502
|
+
audioToPlay = inputRawAudio
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
const normalizedAudioToPlay = normalizeAudioLevel(audioToPlay)
|
|
489
506
|
|
|
490
|
-
await playAudioWithWordTimeline(
|
|
507
|
+
await playAudioWithWordTimeline(normalizedAudioToPlay, wordTimeline, transcript)
|
|
491
508
|
}
|
|
492
509
|
}
|
|
493
510
|
|
|
@@ -520,11 +537,11 @@ async function align(commandArgs: string[], cliOptions: Map<string, string>) {
|
|
|
520
537
|
|
|
521
538
|
let text: string
|
|
522
539
|
|
|
523
|
-
if (referenceFileExtension ==
|
|
540
|
+
if (referenceFileExtension == 'txt') {
|
|
524
541
|
text = fileContent
|
|
525
|
-
} else if (referenceFileExtension ==
|
|
542
|
+
} else if (referenceFileExtension == 'html' || referenceFileExtension == 'htm') {
|
|
526
543
|
text = await convertHtmlToText(fileContent)
|
|
527
|
-
} else if (referenceFileExtension ==
|
|
544
|
+
} else if (referenceFileExtension == 'srt' || referenceFileExtension == 'vtt') {
|
|
528
545
|
text = subtitlesToText(fileContent)
|
|
529
546
|
} else {
|
|
530
547
|
throw new Error(`align only supports reference files with extensions 'txt', 'html', 'htm', 'srt' or 'vtt'`)
|
|
@@ -538,18 +555,18 @@ async function align(commandArgs: string[], cliOptions: Map<string, string>) {
|
|
|
538
555
|
cliOptions.set('play', `${outputFilenames.length == 0}`)
|
|
539
556
|
}
|
|
540
557
|
|
|
541
|
-
const options: API.AlignmentOptions = await cliOptionsMapToOptionsObject(cliOptions,
|
|
558
|
+
const options: API.AlignmentOptions = await cliOptionsMapToOptionsObject(cliOptions, 'AlignmentOptions', additionalOptionsSchema)
|
|
542
559
|
|
|
543
560
|
const allowOverwrite = getWithDefault((options as any).overwrite, overwriteByDefault)
|
|
544
|
-
const {
|
|
561
|
+
const { includesPlaceholderPattern } = await checkOutputFilenames(outputFilenames, true, true, true)
|
|
545
562
|
|
|
546
|
-
const { timeline, wordTimeline, transcript, language, inputRawAudio } = await API.align(audioFilename, text, options)
|
|
563
|
+
const { timeline, wordTimeline, transcript, language, inputRawAudio, isolatedRawAudio, backgroundRawAudio } = await API.align(audioFilename, text, options)
|
|
547
564
|
|
|
548
565
|
if (outputFilenames.length > 0) {
|
|
549
|
-
logger.start(
|
|
566
|
+
logger.start('\nWrite output files')
|
|
550
567
|
}
|
|
551
568
|
|
|
552
|
-
if (
|
|
569
|
+
if (includesPlaceholderPattern) {
|
|
553
570
|
for (let segmentIndex = 0; segmentIndex < timeline.length; segmentIndex++) {
|
|
554
571
|
const segmentEntry = timeline[segmentIndex]
|
|
555
572
|
const segmentAudio = sliceRawAudioByTime(inputRawAudio, segmentEntry.startTime, segmentEntry.endTime)
|
|
@@ -559,24 +576,34 @@ async function align(commandArgs: string[], cliOptions: Map<string, string>) {
|
|
|
559
576
|
}
|
|
560
577
|
}
|
|
561
578
|
|
|
562
|
-
for (const
|
|
563
|
-
const partPatternMatch =
|
|
579
|
+
for (const outputFilename of outputFilenames) {
|
|
580
|
+
const partPatternMatch = outputFilename.match(filenamePlaceholderPattern)
|
|
564
581
|
|
|
565
582
|
if (partPatternMatch) {
|
|
566
583
|
continue
|
|
567
584
|
}
|
|
568
585
|
|
|
569
|
-
const fileSaver = getFileSaver(
|
|
586
|
+
const fileSaver = getFileSaver(outputFilename, allowOverwrite)
|
|
570
587
|
|
|
571
588
|
await fileSaver(inputRawAudio, timeline, transcript, options.subtitles)
|
|
589
|
+
|
|
590
|
+
await writeSourceSeparationOutputIfNeeded(outputFilename, isolatedRawAudio, backgroundRawAudio, allowOverwrite, true)
|
|
572
591
|
}
|
|
573
592
|
|
|
574
593
|
logger.end()
|
|
575
594
|
|
|
576
595
|
if ((options as any).play) {
|
|
577
|
-
|
|
596
|
+
let audioToPlay: RawAudio
|
|
578
597
|
|
|
579
|
-
|
|
598
|
+
if (isolatedRawAudio) {
|
|
599
|
+
audioToPlay = isolatedRawAudio
|
|
600
|
+
} else {
|
|
601
|
+
audioToPlay = inputRawAudio
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
const normalizedAudioToPlay = normalizeAudioLevel(audioToPlay)
|
|
605
|
+
|
|
606
|
+
await playAudioWithWordTimeline(normalizedAudioToPlay, wordTimeline, transcript)
|
|
580
607
|
}
|
|
581
608
|
}
|
|
582
609
|
|
|
@@ -602,40 +629,74 @@ async function translateSpeech(commandArgs: string[], cliOptions: Map<string, st
|
|
|
602
629
|
cliOptions.set('play', `${outputFilenames.length == 0}`)
|
|
603
630
|
}
|
|
604
631
|
|
|
605
|
-
const options: API.SpeechTranslationOptions = await cliOptionsMapToOptionsObject(cliOptions,
|
|
632
|
+
const options: API.SpeechTranslationOptions = await cliOptionsMapToOptionsObject(cliOptions, 'SpeechTranslationOptions', additionalOptionsSchema)
|
|
606
633
|
|
|
607
634
|
const allowOverwrite = getWithDefault((options as any).overwrite, overwriteByDefault)
|
|
608
635
|
|
|
609
636
|
await checkOutputFilenames(outputFilenames, true, true, true)
|
|
610
637
|
|
|
611
|
-
const { transcript, timeline, wordTimeline, sourceLanguage, targetLanguage, inputRawAudio } = await API.translateSpeech(inputFilename, options)
|
|
638
|
+
const { transcript, timeline, wordTimeline, sourceLanguage, targetLanguage, inputRawAudio, isolatedRawAudio, backgroundRawAudio } = await API.translateSpeech(inputFilename, options)
|
|
612
639
|
|
|
613
640
|
if (outputFilenames.length > 0) {
|
|
614
|
-
logger.start(
|
|
641
|
+
logger.start('\nWrite output files')
|
|
615
642
|
}
|
|
616
643
|
|
|
617
|
-
for (const
|
|
618
|
-
const partPatternMatch =
|
|
644
|
+
for (const outputFilename of outputFilenames) {
|
|
645
|
+
const partPatternMatch = outputFilename.match(filenamePlaceholderPattern)
|
|
619
646
|
|
|
620
647
|
if (partPatternMatch) {
|
|
621
648
|
continue
|
|
622
649
|
}
|
|
623
650
|
|
|
624
|
-
const fileSaver = getFileSaver(
|
|
651
|
+
const fileSaver = getFileSaver(outputFilename, allowOverwrite)
|
|
625
652
|
|
|
626
653
|
await fileSaver(inputRawAudio, timeline, transcript, options.subtitles)
|
|
654
|
+
|
|
655
|
+
await writeSourceSeparationOutputIfNeeded(outputFilename, isolatedRawAudio, backgroundRawAudio, allowOverwrite, true)
|
|
627
656
|
}
|
|
628
657
|
|
|
629
658
|
logger.end()
|
|
630
659
|
|
|
631
660
|
if ((options as any).play) {
|
|
632
|
-
|
|
661
|
+
let audioToPlay: RawAudio
|
|
662
|
+
|
|
663
|
+
if (isolatedRawAudio) {
|
|
664
|
+
audioToPlay = isolatedRawAudio
|
|
665
|
+
} else {
|
|
666
|
+
audioToPlay = inputRawAudio
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
const normalizedAudioToPlay = normalizeAudioLevel(audioToPlay)
|
|
633
670
|
|
|
634
|
-
|
|
671
|
+
let transcriptToPlay: string
|
|
672
|
+
let timelineToPlay: Timeline
|
|
673
|
+
|
|
674
|
+
if (wordTimeline) {
|
|
675
|
+
transcriptToPlay = transcript
|
|
676
|
+
timelineToPlay = wordTimeline
|
|
677
|
+
} else {
|
|
678
|
+
timelineToPlay = timeline.map(entry => ({
|
|
679
|
+
type: 'word',
|
|
680
|
+
text: entry.text.trim(),
|
|
681
|
+
startTime: entry.startTime,
|
|
682
|
+
endTime: entry.endTime
|
|
683
|
+
}))
|
|
684
|
+
|
|
685
|
+
transcriptToPlay = ''
|
|
686
|
+
|
|
687
|
+
for (const entry of timelineToPlay) {
|
|
688
|
+
transcriptToPlay += entry.text
|
|
689
|
+
transcriptToPlay += ' '
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
transcriptToPlay = transcriptToPlay.trim()
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
await playAudioWithWordTimeline(normalizedAudioToPlay, timelineToPlay, transcriptToPlay)
|
|
635
696
|
}
|
|
636
697
|
}
|
|
637
698
|
|
|
638
|
-
async function detectLanguage(commandArgs: string[], cliOptions: Map<string, string>, mode:
|
|
699
|
+
async function detectLanguage(commandArgs: string[], cliOptions: Map<string, string>, mode: 'speech' | 'text' | 'auto') {
|
|
639
700
|
const logger = new Logger()
|
|
640
701
|
|
|
641
702
|
const inputFilePath = commandArgs[0]
|
|
@@ -649,13 +710,13 @@ async function detectLanguage(commandArgs: string[], cliOptions: Map<string, str
|
|
|
649
710
|
additionalOptionsSchema.set('overwrite', { type: 'boolean' })
|
|
650
711
|
|
|
651
712
|
const inputFileExtension = getLowercaseFileExtension(inputFilePath)
|
|
652
|
-
const supportedInputTextFormats = [
|
|
713
|
+
const supportedInputTextFormats = ['txt', 'srt', 'vtt']
|
|
653
714
|
|
|
654
715
|
let results: API.LanguageDetectionResults
|
|
655
716
|
|
|
656
717
|
let allowOverwrite: boolean
|
|
657
718
|
|
|
658
|
-
if (mode ==
|
|
719
|
+
if (mode == 'text' || (mode == 'auto' && supportedInputTextFormats.includes(inputFileExtension))) {
|
|
659
720
|
if (inputFilePath == undefined) {
|
|
660
721
|
throw new Error(`detect-text-language requires an argument containing the input file path.`)
|
|
661
722
|
}
|
|
@@ -664,14 +725,14 @@ async function detectLanguage(commandArgs: string[], cliOptions: Map<string, str
|
|
|
664
725
|
throw new Error(`'detect-text-language' doesn't support input file extension '${inputFileExtension}'`)
|
|
665
726
|
}
|
|
666
727
|
|
|
667
|
-
const options: API.TextLanguageDetectionOptions = await cliOptionsMapToOptionsObject(cliOptions,
|
|
728
|
+
const options: API.TextLanguageDetectionOptions = await cliOptionsMapToOptionsObject(cliOptions, 'TextLanguageDetectionOptions', additionalOptionsSchema)
|
|
668
729
|
allowOverwrite = getWithDefault((options as any).overwrite, overwriteByDefault)
|
|
669
730
|
|
|
670
731
|
await checkOutputFilenames(outputFilenames, false, true, false)
|
|
671
732
|
|
|
672
|
-
let text = await readFile(inputFilePath, { encoding:
|
|
733
|
+
let text = await readFile(inputFilePath, { encoding: 'utf-8' })
|
|
673
734
|
|
|
674
|
-
if (inputFileExtension ==
|
|
735
|
+
if (inputFileExtension == 'srt' || inputFileExtension == 'vtt') {
|
|
675
736
|
text = subtitlesToText(text)
|
|
676
737
|
}
|
|
677
738
|
|
|
@@ -683,7 +744,7 @@ async function detectLanguage(commandArgs: string[], cliOptions: Map<string, str
|
|
|
683
744
|
throw new Error(`detect-speech-language requires an argument containing the input audio file path.`)
|
|
684
745
|
}
|
|
685
746
|
|
|
686
|
-
const options: API.SpeechLanguageDetectionOptions = await cliOptionsMapToOptionsObject(cliOptions,
|
|
747
|
+
const options: API.SpeechLanguageDetectionOptions = await cliOptionsMapToOptionsObject(cliOptions, 'SpeechLanguageDetectionOptions', additionalOptionsSchema)
|
|
687
748
|
allowOverwrite = getWithDefault((options as any).overwrite, overwriteByDefault)
|
|
688
749
|
|
|
689
750
|
await checkOutputFilenames(outputFilenames, false, true, false)
|
|
@@ -694,19 +755,19 @@ async function detectLanguage(commandArgs: string[], cliOptions: Map<string, str
|
|
|
694
755
|
}
|
|
695
756
|
|
|
696
757
|
if (outputFilenames.length > 0) {
|
|
697
|
-
logger.start(
|
|
758
|
+
logger.start('\nWrite output files')
|
|
698
759
|
|
|
699
|
-
const resultsAsText = results.map(result => `${formatLanguageCodeWithName(result.language)}: ${result.probability.toFixed(5)}`).join(
|
|
760
|
+
const resultsAsText = results.map(result => `${formatLanguageCodeWithName(result.language)}: ${result.probability.toFixed(5)}`).join('\n')
|
|
700
761
|
|
|
701
|
-
for (const
|
|
702
|
-
const fileSaver = getFileSaver(
|
|
762
|
+
for (const outputFilename of outputFilenames) {
|
|
763
|
+
const fileSaver = getFileSaver(outputFilename, allowOverwrite)
|
|
703
764
|
|
|
704
765
|
await fileSaver(getEmptyRawAudio(0, 0), results as any, resultsAsText)
|
|
705
766
|
}
|
|
706
767
|
} else {
|
|
707
|
-
const resultsAsText = results.slice(0, 10).map(result => `${formatLanguageCodeWithName(result.language)}: ${result.probability.toFixed(5)}`).join(
|
|
768
|
+
const resultsAsText = results.slice(0, 10).map(result => `${formatLanguageCodeWithName(result.language)}: ${result.probability.toFixed(5)}`).join('\n')
|
|
708
769
|
|
|
709
|
-
logger.log(
|
|
770
|
+
logger.log('')
|
|
710
771
|
logger.log(resultsAsText)
|
|
711
772
|
}
|
|
712
773
|
|
|
@@ -735,28 +796,40 @@ async function detectVoiceActivity(commandArgs: string[], cliOptions: Map<string
|
|
|
735
796
|
cliOptions.set('play', `${outputFilenames.length == 0}`)
|
|
736
797
|
}
|
|
737
798
|
|
|
738
|
-
const options: API.VADOptions = await cliOptionsMapToOptionsObject(cliOptions,
|
|
799
|
+
const options: API.VADOptions = await cliOptionsMapToOptionsObject(cliOptions, 'VADOptions', additionalOptionsSchema)
|
|
739
800
|
|
|
740
801
|
const allowOverwrite = getWithDefault((options as any).overwrite, overwriteByDefault)
|
|
741
802
|
|
|
742
|
-
await checkOutputFilenames(outputFilenames, true, true,
|
|
803
|
+
await checkOutputFilenames(outputFilenames, true, true, true)
|
|
743
804
|
|
|
744
|
-
|
|
805
|
+
let { timeline, verboseTimeline, inputRawAudio, croppedRawAudio } = await API.detectVoiceActivity(audioFilename, options)
|
|
745
806
|
|
|
746
807
|
if (outputFilenames.length > 0) {
|
|
747
|
-
logger.start(
|
|
808
|
+
logger.start('\nWrite output files')
|
|
748
809
|
}
|
|
749
810
|
|
|
750
|
-
for (const
|
|
751
|
-
const partPatternMatch =
|
|
811
|
+
for (const outputFilename of outputFilenames) {
|
|
812
|
+
const partPatternMatch = outputFilename.match(filenamePlaceholderPattern)
|
|
752
813
|
|
|
753
814
|
if (partPatternMatch) {
|
|
754
815
|
continue
|
|
755
816
|
}
|
|
756
817
|
|
|
757
|
-
const fileSaver = getFileSaver(
|
|
818
|
+
const fileSaver = getFileSaver(outputFilename, allowOverwrite)
|
|
819
|
+
|
|
820
|
+
await fileSaver(inputRawAudio, timeline, '', { maxAddedDuration: 0 })
|
|
821
|
+
|
|
822
|
+
const fileExtension = getLowercaseFileExtension(outputFilename)
|
|
823
|
+
|
|
824
|
+
if (supportedOutputMediaFileExtensions.includes(fileExtension)) {
|
|
825
|
+
const pathWithoutExtension = outputFilename.substring(0, outputFilename.lastIndexOf('.'))
|
|
826
|
+
|
|
827
|
+
const isolatedOutputFilePath = `${pathWithoutExtension}.cropped.${fileExtension}`
|
|
758
828
|
|
|
759
|
-
|
|
829
|
+
const fileSaver = getFileSaver(isolatedOutputFilePath, allowOverwrite)
|
|
830
|
+
|
|
831
|
+
await fileSaver(croppedRawAudio, [], '')
|
|
832
|
+
}
|
|
760
833
|
}
|
|
761
834
|
|
|
762
835
|
logger.end()
|
|
@@ -764,8 +837,8 @@ async function detectVoiceActivity(commandArgs: string[], cliOptions: Map<string
|
|
|
764
837
|
if ((options as any).play) {
|
|
765
838
|
const normalizedAudio = normalizeAudioLevel(inputRawAudio)
|
|
766
839
|
|
|
767
|
-
const timelineToPlay =
|
|
768
|
-
return {...entry, type:
|
|
840
|
+
const timelineToPlay = verboseTimeline.map(entry => {
|
|
841
|
+
return {...entry, type: 'word' } as TimelineEntry
|
|
769
842
|
})
|
|
770
843
|
|
|
771
844
|
await playAudioWithWordTimeline(normalizedAudio, timelineToPlay)
|
|
@@ -794,7 +867,7 @@ async function denoise(commandArgs: string[], cliOptions: Map<string, string>) {
|
|
|
794
867
|
cliOptions.set('play', `${outputFilenames.length == 0}`)
|
|
795
868
|
}
|
|
796
869
|
|
|
797
|
-
const options: API.DenoisingOptions = await cliOptionsMapToOptionsObject(cliOptions,
|
|
870
|
+
const options: API.DenoisingOptions = await cliOptionsMapToOptionsObject(cliOptions, 'DenoisingOptions', additionalOptionsSchema)
|
|
798
871
|
|
|
799
872
|
const allowOverwrite = getWithDefault((options as any).overwrite, overwriteByDefault)
|
|
800
873
|
|
|
@@ -803,13 +876,13 @@ async function denoise(commandArgs: string[], cliOptions: Map<string, string>) {
|
|
|
803
876
|
const { denoisedAudio } = await API.denoise(audioFilename, options)
|
|
804
877
|
|
|
805
878
|
if (outputFilenames.length > 0) {
|
|
806
|
-
logger.start(
|
|
879
|
+
logger.start('\nWrite output files')
|
|
807
880
|
}
|
|
808
881
|
|
|
809
|
-
for (const
|
|
810
|
-
const fileSaver = getFileSaver(
|
|
882
|
+
for (const outputFilename of outputFilenames) {
|
|
883
|
+
const fileSaver = getFileSaver(outputFilename, allowOverwrite)
|
|
811
884
|
|
|
812
|
-
await fileSaver(denoisedAudio, [],
|
|
885
|
+
await fileSaver(denoisedAudio, [], '')
|
|
813
886
|
}
|
|
814
887
|
|
|
815
888
|
logger.end()
|
|
@@ -819,6 +892,51 @@ async function denoise(commandArgs: string[], cliOptions: Map<string, string>) {
|
|
|
819
892
|
}
|
|
820
893
|
}
|
|
821
894
|
|
|
895
|
+
async function isolate(commandArgs: string[], cliOptions: Map<string, string>) {
|
|
896
|
+
const logger = new Logger()
|
|
897
|
+
|
|
898
|
+
const audioFilename = commandArgs[0]
|
|
899
|
+
const outputFilenames = commandArgs.slice(1)
|
|
900
|
+
|
|
901
|
+
if (audioFilename == undefined) {
|
|
902
|
+
throw new Error(`'isolate' requires an argument containing the audio file path.`)
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
if (!existsSync(audioFilename)) {
|
|
906
|
+
throw new Error(`The given source audio file '${audioFilename}' was not found.`)
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
const additionalOptionsSchema = new Map<string, SchemaTypeDefinition>()
|
|
910
|
+
additionalOptionsSchema.set('play', { type: 'boolean' })
|
|
911
|
+
additionalOptionsSchema.set('overwrite', { type: 'boolean' })
|
|
912
|
+
|
|
913
|
+
if (!cliOptions.has('play') && !cliOptions.has('no-play')) {
|
|
914
|
+
cliOptions.set('play', `${outputFilenames.length == 0}`)
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
const options: API.SourceSeparationOptions = await cliOptionsMapToOptionsObject(cliOptions, 'SourceSeparationOptions', additionalOptionsSchema)
|
|
918
|
+
|
|
919
|
+
const allowOverwrite = getWithDefault((options as any).overwrite, overwriteByDefault)
|
|
920
|
+
|
|
921
|
+
await checkOutputFilenames(outputFilenames, true, false, false)
|
|
922
|
+
|
|
923
|
+
const { inputRawAudio, isolatedRawAudio, backgroundRawAudio } = await API.isolate(audioFilename, options)
|
|
924
|
+
|
|
925
|
+
if (outputFilenames.length > 0) {
|
|
926
|
+
logger.start('\nWrite output files')
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
for (let outputFilename of outputFilenames) {
|
|
930
|
+
await writeSourceSeparationOutputIfNeeded(outputFilename, isolatedRawAudio, backgroundRawAudio, allowOverwrite, false)
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
logger.end()
|
|
934
|
+
|
|
935
|
+
if ((options as any).play) {
|
|
936
|
+
await playAudioSamples(isolatedRawAudio)
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
|
|
822
940
|
async function listEngines(commandArgs: string[], cliOptions: Map<string, string>) {
|
|
823
941
|
const logger = new Logger()
|
|
824
942
|
|
|
@@ -840,6 +958,12 @@ async function listEngines(commandArgs: string[], cliOptions: Map<string, string
|
|
|
840
958
|
break
|
|
841
959
|
}
|
|
842
960
|
|
|
961
|
+
case 'list-voices': {
|
|
962
|
+
engines = API.synthesisEngines
|
|
963
|
+
|
|
964
|
+
break
|
|
965
|
+
}
|
|
966
|
+
|
|
843
967
|
case 'transcribe': {
|
|
844
968
|
engines = API.recognitionEngines
|
|
845
969
|
|
|
@@ -888,8 +1012,8 @@ async function listEngines(commandArgs: string[], cliOptions: Map<string, string
|
|
|
888
1012
|
break
|
|
889
1013
|
}
|
|
890
1014
|
|
|
891
|
-
case '
|
|
892
|
-
engines = API.
|
|
1015
|
+
case 'isolate': {
|
|
1016
|
+
engines = API.sourceSeparationEngines
|
|
893
1017
|
|
|
894
1018
|
break
|
|
895
1019
|
}
|
|
@@ -913,7 +1037,7 @@ async function listEngines(commandArgs: string[], cliOptions: Map<string, string
|
|
|
913
1037
|
logger.logTitledMessage('Type', engine.type)
|
|
914
1038
|
|
|
915
1039
|
if (index < engines.length - 1) {
|
|
916
|
-
logger.log(
|
|
1040
|
+
logger.log('')
|
|
917
1041
|
}
|
|
918
1042
|
}
|
|
919
1043
|
}
|
|
@@ -926,9 +1050,9 @@ async function listTTSVoices(commandArgs: string[], cliOptions: Map<string, stri
|
|
|
926
1050
|
|
|
927
1051
|
if (!targetEngine) {
|
|
928
1052
|
const optionsSchema = await getOptionsSchema()
|
|
929
|
-
const { enum: ttsEnginesEnum } = getOptionTypeFromSchema([
|
|
1053
|
+
const { enum: ttsEnginesEnum } = getOptionTypeFromSchema(['VoiceListRequestOptions', 'engine'], optionsSchema)
|
|
930
1054
|
|
|
931
|
-
throw new Error(`list-voices requires an argument specifying one of these supported engines:\n${ttsEnginesEnum!.join(
|
|
1055
|
+
throw new Error(`list-voices requires an argument specifying one of these supported engines:\n${ttsEnginesEnum!.join(', ')}`)
|
|
932
1056
|
}
|
|
933
1057
|
|
|
934
1058
|
const additionalOptionsSchema = new Map<string, SchemaTypeDefinition>()
|
|
@@ -936,7 +1060,7 @@ async function listTTSVoices(commandArgs: string[], cliOptions: Map<string, stri
|
|
|
936
1060
|
|
|
937
1061
|
cliOptions.set('engine', targetEngine)
|
|
938
1062
|
|
|
939
|
-
const options: API.VoiceListRequestOptions = await cliOptionsMapToOptionsObject(cliOptions,
|
|
1063
|
+
const options: API.VoiceListRequestOptions = await cliOptionsMapToOptionsObject(cliOptions, 'VoiceListRequestOptions', additionalOptionsSchema)
|
|
940
1064
|
|
|
941
1065
|
const allowOverwrite = getWithDefault((options as any).overwrite, overwriteByDefault)
|
|
942
1066
|
|
|
@@ -946,7 +1070,7 @@ async function listTTSVoices(commandArgs: string[], cliOptions: Map<string, stri
|
|
|
946
1070
|
|
|
947
1071
|
const voiceListText = voiceList.map(entry => {
|
|
948
1072
|
const nameText = entry.name
|
|
949
|
-
const languagesNamesText = entry.languages.map(language => formatLanguageCodeWithName(language)).join(
|
|
1073
|
+
const languagesNamesText = entry.languages.map(language => formatLanguageCodeWithName(language)).join(', ')
|
|
950
1074
|
const genderText = entry.gender
|
|
951
1075
|
|
|
952
1076
|
let entryText = `${chalk.cyanBright('Identifier')}: ${chalk.magentaBright(nameText)}\n${chalk.cyanBright('Languages')}: ${languagesNamesText}\n${chalk.cyanBright('Gender')}: ${genderText}`
|
|
@@ -958,10 +1082,10 @@ async function listTTSVoices(commandArgs: string[], cliOptions: Map<string, stri
|
|
|
958
1082
|
}
|
|
959
1083
|
|
|
960
1084
|
return entryText
|
|
961
|
-
}).join(
|
|
1085
|
+
}).join('\n\n')
|
|
962
1086
|
|
|
963
1087
|
if (outputFilenames.length > 0) {
|
|
964
|
-
logger.start(
|
|
1088
|
+
logger.start('\nWrite output files')
|
|
965
1089
|
|
|
966
1090
|
for (const filename of outputFilenames) {
|
|
967
1091
|
const fileSaver = getFileSaver(filename, allowOverwrite)
|
|
@@ -982,7 +1106,7 @@ async function installPackages(commandArgs: string[], cliOptions: Map<string, st
|
|
|
982
1106
|
const logger = new Logger()
|
|
983
1107
|
|
|
984
1108
|
if (commandArgs.length == 0) {
|
|
985
|
-
throw new Error(
|
|
1109
|
+
throw new Error('No package names specified')
|
|
986
1110
|
}
|
|
987
1111
|
|
|
988
1112
|
const failedPackageNames: string[] = []
|
|
@@ -1011,7 +1135,7 @@ async function uninstallPackages(commandArgs: string[], cliOptions: Map<string,
|
|
|
1011
1135
|
const logger = new Logger()
|
|
1012
1136
|
|
|
1013
1137
|
if (commandArgs.length == 0) {
|
|
1014
|
-
throw new Error(
|
|
1138
|
+
throw new Error('No package names specified')
|
|
1015
1139
|
}
|
|
1016
1140
|
|
|
1017
1141
|
const failedPackageNames: string[] = []
|
|
@@ -1061,11 +1185,11 @@ async function listPackages(commandArgs: string[], cliOptions: Map<string, strin
|
|
|
1061
1185
|
|
|
1062
1186
|
logger.log(`Total of ${installedPackageNamesFormatted.length} packages installed in '${packagesDir}'\n`)
|
|
1063
1187
|
|
|
1064
|
-
logger.log(installedPackageNamesFormatted.join(
|
|
1188
|
+
logger.log(installedPackageNamesFormatted.join('\n'))
|
|
1065
1189
|
}
|
|
1066
1190
|
|
|
1067
1191
|
async function serve(commandArgs: string[], cliOptions: Map<string, string>) {
|
|
1068
|
-
const options: ServerOptions = await cliOptionsMapToOptionsObject(cliOptions,
|
|
1192
|
+
const options: ServerOptions = await cliOptionsMapToOptionsObject(cliOptions, 'ServerOptions')
|
|
1069
1193
|
|
|
1070
1194
|
async function onServerStarted(serverOptions: ServerOptions) {
|
|
1071
1195
|
// Run a test routine (early development)
|
|
@@ -1075,6 +1199,31 @@ async function serve(commandArgs: string[], cliOptions: Map<string, string>) {
|
|
|
1075
1199
|
await startServer(options, onServerStarted)
|
|
1076
1200
|
}
|
|
1077
1201
|
|
|
1202
|
+
async function writeSourceSeparationOutputIfNeeded(outputFilename: string, isolatedRawAudio: RawAudio | undefined, backgroundRawAudio: RawAudio | undefined, allowOverwrite: boolean, prefixIsolated: boolean) {
|
|
1203
|
+
// Write source separation output if needed
|
|
1204
|
+
const fileExtension = getLowercaseFileExtension(outputFilename)
|
|
1205
|
+
|
|
1206
|
+
if (isolatedRawAudio && backgroundRawAudio && supportedOutputMediaFileExtensions.includes(fileExtension)) {
|
|
1207
|
+
const pathWithoutExtension = outputFilename.substring(0, outputFilename.lastIndexOf('.'))
|
|
1208
|
+
|
|
1209
|
+
{
|
|
1210
|
+
const isolatedOutputFilePath = prefixIsolated ? `${pathWithoutExtension}.isolated.${fileExtension}` : outputFilename
|
|
1211
|
+
|
|
1212
|
+
const fileSaver = getFileSaver(isolatedOutputFilePath, allowOverwrite)
|
|
1213
|
+
|
|
1214
|
+
await fileSaver(isolatedRawAudio, [], '')
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
{
|
|
1218
|
+
const backgroundOutputFilePath = `${pathWithoutExtension}.background.${fileExtension}`
|
|
1219
|
+
|
|
1220
|
+
const fileSaver = getFileSaver(backgroundOutputFilePath, allowOverwrite)
|
|
1221
|
+
|
|
1222
|
+
await fileSaver(backgroundRawAudio, [], '')
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1078
1227
|
async function cliOptionsMapToOptionsObject(cliOptionsMap: Map<string, string>, optionsRoot: keyof APIOptions, additionalOptionsSchema?: Map<string, SchemaTypeDefinition>) {
|
|
1079
1228
|
const optionsSchema = await getOptionsSchema()
|
|
1080
1229
|
const resultingObj: any = {}
|
|
@@ -1100,7 +1249,7 @@ async function cliOptionsMapToOptionsObject(cliOptionsMap: Map<string, string>,
|
|
|
1100
1249
|
for (let [key, value] of cliOptionsMap) {
|
|
1101
1250
|
let isNegated = false
|
|
1102
1251
|
|
|
1103
|
-
if (key.startsWith(
|
|
1252
|
+
if (key.startsWith('no-')) {
|
|
1104
1253
|
isNegated = true
|
|
1105
1254
|
key = key.slice(3)
|
|
1106
1255
|
|
|
@@ -1109,7 +1258,7 @@ async function cliOptionsMapToOptionsObject(cliOptionsMap: Map<string, string>,
|
|
|
1109
1258
|
}
|
|
1110
1259
|
}
|
|
1111
1260
|
|
|
1112
|
-
const path = key.split(
|
|
1261
|
+
const path = key.split('.')
|
|
1113
1262
|
|
|
1114
1263
|
let optionType: string | undefined
|
|
1115
1264
|
let optionEnum: any[] | undefined
|
|
@@ -1169,7 +1318,7 @@ async function cliOptionsMapToOptionsObject(cliOptionsMap: Map<string, string>,
|
|
|
1169
1318
|
}
|
|
1170
1319
|
|
|
1171
1320
|
if (optionEnum && !optionEnum.includes(parsedValue)) {
|
|
1172
|
-
throw new Error(`The property '${key}' must be one of ${optionEnum.join(
|
|
1321
|
+
throw new Error(`The property '${key}' must be one of ${optionEnum.join(', ')}`)
|
|
1173
1322
|
}
|
|
1174
1323
|
|
|
1175
1324
|
setValueAtPath(path, parsedValue)
|
|
@@ -1181,7 +1330,7 @@ async function cliOptionsMapToOptionsObject(cliOptionsMap: Map<string, string>,
|
|
|
1181
1330
|
let cachedOptionsSchema: any
|
|
1182
1331
|
export async function getOptionsSchema() {
|
|
1183
1332
|
if (!cachedOptionsSchema) {
|
|
1184
|
-
cachedOptionsSchema = await readAndParseJsonFile(resolveToModuleRootDir(
|
|
1333
|
+
cachedOptionsSchema = await readAndParseJsonFile(resolveToModuleRootDir('data/schemas/options.json'))
|
|
1185
1334
|
}
|
|
1186
1335
|
|
|
1187
1336
|
return cachedOptionsSchema
|
|
@@ -1202,13 +1351,13 @@ export async function checkOutputFilenames(outputFilenames: string[], acceptMedi
|
|
|
1202
1351
|
supportedFileExtensions.push(...supportedSubtitleFileExtensions)
|
|
1203
1352
|
}
|
|
1204
1353
|
|
|
1205
|
-
let
|
|
1354
|
+
let includesPlaceholderPattern = false
|
|
1206
1355
|
|
|
1207
1356
|
for (const outputFilename of outputFilenames) {
|
|
1208
1357
|
const fileExtension = getLowercaseFileExtension(outputFilename)
|
|
1209
1358
|
|
|
1210
1359
|
if (!supportedFileExtensions.includes(fileExtension)) {
|
|
1211
|
-
let errorText =
|
|
1360
|
+
let errorText = ''
|
|
1212
1361
|
errorText += `\nThe specified output path '${outputFilename}' doesn't have a supported file extension.\n`
|
|
1213
1362
|
errorText += `\nSupported extensions are:\n`
|
|
1214
1363
|
|
|
@@ -1220,25 +1369,25 @@ export async function checkOutputFilenames(outputFilenames: string[], acceptMedi
|
|
|
1220
1369
|
errorText += `${formatListWithQuotedElements(supportedMetadataFileExtensions)} for metadata output files.\n`
|
|
1221
1370
|
}
|
|
1222
1371
|
|
|
1223
|
-
if (
|
|
1372
|
+
if (acceptSubtitleOutputs) {
|
|
1224
1373
|
errorText += `${formatListWithQuotedElements(supportedSubtitleFileExtensions)} for subtitle output files.\n`
|
|
1225
1374
|
}
|
|
1226
1375
|
|
|
1227
1376
|
throw new Error(errorText)
|
|
1228
1377
|
}
|
|
1229
1378
|
|
|
1230
|
-
const
|
|
1379
|
+
const placeholderPatternMatch = outputFilename.match(filenamePlaceholderPattern)
|
|
1231
1380
|
|
|
1232
|
-
if (
|
|
1233
|
-
if (
|
|
1234
|
-
throw new Error(`Invalid
|
|
1381
|
+
if (placeholderPatternMatch) {
|
|
1382
|
+
if (placeholderPatternMatch[1] != 'segment') {
|
|
1383
|
+
throw new Error(`Invalid placeholder pattern: '${placeholderPatternMatch[1]}'. Placeholder output filename pattern currently only supports 'segment'. For example: '/out/[segment].wav'`)
|
|
1235
1384
|
}
|
|
1236
1385
|
|
|
1237
|
-
|
|
1386
|
+
includesPlaceholderPattern = true
|
|
1238
1387
|
}
|
|
1239
1388
|
}
|
|
1240
1389
|
|
|
1241
|
-
return {
|
|
1390
|
+
return { includesPlaceholderPattern }
|
|
1242
1391
|
}
|
|
1243
1392
|
|
|
1244
1393
|
export async function writeOutputFilesForSegment(outputFilenames: string[], index: number, total: number, audio: RawAudio, timeline: Timeline, text: string, language: string, allowOverwrite: boolean) {
|
|
@@ -1246,7 +1395,7 @@ export async function writeOutputFilesForSegment(outputFilenames: string[], inde
|
|
|
1246
1395
|
|
|
1247
1396
|
const segmentWords = (await splitToWords(text, language)).filter(text => wordCharacterPattern.test(text))
|
|
1248
1397
|
|
|
1249
|
-
const segmentJoinedWords = segmentWords.join(
|
|
1398
|
+
const segmentJoinedWords = segmentWords.join(' ').trim()
|
|
1250
1399
|
|
|
1251
1400
|
let initialText: string
|
|
1252
1401
|
|
|
@@ -1255,17 +1404,17 @@ export async function writeOutputFilesForSegment(outputFilenames: string[], inde
|
|
|
1255
1404
|
if (segmentJoinedWords.length < maxLength) {
|
|
1256
1405
|
initialText = segmentJoinedWords.substring(0, maxLength).trim()
|
|
1257
1406
|
} else {
|
|
1258
|
-
initialText = segmentJoinedWords.substring(0, maxLength - 4).trim() +
|
|
1407
|
+
initialText = segmentJoinedWords.substring(0, maxLength - 4).trim() + '.. '
|
|
1259
1408
|
}
|
|
1260
1409
|
|
|
1261
1410
|
for (const outputFilename of outputFilenames) {
|
|
1262
|
-
const
|
|
1411
|
+
const placeholderPatternMatch = outputFilename.match(filenamePlaceholderPattern)
|
|
1263
1412
|
|
|
1264
|
-
if (!
|
|
1413
|
+
if (!placeholderPatternMatch) {
|
|
1265
1414
|
continue
|
|
1266
1415
|
}
|
|
1267
1416
|
|
|
1268
|
-
const segmentFilename = outputFilename.replace(
|
|
1417
|
+
const segmentFilename = outputFilename.replace(filenamePlaceholderPattern, `${formatIntegerWithLeadingZeros(index + 1, digitCount)} - ${initialText}.$2`)
|
|
1269
1418
|
|
|
1270
1419
|
const fileSaver = getFileSaver(segmentFilename, allowOverwrite)
|
|
1271
1420
|
await fileSaver(audio, timeline, text)
|
|
@@ -1277,13 +1426,13 @@ type FileSaver = (audio: RawAudio, timeline: Timeline, text: string, subtitlesCo
|
|
|
1277
1426
|
function getFileSaver(outputFilePath: string, allowOverwrite: boolean): FileSaver {
|
|
1278
1427
|
const parsedPath = parsePath(outputFilePath)
|
|
1279
1428
|
|
|
1280
|
-
const fileDir = parsedPath.dir ||
|
|
1429
|
+
const fileDir = parsedPath.dir || './'
|
|
1281
1430
|
|
|
1282
1431
|
if (!allowOverwrite) {
|
|
1283
1432
|
const filenameParts = splitFilenameOnExtendedExtension(parsedPath.base)
|
|
1284
1433
|
|
|
1285
1434
|
for (let i = 1; existsSync(outputFilePath); i++) {
|
|
1286
|
-
outputFilePath = path.join(parsedPath.dir, `${filenameParts[0]}
|
|
1435
|
+
outputFilePath = path.join(parsedPath.dir, `${filenameParts[0]}_${formatIntegerWithLeadingZeros(i, 3)}`)
|
|
1287
1436
|
|
|
1288
1437
|
if (filenameParts[1]) {
|
|
1289
1438
|
outputFilePath += `.${filenameParts[1]}`
|
|
@@ -1295,24 +1444,24 @@ function getFileSaver(outputFilePath: string, allowOverwrite: boolean): FileSave
|
|
|
1295
1444
|
|
|
1296
1445
|
let fileSaver: FileSaver
|
|
1297
1446
|
|
|
1298
|
-
if (fileExtension ==
|
|
1447
|
+
if (fileExtension == 'txt') {
|
|
1299
1448
|
fileSaver = async (audio, timeline, text) => {
|
|
1300
1449
|
await ensureDir(fileDir)
|
|
1301
|
-
return writeFileSafe(outputFilePath, text, { encoding:
|
|
1450
|
+
return writeFileSafe(outputFilePath, text, { encoding: 'utf-8' })
|
|
1302
1451
|
}
|
|
1303
|
-
} else if (fileExtension ==
|
|
1452
|
+
} else if (fileExtension == 'json') {
|
|
1304
1453
|
fileSaver = async (audio, timeline, text) => {
|
|
1305
1454
|
await ensureDir(fileDir)
|
|
1306
1455
|
|
|
1307
1456
|
const roundedTimeline = roundTimelineProperties(timeline)
|
|
1308
1457
|
return writeFileSafe(outputFilePath, stringifyAndFormatJson(roundedTimeline))
|
|
1309
1458
|
}
|
|
1310
|
-
} else if (fileExtension ==
|
|
1459
|
+
} else if (fileExtension == 'srt') {
|
|
1311
1460
|
fileSaver = async (audio, timeline, text, subtitlesConfig) => {
|
|
1312
1461
|
await ensureDir(fileDir)
|
|
1313
1462
|
|
|
1314
1463
|
const extraSubtitleConfigOptions: SubtitlesConfig = {
|
|
1315
|
-
format:
|
|
1464
|
+
format: 'srt',
|
|
1316
1465
|
originalText: text,
|
|
1317
1466
|
totalDuration: getRawAudioDuration(audio)
|
|
1318
1467
|
}
|
|
@@ -1323,12 +1472,12 @@ function getFileSaver(outputFilePath: string, allowOverwrite: boolean): FileSave
|
|
|
1323
1472
|
|
|
1324
1473
|
return writeFileSafe(outputFilePath, subtitles)
|
|
1325
1474
|
}
|
|
1326
|
-
} else if (fileExtension ==
|
|
1475
|
+
} else if (fileExtension == 'vtt') {
|
|
1327
1476
|
fileSaver = async (audio, timeline, text, subtitlesConfig) => {
|
|
1328
1477
|
await ensureDir(fileDir)
|
|
1329
1478
|
|
|
1330
1479
|
const extraSubtitleConfigOptions: SubtitlesConfig = {
|
|
1331
|
-
format:
|
|
1480
|
+
format: 'webvtt',
|
|
1332
1481
|
originalText: text,
|
|
1333
1482
|
totalDuration: getRawAudioDuration(audio)
|
|
1334
1483
|
}
|
|
@@ -1339,11 +1488,11 @@ function getFileSaver(outputFilePath: string, allowOverwrite: boolean): FileSave
|
|
|
1339
1488
|
|
|
1340
1489
|
return writeFileSafe(outputFilePath, subtitles)
|
|
1341
1490
|
}
|
|
1342
|
-
} else if (fileExtension ==
|
|
1491
|
+
} else if (fileExtension == 'wav') {
|
|
1343
1492
|
fileSaver = async (audio) => {
|
|
1344
1493
|
await ensureDir(fileDir)
|
|
1345
1494
|
|
|
1346
|
-
return writeFileSafe(outputFilePath,
|
|
1495
|
+
return writeFileSafe(outputFilePath, encodeRawAudioToWave(audio))
|
|
1347
1496
|
}
|
|
1348
1497
|
} else if (supportedOutputMediaFileExtensions.includes(fileExtension)) {
|
|
1349
1498
|
fileSaver = async (audio) => {
|
|
@@ -1358,7 +1507,7 @@ function getFileSaver(outputFilePath: string, allowOverwrite: boolean): FileSave
|
|
|
1358
1507
|
return
|
|
1359
1508
|
}
|
|
1360
1509
|
} else {
|
|
1361
|
-
throw new Error(
|
|
1510
|
+
throw new Error('Unsupported output file extension')
|
|
1362
1511
|
}
|
|
1363
1512
|
|
|
1364
1513
|
return fileSaver
|
|
@@ -1366,9 +1515,9 @@ function getFileSaver(outputFilePath: string, allowOverwrite: boolean): FileSave
|
|
|
1366
1515
|
|
|
1367
1516
|
const supportedMetadataFileExtensions = ['txt', 'json']
|
|
1368
1517
|
const supportedSubtitleFileExtensions = ['srt', 'vtt']
|
|
1369
|
-
const supportedOutputMediaFileExtensions = [
|
|
1518
|
+
const supportedOutputMediaFileExtensions = ['wav', 'mp3', 'opus', 'm4a', 'ogg', 'flac']
|
|
1370
1519
|
|
|
1371
|
-
const
|
|
1520
|
+
const filenamePlaceholderPattern = /\[(.*)\]\.(.*)$/
|
|
1372
1521
|
|
|
1373
1522
|
const overwriteByDefault = false
|
|
1374
1523
|
|