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
JavaScript
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