UNPKG

echogarden

Version:

An easy-to-use speech toolset. Includes tools for synthesis, recognition, alignment, speech translation, language detection, source separation and more.

245 lines 9.49 kB
import * as fsExtra from 'fs-extra/esm'; import gracefulFS from 'graceful-fs'; import * as os from 'node:os'; import { promisify } from 'node:util'; import { getRandomHexString, parseJson, stringifyAndFormatJson } from './Utilities.js'; import { appName } from '../api/Common.js'; import { getAppTempDir, getDirName, joinPath, normalizePath, parsePath } from './PathUtilities.js'; import { createDynamicUint8Array } from '../data-structures/DynamicTypedArray.js'; import { ChunkedUtf8Decoder } from '../encodings/Utf8.js'; import { FileWriter } from './FileWriter.js'; import { FileReader } from './FileReader.js'; export const open = promisify(gracefulFS.open); export const close = promisify(gracefulFS.close); export const read = promisify(gracefulFS.read); export const write = promisify(gracefulFS.write); export const existsSync = gracefulFS.existsSync; export const stat = promisify(gracefulFS.stat); export const chmod = promisify(gracefulFS.chmod); export const access = promisify(gracefulFS.access); export const readdir = promisify(gracefulFS.readdir); export const copyFile = promisify(gracefulFS.copyFile); export const remove = fsExtra.remove; export const copy = fsExtra.copy; /////////////////////////////////////////////////////////////////////////////////////////// // File read operations /////////////////////////////////////////////////////////////////////////////////////////// export async function readFileAsBinary(filePath) { const chunkSize = 2 ** 20; const fileInfo = await stat(filePath); const fileSize = fileInfo.size; const fileReader = new FileReader(filePath); const buffer = new Uint8Array(chunkSize); const result = createDynamicUint8Array(fileSize); while (!fileReader.isFinished) { const chunk = await fileReader.readChunk(buffer); result.addArray(chunk); } return result.toTypedArray(); } export async function readFileAsUtf8(filePath) { const chunkSize = 2 ** 20; const fileReader = new FileReader(filePath); const buffer = new Uint8Array(chunkSize); const result = new ChunkedUtf8Decoder(); while (!fileReader.isFinished) { const chunk = await fileReader.readChunk(buffer); result.writeChunk(chunk); } return result.toString(); } export async function readAndParseJsonFile(jsonFilePath, useJson5 = false) { const textContent = await readFileAsUtf8(jsonFilePath); return parseJson(textContent, useJson5); } /////////////////////////////////////////////////////////////////////////////////////////// // File write operations /////////////////////////////////////////////////////////////////////////////////////////// export async function writeFile(filePath, content) { if (content instanceof Uint8Array) { return writeBinaryFile(filePath, content); } else if (typeof content === 'string') { return writeUtf8File(filePath, content); } else { throw new Error(`Content can only be a Uint8Array or string`); } } export async function writeBinaryFile(filePath, content) { const maxChunkSize = 2 ** 20; const fileDir = getDirName(filePath); await ensureDir(fileDir); const fileWriter = new FileWriter(filePath); let readOffset = 0; while (true) { const chunk = content.subarray(readOffset, readOffset + maxChunkSize); if (chunk.length === 0) { break; } await fileWriter.write(chunk); readOffset += chunk.length; } await fileWriter.dispose(); } export async function writeUtf8File(filePath, content) { const maxChunkSize = 2 ** 20; const fileDir = getDirName(filePath); await ensureDir(fileDir); const fileWriter = new FileWriter(filePath); const textEncoder = new TextEncoder(); let readOffset = 0; while (true) { const stringChunk = content.substring(readOffset, readOffset + maxChunkSize); const chunk = textEncoder.encode(stringChunk); await fileWriter.write(chunk); readOffset += stringChunk.length; if (stringChunk.length === 0) { break; } } await fileWriter.dispose(); } export async function writeJsonFile(filePath, content, useJson5 = false) { const textContent = await stringifyAndFormatJson(content, useJson5); await writeUtf8File(filePath, textContent); } export async function writeFileSafe(filePath, content) { const tempDir = getAppTempDir(appName); const tempFilePath = joinPath(tempDir, `${getRandomHexString(16)}.partial`); await writeFile(tempFilePath, content); await move(tempFilePath, filePath); } /////////////////////////////////////////////////////////////////////////////////////////// // Directory operations /////////////////////////////////////////////////////////////////////////////////////////// export async function ensureDir(dirPath) { dirPath = normalizePath(dirPath); if (existsSync(dirPath)) { const dirStats = await stat(dirPath); if (!dirStats.isDirectory()) { throw new Error(`The path '${dirPath}' exists but is not a directory.`); } } else { return fsExtra.ensureDir(dirPath); } } export async function testDirectoryIsWritable(dir) { const testFileName = joinPath(dir, getRandomHexString(16)); try { await fsExtra.createFile(testFileName); await remove(testFileName); } catch (e) { return false; } return true; } export async function readDirRecursive(dir, pathFilter) { if (!(await stat(dir)).isDirectory()) { throw new Error(`'${dir}' is not a directory`); } const filenamesInDir = await readdir(dir); const filesInDir = filenamesInDir.map(filename => joinPath(dir, filename)); const result = []; const subDirectories = []; for (const filePath of filesInDir) { if ((await stat(filePath)).isDirectory()) { subDirectories.push(filePath); } else { if (pathFilter && !pathFilter(filePath)) { continue; } result.push(filePath); } } for (const subDirectory of subDirectories) { const filesInSubdirectory = await readDirRecursive(subDirectory, pathFilter); result.push(...filesInSubdirectory); } return result; } export function getAppDataDir(appName) { let dataDir; const platform = process.platform; const homeDir = os.homedir(); if (platform == 'win32') { dataDir = joinPath(homeDir, 'AppData', 'Local', appName); } else if (platform == 'darwin') { dataDir = joinPath(homeDir, 'Library', 'Application Support', appName); } else if (platform == 'linux') { dataDir = joinPath(homeDir, '.local', 'share', appName); } else { throw new Error(`Unsupport platform ${platform}`); } return dataDir; } /////////////////////////////////////////////////////////////////////////////////////////// // Copy operations /////////////////////////////////////////////////////////////////////////////////////////// export async function move(source, dest) { source = normalizePath(source); dest = normalizePath(dest); if (existsSync(dest)) { const destPathExistsAndIsWritable = await existsAndIsWritable(dest); if (!destPathExistsAndIsWritable) { throw new Error(`The destination path '${dest}' exists but is not writable. There may be a permissions or locking issue.`); } } else { const destDir = parsePath(dest).dir; const destDirIsWritable = await testDirectoryIsWritable(destDir); if (!destDirIsWritable) { throw new Error(`The directory ${destDir} is not writable. There may be a permissions issue.`); } } return fsExtra.move(source, dest, { overwrite: true }); } /////////////////////////////////////////////////////////////////////////////////////////// // Misc operations /////////////////////////////////////////////////////////////////////////////////////////// export async function existsAndIsWritable(targetPath) { try { await access(targetPath, gracefulFS.constants.W_OK); } catch { return false; } return true; } export async function chmodRecursive(rootPath, newMode) { const rootPathStat = await stat(rootPath); await chmod(rootPath, newMode); if (rootPathStat.isDirectory()) { const fileList = await readdir(rootPath); for (const filename of fileList) { const filePath = joinPath(rootPath, filename); await chmodRecursive(filePath, newMode); } } } export async function isFileIsUpToDate(filePath, maxTimeDifferenceSeconds) { const fileUpdateTime = (await stat(filePath)).mtime.valueOf(); const currentTime = (new Date()).valueOf(); const differenceInMilliseconds = currentTime - fileUpdateTime; const differenceInSeconds = differenceInMilliseconds / 1000; return differenceInSeconds <= maxTimeDifferenceSeconds; } export async function computeFileSha256Hex(filePath) { const crypto = await import('crypto'); const hash = crypto.createHash('sha256'); const fileReader = new FileReader(filePath); const buffer = new Uint8Array(2 ** 16); while (!fileReader.isFinished) { const chunk = await fileReader.readChunk(buffer); hash.update(chunk); } const result = hash.digest('hex'); return result; } //# sourceMappingURL=FileSystem.js.map