import * as fs from 'fs'
import * as path from 'path'
import * as crypto from 'crypto'
/**
* Supported image file extensions for compression.
*/
export const SUPPORTED_EXTENSIONS = ['.png', '.jpg', '.jpeg', '.webp', '.avif']
/**
* Name of the record file to track compressed images.
*/
export const RECORD_FILE_NAME = 'record.json'
/**
* Check if a file is a supported image format.
*
* @param filename The filename to check.
* @returns True if the file is a supported image format.
*/
export function isSupportedImageFile(filename: string): boolean {
const ext = path.extname(filename).toLowerCase()
return SUPPORTED_EXTENSIONS.includes(ext)
}
/**
* Calculate MD5 hash of a file for content-based deduplication.
*
* @param filePath The path to the file.
* @returns Promise that resolves to the MD5 hash string.
*/
export async function calculateFileHash(filePath: string): Promise<string> {
return new Promise((resolve, reject) => {
const hash = crypto.createHash('md5')
const stream = fs.createReadStream(filePath)
stream.on('error', err => reject(err))
stream.on('data', chunk => hash.update(chunk))
stream.on('end', () => resolve(hash.digest('hex')))
})
}
/**
* Recursively find all image files in a directory and subdirectories.
*
* @param dirPath The directory path to scan.
* @param basePath The base path for calculating relative paths.
* @returns Array of objects with full path and relative path.
*/
export async function findAllImageFiles(dirPath: string, basePath: string = dirPath): Promise<Array<{
fullPath: string
relativePath: string
}>> {
const result: Array<{ fullPath: string, relativePath: string }> = []
try {
const entries = await fs.promises.readdir(dirPath, { withFileTypes: true })
for (const entry of entries) {
if (entry.name === RECORD_FILE_NAME) continue
const fullPath = path.join(dirPath, entry.name)
if (entry.isDirectory()) {
/* Recursively scan subdirectories */
const subFiles = await findAllImageFiles(fullPath, basePath)
result.push(...subFiles)
} else if (entry.isFile() && isSupportedImageFile(entry.name)) {
const relativePath = path.relative(basePath, fullPath)
result.push({ fullPath, relativePath })
}
}
} catch (error) {
/* Ignore directories that cannot be accessed */
}
return result
}