Skip to main content
Glama
compressionUtils.ts5.06 kB
import * as fs from 'fs' import tinify from 'tinify' import { calculateFileHash, findAllImageFiles } from './fileUtils.js' import { readCompressionRecord } from './recordUtils.js' /** * Validate TinyPNG API key. * * @param apiKey The API key to validate. * @returns Promise that resolves if key is valid. */ export async function validateApiKey(apiKey: string): Promise<void> { return new Promise((resolve, reject) => { tinify.key = apiKey tinify.validate((err: any) => { if (err) { reject(new Error(`Invalid API key: ${err.message}`)) } else { resolve() } }) }) } /** * Compress a single image file using TinyPNG. * * @param filePath The path to the image file. * @returns Promise that resolves when compression is complete. */ export async function compressImage(filePath: string): Promise<void> { return new Promise((resolve, reject) => { const source = tinify.fromFile(filePath) source.toFile(filePath, (err: any) => { if (err) { reject(err) } else { resolve() } }) }) } /** * Get all image files with smart duplicate detection (supports nested folders). * * @param directoryPath The path to the directory. * @returns Object containing categorized image files and detection results. */ export async function getImageFiles(directoryPath: string): Promise<{ allImageFiles: Array<{ fullPath: string, relativePath: string }> filesToCompress: Array<{ fullPath: string, relativePath: string }> alreadyCompressed: Array<{ fullPath: string, relativePath: string }> renamedFiles: Array<{ currentPath: string, originalPath: string }> replacedFiles: Array<{ relativePath: string, reason: string }> }> { /* Find all image files recursively */ const allImageFiles = await findAllImageFiles(directoryPath) /* Read existing compression record */ const record = await readCompressionRecord(directoryPath) const compressedFiles = record?.compressedFiles ?? {} const filesToCompress: Array<{ fullPath: string, relativePath: string }> = [] const alreadyCompressed: Array<{ fullPath: string, relativePath: string }> = [] const renamedFiles: Array<{ currentPath: string, originalPath: string }> = [] const replacedFiles: Array<{ relativePath: string, reason: string }> = [] /* Create reverse hash lookup for detecting renamed/moved files */ const hashToRelativePath = new Map<string, string>() for (const [relativePath, info] of Object.entries(compressedFiles)) { hashToRelativePath.set(info.hash, relativePath) } for (const imageFile of allImageFiles) { const { fullPath, relativePath } = imageFile const recordInfo = compressedFiles[relativePath] if (recordInfo) { /* Relative path exists in record, verify if it's the same file */ try { const currentStats = await fs.promises.stat(fullPath) const currentSize = currentStats.size const currentMtime = currentStats.mtime.getTime() /* Quick metadata check first */ if (currentSize === recordInfo.size && currentMtime === recordInfo.mtime) { /* Very likely the same file, skip hash calculation */ alreadyCompressed.push(imageFile) continue } /* Metadata differs, perform hash verification */ const currentHash = await calculateFileHash(fullPath) if (currentHash === recordInfo.hash) { /* Same content, just metadata changed */ alreadyCompressed.push(imageFile) continue } else { /* Different content - new file replaced the old one */ filesToCompress.push(imageFile) replacedFiles.push({ relativePath, reason: 'New file replaced existing compressed file (content changed)', }) continue } } catch (error) { /* Error accessing file, treat as new file */ filesToCompress.push(imageFile) replacedFiles.push({ relativePath, reason: `Could not verify file integrity: ${error instanceof Error ? error.message : 'Unknown error'}`, }) continue } } else { /* Relative path not in record, check if it's a renamed/moved file */ try { const currentHash = await calculateFileHash(fullPath) const originalRelativePath = hashToRelativePath.get(currentHash) if (originalRelativePath) { /* This is a renamed/moved file! */ alreadyCompressed.push(imageFile) renamedFiles.push({ currentPath: relativePath, originalPath: originalRelativePath, }) continue } else { /* Truly new file */ filesToCompress.push(imageFile) continue } } catch (error) { /* Hash calculation failed, treat as new file */ filesToCompress.push(imageFile) continue } } } return { allImageFiles, filesToCompress, alreadyCompressed, renamedFiles, replacedFiles, } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Alvinnn1/tinify-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server