@eleven-am/transcoder
Version:
High-performance HLS transcoding library with hardware acceleration, intelligent client management, and distributed processing support for Node.js
205 lines • 7.97 kB
JavaScript
;
/*
* @eleven-am/transcoder
* Copyright (C) 2025 Roy OSSAI
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.FileStorage = void 0;
const crypto = __importStar(require("crypto"));
const fs = __importStar(require("fs"));
const pfs = __importStar(require("fs/promises"));
const path = __importStar(require("path"));
const fp_1 = require("@eleven-am/fp");
class FileStorage {
constructor(cacheDirectory) {
this.cacheDirectory = cacheDirectory;
fs.mkdirSync(this.cacheDirectory, { recursive: true });
}
/**
* Generates a unique file ID based on the file's inode number.
* @param filePath The path to the file.
* @returns A TaskEither containing the generated file ID or an error.
*/
generateFileId(filePath) {
return fp_1.TaskEither
.tryCatch(() => pfs.stat(filePath), 'Failed to generate file ID')
.map((stat) => {
const hash = crypto.createHash('sha256');
hash.update(stat.ino.toString());
return hash.digest('hex');
});
}
/**
* Deletes a file at the specified path.
* @param path The path to the file to delete.
* @returns A TaskEither indicating success or failure.
*/
deleteFile(path) {
return fp_1.TaskEither
.tryCatch(() => pfs.unlink(path), 'Failed to delete file')
.orElse((err) => {
if (err.error.message.includes('ENOENT')) {
return fp_1.TaskEither.of(undefined);
}
return fp_1.TaskEither.error((0, fp_1.createUnknownError)('Failed to delete file')(err.error));
});
}
/**
* Deletes all files with a specified prefix.
* @param prefix The prefix to match files against.
* @returns A TaskEither indicating success or failure.
*/
deleteFilesWithPrefix(prefix) {
return this.listFiles(prefix)
.chainItems((file) => this.deleteFile(file))
.map(() => undefined);
}
/**
* Checks if a file exists at the specified path.
* @param path The path to check.
* @returns A TaskEither containing true if the file exists, false otherwise.
*/
exists(path) {
return fp_1.TaskEither
.tryCatch(() => pfs.stat(path), 'Failed to check file existence')
.map((stat) => stat.isFile())
.orElse(() => fp_1.TaskEither.of(false));
}
/**
* Gets the base path for a given file ID.
* @param fileId The file ID.
* @returns The base path for the file ID.
*/
getBasePath(fileId) {
return path.join(this.cacheDirectory, fileId);
}
/**
* Gets a media source for a file at the specified path.
* @param filePath The path to the file.
* @returns A MediaSource object for the file.
*/
getFileStream(filePath) {
return fp_1.TaskEither
.tryCatch(() => pfs.access(filePath, fs.constants.F_OK), 'Failed to create read stream')
.map(() => fs.createReadStream(filePath));
}
/**
* Lists all files that begin with the given prefix.
* @param prefix The path prefix to search for.
*/
listFiles(prefix) {
const itemStats = (item) => fp_1.TaskEither
.tryCatch(() => pfs.stat(item), 'Failed to get file stats')
.map((stat) => ({
path: item,
isDirectory: stat.isDirectory(),
}))
.matchTask([
{
predicate: (item) => item.isDirectory,
// eslint-disable-next-line @typescript-eslint/no-use-before-define
run: (item) => readDir(item.path),
},
{
predicate: (item) => !item.isDirectory,
run: (item) => fp_1.TaskEither.of([item.path]),
},
]);
const readDir = (dir) => fp_1.TaskEither
.tryCatch(() => pfs.readdir(dir), 'Failed to read directory')
.chainItems(itemStats)
.map((items) => items.flat());
return fp_1.TaskEither
.tryCatch(() => pfs.readdir(prefix), 'Failed to read directory')
.chainItems(itemStats)
.map((items) => items.flat());
}
/**
* Saves data from a readable stream to storage.
* @param filePath The destination path.
* @param content The readable stream containing the data to save.
*/
saveFile(filePath, content) {
const promise = new Promise((resolve, reject) => {
const writeStream = fs.createWriteStream(filePath);
content.on('error', reject);
writeStream.on('finish', resolve);
writeStream.on('error', reject);
content.pipe(writeStream);
});
return fp_1.TaskEither
.of(fs.mkdirSync(path.dirname(filePath), { recursive: true }))
.chain(() => fp_1.TaskEither
.tryCatch(() => promise, 'Failed to save file'));
}
/**
* Gets the size of a file in bytes.
* @param path The path to the file.
* @returns A TaskEither containing the file size or an error.
*/
getFileSize(path) {
return this.exists(path)
.filter((exists) => exists, () => (0, fp_1.createNotFoundError)(`File not found: ${path}`))
.chain(() => fp_1.TaskEither
.tryCatch(() => pfs.stat(path), 'Failed to get file size')
.map((stat) => stat.size));
}
/**
* Ensures that the directory for a given path exists.
* @param path The path of the directory to check.
* @returns A TaskEither containing the path if successful, or an error.
*/
ensureDirectoryExists(path) {
return fp_1.TaskEither
.tryCatch(() => pfs.mkdir(path, { recursive: true }), 'Failed to create directory')
.map(() => path);
}
}
exports.FileStorage = FileStorage;
//# sourceMappingURL=fileStorage.js.map