echogarden 2.10.2 → 3.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/LICENSE.MIT.md +7 -0
- package/README.md +9 -1
- package/data/lexicons/heteronyms.en.json +44 -10
- package/data/lexicons/words.en.json +726 -22
- package/data/schemas/options.json +91 -153
- package/dist/alignment/DTWMfccSequenceAlignment.js.map +1 -1
- package/dist/alignment/SemanticTextAlignment.d.ts +4 -2
- package/dist/alignment/SemanticTextAlignment.d.ts.map +1 -1
- package/dist/alignment/SemanticTextAlignment.js +7 -7
- package/dist/alignment/SemanticTextAlignment.js.map +1 -1
- package/dist/alignment/SpeechAlignment.d.ts +9 -10
- package/dist/alignment/SpeechAlignment.d.ts.map +1 -1
- package/dist/alignment/SpeechAlignment.js +23 -96
- package/dist/alignment/SpeechAlignment.js.map +1 -1
- package/dist/api/APIOptions.d.ts +1 -1
- package/dist/api/APIOptions.d.ts.map +1 -1
- package/dist/api/Alignment.d.ts +8 -6
- package/dist/api/Alignment.d.ts.map +1 -1
- package/dist/api/Alignment.js +31 -40
- package/dist/api/Alignment.js.map +1 -1
- package/dist/api/Common.d.ts +7 -0
- package/dist/api/Common.d.ts.map +1 -1
- package/dist/api/Common.js.map +1 -1
- package/dist/api/Denoising.d.ts +5 -2
- package/dist/api/Denoising.d.ts.map +1 -1
- package/dist/api/Denoising.js +15 -15
- package/dist/api/Denoising.js.map +1 -1
- package/dist/api/GlobalOptions.d.ts +2 -8
- package/dist/api/GlobalOptions.d.ts.map +1 -1
- package/dist/api/GlobalOptions.js +2 -13
- package/dist/api/GlobalOptions.js.map +1 -1
- package/dist/api/Recognition.d.ts +11 -12
- package/dist/api/Recognition.d.ts.map +1 -1
- package/dist/api/Recognition.js +32 -82
- package/dist/api/Recognition.js.map +1 -1
- package/dist/api/SourceSeparation.d.ts +7 -2
- package/dist/api/SourceSeparation.d.ts.map +1 -1
- package/dist/api/SourceSeparation.js +12 -10
- package/dist/api/SourceSeparation.js.map +1 -1
- package/dist/api/SpeechLanguageDetection.d.ts +11 -7
- package/dist/api/SpeechLanguageDetection.d.ts.map +1 -1
- package/dist/api/SpeechLanguageDetection.js +37 -30
- package/dist/api/SpeechLanguageDetection.js.map +1 -1
- package/dist/api/SpeechSearch.d.ts +4 -1
- package/dist/api/SpeechSearch.d.ts.map +1 -1
- package/dist/api/SpeechSearch.js.map +1 -1
- package/dist/api/SpeechTranslation.d.ts +10 -6
- package/dist/api/SpeechTranslation.d.ts.map +1 -1
- package/dist/api/SpeechTranslation.js +28 -26
- package/dist/api/SpeechTranslation.js.map +1 -1
- package/dist/api/Synthesis.d.ts +13 -8
- package/dist/api/Synthesis.d.ts.map +1 -1
- package/dist/api/Synthesis.js +67 -89
- package/dist/api/Synthesis.js.map +1 -1
- package/dist/api/TextLanguageDetection.d.ts +4 -2
- package/dist/api/TextLanguageDetection.d.ts.map +1 -1
- package/dist/api/TextLanguageDetection.js +4 -7
- package/dist/api/TextLanguageDetection.js.map +1 -1
- package/dist/api/TextTranslation.d.ts +4 -2
- package/dist/api/TextTranslation.d.ts.map +1 -1
- package/dist/api/TextTranslation.js +13 -24
- package/dist/api/TextTranslation.js.map +1 -1
- package/dist/api/TimelineTranslationAlignment.d.ts +6 -3
- package/dist/api/TimelineTranslationAlignment.d.ts.map +1 -1
- package/dist/api/TimelineTranslationAlignment.js +20 -11
- package/dist/api/TimelineTranslationAlignment.js.map +1 -1
- package/dist/api/TranscriptAndTranslationAlignment.d.ts +6 -4
- package/dist/api/TranscriptAndTranslationAlignment.d.ts.map +1 -1
- package/dist/api/TranscriptAndTranslationAlignment.js +11 -12
- package/dist/api/TranscriptAndTranslationAlignment.js.map +1 -1
- package/dist/api/TranslationAlignment.d.ts +6 -4
- package/dist/api/TranslationAlignment.d.ts.map +1 -1
- package/dist/api/TranslationAlignment.js +20 -21
- package/dist/api/TranslationAlignment.js.map +1 -1
- package/dist/api/VoiceActivityDetection.d.ts +9 -8
- package/dist/api/VoiceActivityDetection.d.ts.map +1 -1
- package/dist/api/VoiceActivityDetection.js +20 -43
- package/dist/api/VoiceActivityDetection.js.map +1 -1
- package/dist/audio/AudioBufferConversion.js.map +1 -1
- package/dist/audio/AudioPlayer.d.ts +1 -3
- package/dist/audio/AudioPlayer.d.ts.map +1 -1
- package/dist/audio/AudioPlayer.js +3 -188
- package/dist/audio/AudioPlayer.js.map +1 -1
- package/dist/audio/AudioUtilities.d.ts +2 -1
- package/dist/audio/AudioUtilities.d.ts.map +1 -1
- package/dist/audio/AudioUtilities.js +2 -2
- package/dist/audio/AudioUtilities.js.map +1 -1
- package/dist/build-tools/MakeTarballsForInstalledPackages.js +3 -3
- package/dist/build-tools/MakeTarballsForInstalledPackages.js.map +1 -1
- package/dist/cli/CLI.d.ts.map +1 -1
- package/dist/cli/CLI.js +139 -92
- package/dist/cli/CLI.js.map +1 -1
- package/dist/codecs/FFMpegTranscoder.d.ts +4 -3
- package/dist/codecs/FFMpegTranscoder.d.ts.map +1 -1
- package/dist/codecs/FFMpegTranscoder.js +16 -14
- package/dist/codecs/FFMpegTranscoder.js.map +1 -1
- package/dist/codecs/TIMITCodec.d.ts.map +1 -1
- package/dist/data-structures/DynamicTypedArray.d.ts.map +1 -1
- package/dist/data-structures/DynamicTypedArray.js.map +1 -1
- package/dist/data-structures/Queue.d.ts.map +1 -1
- package/dist/data-structures/WindowedList.d.ts.map +1 -1
- package/dist/data-structures/WindowedList.js.map +1 -1
- package/dist/denoising/NSNet2.d.ts +3 -2
- package/dist/denoising/NSNet2.d.ts.map +1 -1
- package/dist/denoising/NSNet2.js +4 -4
- package/dist/denoising/NSNet2.js.map +1 -1
- package/dist/denoising/RNNoise.d.ts +2 -1
- package/dist/denoising/RNNoise.d.ts.map +1 -1
- package/dist/denoising/RNNoise.js +10 -6
- package/dist/denoising/RNNoise.js.map +1 -1
- package/dist/dsp/BiquadFilter.d.ts.map +1 -1
- package/dist/dsp/DecayingPeakEstimator.d.ts.map +1 -1
- package/dist/dsp/DecayingPeakEstimator.js.map +1 -1
- package/dist/dsp/FFT.d.ts.map +1 -1
- package/dist/dsp/FFT.js +2 -1
- package/dist/dsp/FFT.js.map +1 -1
- package/dist/dsp/KWeightingFilter.d.ts.map +1 -1
- package/dist/dsp/KWeightingFilter.js.map +1 -1
- package/dist/dsp/LoudnessEstimator.d.ts.map +1 -1
- package/dist/dsp/LoudnessEstimator.js.map +1 -1
- package/dist/dsp/MFCC.d.ts +3 -2
- package/dist/dsp/MFCC.d.ts.map +1 -1
- package/dist/dsp/MFCC.js +4 -4
- package/dist/dsp/MFCC.js.map +1 -1
- package/dist/dsp/MelSpectrogram.d.ts +10 -8
- package/dist/dsp/MelSpectrogram.d.ts.map +1 -1
- package/dist/dsp/MelSpectrogram.js +31 -34
- package/dist/dsp/MelSpectrogram.js.map +1 -1
- package/dist/dsp/Rubberband.d.ts.map +1 -1
- package/dist/dsp/Rubberband.js +8 -3
- package/dist/dsp/Rubberband.js.map +1 -1
- package/dist/dsp/Sonic.d.ts.map +1 -1
- package/dist/dsp/Sonic.js +1 -1
- package/dist/dsp/Sonic.js.map +1 -1
- package/dist/dsp/SpeexResampler.d.ts.map +1 -1
- package/dist/dsp/SpeexResampler.js +1 -1
- package/dist/dsp/SpeexResampler.js.map +1 -1
- package/dist/encodings/Ascii.d.ts +1 -1
- package/dist/encodings/Ascii.d.ts.map +1 -1
- package/dist/encodings/Base64.js.map +1 -1
- package/dist/encodings/Utf16.d.ts.map +1 -1
- package/dist/encodings/Utf8.d.ts.map +1 -1
- package/dist/math/VectorMath.d.ts +1 -0
- package/dist/math/VectorMath.d.ts.map +1 -1
- package/dist/math/VectorMath.js +9 -5
- package/dist/math/VectorMath.js.map +1 -1
- package/dist/nlp/ChineseSegmentation.d.ts +1 -1
- package/dist/nlp/ChineseSegmentation.d.ts.map +1 -1
- package/dist/nlp/EspeakPhonemizer.d.ts +1 -1
- package/dist/nlp/EspeakPhonemizer.d.ts.map +1 -1
- package/dist/nlp/EspeakPhonemizer.js +2 -2
- package/dist/nlp/EspeakPhonemizer.js.map +1 -1
- package/dist/nlp/IPA.d.ts +2 -2
- package/dist/nlp/Segmentation.d.ts +1 -1
- package/dist/nlp/Segmentation.d.ts.map +1 -1
- package/dist/nlp/Segmentation.js +8 -4
- package/dist/nlp/Segmentation.js.map +1 -1
- package/dist/recognition/AmazonTranscribeSTT.d.ts +2 -1
- package/dist/recognition/AmazonTranscribeSTT.d.ts.map +1 -1
- package/dist/recognition/AmazonTranscribeSTT.js +3 -3
- package/dist/recognition/AmazonTranscribeSTT.js.map +1 -1
- package/dist/recognition/AzureCognitiveServicesSTT.d.ts +2 -1
- package/dist/recognition/AzureCognitiveServicesSTT.d.ts.map +1 -1
- package/dist/recognition/AzureCognitiveServicesSTT.js +4 -4
- package/dist/recognition/AzureCognitiveServicesSTT.js.map +1 -1
- package/dist/recognition/DeepgramSTT.d.ts +2 -1
- package/dist/recognition/DeepgramSTT.d.ts.map +1 -1
- package/dist/recognition/DeepgramSTT.js +12 -13
- package/dist/recognition/DeepgramSTT.js.map +1 -1
- package/dist/recognition/GoogleCloudSTT.d.ts +2 -1
- package/dist/recognition/GoogleCloudSTT.d.ts.map +1 -1
- package/dist/recognition/GoogleCloudSTT.js +11 -10
- package/dist/recognition/GoogleCloudSTT.js.map +1 -1
- package/dist/recognition/OpenAICloudSTT.d.ts +2 -1
- package/dist/recognition/OpenAICloudSTT.d.ts.map +1 -1
- package/dist/recognition/OpenAICloudSTT.js +6 -6
- package/dist/recognition/OpenAICloudSTT.js.map +1 -1
- package/dist/recognition/WhisperCommon.d.ts +9 -0
- package/dist/recognition/WhisperCommon.d.ts.map +1 -0
- package/dist/recognition/WhisperCommon.js +52 -0
- package/dist/recognition/WhisperCommon.js.map +1 -0
- package/dist/recognition/{WhisperCppSTT.d.ts → WhisperCppCliSTT.d.ts} +14 -16
- package/dist/recognition/WhisperCppCliSTT.d.ts.map +1 -0
- package/dist/recognition/{WhisperCppSTT.js → WhisperCppCliSTT.js} +55 -84
- package/dist/recognition/WhisperCppCliSTT.js.map +1 -0
- package/dist/recognition/WhisperSTT.d.ts +72 -85
- package/dist/recognition/WhisperSTT.d.ts.map +1 -1
- package/dist/recognition/WhisperSTT.js +384 -519
- package/dist/recognition/WhisperSTT.js.map +1 -1
- package/dist/server/Client.d.ts +2 -2
- package/dist/server/Client.d.ts.map +1 -1
- package/dist/server/Client.js.map +1 -1
- package/dist/server/Worker.d.ts +6 -7
- package/dist/server/Worker.d.ts.map +1 -1
- package/dist/server/Worker.js +42 -44
- package/dist/server/Worker.js.map +1 -1
- package/dist/source-separation/MDXNetSourceSeparation.d.ts +3 -2
- package/dist/source-separation/MDXNetSourceSeparation.d.ts.map +1 -1
- package/dist/source-separation/MDXNetSourceSeparation.js +10 -8
- package/dist/source-separation/MDXNetSourceSeparation.js.map +1 -1
- package/dist/speech-embeddings/WavToVec2BertFeatureEmbeddings.d.ts +3 -2
- package/dist/speech-embeddings/WavToVec2BertFeatureEmbeddings.d.ts.map +1 -1
- package/dist/speech-embeddings/WavToVec2BertFeatureEmbeddings.js +5 -5
- package/dist/speech-embeddings/WavToVec2BertFeatureEmbeddings.js.map +1 -1
- package/dist/speech-language-detection/SileroLanguageDetection.d.ts +5 -3
- package/dist/speech-language-detection/SileroLanguageDetection.d.ts.map +1 -1
- package/dist/speech-language-detection/SileroLanguageDetection.js +8 -8
- package/dist/speech-language-detection/SileroLanguageDetection.js.map +1 -1
- package/dist/speech-search/DTWSpeechSearch.d.ts +2 -1
- package/dist/speech-search/DTWSpeechSearch.d.ts.map +1 -1
- package/dist/speech-search/DTWSpeechSearch.js +4 -4
- package/dist/speech-search/DTWSpeechSearch.js.map +1 -1
- package/dist/subtitles/Subtitles.d.ts +13 -1
- package/dist/subtitles/Subtitles.d.ts.map +1 -1
- package/dist/subtitles/Subtitles.js +22 -22
- package/dist/subtitles/Subtitles.js.map +1 -1
- package/dist/synthesis/AwsPollyTTS.d.ts +3 -2
- package/dist/synthesis/AwsPollyTTS.d.ts.map +1 -1
- package/dist/synthesis/AwsPollyTTS.js +5 -5
- package/dist/synthesis/AwsPollyTTS.js.map +1 -1
- package/dist/synthesis/AzureCognitiveServicesTTS.d.ts +2 -1
- package/dist/synthesis/AzureCognitiveServicesTTS.d.ts.map +1 -1
- package/dist/synthesis/AzureCognitiveServicesTTS.js +3 -3
- package/dist/synthesis/AzureCognitiveServicesTTS.js.map +1 -1
- package/dist/synthesis/CoquiServerTTS.d.ts +2 -1
- package/dist/synthesis/CoquiServerTTS.d.ts.map +1 -1
- package/dist/synthesis/CoquiServerTTS.js +7 -8
- package/dist/synthesis/CoquiServerTTS.js.map +1 -1
- package/dist/synthesis/DeepgramTTS.d.ts +2 -2
- package/dist/synthesis/DeepgramTTS.d.ts.map +1 -1
- package/dist/synthesis/DeepgramTTS.js +13 -13
- package/dist/synthesis/DeepgramTTS.js.map +1 -1
- package/dist/synthesis/ElevenLabsTTS.d.ts +3 -3
- package/dist/synthesis/ElevenLabsTTS.d.ts.map +1 -1
- package/dist/synthesis/ElevenLabsTTS.js +22 -21
- package/dist/synthesis/ElevenLabsTTS.js.map +1 -1
- package/dist/synthesis/EspeakTTS.d.ts +11 -9
- package/dist/synthesis/EspeakTTS.d.ts.map +1 -1
- package/dist/synthesis/EspeakTTS.js +118 -68
- package/dist/synthesis/EspeakTTS.js.map +1 -1
- package/dist/synthesis/FliteTTS.d.ts +2 -2
- package/dist/synthesis/FliteTTS.d.ts.map +1 -1
- package/dist/synthesis/FliteTTS.js +2 -2
- package/dist/synthesis/FliteTTS.js.map +1 -1
- package/dist/synthesis/GnuSpeechTTS.d.ts +2 -2
- package/dist/synthesis/GnuSpeechTTS.d.ts.map +1 -1
- package/dist/synthesis/GnuSpeechTTS.js +1 -1
- package/dist/synthesis/GnuSpeechTTS.js.map +1 -1
- package/dist/synthesis/GoogleCloudTTS.d.ts +3 -2
- package/dist/synthesis/GoogleCloudTTS.d.ts.map +1 -1
- package/dist/synthesis/GoogleCloudTTS.js +15 -14
- package/dist/synthesis/GoogleCloudTTS.js.map +1 -1
- package/dist/synthesis/GoogleTranslateTTS.d.ts +3 -2
- package/dist/synthesis/GoogleTranslateTTS.d.ts.map +1 -1
- package/dist/synthesis/GoogleTranslateTTS.js +11 -11
- package/dist/synthesis/GoogleTranslateTTS.js.map +1 -1
- package/dist/synthesis/KokoroTTS.d.ts +4 -4
- package/dist/synthesis/KokoroTTS.d.ts.map +1 -1
- package/dist/synthesis/KokoroTTS.js +11 -10
- package/dist/synthesis/KokoroTTS.js.map +1 -1
- package/dist/synthesis/MicrosoftEdgeTTS.d.ts +4 -3
- package/dist/synthesis/MicrosoftEdgeTTS.d.ts.map +1 -1
- package/dist/synthesis/MicrosoftEdgeTTS.js +41 -20
- package/dist/synthesis/MicrosoftEdgeTTS.js.map +1 -1
- package/dist/synthesis/OpenAICloudTTS.d.ts +2 -2
- package/dist/synthesis/OpenAICloudTTS.d.ts.map +1 -1
- package/dist/synthesis/OpenAICloudTTS.js +6 -5
- package/dist/synthesis/OpenAICloudTTS.js.map +1 -1
- package/dist/synthesis/SamTTS.d.ts +2 -1
- package/dist/synthesis/SamTTS.d.ts.map +1 -1
- package/dist/synthesis/SamTTS.js +2 -2
- package/dist/synthesis/SamTTS.js.map +1 -1
- package/dist/synthesis/SapiTTS.d.ts +2 -2
- package/dist/synthesis/SapiTTS.d.ts.map +1 -1
- package/dist/synthesis/SapiTTS.js +2 -2
- package/dist/synthesis/SapiTTS.js.map +1 -1
- package/dist/synthesis/SvoxPicoTTS.d.ts +2 -2
- package/dist/synthesis/SvoxPicoTTS.d.ts.map +1 -1
- package/dist/synthesis/SvoxPicoTTS.js +3 -3
- package/dist/synthesis/SvoxPicoTTS.js.map +1 -1
- package/dist/synthesis/VitsTTS.d.ts +3 -3
- package/dist/synthesis/VitsTTS.d.ts.map +1 -1
- package/dist/synthesis/VitsTTS.js +14 -11
- package/dist/synthesis/VitsTTS.js.map +1 -1
- package/dist/text-translation/DeepLTextTranslation.d.ts +2 -2
- package/dist/text-translation/DeepLTextTranslation.d.ts.map +1 -1
- package/dist/text-translation/DeepLTextTranslation.js +10 -9
- package/dist/text-translation/DeepLTextTranslation.js.map +1 -1
- package/dist/text-translation/GoogleTranslateTextTranslation.d.ts +4 -4
- package/dist/text-translation/GoogleTranslateTextTranslation.d.ts.map +1 -1
- package/dist/text-translation/GoogleTranslateTextTranslation.js +20 -18
- package/dist/text-translation/GoogleTranslateTextTranslation.js.map +1 -1
- package/dist/text-translation/NLLBTextTranslation.d.ts +2 -2
- package/dist/text-translation/NLLBTextTranslation.d.ts.map +1 -1
- package/dist/text-translation/NLLBTextTranslation.js +4 -6
- package/dist/text-translation/NLLBTextTranslation.js.map +1 -1
- package/dist/typings/TypedArray.d.ts.map +1 -1
- package/dist/utilities/Compression.d.ts +4 -3
- package/dist/utilities/Compression.d.ts.map +1 -1
- package/dist/utilities/Compression.js +9 -9
- package/dist/utilities/Compression.js.map +1 -1
- package/dist/utilities/FileDownloader.d.ts +6 -3
- package/dist/utilities/FileDownloader.d.ts.map +1 -1
- package/dist/utilities/FileDownloader.js +25 -21
- package/dist/utilities/FileDownloader.js.map +1 -1
- package/dist/utilities/FileReader.d.ts.map +1 -1
- package/dist/utilities/FileReader.js.map +1 -1
- package/dist/utilities/FileSystem.js +1 -1
- package/dist/utilities/FileSystem.js.map +1 -1
- package/dist/utilities/FileWriter.d.ts.map +1 -1
- package/dist/utilities/FileWriter.js.map +1 -1
- package/dist/utilities/Hashing.d.ts.map +1 -1
- package/dist/utilities/Hashing.js.map +1 -1
- package/dist/utilities/Locale.js.map +1 -1
- package/dist/utilities/Logger.d.ts +17 -9
- package/dist/utilities/Logger.d.ts.map +1 -1
- package/dist/utilities/Logger.js +50 -35
- package/dist/utilities/Logger.js.map +1 -1
- package/dist/utilities/NpmUtilities.d.ts.map +1 -1
- package/dist/utilities/NpmUtilities.js +4 -5
- package/dist/utilities/NpmUtilities.js.map +1 -1
- package/dist/utilities/ObjectUtilities.js.map +1 -1
- package/dist/utilities/OpenPromise.d.ts.map +1 -1
- package/dist/utilities/OpenPromise.js.map +1 -1
- package/dist/utilities/PackageManager.d.ts +4 -1
- package/dist/utilities/PackageManager.d.ts.map +1 -1
- package/dist/utilities/PackageManager.js +37 -22
- package/dist/utilities/PackageManager.js.map +1 -1
- package/dist/utilities/PathUtilities.d.ts +2 -0
- package/dist/utilities/PathUtilities.d.ts.map +1 -1
- package/dist/utilities/PathUtilities.js +5 -2
- package/dist/utilities/PathUtilities.js.map +1 -1
- package/dist/utilities/RandomGenerator.d.ts.map +1 -1
- package/dist/utilities/SignalChannel.d.ts.map +1 -1
- package/dist/utilities/SmoothEstimator.d.ts.map +1 -1
- package/dist/utilities/SmoothEstimator.js.map +1 -1
- package/dist/utilities/StringBuilder.d.ts.map +1 -1
- package/dist/utilities/TarballMaker.d.ts +4 -3
- package/dist/utilities/TarballMaker.d.ts.map +1 -1
- package/dist/utilities/TarballMaker.js +6 -6
- package/dist/utilities/TarballMaker.js.map +1 -1
- package/dist/utilities/Timeline.js.map +1 -1
- package/dist/utilities/Timer.d.ts.map +1 -1
- package/dist/utilities/Utilities.d.ts +9 -7
- package/dist/utilities/Utilities.d.ts.map +1 -1
- package/dist/utilities/Utilities.js +49 -33
- package/dist/utilities/Utilities.js.map +1 -1
- package/dist/utilities/VirtualFileReadStream.d.ts.map +1 -1
- package/dist/utilities/WebReader.d.ts +2 -1
- package/dist/utilities/WebReader.d.ts.map +1 -1
- package/dist/utilities/WebReader.js +7 -6
- package/dist/utilities/WebReader.js.map +1 -1
- package/dist/utilities/WikipediaReader.d.ts +2 -1
- package/dist/utilities/WikipediaReader.d.ts.map +1 -1
- package/dist/utilities/WikipediaReader.js +1 -5
- package/dist/utilities/WikipediaReader.js.map +1 -1
- package/dist/voice-activity-detection/AdaptiveGateVAD.d.ts +2 -1
- package/dist/voice-activity-detection/AdaptiveGateVAD.d.ts.map +1 -1
- package/dist/voice-activity-detection/AdaptiveGateVAD.js +1 -1
- package/dist/voice-activity-detection/AdaptiveGateVAD.js.map +1 -1
- package/dist/voice-activity-detection/SileroVAD.d.ts.map +1 -1
- package/dist/voice-activity-detection/SileroVAD.js.map +1 -1
- package/dist/voice-activity-detection/WebRtcVAD.d.ts.map +1 -1
- package/dist/voice-activity-detection/WebRtcVAD.js +1 -1
- package/dist/voice-activity-detection/WebRtcVAD.js.map +1 -1
- package/docs/API.md +62 -17
- package/docs/CUDA.md +4 -3
- package/docs/Engines.md +8 -7
- package/docs/Licenses.md +0 -3
- package/docs/Options.md +27 -31
- package/docs/Server.md +1 -0
- package/docs/Tasklist.md +2 -1
- package/docs/Technical.md +2 -2
- package/package.json +45 -35
- package/src/alignment/SemanticTextAlignment.ts +14 -7
- package/src/alignment/SpeechAlignment.ts +47 -150
- package/src/api/APIOptions.ts +1 -1
- package/src/api/Alignment.ts +95 -61
- package/src/api/Common.ts +11 -0
- package/src/api/Denoising.ts +22 -17
- package/src/api/GlobalOptions.ts +4 -20
- package/src/api/Recognition.ts +106 -123
- package/src/api/SourceSeparation.ts +39 -21
- package/src/api/SpeechLanguageDetection.ts +77 -37
- package/src/api/SpeechSearch.ts +6 -1
- package/src/api/SpeechTranslation.ts +75 -38
- package/src/api/Synthesis.ts +220 -116
- package/src/api/TextLanguageDetection.ts +10 -12
- package/src/api/TextTranslation.ts +43 -26
- package/src/api/TimelineTranslationAlignment.ts +41 -13
- package/src/api/TranscriptAndTranslationAlignment.ts +28 -15
- package/src/api/TranslationAlignment.ts +53 -24
- package/src/api/VoiceActivityDetection.ts +33 -68
- package/src/audio/AudioPlayer.ts +4 -232
- package/src/audio/AudioUtilities.ts +9 -2
- package/src/build-tools/MakeTarballsForInstalledPackages.ts +4 -3
- package/src/cli/CLI.ts +209 -101
- package/src/codecs/FFMpegTranscoder.ts +18 -16
- package/src/denoising/NSNet2.ts +5 -4
- package/src/denoising/RNNoise.ts +14 -6
- package/src/dsp/FFT.ts +2 -1
- package/src/dsp/MFCC.ts +7 -5
- package/src/dsp/MelSpectrogram.ts +56 -36
- package/src/dsp/Rubberband.ts +10 -3
- package/src/dsp/Sonic.ts +2 -1
- package/src/dsp/SpeexResampler.ts +2 -1
- package/src/encodings/Ascii.ts +1 -1
- package/src/encodings/Base64.ts +1 -1
- package/src/math/VectorMath.ts +10 -6
- package/src/nlp/EspeakPhonemizer.ts +2 -2
- package/src/nlp/Segmentation.ts +9 -4
- package/src/recognition/AmazonTranscribeSTT.ts +4 -3
- package/src/recognition/AzureCognitiveServicesSTT.ts +5 -4
- package/src/recognition/DeepgramSTT.ts +16 -16
- package/src/recognition/GoogleCloudSTT.ts +14 -12
- package/src/recognition/OpenAICloudSTT.ts +12 -6
- package/src/recognition/WhisperCommon.ts +130 -0
- package/src/recognition/{WhisperCppSTT.ts → WhisperCppCliSTT.ts} +73 -133
- package/src/recognition/WhisperSTT.ts +569 -718
- package/src/server/Client.ts +4 -4
- package/src/server/Worker.ts +59 -59
- package/src/source-separation/MDXNetSourceSeparation.ts +15 -10
- package/src/speech-embeddings/WavToVec2BertFeatureEmbeddings.ts +7 -5
- package/src/speech-language-detection/SileroLanguageDetection.ts +18 -9
- package/src/speech-search/DTWSpeechSearch.ts +6 -5
- package/src/subtitles/Subtitles.ts +22 -22
- package/src/synthesis/AwsPollyTTS.ts +12 -5
- package/src/synthesis/AzureCognitiveServicesTTS.ts +11 -3
- package/src/synthesis/CoquiServerTTS.ts +8 -7
- package/src/synthesis/DeepgramTTS.ts +21 -15
- package/src/synthesis/ElevenLabsTTS.ts +33 -23
- package/src/synthesis/EspeakTTS.ts +145 -69
- package/src/synthesis/FliteTTS.ts +3 -3
- package/src/synthesis/GnuSpeechTTS.ts +2 -2
- package/src/synthesis/GoogleCloudTTS.ts +23 -14
- package/src/synthesis/GoogleTranslateTTS.ts +19 -11
- package/src/synthesis/KokoroTTS.ts +37 -14
- package/src/synthesis/MicrosoftEdgeTTS.ts +71 -23
- package/src/synthesis/OpenAICloudTTS.ts +7 -6
- package/src/synthesis/SamTTS.ts +3 -2
- package/src/synthesis/SapiTTS.ts +3 -3
- package/src/synthesis/SvoxPicoTTS.ts +4 -5
- package/src/synthesis/VitsTTS.ts +43 -15
- package/src/text-translation/DeepLTextTranslation.ts +12 -10
- package/src/text-translation/GoogleTranslateTextTranslation.ts +35 -21
- package/src/text-translation/NLLBTextTranslation.ts +5 -9
- package/src/typings/Fillers.d.ts +0 -60
- package/src/typings/TypedArray.ts +12 -1
- package/src/utilities/Compression.ts +10 -9
- package/src/utilities/FileDownloader.ts +38 -23
- package/src/utilities/FileSystem.ts +1 -1
- package/src/utilities/Logger.ts +59 -42
- package/src/utilities/NpmUtilities.ts +4 -6
- package/src/utilities/OpenPromise.ts +1 -2
- package/src/utilities/PackageManager.ts +53 -26
- package/src/utilities/PathUtilities.ts +6 -2
- package/src/utilities/TarballMaker.ts +7 -7
- package/src/utilities/Utilities.ts +58 -41
- package/src/utilities/WebReader.ts +11 -6
- package/src/utilities/WikipediaReader.ts +2 -8
- package/src/voice-activity-detection/AdaptiveGateVAD.ts +13 -8
- package/src/voice-activity-detection/WebRtcVAD.ts +1 -2
- package/tsconfig.json +53 -54
- package/dist/audio/AudioRecorder.d.ts +0 -4
- package/dist/audio/AudioRecorder.d.ts.map +0 -1
- package/dist/audio/AudioRecorder.js +0 -68
- package/dist/audio/AudioRecorder.js.map +0 -1
- package/dist/audio/SoxPath.d.ts +0 -2
- package/dist/audio/SoxPath.d.ts.map +0 -1
- package/dist/audio/SoxPath.js +0 -24
- package/dist/audio/SoxPath.js.map +0 -1
- package/dist/recognition/SileroSTT.d.ts +0 -30
- package/dist/recognition/SileroSTT.d.ts.map +0 -1
- package/dist/recognition/SileroSTT.js +0 -152
- package/dist/recognition/SileroSTT.js.map +0 -1
- package/dist/recognition/VoskSTT.d.ts +0 -11
- package/dist/recognition/VoskSTT.d.ts.map +0 -1
- package/dist/recognition/VoskSTT.js +0 -72
- package/dist/recognition/VoskSTT.js.map +0 -1
- package/dist/recognition/WhisperCppSTT.d.ts.map +0 -1
- package/dist/recognition/WhisperCppSTT.js.map +0 -1
- package/dist/synthesis/StreamlabsPollyTTS.d.ts +0 -12
- package/dist/synthesis/StreamlabsPollyTTS.d.ts.map +0 -1
- package/dist/synthesis/StreamlabsPollyTTS.js +0 -88
- package/dist/synthesis/StreamlabsPollyTTS.js.map +0 -1
- package/dist/utilities/BinaryUtilities.d.ts +0 -14
- package/dist/utilities/BinaryUtilities.d.ts.map +0 -1
- package/dist/utilities/BinaryUtilities.js +0 -113
- package/dist/utilities/BinaryUtilities.js.map +0 -1
- package/src/audio/AudioRecorder.ts +0 -91
- package/src/audio/SoxPath.ts +0 -31
- package/src/recognition/SileroSTT.ts +0 -222
- package/src/recognition/VoskSTT.ts +0 -112
- package/src/synthesis/StreamlabsPollyTTS.ts +0 -114
- package/src/utilities/BinaryUtilities.ts +0 -138
- /package/{LICENSE.md → LICENSE.GPLv3.md} +0 -0
|
@@ -1,30 +1,29 @@
|
|
|
1
|
-
import
|
|
1
|
+
import chalk from 'chalk'
|
|
2
2
|
|
|
3
3
|
import * as API from '../api/API.js'
|
|
4
4
|
|
|
5
|
+
import { clip } from '../utilities/Utilities.js'
|
|
6
|
+
|
|
5
7
|
import { computeMFCCs, extendDefaultMfccOptions, MfccOptions } from '../dsp/MFCC.js'
|
|
6
8
|
import { alignMFCC_DTW, getCostMatrixMemorySizeMB } from './DTWMfccSequenceAlignment.js'
|
|
7
9
|
import { Logger } from '../utilities/Logger.js'
|
|
8
10
|
import { addTimeOffsetToTimeline, Timeline, TimelineEntry } from '../utilities/Timeline.js'
|
|
9
11
|
import { concatAudioSegments, downmixToMonoAndNormalize, getEmptyRawAudio, getEndingSilentSampleCount, getRawAudioDuration, getStartingSilentSampleCount, RawAudio } from '../audio/AudioUtilities.js'
|
|
10
|
-
import chalk from 'chalk'
|
|
11
12
|
import { synthesize } from '../api/API.js'
|
|
12
13
|
import { resampleAudioSpeex } from '../dsp/SpeexResampler.js'
|
|
13
14
|
import { deepClone } from '../utilities/ObjectUtilities.js'
|
|
14
|
-
import {
|
|
15
|
+
import { zeroIfNaN } from '../math/VectorMath.js'
|
|
15
16
|
import { EspeakEvent, EspeakOptions } from '../synthesis/EspeakTTS.js'
|
|
16
|
-
import { alignDTWWindowed } from './DTWSequenceAlignmentWindowed.js'
|
|
17
|
-
import { loadPackage } from '../utilities/PackageManager.js'
|
|
18
|
-
import { joinPath } from '../utilities/PathUtilities.js'
|
|
19
17
|
|
|
20
18
|
export async function alignUsingDtw(
|
|
21
19
|
sourceRawAudio: RawAudio,
|
|
22
20
|
referenceRawAudio: RawAudio,
|
|
23
21
|
referenceTimeline: Timeline,
|
|
24
22
|
granularities: DtwGranularity[],
|
|
25
|
-
windowDurations: number[]
|
|
23
|
+
windowDurations: number[],
|
|
24
|
+
callbacks: API.AlignmentCallbacks) {
|
|
26
25
|
|
|
27
|
-
const logger = new Logger()
|
|
26
|
+
const logger = new Logger(callbacks.logLevel)
|
|
28
27
|
|
|
29
28
|
if (windowDurations.length == 0) {
|
|
30
29
|
throw new Error(`Window durations array has length 0.`)
|
|
@@ -44,7 +43,7 @@ export async function alignUsingDtw(
|
|
|
44
43
|
const granularity = granularities[passIndex]
|
|
45
44
|
const windowDuration = windowDurations[passIndex]
|
|
46
45
|
|
|
47
|
-
logger.logTitledMessage(`\nStarting alignment pass ${passIndex + 1}/${windowDurations.length}`, `granularity: ${granularity}, max window duration: ${windowDuration}s (${(windowDuration / rawAudioDuration * 100).toFixed(1)}%)`, chalk.magentaBright)
|
|
46
|
+
logger.logTitledMessage(`\nStarting alignment pass ${passIndex + 1}/${windowDurations.length}`, `granularity: ${granularity}, max window duration: ${windowDuration}s (${(windowDuration / rawAudioDuration * 100).toFixed(1)}%)`, 'info', chalk.magentaBright)
|
|
48
47
|
|
|
49
48
|
const mfccOptions = extendDefaultMfccOptions({ ...getMfccOptionsForGranularity(granularity), zeroFirstCoefficient: true }) as MfccOptions
|
|
50
49
|
|
|
@@ -52,11 +51,20 @@ export async function alignUsingDtw(
|
|
|
52
51
|
|
|
53
52
|
// Compute reference MFCCs
|
|
54
53
|
logger.start('Compute reference MFCC features')
|
|
55
|
-
const referenceMfccs = await computeMFCCs(
|
|
54
|
+
const referenceMfccs = await computeMFCCs(
|
|
55
|
+
referenceRawAudio,
|
|
56
|
+
mfccOptions,
|
|
57
|
+
{ ...callbacks, logLevel: 'warning' }
|
|
58
|
+
)
|
|
56
59
|
|
|
57
60
|
// Compute source MFCCs
|
|
58
61
|
logger.start('Compute source MFCC features')
|
|
59
|
-
const sourceMfccs = await computeMFCCs(
|
|
62
|
+
const sourceMfccs = await computeMFCCs(
|
|
63
|
+
sourceRawAudio,
|
|
64
|
+
mfccOptions,
|
|
65
|
+
{ ...callbacks, logLevel: 'warning' }
|
|
66
|
+
)
|
|
67
|
+
|
|
60
68
|
logger.end()
|
|
61
69
|
|
|
62
70
|
// Compute path
|
|
@@ -66,7 +74,7 @@ export async function alignUsingDtw(
|
|
|
66
74
|
const minRecommendedWindowDuration = 0.2 * rawAudioDuration
|
|
67
75
|
|
|
68
76
|
if (windowDuration < minRecommendedWindowDuration) {
|
|
69
|
-
logger.logTitledMessage('Warning', `Maximum DTW window duration is set to ${windowDuration.toFixed(1)}s (${(windowDuration / rawAudioDuration * 100).toFixed(1)}%), which is less than 20% of the source audio duration (audio duration is ${rawAudioDuration.toFixed(1)}s and a 20% window would be ${(rawAudioDuration * 0.2).toFixed(1)}s). This may lead to suboptimal results in some cases. Consider increasing window duration if needed.`, chalk.yellowBright
|
|
77
|
+
logger.logTitledMessage('Warning', `Maximum DTW window duration is set to ${windowDuration.toFixed(1)}s (${(windowDuration / rawAudioDuration * 100).toFixed(1)}%), which is less than 20% of the source audio duration (audio duration is ${rawAudioDuration.toFixed(1)}s and a 20% window would be ${(rawAudioDuration * 0.2).toFixed(1)}s). This may lead to suboptimal results in some cases. Consider increasing window duration if needed.`, 'warning', chalk.yellowBright)
|
|
70
78
|
}
|
|
71
79
|
}
|
|
72
80
|
|
|
@@ -115,9 +123,10 @@ export async function alignUsingDtwWithRecognition(
|
|
|
115
123
|
granularities: DtwGranularity[],
|
|
116
124
|
windowDurations: number[],
|
|
117
125
|
espeakOptions: EspeakOptions,
|
|
118
|
-
phoneAlignmentMethod: API.PhoneAlignmentMethod = 'interpolation'
|
|
126
|
+
phoneAlignmentMethod: API.PhoneAlignmentMethod = 'interpolation',
|
|
127
|
+
callbacks: API.AlignmentCallbacks) {
|
|
119
128
|
|
|
120
|
-
const logger = new Logger()
|
|
129
|
+
const logger = new Logger(callbacks.logLevel)
|
|
121
130
|
|
|
122
131
|
if (recognitionTimeline.length == 0) {
|
|
123
132
|
const sourceDuration = getRawAudioDuration(sourceRawAudio)
|
|
@@ -145,7 +154,11 @@ export async function alignUsingDtwWithRecognition(
|
|
|
145
154
|
const {
|
|
146
155
|
rawAudio: synthesizedRecognizedTranscriptRawAudio,
|
|
147
156
|
timeline: synthesizedRecognitionTimeline
|
|
148
|
-
} = await createAlignmentReferenceUsingEspeakForFragments(
|
|
157
|
+
} = await createAlignmentReferenceUsingEspeakForFragments(
|
|
158
|
+
recognizedWords,
|
|
159
|
+
espeakOptions,
|
|
160
|
+
{ ...callbacks, logLevel: 'warning' }
|
|
161
|
+
)
|
|
149
162
|
|
|
150
163
|
let recognitionTimelineWithPhones: Timeline
|
|
151
164
|
|
|
@@ -167,7 +180,8 @@ export async function alignUsingDtwWithRecognition(
|
|
|
167
180
|
recognitionTimeline,
|
|
168
181
|
synthesizedRecognizedTranscriptRawAudio,
|
|
169
182
|
synthesizedRecognitionTimeline,
|
|
170
|
-
60
|
|
183
|
+
60,
|
|
184
|
+
callbacks)
|
|
171
185
|
} else {
|
|
172
186
|
throw new Error(`Unknown phone alignment method: ${phoneAlignmentMethod}`)
|
|
173
187
|
}
|
|
@@ -224,7 +238,8 @@ export async function alignUsingDtwWithRecognition(
|
|
|
224
238
|
referenceRawAudio,
|
|
225
239
|
referenceTimeline,
|
|
226
240
|
granularities,
|
|
227
|
-
windowDurations
|
|
241
|
+
windowDurations,
|
|
242
|
+
{ ...callbacks, logLevel: 'warning' })
|
|
228
243
|
|
|
229
244
|
let currentSynthesizedToRecognizedMappingIndex = 0
|
|
230
245
|
|
|
@@ -278,117 +293,6 @@ export async function alignUsingDtwWithRecognition(
|
|
|
278
293
|
return result
|
|
279
294
|
}
|
|
280
295
|
|
|
281
|
-
// This is experimental code. It doesn't work well enough to be usable for anything.
|
|
282
|
-
// Just testing some alternative approaches.
|
|
283
|
-
export async function alignUsingDtwWithEmbeddings(
|
|
284
|
-
sourceRawAudio: RawAudio,
|
|
285
|
-
referenceRawAudio: RawAudio,
|
|
286
|
-
referenceTimeline: Timeline,
|
|
287
|
-
language: string,
|
|
288
|
-
granularities: DtwGranularity[],
|
|
289
|
-
windowDurations: number[]) {
|
|
290
|
-
|
|
291
|
-
const logger = new Logger()
|
|
292
|
-
|
|
293
|
-
if (sourceRawAudio.sampleRate != 16000) {
|
|
294
|
-
throw new Error('Source audio must have a sample rate of 16000 Hz')
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
if (referenceRawAudio.sampleRate != 16000) {
|
|
298
|
-
throw new Error('Reference audio must have a sample rate of 16000 Hz')
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
const embeddingType: 'w2v-bert-2.0' | 'whisper' = 'w2v-bert-2.0'
|
|
302
|
-
|
|
303
|
-
let sourceEmbeddings: Float32Array[]
|
|
304
|
-
let referenceEmbeddings: Float32Array[]
|
|
305
|
-
let framesPerSecond: number
|
|
306
|
-
|
|
307
|
-
if (embeddingType === 'w2v-bert-2.0') {
|
|
308
|
-
const packageName = 'w2v-bert-2.0-uint8'
|
|
309
|
-
const modelDir = await loadPackage(packageName)
|
|
310
|
-
const modelFilePath = joinPath(modelDir, `${packageName}.onnx`)
|
|
311
|
-
|
|
312
|
-
const { Wav2Vec2BertFeatureEmbeddings } = await import('../speech-embeddings/WavToVec2BertFeatureEmbeddings.js')
|
|
313
|
-
|
|
314
|
-
const wav2vecBert = new Wav2Vec2BertFeatureEmbeddings(
|
|
315
|
-
modelFilePath,
|
|
316
|
-
['cpu'],
|
|
317
|
-
)
|
|
318
|
-
|
|
319
|
-
logger.start(`Extract source audio embeddings using the W2V-BERT-2.0 model`)
|
|
320
|
-
sourceEmbeddings = await wav2vecBert.computeEmbeddings(sourceRawAudio)
|
|
321
|
-
|
|
322
|
-
logger.start(`Extract reference audio embeddings using the W2V-BERT-2.0 model`)
|
|
323
|
-
referenceEmbeddings = await wav2vecBert.computeEmbeddings(referenceRawAudio)
|
|
324
|
-
|
|
325
|
-
framesPerSecond = 1000 / 10 / 2
|
|
326
|
-
} else if (embeddingType === 'whisper') {
|
|
327
|
-
const sourceSamples = sourceRawAudio.audioChannels[0]
|
|
328
|
-
const referenceSamples = referenceRawAudio.audioChannels[0]
|
|
329
|
-
|
|
330
|
-
const WhisperSTT = await import(`../recognition/WhisperSTT.js`)
|
|
331
|
-
|
|
332
|
-
const { modelName, modelDir } = await WhisperSTT.loadPackagesAndGetPaths('base.en', language)
|
|
333
|
-
|
|
334
|
-
const whisper = new WhisperSTT.Whisper(modelName, modelDir, ['dml', 'cpu'], ['cpu'])
|
|
335
|
-
|
|
336
|
-
async function encodeToAudioFeatures(samples: Float32Array) {
|
|
337
|
-
const featureVectors: Float32Array[] = []
|
|
338
|
-
|
|
339
|
-
for (let i = 0; i < samples.length; i += 16000 * 30) {
|
|
340
|
-
const startSampleIndex = i
|
|
341
|
-
const endSampleIndex = Math.min(samples.length, i + 16000 * 30)
|
|
342
|
-
const partSampleCount = endSampleIndex - startSampleIndex
|
|
343
|
-
|
|
344
|
-
const audioPart = samples.subarray(startSampleIndex, endSampleIndex)
|
|
345
|
-
const rawAudioForPart = { audioChannels: [audioPart], sampleRate: 16000 } as RawAudio
|
|
346
|
-
|
|
347
|
-
const resultTensor = await whisper.encodeAudio(rawAudioForPart)
|
|
348
|
-
|
|
349
|
-
const vectorLength = resultTensor.dims[2]
|
|
350
|
-
|
|
351
|
-
let featureVectorsForPart = splitFloat32Array(resultTensor.data as Float32Array, vectorLength)
|
|
352
|
-
|
|
353
|
-
featureVectorsForPart = featureVectorsForPart.slice(0, Math.floor((partSampleCount / (16000 * 30)) * 1500))
|
|
354
|
-
|
|
355
|
-
featureVectors.push(...featureVectorsForPart)
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
return featureVectors
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
logger.start(`Extract source audio embeddings using the Whisper encoder model`)
|
|
362
|
-
sourceEmbeddings = await encodeToAudioFeatures(sourceSamples)
|
|
363
|
-
|
|
364
|
-
logger.start(`Extract reference audio embeddings using the Whisper encoder model`)
|
|
365
|
-
referenceEmbeddings = await encodeToAudioFeatures(referenceSamples)
|
|
366
|
-
|
|
367
|
-
framesPerSecond = 1500 / 30
|
|
368
|
-
} else {
|
|
369
|
-
throw new Error(`Unknown embedding type: ${embeddingType}`)
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
logger.start(`Align source and reference audio embeddings using DTW`)
|
|
373
|
-
|
|
374
|
-
const { path: alignmentPath } = alignDTWWindowed(
|
|
375
|
-
referenceEmbeddings,
|
|
376
|
-
sourceEmbeddings,
|
|
377
|
-
cosineDistance,
|
|
378
|
-
1000 * 1000
|
|
379
|
-
)
|
|
380
|
-
|
|
381
|
-
const compactedPath = compactPath(alignmentPath)
|
|
382
|
-
|
|
383
|
-
logger.start('\nConvert path to timeline')
|
|
384
|
-
|
|
385
|
-
const mappedTimeline = referenceTimeline.map(entry => getMappedTimelineEntry(entry, sourceRawAudio, framesPerSecond, compactedPath))
|
|
386
|
-
|
|
387
|
-
logger.end()
|
|
388
|
-
|
|
389
|
-
return mappedTimeline
|
|
390
|
-
}
|
|
391
|
-
|
|
392
296
|
function getMappedTimelineEntry(
|
|
393
297
|
timelineEntry: TimelineEntry,
|
|
394
298
|
sourceRawAudio: RawAudio,
|
|
@@ -496,14 +400,15 @@ export async function alignPhoneTimelines(
|
|
|
496
400
|
sourceWordTimeline: Timeline,
|
|
497
401
|
referenceRawAudio: RawAudio,
|
|
498
402
|
referenceTimeline: Timeline,
|
|
499
|
-
windowDuration: number
|
|
403
|
+
windowDuration: number,
|
|
404
|
+
callbacks: API.AlignmentCallbacks) {
|
|
500
405
|
|
|
501
406
|
const mfccOptions: MfccOptions = extendDefaultMfccOptions({ zeroFirstCoefficient: true })
|
|
502
407
|
|
|
503
408
|
const framesPerSecond = 1 / mfccOptions.hopDuration!
|
|
504
409
|
|
|
505
|
-
const referenceMfccs = await computeMFCCs(referenceRawAudio, mfccOptions)
|
|
506
|
-
const sourceMfccs = await computeMFCCs(sourceRawAudio, mfccOptions)
|
|
410
|
+
const referenceMfccs = await computeMFCCs(referenceRawAudio, mfccOptions, { ...callbacks, logLevel: 'warning' })
|
|
411
|
+
const sourceMfccs = await computeMFCCs(sourceRawAudio, mfccOptions, { ...callbacks, logLevel: 'warning' })
|
|
507
412
|
|
|
508
413
|
const alignedWordTimeline: Timeline = []
|
|
509
414
|
|
|
@@ -572,14 +477,9 @@ export async function alignPhoneTimelines(
|
|
|
572
477
|
return alignedWordTimeline
|
|
573
478
|
}
|
|
574
479
|
|
|
575
|
-
export async function createAlignmentReferenceUsingEspeakForFragments(fragments: string[], espeakOptions: EspeakOptions) {
|
|
576
|
-
const progressLogger = new Logger()
|
|
577
|
-
|
|
578
|
-
progressLogger.start("Load espeak module")
|
|
480
|
+
export async function createAlignmentReferenceUsingEspeakForFragments(fragments: string[], espeakOptions: EspeakOptions, callbacks: API.SynthesisCallbacks) {
|
|
579
481
|
const Espeak = await import("../synthesis/EspeakTTS.js")
|
|
580
482
|
|
|
581
|
-
progressLogger.start("Synthesize alignment reference using eSpeak")
|
|
582
|
-
|
|
583
483
|
const result = {
|
|
584
484
|
rawAudio: getEmptyRawAudio(1, await Espeak.getSampleRate()) as RawAudio,
|
|
585
485
|
timeline: [] as Timeline,
|
|
@@ -606,7 +506,7 @@ export async function createAlignmentReferenceUsingEspeakForFragments(fragments:
|
|
|
606
506
|
if (currentChunkCharacterCount >= maxCharactersInChunk || fragmentIndex === fragments.length - 1) {
|
|
607
507
|
// Process current chunk
|
|
608
508
|
|
|
609
|
-
const chunkResult = await Espeak.synthesizeFragments(currentChunk, espeakOptions)
|
|
509
|
+
const chunkResult = await Espeak.synthesizeFragments(currentChunk, espeakOptions, callbacks)
|
|
610
510
|
|
|
611
511
|
result.rawAudio = {
|
|
612
512
|
sampleRate: result.rawAudio.sampleRate,
|
|
@@ -633,22 +533,19 @@ export async function createAlignmentReferenceUsingEspeakForFragments(fragments:
|
|
|
633
533
|
wordEntry.timeline = wordEntry.timeline!.flatMap(tokenEntry => tokenEntry.timeline!)
|
|
634
534
|
}
|
|
635
535
|
|
|
636
|
-
progressLogger.end()
|
|
637
|
-
|
|
638
536
|
return result
|
|
639
537
|
}
|
|
640
538
|
|
|
641
539
|
export async function createAlignmentReferenceUsingEspeak(
|
|
642
540
|
transcript: string,
|
|
643
541
|
language: string,
|
|
644
|
-
plaintextOptions
|
|
645
|
-
customLexiconPaths
|
|
646
|
-
insertSeparators
|
|
647
|
-
useKlatt
|
|
542
|
+
plaintextOptions: API.PlainTextOptions,
|
|
543
|
+
customLexiconPaths: string[],
|
|
544
|
+
insertSeparators: boolean,
|
|
545
|
+
useKlatt: boolean,
|
|
546
|
+
callbacks: API.OperationCallbacks) {
|
|
648
547
|
|
|
649
|
-
const logger = new Logger()
|
|
650
|
-
|
|
651
|
-
logger.start('Synthesize alignment reference using eSpeak')
|
|
548
|
+
const logger = new Logger(callbacks.logLevel)
|
|
652
549
|
|
|
653
550
|
const synthesisOptions: API.SynthesisOptions = {
|
|
654
551
|
engine: 'espeak',
|
|
@@ -660,14 +557,14 @@ export async function createAlignmentReferenceUsingEspeak(
|
|
|
660
557
|
espeak: {
|
|
661
558
|
useKlatt,
|
|
662
559
|
insertSeparators,
|
|
663
|
-
}
|
|
560
|
+
},
|
|
664
561
|
}
|
|
665
562
|
|
|
666
563
|
let {
|
|
667
564
|
audio: referenceRawAudio,
|
|
668
565
|
timeline: segmentTimeline,
|
|
669
|
-
voice: espeakVoice
|
|
670
|
-
} = await synthesize(transcript, synthesisOptions)
|
|
566
|
+
voice: espeakVoice,
|
|
567
|
+
} = await synthesize(transcript, synthesisOptions, callbacks)
|
|
671
568
|
|
|
672
569
|
const sentenceTimeline = segmentTimeline.flatMap(entry => entry.timeline!)
|
|
673
570
|
const wordTimeline = sentenceTimeline.flatMap(entry => entry.timeline!)
|
package/src/api/APIOptions.ts
CHANGED
|
@@ -20,7 +20,7 @@ export interface APIOptions {
|
|
|
20
20
|
SpeechLanguageDetectionOptions: API.SpeechLanguageDetectionOptions
|
|
21
21
|
TextLanguageDetectionOptions: API.TextLanguageDetectionOptions
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
VoiceActivityDetectionOptions: API.VoiceActivityDetectionOptions
|
|
24
24
|
|
|
25
25
|
DenoisingOptions: API.DenoisingOptions
|
|
26
26
|
|
package/src/api/Alignment.ts
CHANGED
|
@@ -1,55 +1,62 @@
|
|
|
1
|
+
import chalk from 'chalk'
|
|
2
|
+
|
|
3
|
+
import * as API from './API.js'
|
|
4
|
+
|
|
1
5
|
import { extendDeep } from '../utilities/ObjectUtilities.js'
|
|
2
6
|
|
|
3
7
|
import { logToStderr } from '../utilities/Utilities.js'
|
|
4
8
|
import { AudioSourceParam, RawAudio, ensureRawAudio, getRawAudioDuration, normalizeAudioLevelInPlace, trimAudioEnd } from '../audio/AudioUtilities.js'
|
|
5
9
|
import { Logger } from '../utilities/Logger.js'
|
|
6
10
|
|
|
7
|
-
import * as API from './API.js'
|
|
8
11
|
import { Timeline, addTimeOffsetToTimeline, addWordTextOffsetsToTimelineInPlace, wordTimelineToSegmentSentenceTimeline } from '../utilities/Timeline.js'
|
|
9
12
|
import { formatLanguageCodeWithName, getDefaultDialectForLanguageCodeIfPossible, getShortLanguageCode, parseLangIdentifier } from '../utilities/Locale.js'
|
|
10
13
|
import { type WhisperAlignmentOptions } from '../recognition/WhisperSTT.js'
|
|
11
|
-
import
|
|
12
|
-
import { DtwGranularity, alignUsingDtwWithEmbeddings, createAlignmentReferenceUsingEspeak } from '../alignment/SpeechAlignment.js'
|
|
14
|
+
import { DtwGranularity, createAlignmentReferenceUsingEspeak } from '../alignment/SpeechAlignment.js'
|
|
13
15
|
import { type SubtitlesConfig } from '../subtitles/Subtitles.js'
|
|
14
16
|
import { type EspeakOptions, defaultEspeakOptions } from '../synthesis/EspeakTTS.js'
|
|
15
17
|
import { isWord } from '../nlp/Segmentation.js'
|
|
16
18
|
|
|
17
19
|
const log = logToStderr
|
|
18
20
|
|
|
19
|
-
export async function align(input: AudioSourceParam, transcript: string, options: AlignmentOptions): Promise<AlignmentResult> {
|
|
20
|
-
|
|
21
|
+
export async function align(input: AudioSourceParam, transcript: string, options: AlignmentOptions, callbacks?: AlignmentCallbacks): Promise<AlignmentResult> {
|
|
22
|
+
options = extendDeep(defaultAlignmentOptions, options)
|
|
23
|
+
callbacks = { logLevel: API.getGlobalLogLevel(), ...callbacks }
|
|
21
24
|
|
|
22
|
-
const
|
|
25
|
+
const logger = new Logger(callbacks.logLevel)
|
|
23
26
|
|
|
24
|
-
|
|
27
|
+
const startTimestamp = logger.getTimestamp()
|
|
25
28
|
|
|
26
|
-
const inputRawAudio = await ensureRawAudio(input)
|
|
29
|
+
const inputRawAudio = await ensureRawAudio(input, undefined, undefined, callbacks)
|
|
27
30
|
|
|
28
31
|
let sourceRawAudio: RawAudio
|
|
29
32
|
let isolatedRawAudio: RawAudio | undefined
|
|
30
33
|
let backgroundRawAudio: RawAudio | undefined
|
|
31
34
|
|
|
32
35
|
if (options.isolate) {
|
|
33
|
-
logger.
|
|
34
|
-
logger.end();
|
|
36
|
+
logger.logTitledMessage(`Isolate vocals`, '');
|
|
35
37
|
|
|
36
|
-
({ isolatedRawAudio, backgroundRawAudio } = await API.isolate(
|
|
38
|
+
({ isolatedRawAudio, backgroundRawAudio } = await API.isolate(
|
|
39
|
+
inputRawAudio,
|
|
40
|
+
options.sourceSeparation!,
|
|
41
|
+
{ ...callbacks, logLevel: logger.logLevel }))
|
|
37
42
|
|
|
38
43
|
logger.end()
|
|
39
|
-
logger.log(``)
|
|
40
44
|
|
|
41
45
|
logger.start(`Resample audio to 16kHz mono`)
|
|
42
|
-
sourceRawAudio = await ensureRawAudio(isolatedRawAudio, 16000, 1)
|
|
46
|
+
sourceRawAudio = await ensureRawAudio(isolatedRawAudio, 16000, 1, callbacks)
|
|
43
47
|
} else {
|
|
44
48
|
logger.start(`Resample audio to 16kHz mono`)
|
|
45
|
-
sourceRawAudio = await ensureRawAudio(inputRawAudio, 16000, 1)
|
|
49
|
+
sourceRawAudio = await ensureRawAudio(inputRawAudio, 16000, 1, callbacks)
|
|
46
50
|
}
|
|
47
51
|
|
|
48
52
|
let sourceUncropTimeline: Timeline | undefined
|
|
49
53
|
|
|
50
54
|
if (options.crop) {
|
|
51
55
|
logger.start('Crop using voice activity detection');
|
|
52
|
-
({ timeline: sourceUncropTimeline, croppedRawAudio: sourceRawAudio } = await API.detectVoiceActivity(
|
|
56
|
+
({ timeline: sourceUncropTimeline, croppedRawAudio: sourceRawAudio } = await API.detectVoiceActivity(
|
|
57
|
+
sourceRawAudio,
|
|
58
|
+
options.vad!,
|
|
59
|
+
{ ...callbacks, logLevel: 'warning' }))
|
|
53
60
|
|
|
54
61
|
logger.end()
|
|
55
62
|
}
|
|
@@ -71,7 +78,10 @@ export async function align(input: AudioSourceParam, transcript: string, options
|
|
|
71
78
|
logger.logTitledMessage('Language specified', formatLanguageCodeWithName(language))
|
|
72
79
|
} else {
|
|
73
80
|
logger.start('No language specified. Detect language using reference text')
|
|
74
|
-
const { detectedLanguage } = await API.detectTextLanguage(
|
|
81
|
+
const { detectedLanguage } = await API.detectTextLanguage(
|
|
82
|
+
transcript,
|
|
83
|
+
options.languageDetection!,
|
|
84
|
+
{ abortSignal: callbacks.abortSignal, logLevel: 'warning' })
|
|
75
85
|
|
|
76
86
|
language = detectedLanguage
|
|
77
87
|
|
|
@@ -207,16 +217,31 @@ export async function align(input: AudioSourceParam, transcript: string, options
|
|
|
207
217
|
case 'dtw': {
|
|
208
218
|
const { windowDurations, granularities } = getDtwWindowGranularitiesAndDurations()
|
|
209
219
|
|
|
210
|
-
logger.
|
|
220
|
+
logger.start('Synthesize alignment reference using eSpeak')
|
|
211
221
|
|
|
212
222
|
const {
|
|
213
223
|
referenceRawAudio,
|
|
214
224
|
referenceTimeline
|
|
215
|
-
} = await createAlignmentReferenceUsingEspeak(
|
|
225
|
+
} = await createAlignmentReferenceUsingEspeak(
|
|
226
|
+
transcript,
|
|
227
|
+
language,
|
|
228
|
+
options.plainText!,
|
|
229
|
+
options.customLexiconPaths!,
|
|
230
|
+
false,
|
|
231
|
+
false,
|
|
232
|
+
{ abortSignal: callbacks.abortSignal, logLevel: 'warning' },
|
|
233
|
+
)
|
|
216
234
|
|
|
217
235
|
logger.end()
|
|
218
236
|
|
|
219
|
-
mappedTimeline = await alignUsingDtw(
|
|
237
|
+
mappedTimeline = await alignUsingDtw(
|
|
238
|
+
sourceRawAudio,
|
|
239
|
+
referenceRawAudio,
|
|
240
|
+
referenceTimeline,
|
|
241
|
+
granularities,
|
|
242
|
+
windowDurations,
|
|
243
|
+
{ abortSignal: callbacks.abortSignal, logLevel: callbacks.logLevel },
|
|
244
|
+
)
|
|
220
245
|
|
|
221
246
|
break
|
|
222
247
|
}
|
|
@@ -230,7 +255,11 @@ export async function align(input: AudioSourceParam, transcript: string, options
|
|
|
230
255
|
extendDeep({ crop: options.crop, language }, options.recognition)
|
|
231
256
|
|
|
232
257
|
// Recognize source audio
|
|
233
|
-
let { wordTimeline: recognitionTimeline } = await API.recognize(
|
|
258
|
+
let { wordTimeline: recognitionTimeline } = await API.recognize(
|
|
259
|
+
sourceRawAudio,
|
|
260
|
+
recognitionOptions,
|
|
261
|
+
callbacks
|
|
262
|
+
)
|
|
234
263
|
|
|
235
264
|
logger.log('')
|
|
236
265
|
|
|
@@ -244,7 +273,15 @@ export async function align(input: AudioSourceParam, transcript: string, options
|
|
|
244
273
|
referenceRawAudio,
|
|
245
274
|
referenceTimeline,
|
|
246
275
|
espeakVoice,
|
|
247
|
-
} = await createAlignmentReferenceUsingEspeak(
|
|
276
|
+
} = await createAlignmentReferenceUsingEspeak(
|
|
277
|
+
transcript,
|
|
278
|
+
language,
|
|
279
|
+
options.plainText!,
|
|
280
|
+
options.customLexiconPaths!,
|
|
281
|
+
false,
|
|
282
|
+
false,
|
|
283
|
+
{ ...callbacks, logLevel: 'warning' },
|
|
284
|
+
)
|
|
248
285
|
|
|
249
286
|
logger.end()
|
|
250
287
|
|
|
@@ -268,50 +305,44 @@ export async function align(input: AudioSourceParam, transcript: string, options
|
|
|
268
305
|
granularities,
|
|
269
306
|
windowDurations,
|
|
270
307
|
espeakOptions,
|
|
271
|
-
phoneAlignmentMethod
|
|
272
|
-
|
|
273
|
-
break
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
case 'dtw-ea': {
|
|
277
|
-
const { windowDurations, granularities } = getDtwWindowGranularitiesAndDurations()
|
|
278
|
-
|
|
279
|
-
logger.end()
|
|
308
|
+
phoneAlignmentMethod,
|
|
280
309
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
const {
|
|
284
|
-
referenceRawAudio,
|
|
285
|
-
referenceTimeline
|
|
286
|
-
} = await createAlignmentReferenceUsingEspeak(transcript, language, options.plainText, options.customLexiconPaths, false, true)
|
|
287
|
-
|
|
288
|
-
logger.end()
|
|
289
|
-
|
|
290
|
-
const shortLanguageCode = getShortLanguageCode(language)
|
|
291
|
-
|
|
292
|
-
mappedTimeline = await alignUsingDtwWithEmbeddings(
|
|
293
|
-
sourceRawAudio,
|
|
294
|
-
referenceRawAudio,
|
|
295
|
-
referenceTimeline,
|
|
296
|
-
shortLanguageCode,
|
|
297
|
-
granularities,
|
|
298
|
-
windowDurations)
|
|
310
|
+
callbacks,
|
|
311
|
+
)
|
|
299
312
|
|
|
300
313
|
break
|
|
301
314
|
}
|
|
302
315
|
|
|
303
316
|
case 'whisper': {
|
|
304
317
|
const WhisperSTT = await import('../recognition/WhisperSTT.js')
|
|
318
|
+
const WhisperCommon = await import('../recognition/WhisperCommon.js')
|
|
305
319
|
|
|
306
|
-
const whisperAlignmnentOptions = options.whisper
|
|
307
|
-
|
|
320
|
+
const whisperAlignmnentOptions: WhisperAlignmentOptions = extendDeep(WhisperSTT.defaultWhisperAlignmentOptions, options.whisper)
|
|
308
321
|
const shortLanguageCode = getShortLanguageCode(language)
|
|
309
322
|
|
|
310
|
-
const {
|
|
323
|
+
const { modelId, modelPath } = await WhisperCommon.loadModelPackage(
|
|
324
|
+
whisperAlignmnentOptions.model,
|
|
325
|
+
shortLanguageCode,
|
|
326
|
+
callbacks,
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
const { libPath } = await WhisperSTT.loadLibraryPackages(
|
|
330
|
+
whisperAlignmnentOptions.enableGPU,
|
|
331
|
+
callbacks,
|
|
332
|
+
)
|
|
311
333
|
|
|
312
334
|
logger.end()
|
|
313
335
|
|
|
314
|
-
mappedTimeline = await WhisperSTT.align(
|
|
336
|
+
mappedTimeline = await WhisperSTT.align(
|
|
337
|
+
sourceRawAudio,
|
|
338
|
+
transcript,
|
|
339
|
+
modelId,
|
|
340
|
+
modelPath,
|
|
341
|
+
libPath,
|
|
342
|
+
shortLanguageCode,
|
|
343
|
+
whisperAlignmnentOptions,
|
|
344
|
+
callbacks,
|
|
345
|
+
)
|
|
315
346
|
|
|
316
347
|
break
|
|
317
348
|
}
|
|
@@ -335,7 +366,7 @@ export async function align(input: AudioSourceParam, transcript: string, options
|
|
|
335
366
|
const { segmentTimeline } = await wordTimelineToSegmentSentenceTimeline(mappedTimeline, transcript, language, options.plainText?.paragraphBreaks, options.plainText?.whitespace)
|
|
336
367
|
|
|
337
368
|
logger.end()
|
|
338
|
-
logger.logDuration(`Total alignment time`, startTimestamp, chalk.magentaBright)
|
|
369
|
+
logger.logDuration(`Total alignment time`, startTimestamp, 'info', chalk.magentaBright)
|
|
339
370
|
|
|
340
371
|
return {
|
|
341
372
|
timeline: segmentTimeline,
|
|
@@ -350,7 +381,7 @@ export async function align(input: AudioSourceParam, transcript: string, options
|
|
|
350
381
|
}
|
|
351
382
|
}
|
|
352
383
|
|
|
353
|
-
export async function alignSegments(sourceRawAudio: RawAudio, segmentTimeline: Timeline, alignmentOptions: AlignmentOptions) {
|
|
384
|
+
export async function alignSegments(sourceRawAudio: RawAudio, segmentTimeline: Timeline, alignmentOptions: AlignmentOptions, callbacks?: AlignmentCallbacks) {
|
|
354
385
|
const timeline: Timeline = []
|
|
355
386
|
|
|
356
387
|
for (const segmentEntry of segmentTimeline) {
|
|
@@ -368,7 +399,7 @@ export async function alignSegments(sourceRawAudio: RawAudio, segmentTimeline: T
|
|
|
368
399
|
sampleRate: sourceRawAudio.sampleRate
|
|
369
400
|
}
|
|
370
401
|
|
|
371
|
-
const { wordTimeline: mappedTimeline } = await align(segmentRawAudio, segmentText, alignmentOptions)
|
|
402
|
+
const { wordTimeline: mappedTimeline } = await align(segmentRawAudio, segmentText, alignmentOptions, callbacks)
|
|
372
403
|
|
|
373
404
|
const segmentTimelineWithOffset = addTimeOffsetToTimeline(mappedTimeline, segmentStartTime)
|
|
374
405
|
|
|
@@ -390,10 +421,10 @@ export interface AlignmentResult {
|
|
|
390
421
|
backgroundRawAudio?: RawAudio
|
|
391
422
|
}
|
|
392
423
|
|
|
393
|
-
export type AlignmentEngine = 'dtw' | 'dtw-ra' | '
|
|
424
|
+
export type AlignmentEngine = 'dtw' | 'dtw-ra' | 'whisper'
|
|
394
425
|
export type PhoneAlignmentMethod = 'interpolation' | 'dtw'
|
|
395
426
|
|
|
396
|
-
export interface AlignmentOptions {
|
|
427
|
+
export interface AlignmentOptions extends API.OperationOptions {
|
|
397
428
|
engine?: AlignmentEngine
|
|
398
429
|
|
|
399
430
|
language?: string
|
|
@@ -406,7 +437,7 @@ export interface AlignmentOptions {
|
|
|
406
437
|
|
|
407
438
|
languageDetection?: API.TextLanguageDetectionOptions
|
|
408
439
|
|
|
409
|
-
vad?: API.
|
|
440
|
+
vad?: API.VoiceActivityDetectionOptions
|
|
410
441
|
|
|
411
442
|
plainText?: API.PlainTextOptions
|
|
412
443
|
|
|
@@ -434,7 +465,7 @@ export const defaultAlignmentOptions: AlignmentOptions = {
|
|
|
434
465
|
|
|
435
466
|
crop: true,
|
|
436
467
|
|
|
437
|
-
customLexiconPaths:
|
|
468
|
+
customLexiconPaths: [],
|
|
438
469
|
|
|
439
470
|
languageDetection: {
|
|
440
471
|
},
|
|
@@ -466,14 +497,17 @@ export const defaultAlignmentOptions: AlignmentOptions = {
|
|
|
466
497
|
},
|
|
467
498
|
|
|
468
499
|
vad: {
|
|
469
|
-
engine: 'adaptive-gate'
|
|
500
|
+
engine: 'adaptive-gate',
|
|
470
501
|
},
|
|
471
502
|
|
|
472
503
|
sourceSeparation: {
|
|
473
504
|
},
|
|
474
505
|
|
|
475
506
|
whisper: {
|
|
476
|
-
}
|
|
507
|
+
},
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
export interface AlignmentCallbacks extends API.RecognitionCallbacks {
|
|
477
511
|
}
|
|
478
512
|
|
|
479
513
|
export const alignmentEngines: API.EngineMetadata[] = [
|
package/src/api/Common.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { LogLevel } from '../utilities/Logger.js'
|
|
2
|
+
|
|
1
3
|
export const appName = 'echogarden'
|
|
2
4
|
|
|
3
5
|
export interface EngineMetadata {
|
|
@@ -14,3 +16,12 @@ export interface PlainTextOptions {
|
|
|
14
16
|
|
|
15
17
|
export type ParagraphBreakType = 'single' | 'double'
|
|
16
18
|
export type WhitespaceProcessing = 'preserve' | 'removeLineBreaks' | 'collapse'
|
|
19
|
+
|
|
20
|
+
export interface OperationOptions {
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface OperationCallbacks {
|
|
24
|
+
abortSignal?: AbortSignal
|
|
25
|
+
logLevel?: LogLevel
|
|
26
|
+
}
|
|
27
|
+
|