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,24 @@
|
|
|
1
|
+
import { getRepetitionScoreRelativeToFirstSubstring, logToStderr, setupProgramTerminationListeners } from "../utilities/Utilities.js"
|
|
2
|
+
import { makeTarballsForInstalledPackages } from "../utilities/TarballMaker.js"
|
|
3
|
+
import { testEspeakSynthesisWithPrePhonemizedInputs, testKirshenbaumPhonemization } from "../synthesis/EspeakTTS.js"
|
|
4
|
+
|
|
5
|
+
const log = logToStderr
|
|
6
|
+
|
|
7
|
+
setupProgramTerminationListeners()
|
|
8
|
+
//process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'
|
|
9
|
+
//process.env.http_proxy = "http://localhost:8080"
|
|
10
|
+
|
|
11
|
+
//const testText = `After a while, finding that nothing more happened, she decided on going into the garden at once; but, alas for poor Alice! when she got to the door, she found she had forgotten the little golden key, and when she went back to the table for it, she found she could not possibly reach it: she could see it quite plainly through the glass, and she tried her best to climb up one of the legs of the table, but it was too slippery; and when she had tired herself out with trying, the poor little thing sat down and cried.`
|
|
12
|
+
|
|
13
|
+
//await testKirshenbaumPhonemization(testText)
|
|
14
|
+
|
|
15
|
+
//await makeTarballsForInstalledPackages(true)
|
|
16
|
+
|
|
17
|
+
//getRepetitionScoreRelativeToFirstSubstring(['a', 'b', 'c', 'a', 'b', 'c'])
|
|
18
|
+
//getRepetitionScoreRelativeToFirstSubstring(['a', 'b', 'a', 'd', 'a', 'b', 'a', 'd'])
|
|
19
|
+
//getRepetitionScoreRelativeToFirstSubstring(['a', 'b', 'a', 'b', 'c', 'a', 'b', 'a', 'b'])
|
|
20
|
+
//getRepetitionScoreRelativeToFirstSubstring(['a', 'a', 'a', 'b', 'a', 'a', 'a', 'b'])
|
|
21
|
+
//getRepetitionScoreRelativeToFirstSubstring(['a', 'b', 'a', 'c', 'a', 'b', 'a', 'c', 'a'])
|
|
22
|
+
//getRepetitionScoreRelativeToFirstSubstring(['a', 'a', 'a', 'b', 'b', 'a', 'a', 'a', 'b'])
|
|
23
|
+
|
|
24
|
+
process.exit(0)
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import path from 'path'
|
|
2
|
+
import type { LanguageDetectionResults } from '../api/LanguageDetection.js'
|
|
3
|
+
import { languageCodeToName } from '../utilities/Locale.js'
|
|
4
|
+
import { OpenPromise } from '../utilities/OpenPromise.js'
|
|
5
|
+
import { resolveModuleMainPath, roundToDigits } from '../utilities/Utilities.js'
|
|
6
|
+
|
|
7
|
+
let fastTextLanguageDetectionModel: any
|
|
8
|
+
|
|
9
|
+
export async function detectLanguage(text: string) {
|
|
10
|
+
const model = await loadLanguageDetectionModel()
|
|
11
|
+
|
|
12
|
+
const predictionsVector = model.predict(text, -1, 0.0)
|
|
13
|
+
|
|
14
|
+
const predictions: LanguageDetectionResults = []
|
|
15
|
+
|
|
16
|
+
for (let i = 0; i < predictionsVector.size(); i++) {
|
|
17
|
+
const prediction = predictionsVector.get(i)
|
|
18
|
+
const languageCode = prediction[1].substring(9)
|
|
19
|
+
const probability = roundToDigits(prediction[0], 3)
|
|
20
|
+
|
|
21
|
+
predictions.push({
|
|
22
|
+
language: languageCode,
|
|
23
|
+
languageName: languageCodeToName(languageCode),
|
|
24
|
+
probability
|
|
25
|
+
})
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return predictions
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function loadLanguageDetectionModel() {
|
|
32
|
+
if (fastTextLanguageDetectionModel) {
|
|
33
|
+
return fastTextLanguageDetectionModel
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const { FastText, addOnPostRun } = await import('@echogarden/fasttext-wasm')
|
|
37
|
+
|
|
38
|
+
const resultPromise = new OpenPromise<any>()
|
|
39
|
+
|
|
40
|
+
addOnPostRun(async () => {
|
|
41
|
+
const fastText = new FastText()
|
|
42
|
+
|
|
43
|
+
const moduleMainPath = await resolveModuleMainPath('@echogarden/fasttext-wasm')
|
|
44
|
+
const modelPath = path.join(path.dirname(moduleMainPath), 'lid.176.ftz')
|
|
45
|
+
|
|
46
|
+
const model = await fastText.loadModel(modelPath)
|
|
47
|
+
|
|
48
|
+
fastTextLanguageDetectionModel = model
|
|
49
|
+
resultPromise.resolve(model)
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
return resultPromise.promise
|
|
53
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { LanguageDetectionResults } from '../api/LanguageDetection.js'
|
|
2
|
+
import { detectAll } from 'tinyld'
|
|
3
|
+
import { languageCodeToName } from '../utilities/Locale.js'
|
|
4
|
+
|
|
5
|
+
export async function detectLanguage(text: string) {
|
|
6
|
+
const tinyldResults = detectAll(text)
|
|
7
|
+
|
|
8
|
+
const results: LanguageDetectionResults =
|
|
9
|
+
tinyldResults.map(result => ({
|
|
10
|
+
language: result.lang,
|
|
11
|
+
languageName: languageCodeToName(result.lang),
|
|
12
|
+
probability: result.accuracy
|
|
13
|
+
}))
|
|
14
|
+
|
|
15
|
+
return results
|
|
16
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
declare module 'moving-median'
|
|
2
|
+
declare module 'html-to-text'
|
|
3
|
+
declare module 'cldr-segmentation'
|
|
4
|
+
|
|
5
|
+
declare module 'html-escaper' {
|
|
6
|
+
export function escape(str: string): string
|
|
7
|
+
export function unescape(str: string): string
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
declare module 'sam-js'
|
|
11
|
+
declare module 'winax'
|
|
12
|
+
|
|
13
|
+
declare module 'tinyld' {
|
|
14
|
+
export declare function detect(text: string, opts?: any): string
|
|
15
|
+
|
|
16
|
+
export declare function detectAll(text: string, opts?: any): {
|
|
17
|
+
lang: string
|
|
18
|
+
accuracy: number
|
|
19
|
+
}[]
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
declare module 'command-exists' {
|
|
23
|
+
function commandExists(commandName: string): Promise<string>
|
|
24
|
+
|
|
25
|
+
export default commandExists
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
declare module 'wtf_wikipedia'
|
|
29
|
+
|
|
30
|
+
declare module 'kuromoji'
|
|
31
|
+
|
|
32
|
+
declare module '@echogarden/espeak-ng-emscripten'
|
|
33
|
+
declare module '@echogarden/svoxpico-wasm'
|
|
34
|
+
declare module '@echogarden/fasttext-wasm'
|
|
35
|
+
declare module '@echogarden/rubberband-wasm'
|
|
36
|
+
declare module '@echogarden/rnnoise-wasm'
|
|
37
|
+
declare module '@echogarden/fvad-wasm'
|
|
38
|
+
declare module '@echogarden/sonic-wasm'
|
|
39
|
+
declare module '@echogarden/kissfft-wasm'
|
|
40
|
+
declare module '@echogarden/speex-resampler-wasm'
|
|
41
|
+
declare module '@echogarden/vosk'
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
// Typed arrays to Buffer (little endian) conversions
|
|
2
|
+
|
|
3
|
+
// int8 <-> bufferLE
|
|
4
|
+
export function int8ToBuffer(ints: Int8Array) {
|
|
5
|
+
const buffer = Buffer.alloc(ints.length)
|
|
6
|
+
|
|
7
|
+
for (let i = 0; i < ints.length; i++) {
|
|
8
|
+
buffer[i] = ints[i] + 128
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
return buffer
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function bufferToInt8(buffer: Buffer) {
|
|
15
|
+
const result = new Int8Array(buffer.length)
|
|
16
|
+
|
|
17
|
+
for (let i = 0; i < result.length; i++) {
|
|
18
|
+
result[i] = buffer[i] - 128
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return result
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// int16 <-> bufferLE
|
|
25
|
+
export function int16ToBufferLE(ints: Int16Array) {
|
|
26
|
+
const buffer = Buffer.alloc(ints.length * 2)
|
|
27
|
+
|
|
28
|
+
for (let i = 0; i < ints.length; i++) {
|
|
29
|
+
buffer.writeInt16LE(ints[i], i * 2)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return buffer
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function bufferLEToInt16(buffer: Buffer) {
|
|
36
|
+
const result = new Int16Array(buffer.length / 2)
|
|
37
|
+
|
|
38
|
+
for (let i = 0; i < result.length; i++) {
|
|
39
|
+
result[i] = buffer.readInt16LE(i * 2)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return result
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// int24 <-> bufferLE (uses int32 for storage)
|
|
46
|
+
export function int24ToBufferLE(ints: Int32Array) {
|
|
47
|
+
const buffer = Buffer.alloc(ints.length * 3)
|
|
48
|
+
|
|
49
|
+
for (let i = 0; i < ints.length; i++) {
|
|
50
|
+
const val = ints[i]
|
|
51
|
+
const encodedVal = val < 0 ? val + 0x1000000 : val
|
|
52
|
+
|
|
53
|
+
buffer[(i * 3) + 0] = (encodedVal >> 0) & 0xff
|
|
54
|
+
buffer[(i * 3) + 1] = (encodedVal >> 8) & 0xff
|
|
55
|
+
buffer[(i * 3) + 2] = (encodedVal >> 16) & 0xff
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return buffer
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function bufferLEToInt24(buffer: Buffer) {
|
|
62
|
+
const result = new Int32Array(buffer.length / 3)
|
|
63
|
+
|
|
64
|
+
for (let i = 0; i < result.length; i++) {
|
|
65
|
+
const b0 = buffer[(i * 3) + 0]
|
|
66
|
+
const b1 = buffer[(i * 3) + 1]
|
|
67
|
+
const b2 = buffer[(i * 3) + 2]
|
|
68
|
+
|
|
69
|
+
const encodedVal = (b0 << 0) + (b1 << 8) + (b2 << 16)
|
|
70
|
+
result[i] = encodedVal > 0x800000 ? encodedVal - 0x1000000 : encodedVal
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return result
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// int32 <-> bufferLE
|
|
77
|
+
export function int32ToBufferLE(ints: Int32Array) {
|
|
78
|
+
const buffer = Buffer.alloc(ints.length * 4)
|
|
79
|
+
|
|
80
|
+
for (let i = 0; i < ints.length; i++) {
|
|
81
|
+
buffer.writeInt32LE(ints[i], i * 4)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return buffer
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function bufferLEToInt32(buffer: Buffer) {
|
|
88
|
+
const result = new Int32Array(buffer.length / 4)
|
|
89
|
+
|
|
90
|
+
for (let i = 0; i < result.length; i++) {
|
|
91
|
+
result[i] = buffer.readInt32LE(i * 4)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return result
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// float32 <-> bufferLE
|
|
98
|
+
export function float32ToBufferLE(floats: Float32Array) {
|
|
99
|
+
const buffer = Buffer.alloc(floats.length * 4)
|
|
100
|
+
|
|
101
|
+
for (let i = 0; i < floats.length; i++) {
|
|
102
|
+
buffer.writeFloatLE(floats[i], i * 4)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return buffer
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function bufferLEToFloat32(buffer: Buffer) {
|
|
109
|
+
const result = new Float32Array(buffer.length / 4)
|
|
110
|
+
|
|
111
|
+
for (let i = 0; i < result.length; i++) {
|
|
112
|
+
result[i] = buffer.readFloatLE(i * 4)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return result
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// float64 <-> bufferLE
|
|
119
|
+
export function float64ToBufferLE(floats: Float64Array) {
|
|
120
|
+
const buffer = Buffer.alloc(floats.length * 8)
|
|
121
|
+
|
|
122
|
+
for (let i = 0; i < floats.length; i++) {
|
|
123
|
+
buffer.writeDoubleLE(floats[i], i * 8)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return buffer
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function bufferLEToFloat64(buffer: Buffer) {
|
|
130
|
+
const result = new Float64Array(buffer.length / 8)
|
|
131
|
+
|
|
132
|
+
for (let i = 0; i < result.length; i++) {
|
|
133
|
+
result[i] = buffer.readDoubleLE(i * 8)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return result
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// float64 <-> float32
|
|
140
|
+
export function float64Tofloat32(doubles: Float64Array) {
|
|
141
|
+
const floats = new Float32Array(doubles.length)
|
|
142
|
+
|
|
143
|
+
for (let i = 0; i < doubles.length; i++) {
|
|
144
|
+
floats[i] = doubles[i]
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return floats
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export function float32Tofloat64(floats: Float32Array) {
|
|
151
|
+
const doubles = new Float64Array(floats.length)
|
|
152
|
+
|
|
153
|
+
for (let i = 0; i < floats.length; i++) {
|
|
154
|
+
doubles[i] = floats[i]
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return doubles
|
|
158
|
+
}
|
|
159
|
+
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import path from 'node:path'
|
|
2
|
+
import { Logger } from './Logger.js'
|
|
3
|
+
import { readdir, stat } from './FileSystem.js'
|
|
4
|
+
|
|
5
|
+
export async function createTarball(filePath: string, outputFile: string, prefixPath = "") {
|
|
6
|
+
const pathStat = await stat(filePath)
|
|
7
|
+
|
|
8
|
+
if (pathStat.isDirectory()) {
|
|
9
|
+
await createTarballForDir(filePath, outputFile, prefixPath)
|
|
10
|
+
} else {
|
|
11
|
+
await createTarballForFile(filePath, outputFile, prefixPath)
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export async function createTarballForFile(filePath: string, outputFile: string, prefixPath = "") {
|
|
16
|
+
const logger = new Logger()
|
|
17
|
+
|
|
18
|
+
logger.start(`Creating ${prefixPath || path.basename(outputFile)}`)
|
|
19
|
+
|
|
20
|
+
const { default: tar } = await import('tar')
|
|
21
|
+
|
|
22
|
+
const inputFileStat = await stat(filePath)
|
|
23
|
+
|
|
24
|
+
if (!inputFileStat.isFile()) {
|
|
25
|
+
throw new Error(`${filePath} is not a file`)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const filename = path.basename(filePath)
|
|
29
|
+
const dirname = path.dirname(filePath)
|
|
30
|
+
|
|
31
|
+
await tar.create({
|
|
32
|
+
gzip: { level: 9, memLevel: 9 },
|
|
33
|
+
file: outputFile,
|
|
34
|
+
cwd: dirname,
|
|
35
|
+
prefix: prefixPath,
|
|
36
|
+
mode: 0o775,
|
|
37
|
+
|
|
38
|
+
filter: (path, stat) => {
|
|
39
|
+
stat.mode |= 0o775
|
|
40
|
+
|
|
41
|
+
return true
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
}, [filename])
|
|
45
|
+
|
|
46
|
+
logger.end()
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export async function createTarballForDir(inputDir: string, outputFile: string, prefixPath = "") {
|
|
50
|
+
const logger = new Logger()
|
|
51
|
+
|
|
52
|
+
logger.start(`Creating ${prefixPath || path.basename(outputFile)}`)
|
|
53
|
+
|
|
54
|
+
const { default: tar } = await import('tar')
|
|
55
|
+
|
|
56
|
+
const inputDirStat = await stat(inputDir)
|
|
57
|
+
|
|
58
|
+
if (!inputDirStat.isDirectory()) {
|
|
59
|
+
throw new Error(`${inputDir} is not a directory`)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const filesInBaseDir = await readdir(inputDir)
|
|
63
|
+
|
|
64
|
+
await tar.create({
|
|
65
|
+
gzip: { level: 9, memLevel: 9 },
|
|
66
|
+
file: outputFile,
|
|
67
|
+
cwd: inputDir,
|
|
68
|
+
prefix: prefixPath,
|
|
69
|
+
mode: 0o775,
|
|
70
|
+
|
|
71
|
+
filter: (path, stat) => {
|
|
72
|
+
stat.mode |= 0o775
|
|
73
|
+
|
|
74
|
+
return true
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
}, filesInBaseDir)
|
|
78
|
+
|
|
79
|
+
logger.end()
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export async function extractTarball(filepath: string, outputDir: string) {
|
|
83
|
+
const { default: tar } = await import('tar')
|
|
84
|
+
|
|
85
|
+
await tar.extract({
|
|
86
|
+
file: filepath,
|
|
87
|
+
cwd: outputDir,
|
|
88
|
+
preserveOwner: false,
|
|
89
|
+
//noChmod: true
|
|
90
|
+
})
|
|
91
|
+
}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { GaxiosOptions, request } from "gaxios"
|
|
2
|
+
import { Readable } from "stream"
|
|
3
|
+
import { getRandomHexString, writeToStderr } from "./Utilities.js"
|
|
4
|
+
import { OpenPromise } from "./OpenPromise.js"
|
|
5
|
+
|
|
6
|
+
import { Timer } from "./Timer.js"
|
|
7
|
+
import { Logger } from "./Logger.js"
|
|
8
|
+
import path from "node:path"
|
|
9
|
+
import { extractTarball } from "./Compression.js"
|
|
10
|
+
import { createWriteStream, move, remove, readdir, ensureDir } from "./FileSystem.js"
|
|
11
|
+
import chalk from "chalk"
|
|
12
|
+
|
|
13
|
+
export async function downloadAndExtractTarball(options: GaxiosOptions, targetDir: string, baseTempPath: string, displayName = "archive") {
|
|
14
|
+
const logger = new Logger()
|
|
15
|
+
|
|
16
|
+
const randomID = getRandomHexString(16).toLowerCase()
|
|
17
|
+
const tempTarballPath = path.join(baseTempPath, `/${randomID}.tarball`)
|
|
18
|
+
const tempDirPath = path.join(baseTempPath, `/${randomID}`)
|
|
19
|
+
await ensureDir(tempDirPath)
|
|
20
|
+
|
|
21
|
+
await downloadFile(options, tempTarballPath, `${chalk.cyanBright('Downloading')} ${chalk.greenBright(displayName)}`)
|
|
22
|
+
logger.end()
|
|
23
|
+
|
|
24
|
+
logger.start(`Extracting ${displayName}`)
|
|
25
|
+
|
|
26
|
+
await extractTarball(tempTarballPath, tempDirPath)
|
|
27
|
+
|
|
28
|
+
await remove(tempTarballPath)
|
|
29
|
+
|
|
30
|
+
for (const filename of await readdir(tempDirPath)) {
|
|
31
|
+
const sourceFilePath = path.join(tempDirPath, filename)
|
|
32
|
+
const targetFilePath = path.join(targetDir, filename)
|
|
33
|
+
|
|
34
|
+
await move(sourceFilePath, targetFilePath)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
await remove(tempDirPath)
|
|
38
|
+
|
|
39
|
+
logger.end()
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export async function downloadFile(options: GaxiosOptions, targetFilePath: string, prompt = "Downloading") {
|
|
43
|
+
const write = writeToStderr
|
|
44
|
+
|
|
45
|
+
const downloadPromise = new OpenPromise<void>()
|
|
46
|
+
|
|
47
|
+
const timer = new Timer()
|
|
48
|
+
|
|
49
|
+
options.responseType = 'stream'
|
|
50
|
+
|
|
51
|
+
const response = await request<Readable>(options)
|
|
52
|
+
|
|
53
|
+
const ttyOutput = process.stderr.isTTY === true
|
|
54
|
+
|
|
55
|
+
write(`\n${prompt}.. `)
|
|
56
|
+
|
|
57
|
+
const rateAveragingWindowSeconds = 5.0
|
|
58
|
+
|
|
59
|
+
let downloadStarted = false
|
|
60
|
+
let downloadedBytes = 0
|
|
61
|
+
let totalBytes: number | undefined = undefined
|
|
62
|
+
|
|
63
|
+
const statusUpdateInterval = 250
|
|
64
|
+
|
|
65
|
+
let lastString = prompt
|
|
66
|
+
|
|
67
|
+
const downloadStateHistory: { time: number, downloadedMBytes: number }[] = []
|
|
68
|
+
|
|
69
|
+
function updateStatus() {
|
|
70
|
+
if (!downloadStarted) {
|
|
71
|
+
return
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const totalMBytes = (totalBytes || 0) / 1000 / 1000
|
|
75
|
+
const downloadedMBytes = downloadedBytes / 1000 / 1000
|
|
76
|
+
|
|
77
|
+
const elapsedTime = timer.elapsedTimeSeconds
|
|
78
|
+
const cumulativeDownloadRate = downloadedMBytes / elapsedTime
|
|
79
|
+
|
|
80
|
+
const windowStartRecord = downloadStateHistory.find(r => r.time >= timer.elapsedTimeSeconds - rateAveragingWindowSeconds)
|
|
81
|
+
|
|
82
|
+
let windowDownloadRate: number
|
|
83
|
+
|
|
84
|
+
if (windowStartRecord) {
|
|
85
|
+
windowDownloadRate = (downloadedMBytes - windowStartRecord.downloadedMBytes) / (elapsedTime - windowStartRecord.time)
|
|
86
|
+
} else {
|
|
87
|
+
windowDownloadRate = cumulativeDownloadRate
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
downloadStateHistory.push({ time: elapsedTime, downloadedMBytes })
|
|
91
|
+
|
|
92
|
+
const isLastUpdate = downloadedMBytes == totalMBytes
|
|
93
|
+
|
|
94
|
+
const downloadedMbytesStr = downloadedMBytes.toFixed(2)
|
|
95
|
+
const totalMbytesStr = totalMBytes.toFixed(2)
|
|
96
|
+
const downloadRateStr = windowDownloadRate.toFixed(2)
|
|
97
|
+
const cumulativeDownloadRateStr = cumulativeDownloadRate.toFixed(2)
|
|
98
|
+
|
|
99
|
+
if (ttyOutput) {
|
|
100
|
+
let newString: string
|
|
101
|
+
|
|
102
|
+
if (totalBytes != undefined) {
|
|
103
|
+
const percentage = (downloadedMBytes / totalMBytes) * 100
|
|
104
|
+
|
|
105
|
+
newString = `${prompt}.. ${downloadedMbytesStr}MB/${totalMbytesStr}MB (${chalk.blueBright(percentage.toFixed(1) + '%')}, ${timer.elapsedTimeSeconds.toFixed(1)}s, ${downloadRateStr}MB/s)`
|
|
106
|
+
} else {
|
|
107
|
+
newString = `${prompt}.. ${downloadedMbytesStr}MB (${timer.elapsedTimeSeconds.toFixed(1)}s, ${downloadRateStr}MB/s)`
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (newString != lastString) {
|
|
111
|
+
write('\r')
|
|
112
|
+
write(newString)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
lastString = newString
|
|
116
|
+
} else {
|
|
117
|
+
if (totalBytes == undefined) {
|
|
118
|
+
return
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const percent = downloadedBytes / totalBytes
|
|
122
|
+
const percentDisplay = `${(Math.floor(percent * 10) * 10).toString()}%`
|
|
123
|
+
|
|
124
|
+
if (lastString == prompt) {
|
|
125
|
+
write(`(${totalMbytesStr}MB): `)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (percentDisplay != lastString) {
|
|
129
|
+
write(percentDisplay)
|
|
130
|
+
|
|
131
|
+
if (percent == 1.0) {
|
|
132
|
+
write(` (${timer.elapsedTimeSeconds.toFixed(2)}s, ${cumulativeDownloadRateStr}MB/s)`)
|
|
133
|
+
} else {
|
|
134
|
+
write(` `)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
lastString = percentDisplay
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const partialFilePath = `${targetFilePath}.${getRandomHexString(16)}.partial`
|
|
143
|
+
const fileWriteStream = createWriteStream(partialFilePath, { encoding: "binary", autoClose: true })
|
|
144
|
+
|
|
145
|
+
let statusInterval = setInterval(() => {
|
|
146
|
+
updateStatus()
|
|
147
|
+
}, statusUpdateInterval)
|
|
148
|
+
|
|
149
|
+
response.data.on("data", (chunk: Uint8Array) => {
|
|
150
|
+
try {
|
|
151
|
+
const contentLengthString = response.headers['content-length']
|
|
152
|
+
|
|
153
|
+
totalBytes = contentLengthString != undefined ? parseInt(contentLengthString) : undefined
|
|
154
|
+
|
|
155
|
+
const chunkLength = chunk.length
|
|
156
|
+
|
|
157
|
+
fileWriteStream.write(chunk)
|
|
158
|
+
|
|
159
|
+
downloadedBytes += chunkLength
|
|
160
|
+
|
|
161
|
+
if (downloadStarted == false) {
|
|
162
|
+
downloadStarted = true
|
|
163
|
+
}
|
|
164
|
+
} catch (err) {
|
|
165
|
+
clearInterval(statusInterval)
|
|
166
|
+
|
|
167
|
+
downloadPromise.reject(err)
|
|
168
|
+
}
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
response.data.on("end", async () => {
|
|
172
|
+
try {
|
|
173
|
+
clearInterval(statusInterval)
|
|
174
|
+
updateStatus()
|
|
175
|
+
|
|
176
|
+
fileWriteStream.end()
|
|
177
|
+
|
|
178
|
+
write("\n")
|
|
179
|
+
|
|
180
|
+
await move(partialFilePath, targetFilePath)
|
|
181
|
+
|
|
182
|
+
downloadPromise.resolve()
|
|
183
|
+
} catch (err) {
|
|
184
|
+
clearInterval(statusInterval)
|
|
185
|
+
downloadPromise.reject(err)
|
|
186
|
+
}
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
response.data.on("error", async (err: any) => {
|
|
190
|
+
try {
|
|
191
|
+
clearInterval(statusInterval)
|
|
192
|
+
|
|
193
|
+
fileWriteStream.end()
|
|
194
|
+
await remove(partialFilePath)
|
|
195
|
+
} finally {
|
|
196
|
+
downloadPromise.reject(err)
|
|
197
|
+
}
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
return downloadPromise.promise
|
|
201
|
+
}
|