import crypto from 'crypto'; import path from 'path'; import fs from 'fs'; export class ThumbnailManager { private static readonly THUMBNAIL_WIDTH = 320; private static readonly HASH_ALGORITHM = 'md5'; private static readonly THUMBNAIL_EXTENSION = '.png'; /** * Generate MD5 hash for a file path */ static generateHash(filePath: string): string { return crypto .createHash(this.HASH_ALGORITHM) .update(filePath) .digest('hex'); } /** * Generate thumbnail filename based on hash and width */ static getThumbnailFilename(hash: string, width: number = this.THUMBNAIL_WIDTH): string { return `${hash}_${width}${this.THUMBNAIL_EXTENSION}`; } /** * Get the folder structure for a given hash */ static getFolderStructure(hash: string): { folder1: string; folder2: string } { return { folder1: hash.substring(0, 2), folder2: hash.substring(2, 4) }; } /** * Get the complete thumbnail path information */ static getThumbnailPath(filePath: string): { folderPath: string; filename: string; fullPath: string; url: string; } { const hash = this.generateHash(filePath); const { folder1, folder2 } = this.getFolderStructure(hash); const filename = this.getThumbnailFilename(hash); const folderPath = path.join('thumbnails', folder1, folder2); const fullPath = path.join(process.cwd(), 'public', folderPath, filename); const url = `/${folderPath}/${filename}`; return { folderPath, filename, fullPath, url }; } /** * Ensure thumbnail directory exists */ static ensureDirectory(folderPath: string): void { const fullPath = path.join(process.cwd(), 'public', folderPath); if (!fs.existsSync(fullPath)) { fs.mkdirSync(fullPath, { recursive: true }); } } /** * Get the base thumbnails directory */ static getThumbnailsBaseDir(): string { return path.join(process.cwd(), 'public', 'thumbnails'); } /** * Check if a thumbnail exists */ static thumbnailExists(filePath: string): boolean { const { fullPath } = this.getThumbnailPath(filePath); return fs.existsSync(fullPath); } /** * Get fallback thumbnail URL based on media type */ static getFallbackThumbnailUrl(mediaType: 'video' | 'photo' | 'text'): string { switch (mediaType) { case 'video': return '/placeholder-video.svg'; case 'photo': return '/placeholder-photo.svg'; default: return '/placeholder.svg'; } } }