echogarden
Version:
An easy-to-use speech toolset. Includes tools for synthesis, recognition, alignment, speech translation, language detection, source separation and more.
181 lines (125 loc) • 5.03 kB
text/typescript
import { GaxiosOptions, request } from 'gaxios'
import { Readable } from 'stream'
import { getRandomHexString, writeToStderr } from './Utilities.js'
import { Timer } from './Timer.js'
import { Logger } from './Logger.js'
import { extractTarball } from './Compression.js'
import { move, remove, readdir, ensureDir } from './FileSystem.js'
import chalk from 'chalk'
import { logLevelGreaterOrEqualTo } from '../api/GlobalOptions.js'
import { FileWriter } from './FileWriter.js'
import { joinPath } from './PathUtilities.js'
export async function downloadAndExtractTarball(options: GaxiosOptions, targetDir: string, baseTempPath: string, displayName = 'archive') {
const logger = new Logger()
const randomID = getRandomHexString(16).toLowerCase()
const tempTarballPath = joinPath(baseTempPath, `/${randomID}.tarball`)
const tempDirPath = joinPath(baseTempPath, `/${randomID}`)
await ensureDir(tempDirPath)
logger.end()
await downloadFile(options, tempTarballPath, `${chalk.cyanBright('Downloading')} ${chalk.greenBright(displayName)}`)
logger.end()
logger.start(`Extracting ${displayName}`)
await extractTarball(tempTarballPath, tempDirPath)
await remove(tempTarballPath)
for (const filename of await readdir(tempDirPath)) {
const sourceFilePath = joinPath(tempDirPath, filename)
const targetFilePath = joinPath(targetDir, filename)
await move(sourceFilePath, targetFilePath)
}
await remove(tempDirPath)
logger.end()
}
export async function downloadFile(options: GaxiosOptions, targetFilePath: string, prompt = 'Downloading') {
const write = logLevelGreaterOrEqualTo('info') ? writeToStderr : () => {}
const timer = new Timer()
options.responseType = 'stream'
const response = await request<Readable>(options)
const ttyOutput = process.stderr.isTTY === true
write(`\n${prompt}.. `)
const rateAveragingWindowSeconds = 5.0
let downloadStarted = false
let downloadedBytes = 0
let totalBytes: number | undefined = undefined
const statusUpdateInterval = 250
let lastString = prompt
const downloadStateHistory: { time: number, downloadedMBytes: number }[] = []
function updateStatus() {
if (!downloadStarted) {
return
}
const totalMBytes = (totalBytes || 0) / 1000 / 1000
const downloadedMBytes = downloadedBytes / 1000 / 1000
const elapsedTime = timer.elapsedTimeSeconds
const cumulativeDownloadRate = downloadedMBytes / elapsedTime
const windowStartRecord = downloadStateHistory.find(r => r.time >= timer.elapsedTimeSeconds - rateAveragingWindowSeconds)
let windowDownloadRate: number
if (windowStartRecord) {
windowDownloadRate = (downloadedMBytes - windowStartRecord.downloadedMBytes) / (elapsedTime - windowStartRecord.time)
} else {
windowDownloadRate = cumulativeDownloadRate
}
downloadStateHistory.push({ time: elapsedTime, downloadedMBytes })
const isLastUpdate = downloadedMBytes == totalMBytes
const downloadedMbytesStr = downloadedMBytes.toFixed(2)
const totalMbytesStr = totalMBytes.toFixed(2)
const downloadRateStr = windowDownloadRate.toFixed(2)
const cumulativeDownloadRateStr = cumulativeDownloadRate.toFixed(2)
if (ttyOutput) {
let newString: string
if (totalBytes != undefined) {
const percentage = (downloadedMBytes / totalMBytes) * 100
newString = `${prompt}.. ${downloadedMbytesStr}MB/${totalMbytesStr}MB (${chalk.blueBright(percentage.toFixed(1) + '%')}, ${timer.elapsedTimeSeconds.toFixed(1)}s, ${downloadRateStr}MB/s)`
} else {
newString = `${prompt}.. ${downloadedMbytesStr}MB (${timer.elapsedTimeSeconds.toFixed(1)}s, ${downloadRateStr}MB/s)`
}
if (newString != lastString) {
write('\r')
write(newString)
}
lastString = newString
} else {
if (totalBytes == undefined) {
return
}
const percent = downloadedBytes / totalBytes
const percentDisplay = `${(Math.floor(percent * 10) * 10).toString()}%`
if (lastString == prompt) {
write(`(${totalMbytesStr}MB): `)
}
if (percentDisplay != lastString) {
write(percentDisplay)
if (percent == 1.0) {
write(` (${timer.elapsedTimeSeconds.toFixed(2)}s, ${cumulativeDownloadRateStr}MB/s)`)
} else {
write(` `)
}
lastString = percentDisplay
}
}
}
const contentLengthString = response.headers['content-length']
totalBytes = contentLengthString != undefined ? parseInt(contentLengthString) : undefined
const partialFilePath = `${targetFilePath}.${getRandomHexString(16)}.partial`
const fileWriter = new FileWriter(partialFilePath)
let statusInterval = setInterval(() => {
updateStatus()
}, statusUpdateInterval)
try {
for await (const chunk of response.data) {
if (downloadStarted === false) {
downloadStarted = true
}
downloadedBytes += chunk.length
await fileWriter.write(chunk)
}
} catch (e) {
clearInterval(statusInterval)
await fileWriter.dispose()
throw e
}
clearInterval(statusInterval)
updateStatus()
await fileWriter.dispose()
write('\n')
await move(partialFilePath, targetFilePath)
}