echogarden 0.11.12 → 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.
- package/data/schemas/options.json +16 -0
- package/dist/api/Alignment.js +2 -2
- package/dist/api/Alignment.js.map +1 -1
- package/dist/api/Recognition.js +2 -2
- package/dist/api/Recognition.js.map +1 -1
- package/dist/api/Synthesis.js +5 -4
- package/dist/api/Synthesis.js.map +1 -1
- package/dist/api/Translation.js +2 -2
- package/dist/api/Translation.js.map +1 -1
- package/dist/audio/AudioUtilities.d.ts +1 -0
- package/dist/audio/AudioUtilities.js +25 -7
- package/dist/audio/AudioUtilities.js.map +1 -1
- package/dist/cli/CLI.js +2 -2
- package/dist/cli/CLI.js.map +1 -1
- package/dist/recognition/WhisperSTT.js +2 -2
- package/dist/recognition/WhisperSTT.js.map +1 -1
- package/dist/subtitles/Subtitles.d.ts +10 -7
- package/dist/subtitles/Subtitles.js +268 -207
- package/dist/subtitles/Subtitles.js.map +1 -1
- package/docs/Options.md +4 -2
- package/package.json +7 -6
- package/src/alignment/DTWMfccSequenceAlignment.ts +43 -0
- package/src/alignment/DTWSequenceAlignment.ts +121 -0
- package/src/alignment/DTWSequenceAlignmentWindowed.ts +210 -0
- package/src/alignment/LevenshteinSequenceAlignment.ts +126 -0
- package/src/alignment/SpeechAlignment.ts +488 -0
- package/src/api/API.ts +12 -0
- package/src/api/APIOptions.ts +15 -0
- package/src/api/Alignment.ts +329 -0
- package/src/api/Common.ts +16 -0
- package/src/api/Denoising.ts +120 -0
- package/src/api/LanguageDetection.ts +286 -0
- package/src/api/Recognition.ts +344 -0
- package/src/api/Synthesis.ts +1735 -0
- package/src/api/Translation.ts +143 -0
- package/src/api/Vad.ts +172 -0
- package/src/audio/AudioBufferConversion.ts +248 -0
- package/src/audio/AudioPlayer.ts +358 -0
- package/src/audio/AudioRecorder.ts +91 -0
- package/src/audio/AudioUtilities.ts +392 -0
- package/src/audio/SoxPath.ts +24 -0
- package/src/cli/CLI.ts +1360 -0
- package/src/cli/CLIConfigFile.ts +91 -0
- package/src/cli/CLILauncher.ts +26 -0
- package/src/cli/CLIOptionsSchema.ts +54 -0
- package/src/cli/CLIParser.ts +41 -0
- package/src/cli/CLIStarter.ts +40 -0
- package/src/codecs/FFMpegTranscoder.ts +214 -0
- package/src/codecs/TIMITCodec.ts +17 -0
- package/src/codecs/WaveCodec.ts +260 -0
- package/src/denoising/RNNoise.ts +95 -0
- package/src/dsp/BiquadFilter.ts +488 -0
- package/src/dsp/FFT.ts +187 -0
- package/src/dsp/MFCC.ts +227 -0
- package/src/dsp/MelSpectogram.ts +145 -0
- package/src/dsp/Rubberband.ts +249 -0
- package/src/dsp/Sonic.ts +59 -0
- package/src/dsp/SpeexResampler.ts +79 -0
- package/src/math/VectorMath.ts +812 -0
- package/src/nlp/ChineseSegmentation.ts +68 -0
- package/src/nlp/CompromiseNLP.ts +113 -0
- package/src/nlp/EspeakPhonemizer.ts +168 -0
- package/src/nlp/IPA.ts +139 -0
- package/src/nlp/JapaneseSegmentation.ts +53 -0
- package/src/nlp/Lexicon.ts +119 -0
- package/src/nlp/PhoneConversion.ts +508 -0
- package/src/nlp/Segmentation.ts +237 -0
- package/src/nlp/TextNormalizer.ts +160 -0
- package/src/recognition/AmazonTranscribeSTT.ts +112 -0
- package/src/recognition/AzureCognitiveServicesSTT.ts +76 -0
- package/src/recognition/GoogleCloudSTT.ts +92 -0
- package/src/recognition/SileroSTT.ts +173 -0
- package/src/recognition/VoskSTT.ts +112 -0
- package/src/recognition/WhisperSTT.ts +1518 -0
- package/src/server/Client.ts +297 -0
- package/src/server/Server.ts +178 -0
- package/src/server/ServerStarter.ts +12 -0
- package/src/server/Worker.ts +400 -0
- package/src/server/WorkerStarter.ts +38 -0
- package/src/speech-language-detection/SileroLanguageDetection.ts +105 -0
- package/src/subtitles/Subtitles.ts +478 -0
- package/src/synthesis/AwsPollyTTS.ts +78 -0
- package/src/synthesis/AzureCognitiveServicesTTS.ts +146 -0
- package/src/synthesis/CoquiServerTTS.ts +29 -0
- package/src/synthesis/ElevenLabsTTS.ts +104 -0
- package/src/synthesis/EspeakTTS.ts +552 -0
- package/src/synthesis/FliteTTS.ts +387 -0
- package/src/synthesis/GoogleCloudTTS.ts +112 -0
- package/src/synthesis/GoogleTranslateTTS.ts +210 -0
- package/src/synthesis/MicrosoftEdgeTTS.ts +298 -0
- package/src/synthesis/SamTTS.ts +30 -0
- package/src/synthesis/SapiTTS.ts +222 -0
- package/src/synthesis/StreamlabsPollyTTS.ts +114 -0
- package/src/synthesis/SvoxPicoTTS.ts +318 -0
- package/src/synthesis/VitsTTS.ts +734 -0
- package/src/tests/Test.ts +24 -0
- package/src/text-language-detection/FastTextLanguageDetection.ts +53 -0
- package/src/text-language-detection/TinyLDLanguageDetection.ts +16 -0
- package/src/typings/Fillers.d.ts +41 -0
- package/src/utilities/BinaryArrayConversion.ts +159 -0
- package/src/utilities/Compression.ts +91 -0
- package/src/utilities/FileDownloader.ts +201 -0
- package/src/utilities/FileSystem.ts +265 -0
- package/src/utilities/Hashing.ts +230 -0
- package/src/utilities/Locale.ts +119 -0
- package/src/utilities/Logger.ts +72 -0
- package/src/utilities/NdArrayUtilities.ts +31 -0
- package/src/utilities/ObjectUtilities.ts +169 -0
- package/src/utilities/OpenPromise.ts +13 -0
- package/src/utilities/PackageManager.ts +97 -0
- package/src/utilities/Queue.ts +17 -0
- package/src/utilities/RandomGenerator.ts +237 -0
- package/src/utilities/SignalChannel.ts +22 -0
- package/src/utilities/TarballMaker.ts +68 -0
- package/src/utilities/Timeline.ts +231 -0
- package/src/utilities/Timer.ts +93 -0
- package/src/utilities/Utilities.ts +574 -0
- package/src/utilities/WasmMemoryManager.ts +516 -0
- package/src/utilities/WebReader.ts +55 -0
- package/src/utilities/WikipediaReader.ts +41 -0
- package/src/voice-activity-detection/SileroVAD.ts +86 -0
- package/src/voice-activity-detection/WebRtcVAD.ts +76 -0
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { indexOfMax } from "../math/VectorMath.js"
|
|
2
|
+
import { wordCharacterPattern } from '../nlp/Segmentation.js'
|
|
3
|
+
import Onnx from 'onnxruntime-node'
|
|
4
|
+
import { Logger } from '../utilities/Logger.js'
|
|
5
|
+
import { logToStderr } from "../utilities/Utilities.js"
|
|
6
|
+
import { Timeline } from "../utilities/Timeline.js"
|
|
7
|
+
import { RawAudio, getRawAudioDuration } from "../audio/AudioUtilities.js"
|
|
8
|
+
import { readAndParseJsonFile, readFile } from "../utilities/FileSystem.js"
|
|
9
|
+
import path from "path"
|
|
10
|
+
|
|
11
|
+
const log = logToStderr
|
|
12
|
+
|
|
13
|
+
export async function recognize(rawAudio: RawAudio, modelDirectory: string) {
|
|
14
|
+
const logger = new Logger()
|
|
15
|
+
logger.start("Create ONNX inference session")
|
|
16
|
+
|
|
17
|
+
const modelPath = path.join(modelDirectory, "model.onnx")
|
|
18
|
+
const labelsPath = path.join(modelDirectory, "labels.json")
|
|
19
|
+
|
|
20
|
+
const labels: string[] = await readAndParseJsonFile(labelsPath)
|
|
21
|
+
|
|
22
|
+
const onnxOptions: Onnx.InferenceSession.SessionOptions = {
|
|
23
|
+
logSeverityLevel: 3
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const recognition = await Onnx.InferenceSession.create(modelPath, onnxOptions)
|
|
27
|
+
|
|
28
|
+
logger.start("Prepare input data")
|
|
29
|
+
|
|
30
|
+
const audioSamples = rawAudio.audioChannels[0]
|
|
31
|
+
|
|
32
|
+
const inputTensor = new Onnx.Tensor('float32', audioSamples, [1, audioSamples.length])
|
|
33
|
+
|
|
34
|
+
const inputs = { input: inputTensor }
|
|
35
|
+
|
|
36
|
+
logger.start("Recognize with silero model")
|
|
37
|
+
|
|
38
|
+
const results = await recognition.run(inputs)
|
|
39
|
+
|
|
40
|
+
const rawResultValues = results["output"].data as Float32Array
|
|
41
|
+
|
|
42
|
+
const tokenResults: Float32Array[] = []
|
|
43
|
+
|
|
44
|
+
for (let i = 0; i < rawResultValues.length; i += labels.length) {
|
|
45
|
+
tokenResults.push(rawResultValues.subarray(i, i + labels.length))
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const tokens: string[] = []
|
|
49
|
+
|
|
50
|
+
for (const tokenResult of tokenResults) {
|
|
51
|
+
const bestCandidateIndex = indexOfMax(new Array(...tokenResult))
|
|
52
|
+
tokens.push(labels[bestCandidateIndex])
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
//log(tokens.join("|"))
|
|
56
|
+
|
|
57
|
+
const result = processTokens(tokens, getRawAudioDuration(rawAudio))
|
|
58
|
+
|
|
59
|
+
logger.end()
|
|
60
|
+
|
|
61
|
+
return result
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function processTokens(tokens: string[], totalDuration: number) {
|
|
65
|
+
const tokenCount = tokens.length
|
|
66
|
+
|
|
67
|
+
const decodedTokens: string[] = []
|
|
68
|
+
let tokenGroupIndexes: number[][] = [[]]
|
|
69
|
+
|
|
70
|
+
for (let i = 0; i < tokenCount; i++) {
|
|
71
|
+
const token = tokens[i]
|
|
72
|
+
|
|
73
|
+
if (token == "2") {
|
|
74
|
+
if (decodedTokens.length > 0) {
|
|
75
|
+
const previousDecodedToken = decodedTokens[decodedTokens.length - 1]
|
|
76
|
+
decodedTokens.push("$")
|
|
77
|
+
decodedTokens.push(previousDecodedToken)
|
|
78
|
+
|
|
79
|
+
tokenGroupIndexes[tokenGroupIndexes.length - 1].push(i)
|
|
80
|
+
} else {
|
|
81
|
+
decodedTokens.push(" ")
|
|
82
|
+
tokenGroupIndexes.push([])
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
continue
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (token == "_") {
|
|
89
|
+
continue
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
decodedTokens.push(token)
|
|
93
|
+
|
|
94
|
+
if (token == " ") {
|
|
95
|
+
tokenGroupIndexes.push([])
|
|
96
|
+
} else {
|
|
97
|
+
tokenGroupIndexes[tokenGroupIndexes.length - 1].push(i)
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
let decodedString = ""
|
|
102
|
+
|
|
103
|
+
for (let i = 0; i < decodedTokens.length; i++) {
|
|
104
|
+
const currentToken = decodedTokens[i]
|
|
105
|
+
const previousToken = decodedTokens[i - 1]
|
|
106
|
+
|
|
107
|
+
if (currentToken != "$" && (previousToken != currentToken || previousToken == undefined)) {
|
|
108
|
+
decodedString += currentToken
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
decodedString = decodedString.trim()
|
|
113
|
+
|
|
114
|
+
tokenGroupIndexes = tokenGroupIndexes.filter(group => group.length > 0)
|
|
115
|
+
|
|
116
|
+
if (tokenGroupIndexes.length > 0) {
|
|
117
|
+
let currentCorrection = Math.min(tokenGroupIndexes[0][0], 1.5)
|
|
118
|
+
|
|
119
|
+
for (let i = 0; i < tokenGroupIndexes.length; i++) {
|
|
120
|
+
const group = tokenGroupIndexes[i]
|
|
121
|
+
|
|
122
|
+
if (group.length == 1) {
|
|
123
|
+
group.push(group[0])
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
group[0] -= currentCorrection
|
|
127
|
+
|
|
128
|
+
if (i == tokenGroupIndexes.length - 1) {
|
|
129
|
+
currentCorrection = Math.min(tokenCount - i, 1.5)
|
|
130
|
+
} else {
|
|
131
|
+
currentCorrection = Math.min((tokenGroupIndexes[i + 1][0] - group[group.length - 1]) / 2, 1.5)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
group[group.length - 1] += currentCorrection
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const words = decodedString.split(" ")
|
|
139
|
+
|
|
140
|
+
const timeMultiplier = totalDuration / tokenCount
|
|
141
|
+
|
|
142
|
+
const timeline: Timeline = []
|
|
143
|
+
|
|
144
|
+
for (let i = 0; i < words.length; i++) {
|
|
145
|
+
const text = words[i]
|
|
146
|
+
|
|
147
|
+
if (!wordCharacterPattern.test(text)) {
|
|
148
|
+
continue
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const group = tokenGroupIndexes[i]
|
|
152
|
+
const startTime = group[0] * timeMultiplier
|
|
153
|
+
const endTime = group[group.length - 1] * timeMultiplier
|
|
154
|
+
|
|
155
|
+
timeline.push({
|
|
156
|
+
type: "word",
|
|
157
|
+
text: text,
|
|
158
|
+
startTime,
|
|
159
|
+
endTime,
|
|
160
|
+
})
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
timeline[timeline.length - 1].endTime = totalDuration
|
|
164
|
+
|
|
165
|
+
return { transcript: decodedString, timeline }
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export const languageCodeToPackageName: { [languageCode: string]: string } = {
|
|
169
|
+
"en": "silero-en-v5",
|
|
170
|
+
"es": "silero-es-v1",
|
|
171
|
+
"de": "silero-de-v1",
|
|
172
|
+
"uk": "silero-ua-v3",
|
|
173
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import * as FFMpegTranscoder from "../codecs/FFMpegTranscoder.js"
|
|
2
|
+
import * as AudioBufferConversion from '../audio/AudioBufferConversion.js'
|
|
3
|
+
import { Logger } from "../utilities/Logger.js"
|
|
4
|
+
import { logToStderr } from "../utilities/Utilities.js"
|
|
5
|
+
import { Timeline } from "../utilities/Timeline.js"
|
|
6
|
+
import { RawAudio } from "../audio/AudioUtilities.js"
|
|
7
|
+
|
|
8
|
+
const log = logToStderr
|
|
9
|
+
|
|
10
|
+
export async function recognizeFile(filename: string, modelPath: string, verbose = true) {
|
|
11
|
+
const rawAudio = await FFMpegTranscoder.decodeToChannels(filename, 16000, 1)
|
|
12
|
+
return recognize(rawAudio, modelPath, verbose)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export async function recognize(rawAudio: RawAudio, modelPath: string, verbose = true) {
|
|
16
|
+
const logger = new Logger()
|
|
17
|
+
logger.start("Initialize vosk recognizer")
|
|
18
|
+
|
|
19
|
+
const audioChannels = rawAudio.audioChannels
|
|
20
|
+
const sampleRate = rawAudio.sampleRate
|
|
21
|
+
|
|
22
|
+
let Vosk = await import('@echogarden/vosk')
|
|
23
|
+
|
|
24
|
+
Vosk.setLogLevel(-1)
|
|
25
|
+
|
|
26
|
+
const model = await new Vosk.Model(modelPath)
|
|
27
|
+
|
|
28
|
+
const recognizer = new Vosk.Recognizer({ model, sampleRate })
|
|
29
|
+
|
|
30
|
+
recognizer.setMaxAlternatives(0)
|
|
31
|
+
recognizer.setWords(true)
|
|
32
|
+
recognizer.setPartialWords(true)
|
|
33
|
+
|
|
34
|
+
logger.start("Recognize with vosk")
|
|
35
|
+
|
|
36
|
+
const recognitionStartTimestamp = logger.getTimestamp()
|
|
37
|
+
|
|
38
|
+
const pcmAudio = AudioBufferConversion.encodeToAudioBuffer(audioChannels, 16)
|
|
39
|
+
const trailingSilence = Buffer.alloc(sampleRate * 4)
|
|
40
|
+
const pcmAudioWithTrailingSilence = Buffer.concat([pcmAudio, trailingSilence])
|
|
41
|
+
const pcmAudioByteCount = pcmAudioWithTrailingSilence.length
|
|
42
|
+
|
|
43
|
+
const maxChunkSize = sampleRate * 2.0
|
|
44
|
+
|
|
45
|
+
let previousResultText = ""
|
|
46
|
+
|
|
47
|
+
for (let readOffset = 0; readOffset < pcmAudioByteCount; readOffset += maxChunkSize) {
|
|
48
|
+
const chunkSize = Math.min(maxChunkSize, pcmAudioByteCount - readOffset)
|
|
49
|
+
|
|
50
|
+
const chunk = pcmAudioWithTrailingSilence.subarray(readOffset, readOffset + chunkSize)
|
|
51
|
+
|
|
52
|
+
const speechEnded = await recognizer.acceptWaveformAsync(chunk)
|
|
53
|
+
|
|
54
|
+
if (verbose) {
|
|
55
|
+
const partialResultText = recognizer.partialResult().partial
|
|
56
|
+
|
|
57
|
+
if (partialResultText != previousResultText) {
|
|
58
|
+
//logger.log(partialResultText)
|
|
59
|
+
//logger.log("")
|
|
60
|
+
|
|
61
|
+
previousResultText = partialResultText
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
//const speechEnded = await recognizer.acceptWaveformAsync(pcmAudioWithTrailingSilence)
|
|
67
|
+
|
|
68
|
+
const result = recognizer.finalResult()
|
|
69
|
+
|
|
70
|
+
recognizer.reset()
|
|
71
|
+
recognizer.free()
|
|
72
|
+
model.free()
|
|
73
|
+
|
|
74
|
+
const transcript: string = result.text
|
|
75
|
+
const events: VoskRecognitionEvent[] = result.result
|
|
76
|
+
|
|
77
|
+
if (events.length == 0) {
|
|
78
|
+
return { transcript, timeline: [] }
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const timeline: Timeline = []
|
|
82
|
+
|
|
83
|
+
for (let i = 0; i < events.length; i++) {
|
|
84
|
+
const event = events[i]
|
|
85
|
+
|
|
86
|
+
const eventText = event.word
|
|
87
|
+
const eventStart = event.start
|
|
88
|
+
const eventEnd = event.end
|
|
89
|
+
const eventConfidence = event.conf
|
|
90
|
+
|
|
91
|
+
timeline.push({
|
|
92
|
+
type: "word",
|
|
93
|
+
text: eventText,
|
|
94
|
+
startTime: eventStart,
|
|
95
|
+
endTime: eventEnd,
|
|
96
|
+
confidence: eventConfidence
|
|
97
|
+
})
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
//logger.logDuration(`Recognition with vosk`, recognitionStartTimestamp)
|
|
101
|
+
|
|
102
|
+
logger.end()
|
|
103
|
+
|
|
104
|
+
return { transcript, timeline }
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
type VoskRecognitionEvent = {
|
|
108
|
+
word: string
|
|
109
|
+
start: number
|
|
110
|
+
end: number
|
|
111
|
+
conf: number
|
|
112
|
+
}
|