echogarden 1.4.3 → 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.
Files changed (152) hide show
  1. package/README.md +1 -1
  2. package/data/schemas/options.json +267 -19
  3. package/data/tables/lcid-table.json +9 -0
  4. package/dist/alignment/DTWMfccSequenceAlignment.d.ts +1 -1
  5. package/dist/alignment/DTWMfccSequenceAlignment.js +9 -5
  6. package/dist/alignment/DTWMfccSequenceAlignment.js.map +1 -1
  7. package/dist/alignment/DTWSequenceAlignmentWindowed.js +4 -4
  8. package/dist/alignment/DTWSequenceAlignmentWindowed.js.map +1 -1
  9. package/dist/alignment/{TextAlignment.d.ts → SemanticTextAlignment.d.ts} +10 -1
  10. package/dist/alignment/SemanticTextAlignment.js +336 -0
  11. package/dist/alignment/SemanticTextAlignment.js.map +1 -0
  12. package/dist/alignment/SpeechAlignment.d.ts +2 -1
  13. package/dist/alignment/SpeechAlignment.js +106 -38
  14. package/dist/alignment/SpeechAlignment.js.map +1 -1
  15. package/dist/api/API.d.ts +7 -2
  16. package/dist/api/API.js +7 -2
  17. package/dist/api/API.js.map +1 -1
  18. package/dist/api/APIOptions.d.ts +4 -1
  19. package/dist/api/Alignment.d.ts +1 -1
  20. package/dist/api/Alignment.js +14 -6
  21. package/dist/api/Alignment.js.map +1 -1
  22. package/dist/api/LanguageDetectionCommon.d.ts +6 -0
  23. package/dist/api/LanguageDetectionCommon.js +2 -0
  24. package/dist/api/LanguageDetectionCommon.js.map +1 -0
  25. package/dist/api/Recognition.js.map +1 -1
  26. package/dist/api/{LanguageDetection.d.ts → SpeechLanguageDetection.d.ts} +1 -25
  27. package/dist/api/{LanguageDetection.js → SpeechLanguageDetection.js} +1 -68
  28. package/dist/api/SpeechLanguageDetection.js.map +1 -0
  29. package/dist/api/{Translation.js → SpeechTranslation.js} +3 -3
  30. package/dist/api/SpeechTranslation.js.map +1 -0
  31. package/dist/api/Synthesis.js +4 -4
  32. package/dist/api/TextLanguageDetection.d.ts +21 -0
  33. package/dist/api/TextLanguageDetection.js +67 -0
  34. package/dist/api/TextLanguageDetection.js.map +1 -0
  35. package/dist/api/TextTranslation.d.ts +5 -0
  36. package/dist/api/TextTranslation.js +4 -0
  37. package/dist/api/TextTranslation.js.map +1 -0
  38. package/dist/api/TimelineTranslationAlignment.d.ts +23 -0
  39. package/dist/api/TimelineTranslationAlignment.js +92 -0
  40. package/dist/api/TimelineTranslationAlignment.js.map +1 -0
  41. package/dist/api/TranscriptAndTranslationAlignment.d.ts +35 -0
  42. package/dist/api/TranscriptAndTranslationAlignment.js +78 -0
  43. package/dist/api/TranscriptAndTranslationAlignment.js.map +1 -0
  44. package/dist/api/TranslationAlignment.d.ts +4 -3
  45. package/dist/api/TranslationAlignment.js +9 -8
  46. package/dist/api/TranslationAlignment.js.map +1 -1
  47. package/dist/api/VoiceActivityDetection.js +16 -1
  48. package/dist/api/VoiceActivityDetection.js.map +1 -1
  49. package/dist/cli/CLI.d.ts +27 -7
  50. package/dist/cli/CLI.js +205 -34
  51. package/dist/cli/CLI.js.map +1 -1
  52. package/dist/codecs/FFMpegTranscoder.js +7 -0
  53. package/dist/codecs/FFMpegTranscoder.js.map +1 -1
  54. package/dist/dsp/FFT.d.ts +1 -1
  55. package/dist/dsp/FFT.js +6 -0
  56. package/dist/dsp/FFT.js.map +1 -1
  57. package/dist/dsp/KWeightingFilter.js +1 -1
  58. package/dist/dsp/KWeightingFilter.js.map +1 -1
  59. package/dist/dsp/MelSpectogram.d.ts +3 -2
  60. package/dist/dsp/MelSpectogram.js +14 -8
  61. package/dist/dsp/MelSpectogram.js.map +1 -1
  62. package/dist/math/VectorMath.d.ts +22 -20
  63. package/dist/math/VectorMath.js +57 -30
  64. package/dist/math/VectorMath.js.map +1 -1
  65. package/dist/recognition/WhisperCppSTT.d.ts +1 -1
  66. package/dist/recognition/WhisperCppSTT.js +2 -2
  67. package/dist/recognition/WhisperCppSTT.js.map +1 -1
  68. package/dist/recognition/WhisperSTT.js +9 -6
  69. package/dist/recognition/WhisperSTT.js.map +1 -1
  70. package/dist/server/Client.d.ts +3 -2
  71. package/dist/server/Client.js.map +1 -1
  72. package/dist/server/Worker.d.ts +3 -2
  73. package/dist/server/Worker.js +3 -2
  74. package/dist/server/Worker.js.map +1 -1
  75. package/dist/source-separation/MDXNetSourceSeparation.js +1 -1
  76. package/dist/source-separation/MDXNetSourceSeparation.js.map +1 -1
  77. package/dist/speech-embeddings/WavToVec2BertFeatureEmbeddings.d.ts +13 -0
  78. package/dist/speech-embeddings/WavToVec2BertFeatureEmbeddings.js +68 -0
  79. package/dist/speech-embeddings/WavToVec2BertFeatureEmbeddings.js.map +1 -0
  80. package/dist/speech-language-detection/SileroLanguageDetection.d.ts +1 -1
  81. package/dist/speech-language-detection/SileroLanguageDetection.js +1 -1
  82. package/dist/speech-language-detection/SileroLanguageDetection.js.map +1 -1
  83. package/dist/synthesis/EspeakTTS.js +4 -5
  84. package/dist/synthesis/EspeakTTS.js.map +1 -1
  85. package/dist/tests/Test.js +0 -8
  86. package/dist/tests/Test.js.map +1 -1
  87. package/dist/text-language-detection/FastTextLanguageDetection.d.ts +1 -1
  88. package/dist/text-language-detection/FastTextLanguageDetection.js.map +1 -1
  89. package/dist/text-language-detection/TinyLDLanguageDetection.d.ts +1 -1
  90. package/dist/text-language-detection/TinyLDLanguageDetection.js.map +1 -1
  91. package/dist/text-translation/NLLBTextTranslation.js +1 -1
  92. package/dist/text-translation/NLLBTextTranslation.js.map +1 -1
  93. package/dist/utilities/Locale.d.ts +1 -1
  94. package/dist/utilities/Locale.js +1 -1
  95. package/dist/utilities/OnnxUtilities.d.ts +1 -1
  96. package/dist/utilities/PackageManager.js +8 -2
  97. package/dist/utilities/PackageManager.js.map +1 -1
  98. package/dist/utilities/Timeline.d.ts +1 -0
  99. package/dist/utilities/Timeline.js +12 -0
  100. package/dist/utilities/Timeline.js.map +1 -1
  101. package/docs/API.md +81 -3
  102. package/docs/CLI.md +51 -1
  103. package/docs/Engines.md +24 -0
  104. package/docs/Options.md +33 -1
  105. package/docs/Tasklist.md +4 -1
  106. package/package.json +11 -11
  107. package/src/alignment/DTWMfccSequenceAlignment.ts +11 -5
  108. package/src/alignment/DTWSequenceAlignmentWindowed.ts +4 -4
  109. package/src/alignment/SemanticTextAlignment.ts +467 -0
  110. package/src/alignment/SpeechAlignment.ts +180 -53
  111. package/src/api/API.ts +18 -2
  112. package/src/api/APIOptions.ts +14 -1
  113. package/src/api/Alignment.ts +32 -10
  114. package/src/api/LanguageDetectionCommon.ts +7 -0
  115. package/src/api/Recognition.ts +2 -0
  116. package/src/api/{LanguageDetection.ts → SpeechLanguageDetection.ts} +1 -119
  117. package/src/api/{Translation.ts → SpeechTranslation.ts} +2 -2
  118. package/src/api/Synthesis.ts +4 -4
  119. package/src/api/TextLanguageDetection.ts +116 -0
  120. package/src/api/TextTranslation.ts +9 -0
  121. package/src/api/TimelineTranslationAlignment.ts +162 -0
  122. package/src/api/TranscriptAndTranslationAlignment.ts +164 -0
  123. package/src/api/TranslationAlignment.ts +12 -10
  124. package/src/api/VoiceActivityDetection.ts +24 -3
  125. package/src/cli/CLI.ts +276 -34
  126. package/src/codecs/FFMpegTranscoder.ts +6 -0
  127. package/src/dsp/FFT.ts +8 -2
  128. package/src/dsp/KWeightingFilter.ts +1 -1
  129. package/src/dsp/MelSpectogram.ts +17 -8
  130. package/src/math/VectorMath.ts +87 -52
  131. package/src/recognition/WhisperCppSTT.ts +2 -2
  132. package/src/recognition/WhisperSTT.ts +10 -6
  133. package/src/server/Client.ts +3 -2
  134. package/src/server/Worker.ts +3 -2
  135. package/src/source-separation/MDXNetSourceSeparation.ts +1 -1
  136. package/src/speech-embeddings/WavToVec2BertFeatureEmbeddings.ts +107 -0
  137. package/src/speech-language-detection/SileroLanguageDetection.ts +2 -1
  138. package/src/synthesis/EspeakTTS.ts +6 -8
  139. package/src/tests/Test.ts +1 -12
  140. package/src/text-language-detection/FastTextLanguageDetection.ts +1 -1
  141. package/src/text-language-detection/TinyLDLanguageDetection.ts +1 -1
  142. package/src/text-translation/NLLBTextTranslation.ts +1 -1
  143. package/src/utilities/Locale.ts +1 -1
  144. package/src/utilities/OnnxUtilities.ts +1 -1
  145. package/src/utilities/PackageManager.ts +9 -2
  146. package/src/utilities/Timeline.ts +14 -0
  147. package/dist/alignment/TextAlignment.js +0 -63
  148. package/dist/alignment/TextAlignment.js.map +0 -1
  149. package/dist/api/LanguageDetection.js.map +0 -1
  150. package/dist/api/Translation.js.map +0 -1
  151. package/src/alignment/TextAlignment.ts +0 -96
  152. /package/dist/api/{Translation.d.ts → SpeechTranslation.d.ts} +0 -0
package/dist/cli/CLI.js CHANGED
@@ -5,7 +5,7 @@ import { getOptionTypeFromSchema } from './CLIOptionsSchema.js';
5
5
  import { parseConfigFile, parseJSONConfigFile } from './CLIConfigFile.js';
6
6
  import chalk from 'chalk';
7
7
  import { applyGainDecibels, encodeRawAudioToWave, getEmptyRawAudio, getRawAudioDuration, normalizeAudioLevel, sliceRawAudioByTime } from '../audio/AudioUtilities.js';
8
- import { subtitlesToText, subtitlesToTimeline, timelineToSubtitles } from '../subtitles/Subtitles.js';
8
+ import { subtitlesToText, timelineToSubtitles } from '../subtitles/Subtitles.js';
9
9
  import { Logger, resetActiveLogger } from '../utilities/Logger.js';
10
10
  import { isMainThread, parentPort } from 'node:worker_threads';
11
11
  import { encodeFromChannels, getDefaultFFMpegOptionsForSpeech } from '../codecs/FFMpegTranscoder.js';
@@ -170,17 +170,21 @@ const help = [
170
170
  `${executableName} ${chalk.magentaBright('speak-url')} url [output files...] [options...]`,
171
171
  ` Speak the HTML document on the given URL\n`,
172
172
  `${executableName} ${chalk.magentaBright('speak-wikipedia')} articleName [output files...] [options...]`,
173
- ` Speak the given wikipedia article, language edition can be specified by --language=<langCode>\n`,
173
+ ` Speak the given Wikipedia article. Language edition can be specified by --language=<langCode>\n`,
174
174
  `${executableName} ${chalk.magentaBright('transcribe')} audioFile [output files...] [options...]`,
175
- ` Transcribe audio file\n`,
176
- `${executableName} ${chalk.magentaBright('align')} audioFile referenceFile [output files...] [options...]`,
177
- ` Align audio file to the reference transcript file\n`,
178
- `${executableName} ${chalk.magentaBright('translate-speech')} inputFile [output files...] [options...]`,
175
+ ` Transcribe a spoken audio file\n`,
176
+ `${executableName} ${chalk.magentaBright('align')} audioFile transcriptFile [output files...] [options...]`,
177
+ ` Align spoken audio file to its transcript\n`,
178
+ `${executableName} ${chalk.magentaBright('translate-speech')} audioFile [output files...] [options...]`,
179
179
  ` Transcribe audio file directly to a different language\n`,
180
- `${executableName} ${chalk.magentaBright('align-translation')} audioFile referenceFile [output files...] [options...]`,
181
- ` Align audio file to the reference translated transcript file\n`,
180
+ `${executableName} ${chalk.magentaBright('align-translation')} audioFile translatedTranscriptFile [output files...] [options...]`,
181
+ ` Align spoken audio file to its translated transcript\n`,
182
+ `${executableName} ${chalk.magentaBright('align-transcript-and-translation')} audioFile transcriptFile translatedTranscriptFile [output files...] [options...]`,
183
+ ` Align spoken audio file to both its transcript and its translated transcript using a two-stage approach.\n`,
184
+ `${executableName} ${chalk.magentaBright('align-timeline-translation')} timelineFile translatedFile [output files...] [options...]`,
185
+ ` Align a given timeline file to its translated text\n`,
182
186
  `${executableName} ${chalk.magentaBright('detect-speech-language')} audioFile [output files...] [options...]`,
183
- ` Detect language of audio file\n`,
187
+ ` Detect language of spoken audio file\n`,
184
188
  `${executableName} ${chalk.magentaBright('detect-text-language')} inputFile [output files...] [options...]`,
185
189
  ` Detect language of textual file\n`,
186
190
  `${executableName} ${chalk.magentaBright('detect-voice-activity')} audioFile [output files...] [options...]`,
@@ -229,6 +233,14 @@ async function startWithArgs(operationData) {
229
233
  await alignTranslation(operationData);
230
234
  break;
231
235
  }
236
+ case 'align-transcript-and-translation': {
237
+ await alignTranscriptAndTranslation(operationData);
238
+ break;
239
+ }
240
+ case 'align-timeline-translation': {
241
+ await alignTimelineTranslation(operationData);
242
+ break;
243
+ }
232
244
  case 'detect-language': {
233
245
  await detectLanguage(operationData, 'auto');
234
246
  break;
@@ -283,7 +295,7 @@ async function startWithArgs(operationData) {
283
295
  }
284
296
  }
285
297
  }
286
- async function speak(operationData) {
298
+ export async function speak(operationData) {
287
299
  const logger = new Logger();
288
300
  const { operationArgs, operation, operationOptionsLookup, cliOptions } = operationData;
289
301
  const mainArg = operationArgs[0];
@@ -345,8 +357,8 @@ async function speak(operationData) {
345
357
  }
346
358
  else if (sourceFileExtension == 'srt' || sourceFileExtension == 'vtt') {
347
359
  const fileContent = await readFile(sourceFile, { encoding: 'utf-8' });
348
- textSegments = subtitlesToTimeline(fileContent).map(entry => entry.text);
349
- //textSegments = [subtitlesToText(fileContent)]
360
+ //textSegments = subtitlesToTimeline(fileContent).map(entry => entry.text)
361
+ textSegments = [subtitlesToText(fileContent)];
350
362
  }
351
363
  else if (sourceFileExtension == 'xml' || sourceFileExtension == 'ssml') {
352
364
  options.ssml = true;
@@ -415,7 +427,7 @@ async function speak(operationData) {
415
427
  }
416
428
  logger.end();
417
429
  }
418
- async function transcribe(operationData) {
430
+ export async function transcribe(operationData) {
419
431
  const logger = new Logger();
420
432
  const { operationArgs, operationOptionsLookup, cliOptions } = operationData;
421
433
  const sourceFilename = operationArgs[0];
@@ -458,7 +470,7 @@ async function transcribe(operationData) {
458
470
  await playAudioWithWordTimeline(normalizedAudioToPlay, wordTimeline, transcript);
459
471
  }
460
472
  }
461
- async function align(operationData) {
473
+ export async function align(operationData) {
462
474
  const logger = new Logger();
463
475
  const { operationArgs, operationOptionsLookup, cliOptions } = operationData;
464
476
  const audioFilename = operationArgs[0];
@@ -531,13 +543,13 @@ async function align(operationData) {
531
543
  await playAudioWithWordTimeline(normalizedAudioToPlay, wordTimeline, transcript);
532
544
  }
533
545
  }
534
- async function alignTranslation(operationData) {
546
+ export async function alignTranslation(operationData) {
535
547
  const logger = new Logger();
536
548
  const { operationArgs, operationOptionsLookup, cliOptions } = operationData;
537
549
  const audioFilename = operationArgs[0];
538
550
  const outputFilenames = operationArgs.slice(2);
539
551
  if (audioFilename == undefined) {
540
- throw new Error(`align-translation requires an argument containing the audio file path.`);
552
+ throw new Error(`align-translation requires a first argument containing the audio file path.`);
541
553
  }
542
554
  if (!existsSync(audioFilename)) {
543
555
  throw new Error(`The given source file '${audioFilename}' was not found.`);
@@ -562,7 +574,7 @@ async function alignTranslation(operationData) {
562
574
  text = subtitlesToText(fileContent);
563
575
  }
564
576
  else {
565
- throw new Error(`align only supports reference files with extensions 'txt', 'html', 'htm', 'srt' or 'vtt'`);
577
+ throw new Error(`align-translation only supports reference files with extensions 'txt', 'html', 'htm', 'srt' or 'vtt'`);
566
578
  }
567
579
  if (cliOptions.play == null) {
568
580
  cliOptions.play = outputFilenames.length === 0;
@@ -570,7 +582,7 @@ async function alignTranslation(operationData) {
570
582
  const options = await optionsLookupToTypedObject(operationOptionsLookup, 'TranslationAlignmentOptions');
571
583
  const allowOverwrite = getWithDefault(cliOptions.overwrite, overwriteByDefault);
572
584
  const { includesPlaceholderPattern } = await checkOutputFilenames(outputFilenames, true, true, true);
573
- const { timeline, wordTimeline, transcript, language, inputRawAudio, isolatedRawAudio, backgroundRawAudio } = await API.alignTranslation(audioFilename, text, options);
585
+ const { timeline, wordTimeline, translatedTranscript, sourceLanguage, targetLanguage, inputRawAudio, isolatedRawAudio, backgroundRawAudio } = await API.alignTranslation(audioFilename, text, options);
574
586
  if (outputFilenames.length > 0) {
575
587
  logger.start('\nWrite output files');
576
588
  }
@@ -579,9 +591,100 @@ async function alignTranslation(operationData) {
579
591
  const segmentEntry = timeline[segmentIndex];
580
592
  const segmentAudio = sliceRawAudioByTime(inputRawAudio, segmentEntry.startTime, segmentEntry.endTime);
581
593
  const sentenceTimeline = addTimeOffsetToTimeline(segmentEntry.timeline, -segmentEntry.startTime);
582
- await writeOutputFilesForSegment(outputFilenames, segmentIndex, timeline.length, segmentAudio, sentenceTimeline, segmentEntry.text, language, allowOverwrite);
594
+ await writeOutputFilesForSegment(outputFilenames, segmentIndex, timeline.length, segmentAudio, sentenceTimeline, segmentEntry.text, targetLanguage, allowOverwrite);
583
595
  }
584
596
  }
597
+ for (const outputFilename of outputFilenames) {
598
+ const partPatternMatch = outputFilename.match(filenamePlaceholderPattern);
599
+ if (partPatternMatch) {
600
+ continue;
601
+ }
602
+ const fileSaver = getFileSaver(outputFilename, allowOverwrite);
603
+ await fileSaver(inputRawAudio, timeline, translatedTranscript, options.subtitles);
604
+ await writeSourceSeparationOutputIfNeeded(outputFilename, isolatedRawAudio, backgroundRawAudio, allowOverwrite, true);
605
+ }
606
+ logger.end();
607
+ if (cliOptions.play) {
608
+ let audioToPlay;
609
+ if (isolatedRawAudio) {
610
+ audioToPlay = isolatedRawAudio;
611
+ }
612
+ else {
613
+ audioToPlay = inputRawAudio;
614
+ }
615
+ const normalizedAudioToPlay = normalizeAudioLevel(audioToPlay);
616
+ await playAudioWithWordTimeline(normalizedAudioToPlay, wordTimeline, translatedTranscript);
617
+ }
618
+ }
619
+ export async function alignTranscriptAndTranslation(operationData) {
620
+ const logger = new Logger();
621
+ const { operationArgs, operationOptionsLookup, cliOptions } = operationData;
622
+ const audioFilename = operationArgs[0];
623
+ const outputFilenames = operationArgs.slice(3);
624
+ if (audioFilename == undefined) {
625
+ throw new Error(`align-transcript-and-translation requires a first argument containing the audio file path.`);
626
+ }
627
+ if (!existsSync(audioFilename)) {
628
+ throw new Error(`The given source file '${audioFilename}' was not found.`);
629
+ }
630
+ const nativeTranscriptFilePath = operationArgs[1];
631
+ if (nativeTranscriptFilePath == undefined) {
632
+ throw new Error(`align-transcript-and-translation requires a second argument containing the native language transcript file path.`);
633
+ }
634
+ if (!existsSync(nativeTranscriptFilePath)) {
635
+ throw new Error(`The given transcript file '${nativeTranscriptFilePath}' was not found.`);
636
+ }
637
+ const translatedTranscriptFilePath = operationArgs[2];
638
+ if (translatedTranscriptFilePath == undefined) {
639
+ throw new Error(`align-transcript-and-translation requires a third argument containing the translated language transcript file path.`);
640
+ }
641
+ if (!existsSync(translatedTranscriptFilePath)) {
642
+ throw new Error(`The given translated transcript file '${nativeTranscriptFilePath}' was not found.`);
643
+ }
644
+ let transcript;
645
+ {
646
+ const nativeTranscriptFileExtension = getLowercaseFileExtension(nativeTranscriptFilePath);
647
+ const fileContent = await readFile(nativeTranscriptFilePath, { encoding: 'utf-8' });
648
+ if (nativeTranscriptFileExtension == 'txt') {
649
+ transcript = fileContent;
650
+ }
651
+ else if (nativeTranscriptFileExtension == 'html' || nativeTranscriptFileExtension == 'htm') {
652
+ transcript = await convertHtmlToText(fileContent);
653
+ }
654
+ else if (nativeTranscriptFileExtension == 'srt' || nativeTranscriptFileExtension == 'vtt') {
655
+ transcript = subtitlesToText(fileContent);
656
+ }
657
+ else {
658
+ throw new Error(`align-transcript-and-translation only supports transcript files with extensions 'txt', 'html', 'htm', 'srt' or 'vtt'`);
659
+ }
660
+ }
661
+ let translatedTranscript;
662
+ {
663
+ const translatedTranscriptFileExtension = getLowercaseFileExtension(translatedTranscriptFilePath);
664
+ const fileContent = await readFile(translatedTranscriptFilePath, { encoding: 'utf-8' });
665
+ if (translatedTranscriptFileExtension == 'txt') {
666
+ translatedTranscript = fileContent;
667
+ }
668
+ else if (translatedTranscriptFileExtension == 'html' || translatedTranscriptFileExtension == 'htm') {
669
+ translatedTranscript = await convertHtmlToText(fileContent);
670
+ }
671
+ else if (translatedTranscriptFileExtension == 'srt' || translatedTranscriptFileExtension == 'vtt') {
672
+ translatedTranscript = subtitlesToText(fileContent);
673
+ }
674
+ else {
675
+ throw new Error(`align-transcript-and-translation only supports transcript files with extensions 'txt', 'html', 'htm', 'srt' or 'vtt'`);
676
+ }
677
+ }
678
+ if (cliOptions.play == null) {
679
+ cliOptions.play = outputFilenames.length === 0;
680
+ }
681
+ const options = await optionsLookupToTypedObject(operationOptionsLookup, 'TranscriptAndTranslationAlignmentOptions');
682
+ const allowOverwrite = getWithDefault(cliOptions.overwrite, overwriteByDefault);
683
+ const { includesPlaceholderPattern } = await checkOutputFilenames(outputFilenames, true, true, true);
684
+ const { timeline, wordTimeline, translatedTimeline, translatedWordTimeline, sourceLanguage, targetLanguage, inputRawAudio, isolatedRawAudio, backgroundRawAudio } = await API.alignTranscriptAndTranslation(audioFilename, transcript, translatedTranscript, options);
685
+ if (outputFilenames.length > 0) {
686
+ logger.start('\nWrite output files');
687
+ }
585
688
  for (const outputFilename of outputFilenames) {
586
689
  const partPatternMatch = outputFilename.match(filenamePlaceholderPattern);
587
690
  if (partPatternMatch) {
@@ -590,6 +693,13 @@ async function alignTranslation(operationData) {
590
693
  const fileSaver = getFileSaver(outputFilename, allowOverwrite);
591
694
  await fileSaver(inputRawAudio, timeline, transcript, options.subtitles);
592
695
  await writeSourceSeparationOutputIfNeeded(outputFilename, isolatedRawAudio, backgroundRawAudio, allowOverwrite, true);
696
+ const fileExtension = getLowercaseFileExtension(outputFilename);
697
+ if (['json', 'txt', 'srt', 'vtt'].includes(fileExtension)) {
698
+ const pathWithoutExtension = outputFilename.substring(0, outputFilename.lastIndexOf('.'));
699
+ const translatedOutputPath = `${pathWithoutExtension}.translated.${fileExtension}`;
700
+ const translatedFileSaver = getFileSaver(translatedOutputPath, allowOverwrite);
701
+ await translatedFileSaver(inputRawAudio, translatedTimeline, translatedTranscript, options.subtitles);
702
+ }
593
703
  }
594
704
  logger.end();
595
705
  if (cliOptions.play) {
@@ -601,10 +711,71 @@ async function alignTranslation(operationData) {
601
711
  audioToPlay = inputRawAudio;
602
712
  }
603
713
  const normalizedAudioToPlay = normalizeAudioLevel(audioToPlay);
604
- await playAudioWithWordTimeline(normalizedAudioToPlay, wordTimeline, transcript);
714
+ await playAudioWithWordTimeline(normalizedAudioToPlay, translatedWordTimeline, translatedTranscript);
715
+ }
716
+ }
717
+ export async function alignTimelineTranslation(operationData) {
718
+ const logger = new Logger();
719
+ const { operationArgs, operationOptionsLookup, cliOptions } = operationData;
720
+ const timelineFilename = operationArgs[0];
721
+ const outputFilenames = operationArgs.slice(2);
722
+ if (timelineFilename == undefined) {
723
+ throw new Error(`align-timeline-translation requires a first argument containing the timeline file path.`);
724
+ }
725
+ if (getLowercaseFileExtension(timelineFilename) != 'json') {
726
+ throw new Error(`align-timeline-translation only supports timeline files with extension 'json'`);
727
+ }
728
+ if (!existsSync(timelineFilename)) {
729
+ throw new Error(`The given timeline file '${timelineFilename}' was not found.`);
730
+ }
731
+ const timeline = await readAndParseJsonFile(timelineFilename);
732
+ const translationFilePath = operationArgs[1];
733
+ if (translationFilePath == undefined) {
734
+ throw new Error(`align-timeline-translation requires a second argument containing the translated reference file path.`);
735
+ }
736
+ if (!existsSync(translationFilePath)) {
737
+ throw new Error(`The given reference file '${translationFilePath}' was not found.`);
738
+ }
739
+ const translationFileExtension = getLowercaseFileExtension(translationFilePath);
740
+ const translationFileContent = await readFile(translationFilePath, { encoding: 'utf-8' });
741
+ let translationText;
742
+ if (translationFileExtension == 'txt') {
743
+ translationText = translationFileContent;
744
+ }
745
+ else if (translationFileExtension == 'html' || translationFileExtension == 'htm') {
746
+ translationText = await convertHtmlToText(translationFileContent);
747
+ }
748
+ else if (translationFileExtension == 'srt' || translationFileExtension == 'vtt') {
749
+ translationText = subtitlesToText(translationFileContent);
750
+ }
751
+ else {
752
+ throw new Error(`align only supports reference files with extensions 'txt', 'html', 'htm', 'srt' or 'vtt'`);
753
+ }
754
+ const options = await optionsLookupToTypedObject(operationOptionsLookup, 'TimelineTranslationAlignmentOptions');
755
+ const { timeline: translationTimeline, wordTimeline: translationWordTimeline, rawAudio } = await API.alignTimelineTranslation(timeline, translationText, options);
756
+ if (outputFilenames.length > 0) {
757
+ logger.start('\nWrite output files');
758
+ }
759
+ const allowOverwrite = getWithDefault(cliOptions.overwrite, overwriteByDefault);
760
+ for (const outputFilename of outputFilenames) {
761
+ const partPatternMatch = outputFilename.match(filenamePlaceholderPattern);
762
+ if (partPatternMatch) {
763
+ continue;
764
+ }
765
+ const fileSaver = getFileSaver(outputFilename, allowOverwrite);
766
+ await fileSaver(getEmptyRawAudio(1, 16000), translationTimeline, translationText, options.subtitles);
767
+ }
768
+ logger.end();
769
+ if (cliOptions.play && rawAudio) {
770
+ const normalizedAudioToPlay = normalizeAudioLevel(rawAudio);
771
+ let transcriptToPlay;
772
+ let timelineToPlay;
773
+ transcriptToPlay = translationText;
774
+ timelineToPlay = translationWordTimeline;
775
+ await playAudioWithWordTimeline(normalizedAudioToPlay, timelineToPlay, transcriptToPlay);
605
776
  }
606
777
  }
607
- async function translateSpeech(operationData) {
778
+ export async function translateSpeech(operationData) {
608
779
  const logger = new Logger();
609
780
  const { operationArgs, operationOptionsLookup, cliOptions } = operationData;
610
781
  const inputFilename = operationArgs[0];
@@ -667,7 +838,7 @@ async function translateSpeech(operationData) {
667
838
  await playAudioWithWordTimeline(normalizedAudioToPlay, timelineToPlay, transcriptToPlay);
668
839
  }
669
840
  }
670
- async function detectLanguage(operationData, mode) {
841
+ export async function detectLanguage(operationData, mode) {
671
842
  const logger = new Logger();
672
843
  const { operationArgs, operationOptionsLookup, cliOptions } = operationData;
673
844
  const inputFilePath = operationArgs[0];
@@ -721,7 +892,7 @@ async function detectLanguage(operationData, mode) {
721
892
  }
722
893
  logger.end();
723
894
  }
724
- async function detectVoiceActivity(operationData) {
895
+ export async function detectVoiceActivity(operationData) {
725
896
  const logger = new Logger();
726
897
  const { operationArgs, operationOptionsLookup, cliOptions } = operationData;
727
898
  const audioFilename = operationArgs[0];
@@ -766,7 +937,7 @@ async function detectVoiceActivity(operationData) {
766
937
  await playAudioWithWordTimeline(normalizedAudio, timelineToPlay);
767
938
  }
768
939
  }
769
- async function denoise(operationData) {
940
+ export async function denoise(operationData) {
770
941
  const logger = new Logger();
771
942
  const { operationArgs, operationOptionsLookup, cliOptions } = operationData;
772
943
  const audioFilename = operationArgs[0];
@@ -796,7 +967,7 @@ async function denoise(operationData) {
796
967
  await playAudioSamples(denoisedAudio);
797
968
  }
798
969
  }
799
- async function isolate(operationData) {
970
+ export async function isolate(operationData) {
800
971
  const logger = new Logger();
801
972
  const { operationArgs, operationOptionsLookup, cliOptions } = operationData;
802
973
  const audioFilename = operationArgs[0];
@@ -825,7 +996,7 @@ async function isolate(operationData) {
825
996
  await playAudioSamples(isolatedRawAudio);
826
997
  }
827
998
  }
828
- async function listEngines(operationData) {
999
+ export async function listEngines(operationData) {
829
1000
  const logger = new Logger();
830
1001
  const { operationArgs } = operationData;
831
1002
  const targetOperation = operationArgs[0];
@@ -905,7 +1076,7 @@ async function listEngines(operationData) {
905
1076
  }
906
1077
  }
907
1078
  }
908
- async function listTTSVoices(operationData) {
1079
+ export async function listTTSVoices(operationData) {
909
1080
  const logger = new Logger();
910
1081
  const { operationArgs, operationOptionsLookup, cliOptions } = operationData;
911
1082
  const targetEngine = operationArgs[0];
@@ -947,7 +1118,7 @@ async function listTTSVoices(operationData) {
947
1118
  }
948
1119
  logger.end();
949
1120
  }
950
- async function installPackages(operationData) {
1121
+ export async function installPackages(operationData) {
951
1122
  const logger = new Logger();
952
1123
  const { operationArgs } = operationData;
953
1124
  if (operationArgs.length == 0) {
@@ -973,7 +1144,7 @@ async function installPackages(operationData) {
973
1144
  }
974
1145
  }
975
1146
  }
976
- async function uninstallPackages(operationData) {
1147
+ export async function uninstallPackages(operationData) {
977
1148
  const logger = new Logger();
978
1149
  const { operationArgs } = operationData;
979
1150
  if (operationArgs.length == 0) {
@@ -994,7 +1165,7 @@ async function uninstallPackages(operationData) {
994
1165
  logger.log(`The packages ${failedPackageNames.join(', ')} failed to uninstall`, 'error');
995
1166
  }
996
1167
  }
997
- async function listPackages(operationData) {
1168
+ export async function listPackages(operationData) {
998
1169
  const logger = new Logger();
999
1170
  const packagesDir = await ensureAndGetPackagesDir();
1000
1171
  const installedPackageNames = await readdir(packagesDir);
@@ -1017,7 +1188,7 @@ async function listPackages(operationData) {
1017
1188
  logger.log(``);
1018
1189
  logger.log(installedPackageNamesFormatted.join('\n'));
1019
1190
  }
1020
- async function serve(operationData) {
1191
+ export async function serve(operationData) {
1021
1192
  const { operationOptionsLookup } = operationData;
1022
1193
  const options = await optionsLookupToTypedObject(operationOptionsLookup, 'ServerOptions');
1023
1194
  async function onServerStarted(serverOptions) {
@@ -1143,13 +1314,13 @@ async function optionsLookupToTypedObject(cliOptionsMap, optionsRoot, additional
1143
1314
  return resultingObj;
1144
1315
  }
1145
1316
  let cachedOptionsSchema;
1146
- export async function getOptionsSchema() {
1317
+ async function getOptionsSchema() {
1147
1318
  if (!cachedOptionsSchema) {
1148
1319
  cachedOptionsSchema = await readAndParseJsonFile(resolveToModuleRootDir('data/schemas/options.json'));
1149
1320
  }
1150
1321
  return cachedOptionsSchema;
1151
1322
  }
1152
- export async function checkOutputFilenames(outputFilenames, acceptMediaOutputs, acceptMetadataOutputs, acceptSubtitleOutputs) {
1323
+ async function checkOutputFilenames(outputFilenames, acceptMediaOutputs, acceptMetadataOutputs, acceptSubtitleOutputs) {
1153
1324
  const supportedFileExtensions = [];
1154
1325
  if (acceptMediaOutputs) {
1155
1326
  supportedFileExtensions.push(...supportedOutputMediaFileExtensions);
@@ -1188,7 +1359,7 @@ export async function checkOutputFilenames(outputFilenames, acceptMediaOutputs,
1188
1359
  }
1189
1360
  return { includesPlaceholderPattern };
1190
1361
  }
1191
- export async function writeOutputFilesForSegment(outputFilenames, index, total, audio, timeline, text, language, allowOverwrite) {
1362
+ async function writeOutputFilesForSegment(outputFilenames, index, total, audio, timeline, text, language, allowOverwrite) {
1192
1363
  const digitCount = Math.max((total + 1).toString().length, 2);
1193
1364
  const segmentWords = (await splitToWords(text, language)).filter(text => wordCharacterPattern.test(text));
1194
1365
  const segmentJoinedWords = segmentWords.join(' ').trim();