UNPKG

@henteko/kumiki

Version:

A video generation tool that creates videos from JSON configurations

155 lines 5.48 kB
import crypto from 'node:crypto'; import fs from 'node:fs/promises'; import path from 'node:path'; import { getNarrationCacheDir } from '../utils/app-dirs.js'; import { logger } from '../utils/logger.js'; export class NarrationCacheService { cacheDir; initialized = false; constructor(cacheDir) { this.cacheDir = cacheDir || getNarrationCacheDir(); } async initialize() { if (this.initialized) return; await this.ensureCacheDir(); this.initialized = true; } async ensureCacheDir() { await fs.mkdir(this.cacheDir, { recursive: true }); } generateCacheKey(key) { const content = JSON.stringify({ text: key.text, voice: key.voice || {}, }); return crypto .createHash('sha256') .update(content) .digest('hex'); } getCachePath(key) { const hash = this.generateCacheKey(key); return path.join(this.cacheDir, `${hash}.wav`); } async exists(key) { const cachePath = this.getCachePath(key); try { await fs.access(cachePath); return true; } catch { return false; } } async get(key) { const cachePath = this.getCachePath(key); if (await this.exists(key)) { logger.debug('Narration cache hit', { text: key.text.substring(0, 50) + '...', cachePath }); return cachePath; } logger.debug('Narration cache miss', { text: key.text.substring(0, 50) + '...', cachePath }); return null; } async set(key, audioPath) { await this.ensureCacheDir(); const cachePath = this.getCachePath(key); // Copy audio file to cache await fs.copyFile(audioPath, cachePath); logger.debug('Narration cached', { text: key.text.substring(0, 50) + '...', cachePath }); return cachePath; } async clear(options) { try { const files = await fs.readdir(this.cacheDir); const wavFilePaths = files .filter(file => file.endsWith('.wav')) .map(file => path.join(this.cacheDir, file)); let filesToDelete = wavFilePaths; if (options?.olderThan) { const olderThanMs = options.olderThan; const statPromises = wavFilePaths.map(async (filePath) => { const stats = await fs.stat(filePath); const age = Date.now() - stats.mtimeMs; return age > olderThanMs ? filePath : null; }); const results = await Promise.all(statPromises); filesToDelete = results.filter((file) => file !== null); } await Promise.all(filesToDelete.map(filePath => fs.unlink(filePath))); const clearedCount = filesToDelete.length; logger.info(`Narration cache cleared: ${clearedCount} files`); return clearedCount; } catch (error) { if (error.code !== 'ENOENT') { throw error; } return 0; } } async getStats() { try { const files = await fs.readdir(this.cacheDir); const wavFiles = files.filter(file => file.endsWith('.wav')); const statsPromises = wavFiles.map(file => fs.stat(path.join(this.cacheDir, file))); const statsArray = await Promise.all(statsPromises); const totalSize = statsArray.reduce((sum, stats) => sum + stats.size, 0); return { totalFiles: wavFiles.length, totalSize }; } catch (error) { if (error.code === 'ENOENT') { return { totalFiles: 0, totalSize: 0 }; } throw error; } } async getStatus() { try { const files = await fs.readdir(this.cacheDir); const wavFiles = files.filter(file => file.endsWith('.wav')); if (wavFiles.length === 0) { return { totalFiles: 0, totalSize: 0 }; } const statsPromises = wavFiles.map(file => fs.stat(path.join(this.cacheDir, file))); const statsArray = await Promise.all(statsPromises); let totalSize = 0; let oldestTime = null; let newestTime = null; for (const stats of statsArray) { totalSize += stats.size; const time = stats.mtimeMs; if (oldestTime === null || time < oldestTime) { oldestTime = time; } if (newestTime === null || time > newestTime) { newestTime = time; } } return { totalFiles: wavFiles.length, totalSize, oldestEntry: oldestTime ? new Date(oldestTime) : undefined, newestEntry: newestTime ? new Date(newestTime) : undefined, }; } catch (error) { if (error.code === 'ENOENT') { return { totalFiles: 0, totalSize: 0 }; } throw error; } } } // Singleton instance export const narrationCacheService = new NarrationCacheService(); //# sourceMappingURL=narration-cache.js.map