echogarden 1.4.4 → 1.5.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/data/schemas/options.json +267 -19
- package/dist/alignment/{TextAlignment.d.ts → SemanticTextAlignment.d.ts} +4 -2
- package/dist/alignment/SemanticTextAlignment.js +336 -0
- package/dist/alignment/SemanticTextAlignment.js.map +1 -0
- package/dist/alignment/SpeechAlignment.d.ts +2 -1
- package/dist/alignment/SpeechAlignment.js +105 -37
- package/dist/alignment/SpeechAlignment.js.map +1 -1
- package/dist/api/API.d.ts +7 -2
- package/dist/api/API.js +7 -2
- package/dist/api/API.js.map +1 -1
- package/dist/api/APIOptions.d.ts +4 -1
- package/dist/api/Alignment.d.ts +1 -1
- package/dist/api/Alignment.js +13 -5
- package/dist/api/Alignment.js.map +1 -1
- package/dist/api/LanguageDetectionCommon.d.ts +6 -0
- package/dist/api/LanguageDetectionCommon.js +2 -0
- package/dist/api/LanguageDetectionCommon.js.map +1 -0
- package/dist/api/Recognition.js.map +1 -1
- package/dist/api/{LanguageDetection.d.ts → SpeechLanguageDetection.d.ts} +1 -25
- package/dist/api/{LanguageDetection.js → SpeechLanguageDetection.js} +1 -68
- package/dist/api/SpeechLanguageDetection.js.map +1 -0
- package/dist/api/{Translation.js → SpeechTranslation.js} +3 -3
- package/dist/api/SpeechTranslation.js.map +1 -0
- package/dist/api/Synthesis.js +4 -4
- package/dist/api/TextLanguageDetection.d.ts +21 -0
- package/dist/api/TextLanguageDetection.js +67 -0
- package/dist/api/TextLanguageDetection.js.map +1 -0
- package/dist/api/TextTranslation.d.ts +5 -0
- package/dist/api/TextTranslation.js +4 -0
- package/dist/api/TextTranslation.js.map +1 -0
- package/dist/api/TimelineTranslationAlignment.d.ts +23 -0
- package/dist/api/TimelineTranslationAlignment.js +92 -0
- package/dist/api/TimelineTranslationAlignment.js.map +1 -0
- package/dist/api/TranscriptAndTranslationAlignment.d.ts +35 -0
- package/dist/api/TranscriptAndTranslationAlignment.js +78 -0
- package/dist/api/TranscriptAndTranslationAlignment.js.map +1 -0
- package/dist/api/TranslationAlignment.d.ts +4 -3
- package/dist/api/TranslationAlignment.js +9 -8
- package/dist/api/TranslationAlignment.js.map +1 -1
- package/dist/api/VoiceActivityDetection.js +16 -1
- package/dist/api/VoiceActivityDetection.js.map +1 -1
- package/dist/cli/CLI.d.ts +27 -7
- package/dist/cli/CLI.js +205 -34
- package/dist/cli/CLI.js.map +1 -1
- package/dist/codecs/FFMpegTranscoder.js +7 -0
- package/dist/codecs/FFMpegTranscoder.js.map +1 -1
- package/dist/dsp/FFT.d.ts +1 -1
- package/dist/dsp/FFT.js +6 -0
- package/dist/dsp/FFT.js.map +1 -1
- package/dist/dsp/KWeightingFilter.js +1 -1
- package/dist/dsp/KWeightingFilter.js.map +1 -1
- package/dist/dsp/MelSpectogram.d.ts +3 -2
- package/dist/dsp/MelSpectogram.js +14 -8
- package/dist/dsp/MelSpectogram.js.map +1 -1
- package/dist/math/VectorMath.d.ts +5 -5
- package/dist/math/VectorMath.js +4 -4
- package/dist/math/VectorMath.js.map +1 -1
- package/dist/recognition/WhisperCppSTT.d.ts +1 -1
- package/dist/recognition/WhisperCppSTT.js +2 -2
- package/dist/recognition/WhisperCppSTT.js.map +1 -1
- package/dist/recognition/WhisperSTT.js +9 -6
- package/dist/recognition/WhisperSTT.js.map +1 -1
- package/dist/server/Client.d.ts +3 -2
- package/dist/server/Client.js.map +1 -1
- package/dist/server/Worker.d.ts +3 -2
- package/dist/server/Worker.js +3 -2
- package/dist/server/Worker.js.map +1 -1
- package/dist/source-separation/MDXNetSourceSeparation.js +1 -1
- package/dist/source-separation/MDXNetSourceSeparation.js.map +1 -1
- package/dist/speech-embeddings/WavToVec2BertFeatureEmbeddings.d.ts +13 -0
- package/dist/speech-embeddings/WavToVec2BertFeatureEmbeddings.js +68 -0
- package/dist/speech-embeddings/WavToVec2BertFeatureEmbeddings.js.map +1 -0
- package/dist/speech-language-detection/SileroLanguageDetection.d.ts +1 -1
- package/dist/speech-language-detection/SileroLanguageDetection.js +1 -1
- package/dist/speech-language-detection/SileroLanguageDetection.js.map +1 -1
- package/dist/tests/Test.js +0 -31
- package/dist/tests/Test.js.map +1 -1
- package/dist/text-language-detection/FastTextLanguageDetection.d.ts +1 -1
- package/dist/text-language-detection/FastTextLanguageDetection.js.map +1 -1
- package/dist/text-language-detection/TinyLDLanguageDetection.d.ts +1 -1
- package/dist/text-language-detection/TinyLDLanguageDetection.js.map +1 -1
- package/dist/text-translation/NLLBTextTranslation.js +1 -1
- package/dist/text-translation/NLLBTextTranslation.js.map +1 -1
- package/dist/utilities/Locale.d.ts +1 -1
- package/dist/utilities/Locale.js +1 -1
- package/dist/utilities/OnnxUtilities.d.ts +1 -1
- package/dist/utilities/PackageManager.js +6 -4
- package/dist/utilities/PackageManager.js.map +1 -1
- package/dist/utilities/Timeline.d.ts +1 -0
- package/dist/utilities/Timeline.js +12 -0
- package/dist/utilities/Timeline.js.map +1 -1
- package/docs/API.md +81 -3
- package/docs/CLI.md +51 -1
- package/docs/Engines.md +24 -0
- package/docs/Options.md +33 -1
- package/package.json +8 -8
- package/src/alignment/SemanticTextAlignment.ts +467 -0
- package/src/alignment/SpeechAlignment.ts +179 -52
- package/src/api/API.ts +18 -2
- package/src/api/APIOptions.ts +14 -1
- package/src/api/Alignment.ts +31 -9
- package/src/api/LanguageDetectionCommon.ts +7 -0
- package/src/api/Recognition.ts +2 -0
- package/src/api/{LanguageDetection.ts → SpeechLanguageDetection.ts} +1 -119
- package/src/api/{Translation.ts → SpeechTranslation.ts} +2 -2
- package/src/api/Synthesis.ts +4 -4
- package/src/api/TextLanguageDetection.ts +116 -0
- package/src/api/TextTranslation.ts +9 -0
- package/src/api/TimelineTranslationAlignment.ts +162 -0
- package/src/api/TranscriptAndTranslationAlignment.ts +164 -0
- package/src/api/TranslationAlignment.ts +12 -10
- package/src/api/VoiceActivityDetection.ts +24 -3
- package/src/cli/CLI.ts +276 -34
- package/src/codecs/FFMpegTranscoder.ts +6 -0
- package/src/dsp/FFT.ts +8 -2
- package/src/dsp/KWeightingFilter.ts +1 -1
- package/src/dsp/MelSpectogram.ts +17 -8
- package/src/math/VectorMath.ts +9 -9
- package/src/recognition/WhisperCppSTT.ts +2 -2
- package/src/recognition/WhisperSTT.ts +10 -6
- package/src/server/Client.ts +3 -2
- package/src/server/Worker.ts +3 -2
- package/src/source-separation/MDXNetSourceSeparation.ts +1 -1
- package/src/speech-embeddings/WavToVec2BertFeatureEmbeddings.ts +107 -0
- package/src/speech-language-detection/SileroLanguageDetection.ts +2 -1
- package/src/tests/Test.ts +1 -38
- package/src/text-language-detection/FastTextLanguageDetection.ts +1 -1
- package/src/text-language-detection/TinyLDLanguageDetection.ts +1 -1
- package/src/text-translation/NLLBTextTranslation.ts +1 -1
- package/src/utilities/Locale.ts +1 -1
- package/src/utilities/OnnxUtilities.ts +1 -1
- package/src/utilities/PackageManager.ts +7 -4
- package/src/utilities/Timeline.ts +14 -0
- package/dist/alignment/TextAlignment.js +0 -156
- package/dist/alignment/TextAlignment.js.map +0 -1
- package/dist/api/LanguageDetection.js.map +0 -1
- package/dist/api/Translation.js.map +0 -1
- package/src/alignment/TextAlignment.ts +0 -234
- /package/dist/api/{Translation.d.ts → SpeechTranslation.d.ts} +0 -0
package/src/cli/CLI.ts
CHANGED
|
@@ -219,17 +219,21 @@ const help = [
|
|
|
219
219
|
`${executableName} ${chalk.magentaBright('speak-url')} url [output files...] [options...]`,
|
|
220
220
|
` Speak the HTML document on the given URL\n`,
|
|
221
221
|
`${executableName} ${chalk.magentaBright('speak-wikipedia')} articleName [output files...] [options...]`,
|
|
222
|
-
` Speak the given
|
|
222
|
+
` Speak the given Wikipedia article. Language edition can be specified by --language=<langCode>\n`,
|
|
223
223
|
`${executableName} ${chalk.magentaBright('transcribe')} audioFile [output files...] [options...]`,
|
|
224
|
-
` Transcribe audio file\n`,
|
|
225
|
-
`${executableName} ${chalk.magentaBright('align')} audioFile
|
|
226
|
-
` Align audio file to
|
|
227
|
-
`${executableName} ${chalk.magentaBright('translate-speech')}
|
|
224
|
+
` Transcribe a spoken audio file\n`,
|
|
225
|
+
`${executableName} ${chalk.magentaBright('align')} audioFile transcriptFile [output files...] [options...]`,
|
|
226
|
+
` Align spoken audio file to its transcript\n`,
|
|
227
|
+
`${executableName} ${chalk.magentaBright('translate-speech')} audioFile [output files...] [options...]`,
|
|
228
228
|
` Transcribe audio file directly to a different language\n`,
|
|
229
|
-
`${executableName} ${chalk.magentaBright('align-translation')} audioFile
|
|
230
|
-
` Align audio file to
|
|
229
|
+
`${executableName} ${chalk.magentaBright('align-translation')} audioFile translatedTranscriptFile [output files...] [options...]`,
|
|
230
|
+
` Align spoken audio file to its translated transcript\n`,
|
|
231
|
+
`${executableName} ${chalk.magentaBright('align-transcript-and-translation')} audioFile transcriptFile translatedTranscriptFile [output files...] [options...]`,
|
|
232
|
+
` Align spoken audio file to both its transcript and its translated transcript using a two-stage approach.\n`,
|
|
233
|
+
`${executableName} ${chalk.magentaBright('align-timeline-translation')} timelineFile translatedFile [output files...] [options...]`,
|
|
234
|
+
` Align a given timeline file to its translated text\n`,
|
|
231
235
|
`${executableName} ${chalk.magentaBright('detect-speech-language')} audioFile [output files...] [options...]`,
|
|
232
|
-
` Detect language of audio file\n`,
|
|
236
|
+
` Detect language of spoken audio file\n`,
|
|
233
237
|
`${executableName} ${chalk.magentaBright('detect-text-language')} inputFile [output files...] [options...]`,
|
|
234
238
|
` Detect language of textual file\n`,
|
|
235
239
|
`${executableName} ${chalk.magentaBright('detect-voice-activity')} audioFile [output files...] [options...]`,
|
|
@@ -285,6 +289,16 @@ async function startWithArgs(operationData: CLIOperationData) {
|
|
|
285
289
|
break
|
|
286
290
|
}
|
|
287
291
|
|
|
292
|
+
case 'align-transcript-and-translation': {
|
|
293
|
+
await alignTranscriptAndTranslation(operationData)
|
|
294
|
+
break
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
case 'align-timeline-translation': {
|
|
298
|
+
await alignTimelineTranslation(operationData)
|
|
299
|
+
break
|
|
300
|
+
}
|
|
301
|
+
|
|
288
302
|
case 'detect-language': {
|
|
289
303
|
await detectLanguage(operationData, 'auto')
|
|
290
304
|
break
|
|
@@ -352,7 +366,7 @@ async function startWithArgs(operationData: CLIOperationData) {
|
|
|
352
366
|
}
|
|
353
367
|
}
|
|
354
368
|
|
|
355
|
-
async function speak(operationData: CLIOperationData) {
|
|
369
|
+
export async function speak(operationData: CLIOperationData) {
|
|
356
370
|
const logger = new Logger()
|
|
357
371
|
|
|
358
372
|
const { operationArgs, operation, operationOptionsLookup, cliOptions } = operationData
|
|
@@ -424,8 +438,8 @@ async function speak(operationData: CLIOperationData) {
|
|
|
424
438
|
textSegments = splitToParagraphs(textContent, 'single', 'preserve')
|
|
425
439
|
} else if (sourceFileExtension == 'srt' || sourceFileExtension == 'vtt') {
|
|
426
440
|
const fileContent = await readFile(sourceFile, { encoding: 'utf-8' })
|
|
427
|
-
textSegments = subtitlesToTimeline(fileContent).map(entry => entry.text)
|
|
428
|
-
|
|
441
|
+
//textSegments = subtitlesToTimeline(fileContent).map(entry => entry.text)
|
|
442
|
+
textSegments = [subtitlesToText(fileContent)]
|
|
429
443
|
} else if (sourceFileExtension == 'xml' || sourceFileExtension == 'ssml') {
|
|
430
444
|
options.ssml = true
|
|
431
445
|
textSegments = [fileContent]
|
|
@@ -510,7 +524,7 @@ async function speak(operationData: CLIOperationData) {
|
|
|
510
524
|
logger.end()
|
|
511
525
|
}
|
|
512
526
|
|
|
513
|
-
async function transcribe(operationData: CLIOperationData) {
|
|
527
|
+
export async function transcribe(operationData: CLIOperationData) {
|
|
514
528
|
const logger = new Logger()
|
|
515
529
|
|
|
516
530
|
const { operationArgs, operationOptionsLookup, cliOptions } = operationData
|
|
@@ -572,7 +586,7 @@ async function transcribe(operationData: CLIOperationData) {
|
|
|
572
586
|
}
|
|
573
587
|
}
|
|
574
588
|
|
|
575
|
-
async function align(operationData: CLIOperationData) {
|
|
589
|
+
export async function align(operationData: CLIOperationData) {
|
|
576
590
|
const logger = new Logger()
|
|
577
591
|
|
|
578
592
|
const { operationArgs, operationOptionsLookup, cliOptions } = operationData
|
|
@@ -668,7 +682,7 @@ async function align(operationData: CLIOperationData) {
|
|
|
668
682
|
}
|
|
669
683
|
}
|
|
670
684
|
|
|
671
|
-
async function alignTranslation(operationData: CLIOperationData) {
|
|
685
|
+
export async function alignTranslation(operationData: CLIOperationData) {
|
|
672
686
|
const logger = new Logger()
|
|
673
687
|
|
|
674
688
|
const { operationArgs, operationOptionsLookup, cliOptions } = operationData
|
|
@@ -677,7 +691,7 @@ async function alignTranslation(operationData: CLIOperationData) {
|
|
|
677
691
|
const outputFilenames = operationArgs.slice(2)
|
|
678
692
|
|
|
679
693
|
if (audioFilename == undefined) {
|
|
680
|
-
throw new Error(`align-translation requires
|
|
694
|
+
throw new Error(`align-translation requires a first argument containing the audio file path.`)
|
|
681
695
|
}
|
|
682
696
|
|
|
683
697
|
if (!existsSync(audioFilename)) {
|
|
@@ -706,7 +720,7 @@ async function alignTranslation(operationData: CLIOperationData) {
|
|
|
706
720
|
} else if (referenceFileExtension == 'srt' || referenceFileExtension == 'vtt') {
|
|
707
721
|
text = subtitlesToText(fileContent)
|
|
708
722
|
} else {
|
|
709
|
-
throw new Error(`align only supports reference files with extensions 'txt', 'html', 'htm', 'srt' or 'vtt'`)
|
|
723
|
+
throw new Error(`align-translation only supports reference files with extensions 'txt', 'html', 'htm', 'srt' or 'vtt'`)
|
|
710
724
|
}
|
|
711
725
|
|
|
712
726
|
if (cliOptions.play == null) {
|
|
@@ -721,8 +735,9 @@ async function alignTranslation(operationData: CLIOperationData) {
|
|
|
721
735
|
const {
|
|
722
736
|
timeline,
|
|
723
737
|
wordTimeline,
|
|
724
|
-
|
|
725
|
-
|
|
738
|
+
translatedTranscript,
|
|
739
|
+
sourceLanguage,
|
|
740
|
+
targetLanguage,
|
|
726
741
|
inputRawAudio,
|
|
727
742
|
isolatedRawAudio,
|
|
728
743
|
backgroundRawAudio } = await API.alignTranslation(audioFilename, text, options)
|
|
@@ -737,10 +752,139 @@ async function alignTranslation(operationData: CLIOperationData) {
|
|
|
737
752
|
const segmentAudio = sliceRawAudioByTime(inputRawAudio, segmentEntry.startTime, segmentEntry.endTime)
|
|
738
753
|
const sentenceTimeline = addTimeOffsetToTimeline(segmentEntry.timeline!, -segmentEntry.startTime)
|
|
739
754
|
|
|
740
|
-
await writeOutputFilesForSegment(outputFilenames, segmentIndex, timeline.length, segmentAudio, sentenceTimeline, segmentEntry.text,
|
|
755
|
+
await writeOutputFilesForSegment(outputFilenames, segmentIndex, timeline.length, segmentAudio, sentenceTimeline, segmentEntry.text, targetLanguage, allowOverwrite)
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
for (const outputFilename of outputFilenames) {
|
|
760
|
+
const partPatternMatch = outputFilename.match(filenamePlaceholderPattern)
|
|
761
|
+
|
|
762
|
+
if (partPatternMatch) {
|
|
763
|
+
continue
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
const fileSaver = getFileSaver(outputFilename, allowOverwrite)
|
|
767
|
+
|
|
768
|
+
await fileSaver(inputRawAudio, timeline, translatedTranscript, options.subtitles)
|
|
769
|
+
|
|
770
|
+
await writeSourceSeparationOutputIfNeeded(outputFilename, isolatedRawAudio, backgroundRawAudio, allowOverwrite, true)
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
logger.end()
|
|
774
|
+
|
|
775
|
+
if (cliOptions.play) {
|
|
776
|
+
let audioToPlay: RawAudio
|
|
777
|
+
|
|
778
|
+
if (isolatedRawAudio) {
|
|
779
|
+
audioToPlay = isolatedRawAudio
|
|
780
|
+
} else {
|
|
781
|
+
audioToPlay = inputRawAudio
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
const normalizedAudioToPlay = normalizeAudioLevel(audioToPlay)
|
|
785
|
+
|
|
786
|
+
await playAudioWithWordTimeline(normalizedAudioToPlay, wordTimeline, translatedTranscript)
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
export async function alignTranscriptAndTranslation(operationData: CLIOperationData) {
|
|
791
|
+
const logger = new Logger()
|
|
792
|
+
|
|
793
|
+
const { operationArgs, operationOptionsLookup, cliOptions } = operationData
|
|
794
|
+
|
|
795
|
+
const audioFilename = operationArgs[0]
|
|
796
|
+
const outputFilenames = operationArgs.slice(3)
|
|
797
|
+
|
|
798
|
+
if (audioFilename == undefined) {
|
|
799
|
+
throw new Error(`align-transcript-and-translation requires a first argument containing the audio file path.`)
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
if (!existsSync(audioFilename)) {
|
|
803
|
+
throw new Error(`The given source file '${audioFilename}' was not found.`)
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
const nativeTranscriptFilePath = operationArgs[1]
|
|
807
|
+
|
|
808
|
+
if (nativeTranscriptFilePath == undefined) {
|
|
809
|
+
throw new Error(`align-transcript-and-translation requires a second argument containing the native language transcript file path.`)
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
if (!existsSync(nativeTranscriptFilePath)) {
|
|
813
|
+
throw new Error(`The given transcript file '${nativeTranscriptFilePath}' was not found.`)
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
const translatedTranscriptFilePath = operationArgs[2]
|
|
817
|
+
|
|
818
|
+
if (translatedTranscriptFilePath == undefined) {
|
|
819
|
+
throw new Error(`align-transcript-and-translation requires a third argument containing the translated language transcript file path.`)
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
if (!existsSync(translatedTranscriptFilePath)) {
|
|
823
|
+
throw new Error(`The given translated transcript file '${nativeTranscriptFilePath}' was not found.`)
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
let transcript: string
|
|
827
|
+
|
|
828
|
+
{
|
|
829
|
+
const nativeTranscriptFileExtension = getLowercaseFileExtension(nativeTranscriptFilePath)
|
|
830
|
+
const fileContent = await readFile(nativeTranscriptFilePath, { encoding: 'utf-8' })
|
|
831
|
+
|
|
832
|
+
|
|
833
|
+
if (nativeTranscriptFileExtension == 'txt') {
|
|
834
|
+
transcript = fileContent
|
|
835
|
+
} else if (nativeTranscriptFileExtension == 'html' || nativeTranscriptFileExtension == 'htm') {
|
|
836
|
+
transcript = await convertHtmlToText(fileContent)
|
|
837
|
+
} else if (nativeTranscriptFileExtension == 'srt' || nativeTranscriptFileExtension == 'vtt') {
|
|
838
|
+
transcript = subtitlesToText(fileContent)
|
|
839
|
+
} else {
|
|
840
|
+
throw new Error(`align-transcript-and-translation only supports transcript files with extensions 'txt', 'html', 'htm', 'srt' or 'vtt'`)
|
|
741
841
|
}
|
|
742
842
|
}
|
|
743
843
|
|
|
844
|
+
let translatedTranscript: string
|
|
845
|
+
|
|
846
|
+
{
|
|
847
|
+
const translatedTranscriptFileExtension = getLowercaseFileExtension(translatedTranscriptFilePath)
|
|
848
|
+
const fileContent = await readFile(translatedTranscriptFilePath, { encoding: 'utf-8' })
|
|
849
|
+
|
|
850
|
+
if (translatedTranscriptFileExtension == 'txt') {
|
|
851
|
+
translatedTranscript = fileContent
|
|
852
|
+
} else if (translatedTranscriptFileExtension == 'html' || translatedTranscriptFileExtension == 'htm') {
|
|
853
|
+
translatedTranscript = await convertHtmlToText(fileContent)
|
|
854
|
+
} else if (translatedTranscriptFileExtension == 'srt' || translatedTranscriptFileExtension == 'vtt') {
|
|
855
|
+
translatedTranscript = subtitlesToText(fileContent)
|
|
856
|
+
} else {
|
|
857
|
+
throw new Error(`align-transcript-and-translation only supports transcript files with extensions 'txt', 'html', 'htm', 'srt' or 'vtt'`)
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
if (cliOptions.play == null) {
|
|
862
|
+
cliOptions.play = outputFilenames.length === 0
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
const options = await optionsLookupToTypedObject(operationOptionsLookup, 'TranscriptAndTranslationAlignmentOptions')
|
|
866
|
+
|
|
867
|
+
const allowOverwrite = getWithDefault(cliOptions.overwrite, overwriteByDefault)
|
|
868
|
+
const { includesPlaceholderPattern } = await checkOutputFilenames(outputFilenames, true, true, true)
|
|
869
|
+
|
|
870
|
+
const {
|
|
871
|
+
timeline,
|
|
872
|
+
wordTimeline,
|
|
873
|
+
|
|
874
|
+
translatedTimeline,
|
|
875
|
+
translatedWordTimeline,
|
|
876
|
+
|
|
877
|
+
sourceLanguage,
|
|
878
|
+
targetLanguage,
|
|
879
|
+
|
|
880
|
+
inputRawAudio,
|
|
881
|
+
isolatedRawAudio,
|
|
882
|
+
backgroundRawAudio } = await API.alignTranscriptAndTranslation(audioFilename, transcript, translatedTranscript, options)
|
|
883
|
+
|
|
884
|
+
if (outputFilenames.length > 0) {
|
|
885
|
+
logger.start('\nWrite output files')
|
|
886
|
+
}
|
|
887
|
+
|
|
744
888
|
for (const outputFilename of outputFilenames) {
|
|
745
889
|
const partPatternMatch = outputFilename.match(filenamePlaceholderPattern)
|
|
746
890
|
|
|
@@ -753,6 +897,16 @@ async function alignTranslation(operationData: CLIOperationData) {
|
|
|
753
897
|
await fileSaver(inputRawAudio, timeline, transcript, options.subtitles)
|
|
754
898
|
|
|
755
899
|
await writeSourceSeparationOutputIfNeeded(outputFilename, isolatedRawAudio, backgroundRawAudio, allowOverwrite, true)
|
|
900
|
+
|
|
901
|
+
const fileExtension = getLowercaseFileExtension(outputFilename)
|
|
902
|
+
|
|
903
|
+
if (['json', 'txt', 'srt', 'vtt'].includes(fileExtension)) {
|
|
904
|
+
const pathWithoutExtension = outputFilename.substring(0, outputFilename.lastIndexOf('.'))
|
|
905
|
+
const translatedOutputPath = `${pathWithoutExtension}.translated.${fileExtension}`
|
|
906
|
+
|
|
907
|
+
const translatedFileSaver = getFileSaver(translatedOutputPath, allowOverwrite)
|
|
908
|
+
await translatedFileSaver(inputRawAudio, translatedTimeline, translatedTranscript, options.subtitles)
|
|
909
|
+
}
|
|
756
910
|
}
|
|
757
911
|
|
|
758
912
|
logger.end()
|
|
@@ -768,11 +922,99 @@ async function alignTranslation(operationData: CLIOperationData) {
|
|
|
768
922
|
|
|
769
923
|
const normalizedAudioToPlay = normalizeAudioLevel(audioToPlay)
|
|
770
924
|
|
|
771
|
-
await playAudioWithWordTimeline(normalizedAudioToPlay,
|
|
925
|
+
await playAudioWithWordTimeline(normalizedAudioToPlay, translatedWordTimeline, translatedTranscript)
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
export async function alignTimelineTranslation(operationData: CLIOperationData) {
|
|
930
|
+
const logger = new Logger()
|
|
931
|
+
|
|
932
|
+
const { operationArgs, operationOptionsLookup, cliOptions } = operationData
|
|
933
|
+
|
|
934
|
+
const timelineFilename = operationArgs[0]
|
|
935
|
+
const outputFilenames = operationArgs.slice(2)
|
|
936
|
+
|
|
937
|
+
if (timelineFilename == undefined) {
|
|
938
|
+
throw new Error(`align-timeline-translation requires a first argument containing the timeline file path.`)
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
if (getLowercaseFileExtension(timelineFilename) != 'json') {
|
|
942
|
+
throw new Error(`align-timeline-translation only supports timeline files with extension 'json'`)
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
if (!existsSync(timelineFilename)) {
|
|
946
|
+
throw new Error(`The given timeline file '${timelineFilename}' was not found.`)
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
const timeline = await readAndParseJsonFile(timelineFilename) as Timeline
|
|
950
|
+
|
|
951
|
+
const translationFilePath = operationArgs[1]
|
|
952
|
+
|
|
953
|
+
if (translationFilePath == undefined) {
|
|
954
|
+
throw new Error(`align-timeline-translation requires a second argument containing the translated reference file path.`)
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
if (!existsSync(translationFilePath)) {
|
|
958
|
+
throw new Error(`The given reference file '${translationFilePath}' was not found.`)
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
const translationFileExtension = getLowercaseFileExtension(translationFilePath)
|
|
962
|
+
const translationFileContent = await readFile(translationFilePath, { encoding: 'utf-8' })
|
|
963
|
+
|
|
964
|
+
let translationText: string
|
|
965
|
+
|
|
966
|
+
if (translationFileExtension == 'txt') {
|
|
967
|
+
translationText = translationFileContent
|
|
968
|
+
} else if (translationFileExtension == 'html' || translationFileExtension == 'htm') {
|
|
969
|
+
translationText = await convertHtmlToText(translationFileContent)
|
|
970
|
+
} else if (translationFileExtension == 'srt' || translationFileExtension == 'vtt') {
|
|
971
|
+
translationText = subtitlesToText(translationFileContent)
|
|
972
|
+
} else {
|
|
973
|
+
throw new Error(`align only supports reference files with extensions 'txt', 'html', 'htm', 'srt' or 'vtt'`)
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
const options = await optionsLookupToTypedObject(operationOptionsLookup, 'TimelineTranslationAlignmentOptions')
|
|
977
|
+
|
|
978
|
+
const {
|
|
979
|
+
timeline: translationTimeline,
|
|
980
|
+
wordTimeline: translationWordTimeline,
|
|
981
|
+
rawAudio
|
|
982
|
+
} = await API.alignTimelineTranslation(timeline, translationText, options)
|
|
983
|
+
|
|
984
|
+
if (outputFilenames.length > 0) {
|
|
985
|
+
logger.start('\nWrite output files')
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
const allowOverwrite = getWithDefault(cliOptions.overwrite, overwriteByDefault)
|
|
989
|
+
|
|
990
|
+
for (const outputFilename of outputFilenames) {
|
|
991
|
+
const partPatternMatch = outputFilename.match(filenamePlaceholderPattern)
|
|
992
|
+
|
|
993
|
+
if (partPatternMatch) {
|
|
994
|
+
continue
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
const fileSaver = getFileSaver(outputFilename, allowOverwrite)
|
|
998
|
+
|
|
999
|
+
await fileSaver(getEmptyRawAudio(1, 16000), translationTimeline, translationText, options.subtitles)
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
logger.end()
|
|
1003
|
+
|
|
1004
|
+
if (cliOptions.play && rawAudio) {
|
|
1005
|
+
const normalizedAudioToPlay = normalizeAudioLevel(rawAudio)
|
|
1006
|
+
|
|
1007
|
+
let transcriptToPlay: string
|
|
1008
|
+
let timelineToPlay: Timeline
|
|
1009
|
+
|
|
1010
|
+
transcriptToPlay = translationText
|
|
1011
|
+
timelineToPlay = translationWordTimeline
|
|
1012
|
+
|
|
1013
|
+
await playAudioWithWordTimeline(normalizedAudioToPlay, timelineToPlay, transcriptToPlay)
|
|
772
1014
|
}
|
|
773
1015
|
}
|
|
774
1016
|
|
|
775
|
-
async function translateSpeech(operationData: CLIOperationData) {
|
|
1017
|
+
export async function translateSpeech(operationData: CLIOperationData) {
|
|
776
1018
|
const logger = new Logger()
|
|
777
1019
|
|
|
778
1020
|
const { operationArgs, operationOptionsLookup, cliOptions } = operationData
|
|
@@ -859,7 +1101,7 @@ async function translateSpeech(operationData: CLIOperationData) {
|
|
|
859
1101
|
}
|
|
860
1102
|
}
|
|
861
1103
|
|
|
862
|
-
async function detectLanguage(operationData: CLIOperationData, mode: 'speech' | 'text' | 'auto') {
|
|
1104
|
+
export async function detectLanguage(operationData: CLIOperationData, mode: 'speech' | 'text' | 'auto') {
|
|
863
1105
|
const logger = new Logger()
|
|
864
1106
|
|
|
865
1107
|
const { operationArgs, operationOptionsLookup, cliOptions } = operationData
|
|
@@ -936,7 +1178,7 @@ async function detectLanguage(operationData: CLIOperationData, mode: 'speech' |
|
|
|
936
1178
|
logger.end()
|
|
937
1179
|
}
|
|
938
1180
|
|
|
939
|
-
async function detectVoiceActivity(operationData: CLIOperationData) {
|
|
1181
|
+
export async function detectVoiceActivity(operationData: CLIOperationData) {
|
|
940
1182
|
const logger = new Logger()
|
|
941
1183
|
|
|
942
1184
|
const { operationArgs, operationOptionsLookup, cliOptions } = operationData
|
|
@@ -1005,7 +1247,7 @@ async function detectVoiceActivity(operationData: CLIOperationData) {
|
|
|
1005
1247
|
}
|
|
1006
1248
|
}
|
|
1007
1249
|
|
|
1008
|
-
async function denoise(operationData: CLIOperationData) {
|
|
1250
|
+
export async function denoise(operationData: CLIOperationData) {
|
|
1009
1251
|
const logger = new Logger()
|
|
1010
1252
|
|
|
1011
1253
|
const { operationArgs, operationOptionsLookup, cliOptions } = operationData
|
|
@@ -1050,7 +1292,7 @@ async function denoise(operationData: CLIOperationData) {
|
|
|
1050
1292
|
}
|
|
1051
1293
|
}
|
|
1052
1294
|
|
|
1053
|
-
async function isolate(operationData: CLIOperationData) {
|
|
1295
|
+
export async function isolate(operationData: CLIOperationData) {
|
|
1054
1296
|
const logger = new Logger()
|
|
1055
1297
|
|
|
1056
1298
|
const { operationArgs, operationOptionsLookup, cliOptions } = operationData
|
|
@@ -1093,7 +1335,7 @@ async function isolate(operationData: CLIOperationData) {
|
|
|
1093
1335
|
}
|
|
1094
1336
|
}
|
|
1095
1337
|
|
|
1096
|
-
async function listEngines(operationData: CLIOperationData) {
|
|
1338
|
+
export async function listEngines(operationData: CLIOperationData) {
|
|
1097
1339
|
const logger = new Logger()
|
|
1098
1340
|
|
|
1099
1341
|
const { operationArgs } = operationData
|
|
@@ -1206,7 +1448,7 @@ async function listEngines(operationData: CLIOperationData) {
|
|
|
1206
1448
|
}
|
|
1207
1449
|
}
|
|
1208
1450
|
|
|
1209
|
-
async function listTTSVoices(operationData: CLIOperationData) {
|
|
1451
|
+
export async function listTTSVoices(operationData: CLIOperationData) {
|
|
1210
1452
|
const logger = new Logger()
|
|
1211
1453
|
|
|
1212
1454
|
const { operationArgs, operationOptionsLookup, cliOptions } = operationData
|
|
@@ -1268,7 +1510,7 @@ async function listTTSVoices(operationData: CLIOperationData) {
|
|
|
1268
1510
|
logger.end()
|
|
1269
1511
|
}
|
|
1270
1512
|
|
|
1271
|
-
async function installPackages(operationData: CLIOperationData) {
|
|
1513
|
+
export async function installPackages(operationData: CLIOperationData) {
|
|
1272
1514
|
const logger = new Logger()
|
|
1273
1515
|
|
|
1274
1516
|
const { operationArgs } = operationData
|
|
@@ -1299,7 +1541,7 @@ async function installPackages(operationData: CLIOperationData) {
|
|
|
1299
1541
|
}
|
|
1300
1542
|
}
|
|
1301
1543
|
|
|
1302
|
-
async function uninstallPackages(operationData: CLIOperationData) {
|
|
1544
|
+
export async function uninstallPackages(operationData: CLIOperationData) {
|
|
1303
1545
|
const logger = new Logger()
|
|
1304
1546
|
|
|
1305
1547
|
const { operationArgs } = operationData
|
|
@@ -1326,7 +1568,7 @@ async function uninstallPackages(operationData: CLIOperationData) {
|
|
|
1326
1568
|
}
|
|
1327
1569
|
}
|
|
1328
1570
|
|
|
1329
|
-
async function listPackages(operationData: CLIOperationData) {
|
|
1571
|
+
export async function listPackages(operationData: CLIOperationData) {
|
|
1330
1572
|
const logger = new Logger()
|
|
1331
1573
|
|
|
1332
1574
|
const packagesDir = await ensureAndGetPackagesDir()
|
|
@@ -1358,7 +1600,7 @@ async function listPackages(operationData: CLIOperationData) {
|
|
|
1358
1600
|
logger.log(installedPackageNamesFormatted.join('\n'))
|
|
1359
1601
|
}
|
|
1360
1602
|
|
|
1361
|
-
async function serve(operationData: CLIOperationData) {
|
|
1603
|
+
export async function serve(operationData: CLIOperationData) {
|
|
1362
1604
|
const { operationOptionsLookup } = operationData
|
|
1363
1605
|
|
|
1364
1606
|
const options = await optionsLookupToTypedObject(operationOptionsLookup, 'ServerOptions')
|
|
@@ -1500,7 +1742,7 @@ async function optionsLookupToTypedObject<K extends keyof APIOptions>(cliOptions
|
|
|
1500
1742
|
}
|
|
1501
1743
|
|
|
1502
1744
|
let cachedOptionsSchema: any
|
|
1503
|
-
|
|
1745
|
+
async function getOptionsSchema() {
|
|
1504
1746
|
if (!cachedOptionsSchema) {
|
|
1505
1747
|
cachedOptionsSchema = await readAndParseJsonFile(resolveToModuleRootDir('data/schemas/options.json'))
|
|
1506
1748
|
}
|
|
@@ -1508,7 +1750,7 @@ export async function getOptionsSchema() {
|
|
|
1508
1750
|
return cachedOptionsSchema
|
|
1509
1751
|
}
|
|
1510
1752
|
|
|
1511
|
-
|
|
1753
|
+
async function checkOutputFilenames(outputFilenames: string[], acceptMediaOutputs: boolean, acceptMetadataOutputs: boolean, acceptSubtitleOutputs: boolean) {
|
|
1512
1754
|
const supportedFileExtensions: string[] = []
|
|
1513
1755
|
|
|
1514
1756
|
if (acceptMediaOutputs) {
|
|
@@ -1562,7 +1804,7 @@ export async function checkOutputFilenames(outputFilenames: string[], acceptMedi
|
|
|
1562
1804
|
return { includesPlaceholderPattern }
|
|
1563
1805
|
}
|
|
1564
1806
|
|
|
1565
|
-
|
|
1807
|
+
async function writeOutputFilesForSegment(outputFilenames: string[], index: number, total: number, audio: RawAudio, timeline: Timeline, text: string, language: string, allowOverwrite: boolean) {
|
|
1566
1808
|
const digitCount = Math.max((total + 1).toString().length, 2)
|
|
1567
1809
|
|
|
1568
1810
|
const segmentWords = (await splitToWords(text, language)).filter(text => wordCharacterPattern.test(text))
|
|
@@ -7,6 +7,7 @@ import { commandExists, logToStderr } from '../utilities/Utilities.js'
|
|
|
7
7
|
import path from 'node:path'
|
|
8
8
|
import { loadPackage } from '../utilities/PackageManager.js'
|
|
9
9
|
import { getGlobalOption } from '../api/GlobalOptions.js'
|
|
10
|
+
import { existsSync } from '../utilities/FileSystem.js'
|
|
10
11
|
|
|
11
12
|
const log = logToStderr
|
|
12
13
|
|
|
@@ -67,6 +68,11 @@ async function transcode_CLI(ffmpegCommand: string, input: string | Buffer, outp
|
|
|
67
68
|
|
|
68
69
|
if (Buffer.isBuffer(input)) {
|
|
69
70
|
process.stdin.end(input)
|
|
71
|
+
} else if (typeof input === 'string') {
|
|
72
|
+
if (!existsSync(input)) {
|
|
73
|
+
reject(`Audio file was not found: ${input}`)
|
|
74
|
+
return
|
|
75
|
+
}
|
|
70
76
|
}
|
|
71
77
|
|
|
72
78
|
const stdoutChunks: Buffer[] = []
|
package/src/dsp/FFT.ts
CHANGED
|
@@ -48,7 +48,7 @@ export async function stftr(samples: Float32Array, fftOrder: number, windowSize:
|
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
binsBufferRef.clear()
|
|
51
|
-
|
|
51
|
+
|
|
52
52
|
m._kiss_fftr(statePtr, frameBufferRef.address, binsBufferRef.address)
|
|
53
53
|
|
|
54
54
|
const bins = binsBufferRef.view.slice(0, fftOrder + 2)
|
|
@@ -229,6 +229,12 @@ export function getWindowWeights(windowType: WindowType, windowSize: number) {
|
|
|
229
229
|
//weights[i] = 0.54 - (0.46 * Math.cos(2 * Math.PI * (i / (windowSize - 1))))
|
|
230
230
|
weights[i] = 0.54 - (0.46 * Math.cos(innerFactor * i))
|
|
231
231
|
}
|
|
232
|
+
} else if (windowType == 'povey') {
|
|
233
|
+
const hannWeights = getWindowWeights('hann', windowSize)
|
|
234
|
+
|
|
235
|
+
for (let i = 0; i < windowSize; i++) {
|
|
236
|
+
weights[i] = hannWeights[i] ** 0.85
|
|
237
|
+
}
|
|
232
238
|
} else {
|
|
233
239
|
throw new Error(`Unsupported window function type: ${windowType}`)
|
|
234
240
|
}
|
|
@@ -255,4 +261,4 @@ export async function testFFT1(rawAudio: RawAudio) {
|
|
|
255
261
|
return recoveredRawAudio
|
|
256
262
|
}
|
|
257
263
|
|
|
258
|
-
export type WindowType = 'hann' | 'hamming'
|
|
264
|
+
export type WindowType = 'hann' | 'hamming' | 'povey'
|
|
@@ -10,7 +10,7 @@ export class KWeightingFilter {
|
|
|
10
10
|
|
|
11
11
|
if (useStandard44100Filters) {
|
|
12
12
|
// These parameter values are taken from ITU-R BS.1770-2
|
|
13
|
-
// and designed only for a 44100 Hz
|
|
13
|
+
// and designed only for a 44100 Hz sample rate:
|
|
14
14
|
this.highShelfFilter = new BiquadFilter({
|
|
15
15
|
b0: 1.53512485958697,
|
|
16
16
|
b1: -2.69169618940638,
|
package/src/dsp/MelSpectogram.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { RawAudio } from '../audio/AudioUtilities.js'
|
|
|
2
2
|
import { Logger } from '../utilities/Logger.js'
|
|
3
3
|
import * as FFT from './FFT.js'
|
|
4
4
|
|
|
5
|
-
export async function computeMelSpectogram(rawAudio: RawAudio, fftOrder: number, windowSize: number, hopLength: number, filterbankCount: number, lowerFrequencyHz: number, upperFrequencyHz: number) {
|
|
5
|
+
export async function computeMelSpectogram(rawAudio: RawAudio, fftOrder: number, windowSize: number, hopLength: number, filterbankCount: number, lowerFrequencyHz: number, upperFrequencyHz: number, windowType: FFT.WindowType = 'hann') {
|
|
6
6
|
const logger = new Logger()
|
|
7
7
|
|
|
8
8
|
logger.start('Compute mel filterbank')
|
|
@@ -18,15 +18,15 @@ export async function computeMelSpectogram(rawAudio: RawAudio, fftOrder: number,
|
|
|
18
18
|
|
|
19
19
|
logger.end()
|
|
20
20
|
|
|
21
|
-
return computeMelSpectogramUsingFilterbanks(rawAudio, fftOrder, windowSize, hopLength, melFilterbanks)
|
|
21
|
+
return computeMelSpectogramUsingFilterbanks(rawAudio, fftOrder, windowSize, hopLength, melFilterbanks, windowType)
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
export async function computeMelSpectogramUsingFilterbanks(rawAudio: RawAudio, fftOrder: number, windowSize: number, hopLength: number, filterbanks: Filterbank[]) {
|
|
24
|
+
export async function computeMelSpectogramUsingFilterbanks(rawAudio: RawAudio, fftOrder: number, windowSize: number, hopLength: number, filterbanks: Filterbank[], windowType: FFT.WindowType = 'hann') {
|
|
25
25
|
const logger = new Logger()
|
|
26
26
|
|
|
27
27
|
logger.start('Compute short-time FFTs')
|
|
28
28
|
const audioSamples = rawAudio.audioChannels[0]
|
|
29
|
-
const fftFrames = await FFT.stftr(audioSamples, fftOrder, windowSize, hopLength,
|
|
29
|
+
const fftFrames = await FFT.stftr(audioSamples, fftOrder, windowSize, hopLength, windowType)
|
|
30
30
|
|
|
31
31
|
logger.start('Convert FFT frames to a mel spectogram')
|
|
32
32
|
const melSpectogram = fftFramesToMelSpectogram(fftFrames, filterbanks)
|
|
@@ -52,17 +52,26 @@ export function powerSpectrumToMelSpectrum(powerSpectrum: Float32Array, filterba
|
|
|
52
52
|
const filterbankStartIndex = filterbank.startIndex
|
|
53
53
|
const filterbankWeights = filterbank.weights
|
|
54
54
|
|
|
55
|
-
if (filterbankStartIndex
|
|
55
|
+
if (filterbankStartIndex === -1) {
|
|
56
56
|
continue
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
let
|
|
59
|
+
let melBandValue = 0
|
|
60
60
|
|
|
61
61
|
for (let i = 0; i < filterbankWeights.length; i++) {
|
|
62
|
-
|
|
62
|
+
const powerSpectrumIndex = filterbankStartIndex + i
|
|
63
|
+
|
|
64
|
+
if (powerSpectrumIndex >= powerSpectrum.length) {
|
|
65
|
+
break
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const weight = filterbankWeights[i]
|
|
69
|
+
const powerSpectrumValue = powerSpectrum[powerSpectrumIndex]
|
|
70
|
+
|
|
71
|
+
melBandValue += weight * powerSpectrumValue
|
|
63
72
|
}
|
|
64
73
|
|
|
65
|
-
melSpectrum[melBandIndex] =
|
|
74
|
+
melSpectrum[melBandIndex] = melBandValue
|
|
66
75
|
}
|
|
67
76
|
|
|
68
77
|
return melSpectrum
|
package/src/math/VectorMath.ts
CHANGED
|
@@ -177,7 +177,7 @@ export function scaleToSumTo1(vector: number[]) {
|
|
|
177
177
|
return scaledVector
|
|
178
178
|
}
|
|
179
179
|
|
|
180
|
-
export function normalizeVector(vector: number
|
|
180
|
+
export function normalizeVector(vector: ArrayLike<number>, kind: 'population' | 'sample' = 'population') {
|
|
181
181
|
if (vector.length == 0) {
|
|
182
182
|
throw new Error('Vector is empty')
|
|
183
183
|
}
|
|
@@ -329,15 +329,15 @@ export function varianceOfVectors(vectors: number[][], kind: 'population' | 'sam
|
|
|
329
329
|
return result
|
|
330
330
|
}
|
|
331
331
|
|
|
332
|
-
export function meanOfVector(vector: number
|
|
332
|
+
export function meanOfVector(vector: ArrayLike<number>) {
|
|
333
333
|
if (vector.length == 0) {
|
|
334
|
-
|
|
334
|
+
return 0
|
|
335
335
|
}
|
|
336
336
|
|
|
337
337
|
return sumVector(vector) / vector.length
|
|
338
338
|
}
|
|
339
339
|
|
|
340
|
-
export function medianOfVector(vector: number
|
|
340
|
+
export function medianOfVector(vector: ArrayLike<number>) {
|
|
341
341
|
if (vector.length == 0) {
|
|
342
342
|
throw new Error('Vector is empty')
|
|
343
343
|
}
|
|
@@ -345,13 +345,13 @@ export function medianOfVector(vector: number[]) {
|
|
|
345
345
|
return vector[Math.floor(vector.length / 2)]
|
|
346
346
|
}
|
|
347
347
|
|
|
348
|
-
export function stdDeviationOfVector(vector: number
|
|
348
|
+
export function stdDeviationOfVector(vector: ArrayLike<number>, kind: 'population' | 'sample' = 'population', mean?: number) {
|
|
349
349
|
return Math.sqrt(varianceOfVector(vector, kind, mean))
|
|
350
350
|
}
|
|
351
351
|
|
|
352
|
-
export function varianceOfVector(vector: number
|
|
352
|
+
export function varianceOfVector(vector: ArrayLike<number>, kind: 'population' | 'sample' = 'population', mean?: number) {
|
|
353
353
|
if (vector.length == 0) {
|
|
354
|
-
|
|
354
|
+
return 0
|
|
355
355
|
}
|
|
356
356
|
|
|
357
357
|
const sampleSizeMetric = kind == 'population' || vector.length == 1 ? vector.length : vector.length - 1
|
|
@@ -362,8 +362,8 @@ export function varianceOfVector(vector: number[], kind: 'population' | 'sample'
|
|
|
362
362
|
|
|
363
363
|
let result = 0.0
|
|
364
364
|
|
|
365
|
-
for (
|
|
366
|
-
result += (
|
|
365
|
+
for (let i = 0; i < vector.length; i++) {
|
|
366
|
+
result += (vector[i] - mean) ** 2
|
|
367
367
|
}
|
|
368
368
|
|
|
369
369
|
return result / sampleSizeMetric
|