echogarden 0.11.11 → 0.11.13

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 (130) hide show
  1. package/data/schemas/options.json +16 -0
  2. package/dist/alignment/DTWMfccSequenceAlignment.js +2 -2
  3. package/dist/alignment/DTWMfccSequenceAlignment.js.map +1 -1
  4. package/dist/alignment/SpeechAlignment.d.ts +1 -1
  5. package/dist/alignment/SpeechAlignment.js +15 -3
  6. package/dist/alignment/SpeechAlignment.js.map +1 -1
  7. package/dist/api/Alignment.js +3 -3
  8. package/dist/api/Alignment.js.map +1 -1
  9. package/dist/api/LanguageDetection.js +1 -1
  10. package/dist/api/LanguageDetection.js.map +1 -1
  11. package/dist/api/Recognition.js +3 -3
  12. package/dist/api/Recognition.js.map +1 -1
  13. package/dist/api/Synthesis.js +7 -6
  14. package/dist/api/Synthesis.js.map +1 -1
  15. package/dist/api/Translation.js +3 -3
  16. package/dist/api/Translation.js.map +1 -1
  17. package/dist/audio/AudioUtilities.d.ts +5 -2
  18. package/dist/audio/AudioUtilities.js +50 -22
  19. package/dist/audio/AudioUtilities.js.map +1 -1
  20. package/dist/cli/CLI.js +2 -2
  21. package/dist/cli/CLI.js.map +1 -1
  22. package/dist/dsp/SpeexResampler.js +1 -1
  23. package/dist/recognition/WhisperSTT.js +2 -2
  24. package/dist/recognition/WhisperSTT.js.map +1 -1
  25. package/dist/subtitles/Subtitles.d.ts +10 -7
  26. package/dist/subtitles/Subtitles.js +268 -207
  27. package/dist/subtitles/Subtitles.js.map +1 -1
  28. package/docs/Options.md +4 -2
  29. package/package.json +12 -11
  30. package/src/alignment/DTWMfccSequenceAlignment.ts +43 -0
  31. package/src/alignment/DTWSequenceAlignment.ts +121 -0
  32. package/src/alignment/DTWSequenceAlignmentWindowed.ts +210 -0
  33. package/src/alignment/LevenshteinSequenceAlignment.ts +126 -0
  34. package/src/alignment/SpeechAlignment.ts +488 -0
  35. package/src/api/API.ts +12 -0
  36. package/src/api/APIOptions.ts +15 -0
  37. package/src/api/Alignment.ts +329 -0
  38. package/src/api/Common.ts +16 -0
  39. package/src/api/Denoising.ts +120 -0
  40. package/src/api/LanguageDetection.ts +286 -0
  41. package/src/api/Recognition.ts +344 -0
  42. package/src/api/Synthesis.ts +1735 -0
  43. package/src/api/Translation.ts +143 -0
  44. package/src/api/Vad.ts +172 -0
  45. package/src/audio/AudioBufferConversion.ts +248 -0
  46. package/src/audio/AudioPlayer.ts +358 -0
  47. package/src/audio/AudioRecorder.ts +91 -0
  48. package/src/audio/AudioUtilities.ts +392 -0
  49. package/src/audio/SoxPath.ts +24 -0
  50. package/src/cli/CLI.ts +1360 -0
  51. package/src/cli/CLIConfigFile.ts +91 -0
  52. package/src/cli/CLILauncher.ts +26 -0
  53. package/src/cli/CLIOptionsSchema.ts +54 -0
  54. package/src/cli/CLIParser.ts +41 -0
  55. package/src/cli/CLIStarter.ts +40 -0
  56. package/src/codecs/FFMpegTranscoder.ts +214 -0
  57. package/src/codecs/TIMITCodec.ts +17 -0
  58. package/src/codecs/WaveCodec.ts +260 -0
  59. package/src/denoising/RNNoise.ts +95 -0
  60. package/src/dsp/BiquadFilter.ts +488 -0
  61. package/src/dsp/FFT.ts +187 -0
  62. package/src/dsp/MFCC.ts +227 -0
  63. package/src/dsp/MelSpectogram.ts +145 -0
  64. package/src/dsp/Rubberband.ts +249 -0
  65. package/src/dsp/Sonic.ts +59 -0
  66. package/src/dsp/SpeexResampler.ts +79 -0
  67. package/src/math/VectorMath.ts +812 -0
  68. package/src/nlp/ChineseSegmentation.ts +68 -0
  69. package/src/nlp/CompromiseNLP.ts +113 -0
  70. package/src/nlp/EspeakPhonemizer.ts +168 -0
  71. package/src/nlp/IPA.ts +139 -0
  72. package/src/nlp/JapaneseSegmentation.ts +53 -0
  73. package/src/nlp/Lexicon.ts +119 -0
  74. package/src/nlp/PhoneConversion.ts +508 -0
  75. package/src/nlp/Segmentation.ts +237 -0
  76. package/src/nlp/TextNormalizer.ts +160 -0
  77. package/src/recognition/AmazonTranscribeSTT.ts +112 -0
  78. package/src/recognition/AzureCognitiveServicesSTT.ts +76 -0
  79. package/src/recognition/GoogleCloudSTT.ts +92 -0
  80. package/src/recognition/SileroSTT.ts +173 -0
  81. package/src/recognition/VoskSTT.ts +112 -0
  82. package/src/recognition/WhisperSTT.ts +1518 -0
  83. package/src/server/Client.ts +297 -0
  84. package/src/server/Server.ts +178 -0
  85. package/src/server/ServerStarter.ts +12 -0
  86. package/src/server/Worker.ts +400 -0
  87. package/src/server/WorkerStarter.ts +38 -0
  88. package/src/speech-language-detection/SileroLanguageDetection.ts +105 -0
  89. package/src/subtitles/Subtitles.ts +478 -0
  90. package/src/synthesis/AwsPollyTTS.ts +78 -0
  91. package/src/synthesis/AzureCognitiveServicesTTS.ts +146 -0
  92. package/src/synthesis/CoquiServerTTS.ts +29 -0
  93. package/src/synthesis/ElevenLabsTTS.ts +104 -0
  94. package/src/synthesis/EspeakTTS.ts +552 -0
  95. package/src/synthesis/FliteTTS.ts +387 -0
  96. package/src/synthesis/GoogleCloudTTS.ts +112 -0
  97. package/src/synthesis/GoogleTranslateTTS.ts +210 -0
  98. package/src/synthesis/MicrosoftEdgeTTS.ts +298 -0
  99. package/src/synthesis/SamTTS.ts +30 -0
  100. package/src/synthesis/SapiTTS.ts +222 -0
  101. package/src/synthesis/StreamlabsPollyTTS.ts +114 -0
  102. package/src/synthesis/SvoxPicoTTS.ts +318 -0
  103. package/src/synthesis/VitsTTS.ts +734 -0
  104. package/src/tests/Test.ts +24 -0
  105. package/src/text-language-detection/FastTextLanguageDetection.ts +53 -0
  106. package/src/text-language-detection/TinyLDLanguageDetection.ts +16 -0
  107. package/src/typings/Fillers.d.ts +41 -0
  108. package/src/utilities/BinaryArrayConversion.ts +159 -0
  109. package/src/utilities/Compression.ts +91 -0
  110. package/src/utilities/FileDownloader.ts +201 -0
  111. package/src/utilities/FileSystem.ts +265 -0
  112. package/src/utilities/Hashing.ts +230 -0
  113. package/src/utilities/Locale.ts +119 -0
  114. package/src/utilities/Logger.ts +72 -0
  115. package/src/utilities/NdArrayUtilities.ts +31 -0
  116. package/src/utilities/ObjectUtilities.ts +169 -0
  117. package/src/utilities/OpenPromise.ts +13 -0
  118. package/src/utilities/PackageManager.ts +97 -0
  119. package/src/utilities/Queue.ts +17 -0
  120. package/src/utilities/RandomGenerator.ts +237 -0
  121. package/src/utilities/SignalChannel.ts +22 -0
  122. package/src/utilities/TarballMaker.ts +68 -0
  123. package/src/utilities/Timeline.ts +231 -0
  124. package/src/utilities/Timer.ts +93 -0
  125. package/src/utilities/Utilities.ts +574 -0
  126. package/src/utilities/WasmMemoryManager.ts +516 -0
  127. package/src/utilities/WebReader.ts +55 -0
  128. package/src/utilities/WikipediaReader.ts +41 -0
  129. package/src/voice-activity-detection/SileroVAD.ts +86 -0
  130. package/src/voice-activity-detection/WebRtcVAD.ts +76 -0
@@ -0,0 +1,237 @@
1
+ import * as CldrSegmentation from 'cldr-segmentation'
2
+ import { splitChineseTextToWords_Jieba } from './ChineseSegmentation.js'
3
+
4
+ import { sumArray, includesAnyOf, indexOfAnyOf, logToStderr } from '../utilities/Utilities.js'
5
+ import { getShortLanguageCode } from '../utilities/Locale.js'
6
+ import { splitJapaneseTextToWords_Kuromoji } from './JapaneseSegmentation.js'
7
+ import { ParagraphBreakType, WhitespaceProcessing } from '../api/Common.js'
8
+
9
+ const log = logToStderr
10
+
11
+ export const wordCharacterPattern = /[\p{Letter}\p{Number}]/u
12
+ export const phraseSeparators = [",", ";", ":"]
13
+ export const sentenceSeparators = [".", "?", "!"]
14
+ export const symbolWords = ["$", "€", "¢", "£", "¥", "©", "®", "™", "%", "&", "#", "~", "@", "+", "±", "÷", "/", "*", "=", "¼", "½", "¾"]
15
+
16
+ export function isWordOrSymbolWord(str: string) {
17
+ return isWord(str) || symbolWords.includes(str)
18
+ }
19
+
20
+ export function isWord(str: string) {
21
+ str = str.trim()
22
+ return wordCharacterPattern.test(str) || symbolWords.includes(str)
23
+ }
24
+
25
+ export class Sentence {
26
+ phrases: Phrase[] = []
27
+
28
+ readonly isSentenceFinalizer = true
29
+
30
+ get length() { return sumArray(this.phrases, (phrase) => phrase.length) }
31
+
32
+ get text() { return this.phrases.reduce<string>((result, phrase) => result + phrase.text, "") }
33
+ }
34
+
35
+ export class Phrase {
36
+ words: Word[] = []
37
+
38
+ get length() { return sumArray(this.words, (word) => word.length) }
39
+
40
+ get text() { return this.words.reduce<string>((result, word) => result + word.text, "") }
41
+
42
+ get lastWord() {
43
+ if (this.words.length == 0) {
44
+ return undefined
45
+ }
46
+
47
+ return this.words[this.words.length - 1]
48
+ }
49
+
50
+ get isSentenceFinalizer() { return this.lastWord != null ? this.lastWord.isSentenceFinalizer : false }
51
+ }
52
+
53
+ export class Word {
54
+ readonly text: string
55
+ isSentenceFinalizer: boolean
56
+
57
+ constructor(text: string, isSentenceFinalizer: boolean) {
58
+ this.text = text
59
+ this.isSentenceFinalizer = isSentenceFinalizer
60
+ }
61
+
62
+ get containsOnlyPunctuation() { return !wordCharacterPattern.test(this.text) && !this.isSymbolWord }
63
+
64
+ get isSymbolWord() { return symbolWords.includes(this.text) }
65
+
66
+ get isPhraseSeperator() { return this.containsOnlyPunctuation && includesAnyOf(this.text, phraseSeparators) }
67
+
68
+ get length() { return this.text.length }
69
+ }
70
+
71
+ export type Segment = Sentence | Phrase | Word
72
+
73
+ export class Fragment {
74
+ segments: Segment[] = []
75
+
76
+ get length() { return sumArray(this.segments, (phrase) => phrase.length) }
77
+
78
+ get text() { return this.segments.reduce<string>((result, segment) => result + segment.text, "") }
79
+
80
+ get isEmpty() { return this.length == 0 }
81
+
82
+ get isNonempty() { return !this.isEmpty }
83
+
84
+ get lastSegment() {
85
+ if (this.isEmpty) {
86
+ return undefined
87
+ }
88
+
89
+ return this.segments[this.segments.length - 1]
90
+ }
91
+ }
92
+
93
+ export async function splitToFragments(text: string, maxFragmentLength: number, langCode: string, preserveSentences = true, preservePhrases = true) {
94
+ const parsedText = await parse(text, langCode)
95
+
96
+ const fragments: Fragment[] = []
97
+ let currentFragment = new Fragment()
98
+
99
+ const remainingCharactersInCurrentFragment = () => maxFragmentLength - currentFragment.length
100
+ const createNewFragmentIfNeeded = () => {
101
+ if (currentFragment.isNonempty) {
102
+ fragments.push(currentFragment)
103
+ currentFragment = new Fragment()
104
+ }
105
+ }
106
+
107
+ const fitsCurrentFragment = (segment: Segment) => segment.length <= remainingCharactersInCurrentFragment()
108
+
109
+ for (const sentence of parsedText) {
110
+ if (fitsCurrentFragment(sentence)) {
111
+ currentFragment.segments.push(sentence)
112
+ continue
113
+ }
114
+
115
+ if (preserveSentences) {
116
+ createNewFragmentIfNeeded()
117
+
118
+ if (fitsCurrentFragment(sentence)) {
119
+ currentFragment.segments.push(sentence)
120
+ continue
121
+ }
122
+ }
123
+
124
+ for (const phrase of sentence.phrases) {
125
+ if (fitsCurrentFragment(phrase)) {
126
+ currentFragment.segments.push(phrase)
127
+ continue
128
+ }
129
+
130
+
131
+ if (preservePhrases) {
132
+ createNewFragmentIfNeeded()
133
+
134
+ if (fitsCurrentFragment(phrase)) {
135
+ currentFragment.segments.push(phrase)
136
+ continue
137
+ }
138
+ }
139
+
140
+ for (const word of phrase.words) {
141
+ if (fitsCurrentFragment(word)) {
142
+ currentFragment.segments.push(word)
143
+ continue
144
+ }
145
+
146
+ createNewFragmentIfNeeded()
147
+
148
+ if (fitsCurrentFragment(word)) {
149
+ currentFragment.segments.push(word)
150
+ continue
151
+ }
152
+
153
+ throw new Error(`Encountered a word of length ${word.length}, which excceeds the maximum fragment length of ${maxFragmentLength}`)
154
+ }
155
+ }
156
+ }
157
+
158
+ createNewFragmentIfNeeded()
159
+
160
+ return fragments
161
+ }
162
+
163
+ export async function parse(text: string, langCode: string) {
164
+ const sentencesText = splitToSentences(text, langCode)
165
+ const sentences: Sentence[] = []
166
+
167
+ for (const sentenceText of sentencesText) {
168
+ const sentence = new Sentence()
169
+
170
+ let currentPhrase = new Phrase()
171
+ const wordTexts = await splitToWords(sentenceText, langCode)
172
+
173
+ for (let wordIndex = 0; wordIndex < wordTexts.length; wordIndex++) {
174
+ const word = new Word(wordTexts[wordIndex], wordIndex == wordTexts.length - 1)
175
+
176
+ if (word.isPhraseSeperator) {
177
+ const separatorIndex = indexOfAnyOf(word.text, phraseSeparators)
178
+ currentPhrase.words.push(new Word(word.text.substring(0, separatorIndex + 1), word.isSentenceFinalizer))
179
+ sentence.phrases.push(currentPhrase)
180
+
181
+ currentPhrase = new Phrase()
182
+ currentPhrase.words.push(new Word(word.text.substring(separatorIndex + 1), false))
183
+ } else {
184
+ currentPhrase.words.push(word)
185
+ }
186
+ }
187
+
188
+ if (currentPhrase.words.length > 0) {
189
+ sentence.phrases.push(currentPhrase)
190
+ }
191
+
192
+ sentences.push(sentence)
193
+ }
194
+
195
+ return sentences
196
+ }
197
+
198
+ export function splitToSentences(text: string, langCode: string): string[] {
199
+ const shortLangCode = getShortLanguageCode(langCode || "")
200
+
201
+ return CldrSegmentation.sentenceSplit(text, CldrSegmentation.suppressions[shortLangCode])
202
+ }
203
+
204
+ export async function splitToWords(text: string, langCode: string): Promise<string[]> {
205
+ const shortLangCode = getShortLanguageCode(langCode || "")
206
+
207
+ if (shortLangCode == "zh" || shortLangCode == "cmn") {
208
+ return splitChineseTextToWords_Jieba(text, undefined, true)
209
+ } else if (shortLangCode == "ja") {
210
+ return splitJapaneseTextToWords_Kuromoji(text)
211
+ } else {
212
+ return CldrSegmentation.wordSplit(text, CldrSegmentation.suppressions[shortLangCode])
213
+ }
214
+ }
215
+
216
+ export function splitToParagraphs(text: string, paragraphBreaks: ParagraphBreakType, whitespace: WhitespaceProcessing) {
217
+ let paragraphs: string[] = []
218
+
219
+ if (paragraphBreaks == 'single') {
220
+ paragraphs = text.split(/(\r?\n)+/g)
221
+ } else if (paragraphBreaks == 'double') {
222
+ paragraphs = text.split(/(\r?\n)(\r?\n)+/g)
223
+ } else {
224
+ throw new Error(`Invalid paragraph break type: ${paragraphBreaks}`)
225
+ }
226
+
227
+ if (whitespace == "removeLineBreaks") {
228
+ paragraphs = paragraphs.map(p => p.replaceAll(/(\r?\n)+/g, " "))
229
+ } else if (whitespace == "collapse") {
230
+ paragraphs = paragraphs.map(p => p.replaceAll(/\s+/g, " "))
231
+ }
232
+
233
+ paragraphs = paragraphs.map(p => p.trim())
234
+ paragraphs = paragraphs.filter(p => p.length > 0)
235
+
236
+ return paragraphs
237
+ }
@@ -0,0 +1,160 @@
1
+ import { getShortLanguageCode } from "../utilities/Locale.js"
2
+
3
+ export function getNormalizedFragmentsForSpeech(words: string[], language: string) {
4
+ language = getShortLanguageCode(language)
5
+
6
+ if (language != "en") {
7
+ return { normalizedFragments: [...words], referenceFragments: [...words] }
8
+ }
9
+
10
+ const numberPattern = /^[0-9][0-9\,\.]*$/
11
+
12
+ const fourDigitYearPattern = /^[0-9][0-9][0-9][0-9]$/
13
+ const fourDigitDecadePattern = /^[0-9][0-9][0-9]0s$/
14
+
15
+ const fourDigitYearRangePattern = /^[0-9][0-9][0-9][0-9][\-\–][0-9][0-9][0-9][0-9]$/
16
+
17
+ const wordsPrecedingAYear = [
18
+ "in", "the", "a", "to", "of", "since", "from", "between", "by", "until", "around", "before", "after",
19
+ "his", "her", "year", "years", "during", "copyright", "©", "early", "mid", "late",
20
+ "january", "february", "march", "april", "may", "june", "july", "august", "september", "october", "november", "december",
21
+ "jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec"
22
+ ]
23
+
24
+ const wordsPrecedingADecade = [
25
+ "the", "in", "early", "mid", "late", "a"
26
+ ]
27
+
28
+ const symbolsPrecedingACurrency = [
29
+ "$", "€", "£", "¥"
30
+ ]
31
+
32
+ const symbolsPrecedingACurrencyAsWords = [
33
+ "dollars", "euros", "pounds", "yen"
34
+ ]
35
+
36
+ const wordsSucceedingACurrency = [
37
+ "million", "billion", "trillion"
38
+ ]
39
+
40
+ const normalizedFragments: string[] = []
41
+ const referenceFragments: string[] = []
42
+
43
+ for (let wordIndex = 0; wordIndex < words.length; wordIndex++) {
44
+ const word = words[wordIndex]
45
+ const lowerCaseWord = word.toLowerCase()
46
+
47
+ const nextWords = words.slice(wordIndex + 1)
48
+ const nextWord = nextWords[0]
49
+
50
+ if ( // Normalize a four digit year pattern, e.g. "in 1995".
51
+ wordsPrecedingAYear.includes(lowerCaseWord) &&
52
+ fourDigitYearPattern.test(nextWord)) {
53
+
54
+ const normalizedString = normalizeFourDigitYearString(nextWord)
55
+
56
+ normalizedFragments.push(word)
57
+ referenceFragments.push(word)
58
+
59
+ normalizedFragments.push(normalizedString)
60
+ referenceFragments.push(nextWord)
61
+
62
+ wordIndex += 1
63
+ } else if ( // Normalize a four digit decade pattern, e.g. "the 1980s".
64
+ wordsPrecedingADecade.includes(lowerCaseWord) &&
65
+ fourDigitDecadePattern.test(nextWord)) {
66
+
67
+ const normalizedString = normalizeFourDigitDecadeString(nextWord)
68
+
69
+ normalizedFragments.push(word)
70
+ referenceFragments.push(word)
71
+
72
+ normalizedFragments.push(normalizedString)
73
+ referenceFragments.push(nextWord)
74
+
75
+ wordIndex += 1
76
+ } else if ( // Normalize a year range pattern, e.g. "1835-1896"
77
+ fourDigitYearRangePattern.test(words.slice(wordIndex, wordIndex + 3).join(""))) {
78
+
79
+ normalizedFragments.push(normalizeFourDigitYearString(words[wordIndex]))
80
+ referenceFragments.push(words[wordIndex])
81
+
82
+ normalizedFragments.push("to")
83
+ referenceFragments.push(words[wordIndex + 1])
84
+
85
+ normalizedFragments.push(normalizeFourDigitYearString(words[wordIndex + 2]))
86
+ referenceFragments.push(words[wordIndex + 2])
87
+
88
+ wordIndex += 2
89
+ } else if ( // Normalize a currency pattern, e.g. "$53.1 million", "€3.53"
90
+ symbolsPrecedingACurrency.includes(lowerCaseWord) &&
91
+ numberPattern.test(nextWord)) {
92
+
93
+ let currencyWord = symbolsPrecedingACurrencyAsWords[symbolsPrecedingACurrency.indexOf(lowerCaseWord)]
94
+
95
+ if (wordsSucceedingACurrency.includes(nextWords[1].toLowerCase())) {
96
+ const normalizedString = `${nextWord} ${nextWords[1]} ${currencyWord}`
97
+
98
+ normalizedFragments.push(normalizedString)
99
+
100
+ const referenceString = `${word}${nextWord} ${nextWords[1]}`
101
+ referenceFragments.push(referenceString)
102
+
103
+ wordIndex += 2
104
+ } else {
105
+ const normalizedString = `${nextWord} ${currencyWord}`
106
+
107
+ normalizedFragments.push(normalizedString)
108
+
109
+ const referenceString = `${word}${nextWord}`
110
+ referenceFragments.push(referenceString)
111
+
112
+ wordIndex += 1
113
+ }
114
+ } else {
115
+ normalizedFragments.push(word)
116
+ referenceFragments.push(word)
117
+ }
118
+ }
119
+
120
+ return { normalizedFragments, referenceFragments }
121
+ }
122
+
123
+ export function normalizeFourDigitYearString(yearString: string) {
124
+ const firstTwoDigitsValue = parseFloat(yearString.substring(0, 2))
125
+ const secondTwoDigitsValue = parseFloat(yearString.substring(2, 4))
126
+
127
+ let normalizedString: string
128
+
129
+ if (firstTwoDigitsValue >= 10 && secondTwoDigitsValue >= 10) {
130
+ normalizedString = `${firstTwoDigitsValue} ${secondTwoDigitsValue}`
131
+ } else if (firstTwoDigitsValue >= 10 && firstTwoDigitsValue % 10 != 0 && secondTwoDigitsValue < 10) {
132
+ normalizedString = `${firstTwoDigitsValue} oh ${secondTwoDigitsValue}`
133
+ } else {
134
+ normalizedString = yearString
135
+ }
136
+
137
+ return normalizedString
138
+ }
139
+
140
+ export function normalizeFourDigitDecadeString(decadeString: string) {
141
+ const firstTwoDigitsValue = parseInt(decadeString.substring(0, 2))
142
+ const secondTwoDigitsValue = parseInt(decadeString.substring(2, 4))
143
+
144
+ let normalizedString: string
145
+
146
+ const isBeforeSecondMillenium = firstTwoDigitsValue < 10
147
+ const isMilleniumDecade = firstTwoDigitsValue % 10 == 0 && secondTwoDigitsValue == 0
148
+
149
+ if (!isBeforeSecondMillenium && !isMilleniumDecade) {
150
+ if (secondTwoDigitsValue != 0) {
151
+ normalizedString = `${firstTwoDigitsValue} ${secondTwoDigitsValue}s`
152
+ } else {
153
+ normalizedString = `${firstTwoDigitsValue} hundreds`
154
+ }
155
+ } else {
156
+ normalizedString = decadeString
157
+ }
158
+
159
+ return normalizedString
160
+ }
@@ -0,0 +1,112 @@
1
+ import type { Item, StartStreamTranscriptionCommandInput } from "@aws-sdk/client-transcribe-streaming"
2
+ import { wordCharacterPattern } from "../nlp/Segmentation.js"
3
+ import * as FFMpegTranscoder from "../codecs/FFMpegTranscoder.js"
4
+ import { Logger } from "../utilities/Logger.js"
5
+ import { Timeline } from "../utilities/Timeline.js"
6
+ import { RawAudio } from "../audio/AudioUtilities.js"
7
+
8
+ export async function recgonize(rawAudio: RawAudio, languageCode: string, region: string, accessKeyId: string, secretAccessKey: string) {
9
+ const flac16Khz16bitMonoAudio = await FFMpegTranscoder.encodeFromChannels(rawAudio, { format: "flac", sampleRate: 16000, sampleFormat: "s16", channelCount: 1 })
10
+
11
+ const logger = new Logger()
12
+ logger.start("Initialize Amazon Transcribe streaming client module")
13
+
14
+ const streamingTranscribeSdk = await import("@aws-sdk/client-transcribe-streaming")
15
+
16
+ const streamingTranscribeClient = new streamingTranscribeSdk.TranscribeStreamingClient({
17
+ region,
18
+ credentials: {
19
+ accessKeyId,
20
+ secretAccessKey
21
+ }
22
+ })
23
+
24
+ const audioStream = async function* () {
25
+ const chunkSize = 2 ** 12
26
+ //const audioSamples = encodeToAudioBuffer(rawAudio.audioChannels, 16, SampleFormat.PCM)
27
+
28
+ for (let i = 0; i < flac16Khz16bitMonoAudio.length; i += chunkSize) {
29
+ const chunk = flac16Khz16bitMonoAudio.subarray(i, i + chunkSize)
30
+
31
+ yield { AudioEvent: { AudioChunk: chunk } }
32
+ }
33
+ }
34
+
35
+ const params: StartStreamTranscriptionCommandInput = {
36
+ LanguageCode: languageCode,
37
+ MediaSampleRateHertz: rawAudio.sampleRate,
38
+ MediaEncoding: "flac",
39
+ AudioStream: audioStream(),
40
+ }
41
+
42
+ logger.start("Request recognition from Amazon Transcribe..")
43
+
44
+ const command = new streamingTranscribeSdk.StartStreamTranscriptionCommand(params)
45
+
46
+ const response = await streamingTranscribeClient.send(command)
47
+
48
+ let transcript = ""
49
+ let events: Item[] = []
50
+
51
+ for await (const event of response.TranscriptResultStream!) {
52
+ if (!event.TranscriptEvent) {
53
+ continue
54
+ }
55
+
56
+ const transcriptEvent = event.TranscriptEvent
57
+
58
+ const results = transcriptEvent.Transcript!.Results!
59
+
60
+ if (results.length == 0) {
61
+ continue
62
+ }
63
+
64
+ const firstResult = results[0]
65
+ const alternatives = firstResult.Alternatives
66
+
67
+ if (!alternatives || alternatives.length == 0) {
68
+ continue
69
+ }
70
+
71
+ const firstAlternative = alternatives[0]
72
+ //logger.log(firstAlternative.Transcript!)
73
+
74
+ if (firstResult.IsPartial === false) {
75
+ events = [...events, ...firstAlternative.Items!]
76
+ transcript += " " + firstAlternative.Transcript!
77
+ }
78
+ }
79
+
80
+ logger.start("Process result")
81
+
82
+ transcript = transcript.replace(/ +/g, " ").trim()
83
+
84
+ const timeline: Timeline = []
85
+
86
+ for (const event of events) {
87
+ const text = event.Content!
88
+
89
+ if (!wordCharacterPattern.test(text)) {
90
+ continue
91
+ }
92
+
93
+ const startTime = event.StartTime!
94
+ const endTime = event.EndTime!
95
+ const confidence = event.Confidence
96
+
97
+ timeline[timeline.length - 1].endTime = startTime
98
+
99
+ timeline.push(
100
+ {
101
+ type: "word",
102
+ text,
103
+ startTime,
104
+ endTime,
105
+ confidence
106
+ })
107
+ }
108
+
109
+ logger.end()
110
+
111
+ return { transcript, timeline }
112
+ }
@@ -0,0 +1,76 @@
1
+ import SpeechSDK from 'microsoft-cognitiveservices-speech-sdk'
2
+
3
+ import { RawAudio, encodeWaveBuffer } from '../audio/AudioUtilities.js'
4
+ import { Logger } from '../utilities/Logger.js'
5
+ import { Timeline } from '../utilities/Timeline.js'
6
+
7
+ export async function recognize(rawAudio: RawAudio, subscriptionKey: string, serviceRegion: string, languageCode: string, profanity: SpeechSDK.ProfanityOption = SpeechSDK.ProfanityOption.Raw) {
8
+ const logger = new Logger()
9
+ logger.start("Request recognition from Azure Cognitive Services")
10
+
11
+ const result = await requestRecognition(rawAudio, subscriptionKey, serviceRegion, languageCode)
12
+
13
+ logger.start("Process result")
14
+
15
+ const transcript = result.text
16
+
17
+ const resultObject = JSON.parse(result.json)
18
+ const bestResult = resultObject.NBest[0]
19
+
20
+ const timeline: Timeline = []
21
+
22
+ for (const wordEntry of bestResult.Words) {
23
+ const text = wordEntry.Word
24
+ const startTime = wordEntry.Offset / 10000000
25
+ const endTime = (wordEntry.Offset + wordEntry.Duration) / 10000000
26
+
27
+ timeline.push({
28
+ type: "word",
29
+ text,
30
+ startTime,
31
+ endTime
32
+ })
33
+ }
34
+
35
+ logger.end()
36
+
37
+ return { transcript, timeline }
38
+ }
39
+
40
+ async function requestRecognition(rawAudio: RawAudio, subscriptionKey: string, serviceRegion: string, languageCode: string, profanity: SpeechSDK.ProfanityOption = SpeechSDK.ProfanityOption.Raw) {
41
+ //const encodedAudio = await FFMpegTranscoder.encodeFromChannels(rawAudio, { format: "wav", sampleRate: 16000, sampleFormat: "s16", channelCount: 1 });
42
+ const encodedAudio = encodeWaveBuffer(rawAudio)
43
+
44
+ return new Promise<SpeechSDK.SpeechRecognitionResult>((resolve, reject) => {
45
+ const audioFormat = SpeechSDK.AudioStreamFormat.getWaveFormat(16000, 16, 1, SpeechSDK.AudioFormatTag.PCM)
46
+
47
+ const inputStream = SpeechSDK.AudioInputStream.createPushStream(audioFormat)
48
+
49
+ inputStream.write(encodedAudio)
50
+ inputStream.close()
51
+
52
+ const audioConfig = SpeechSDK.AudioConfig.fromStreamInput(inputStream)
53
+
54
+ const speechConfig = SpeechSDK.SpeechConfig.fromSubscription(subscriptionKey, serviceRegion)
55
+
56
+ speechConfig.speechRecognitionLanguage = languageCode
57
+
58
+ speechConfig.setProfanity(profanity)
59
+ speechConfig.requestWordLevelTimestamps()
60
+
61
+ speechConfig.outputFormat = SpeechSDK.OutputFormat.Detailed
62
+
63
+ const recognizer = new SpeechSDK.SpeechRecognizer(speechConfig, audioConfig)
64
+
65
+ recognizer.recognizeOnceAsync(
66
+ (result) => {
67
+ recognizer.close()
68
+ resolve(result)
69
+ },
70
+
71
+ (error) => {
72
+ recognizer.close()
73
+ reject(error)
74
+ })
75
+ })
76
+ }
@@ -0,0 +1,92 @@
1
+ import { request } from "gaxios"
2
+
3
+ import * as FFMpegTranscoder from "../codecs/FFMpegTranscoder.js"
4
+ import { Logger } from "../utilities/Logger.js"
5
+ import { Timeline } from "../utilities/Timeline.js"
6
+ import { RawAudio } from "../audio/AudioUtilities.js"
7
+
8
+ export type AudioEncoding = "LINEAR16" | "FLAC" | "MULAW" | "AMR" | "AMR" | "AMR_WB" | "OGG_OPUS" | "SPEEX_WITH_HEADER_BYTE" | "MP3" | "WEBM_OPUS"
9
+
10
+ export async function recognize(rawAudio: RawAudio, apiKey: string, languageCode = "en-US") {
11
+ const flac16Khz16bitMonoAudio = await FFMpegTranscoder.encodeFromChannels(rawAudio, { format: "flac", sampleRate: 16000, sampleFormat: "s16", channelCount: 1 })
12
+
13
+ const logger = new Logger()
14
+ logger.start("Request recognition from Google Cloud")
15
+
16
+ const requestBody = {
17
+ config: {
18
+ encoding: "FLAC",
19
+ sampleRateHertz: 16000,
20
+ audioChannelCount: 1,
21
+ languageCode,
22
+ alternativeLanguageCodes: [],
23
+ maxAlternatives: 1,
24
+ profanityFilter: false,
25
+ enableWordTimeOffsets: true,
26
+ enableWordConfidence: true,
27
+ enableAutomaticPunctuation: true,
28
+ model: "latest_long",
29
+ useEnhanced: true
30
+ },
31
+
32
+ audio: {
33
+ content: flac16Khz16bitMonoAudio.toString("base64")
34
+ }
35
+ }
36
+
37
+ const response = await request<any>({
38
+ method: "POST",
39
+
40
+ url: `https://speech.googleapis.com/v1p1beta1/speech:recognize`,
41
+
42
+ params: {
43
+ "key": apiKey
44
+ },
45
+
46
+ headers: {
47
+ "User-Agent": ""
48
+ },
49
+
50
+ data: requestBody,
51
+
52
+
53
+ responseType: "json"
54
+ })
55
+
56
+ logger.start("Parse response body")
57
+
58
+ const result = parseResponseBody(response.data)
59
+
60
+ logger.end()
61
+
62
+ return result
63
+ }
64
+
65
+ function parseResponseBody(responseBody: any) {
66
+ const results = responseBody.results
67
+
68
+ let transcript = ""
69
+ const timeline: Timeline = []
70
+
71
+ for (const result of results) {
72
+ if (!result.alternatives || !result.alternatives[0] || !result.alternatives[0].transcript) {
73
+ continue
74
+ }
75
+
76
+ const firstAlternative = result.alternatives[0]
77
+
78
+ transcript += firstAlternative.transcript
79
+
80
+ for (const wordEvent of firstAlternative.words) {
81
+ timeline.push({
82
+ type: "word",
83
+ text: wordEvent.word,
84
+ startTime: parseFloat(wordEvent.startTime.replace("s","")),
85
+ endTime: parseFloat(wordEvent.endTime.replace("s", "")),
86
+ confidence: wordEvent.confidence
87
+ })
88
+ }
89
+ }
90
+
91
+ return { transcript, timeline }
92
+ }