Skip to main content
Glama
convertUtils.ts6.05 kB
/** * Image format conversion utilities using TinyPNG API. * This module provides functions to convert images between different formats * such as PNG, JPEG, WebP, and AVIF using the TinyPNG converting API. */ import * as fs from 'fs' import * as path from 'path' import tinify from 'tinify' import { type ConvertOptions, type ConvertResult, type ImageFormat } from './types.js' /** * Mapping of common file extensions to MIME types. */ const EXTENSION_TO_MIME: Record<string, ImageFormat> = { '.png': 'image/png', '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.webp': 'image/webp', '.avif': 'image/avif', } /** * Mapping of MIME types to file extensions. */ const MIME_TO_EXTENSION: Record<ImageFormat, string> = { 'image/png': '.png', 'image/jpeg': '.jpg', 'image/webp': '.webp', 'image/avif': '.avif', } /** * Get the MIME type of an image file based on its extension. * * @param filePath Path to the image file. * @returns The MIME type string or null if not supported. */ function getImageMimeType(filePath: string): ImageFormat | null { const ext = path.extname(filePath).toLowerCase() return EXTENSION_TO_MIME[ext] || null } /** * Generate output path for converted image based on the target format. * * @param originalPath Original image file path. * @param targetFormat Target image format. * @param outputPath Optional custom output path. * @returns The output file path with correct extension. */ function generateOutputPath(originalPath: string, targetFormat: ImageFormat, outputPath?: string): string { if (outputPath) { return outputPath } const dir = path.dirname(originalPath) const basename = path.basename(originalPath, path.extname(originalPath)) const ext = MIME_TO_EXTENSION[targetFormat] return path.join(dir, `${basename}${ext}`) } /** * Convert an image to one or more target formats using TinyPNG API. * If multiple formats are specified, the smallest result will be returned. * * @param imagePath Path to the source image file. * @param options Conversion options including target formats and background color. * @param outputPath Optional output path for the converted image. * @returns Promise resolving to conversion result details. * @throws Error if the conversion fails or input is invalid. */ export async function convertImage( imagePath: string, options: ConvertOptions, outputPath?: string, ): Promise<ConvertResult> { // Validate input file exists if (!fs.existsSync(imagePath)) { throw new Error(`Source image file does not exist: ${imagePath}`) } const stats = await fs.promises.stat(imagePath) if (!stats.isFile()) { throw new Error(`Path is not a file: ${imagePath}`) } // Get original format const originalFormat = getImageMimeType(imagePath) if (!originalFormat) { throw new Error(`Unsupported image format: ${path.extname(imagePath)}`) } const originalSize = stats.size // Prepare conversion options const { targetFormats, backgroundColor } = options const formats = Array.isArray(targetFormats) ? targetFormats : [targetFormats] // Validate target formats const validFormats: ImageFormat[] = ['image/avif', 'image/webp', 'image/jpeg', 'image/png'] for (const format of formats) { if (!validFormats.includes(format)) { throw new Error(`Unsupported target format: ${format}`) } } try { // Create TinyPNG source from file const source = tinify.fromFile(imagePath) // Configure conversion let converted: any if (backgroundColor && formats.includes('image/jpeg')) { // If converting to JPEG (no transparency support) and background is specified converted = source.convert({ type: formats }).transform({ background: backgroundColor }) } else { converted = source.convert({ type: formats }) } // Get the actual format that was chosen (smallest one) const extension = await converted.result().extension() const convertedFormat = EXTENSION_TO_MIME[`.${extension}`] || formats[0] // Generate output path const finalOutputPath = generateOutputPath(imagePath, convertedFormat, outputPath) // Save the converted image await converted.toFile(finalOutputPath) // Get converted file stats const convertedStats = await fs.promises.stat(finalOutputPath) const convertedSize = convertedStats.size const savings = parseFloat(((originalSize - convertedSize) / originalSize * 100).toFixed(1)) return { outputPath: finalOutputPath, originalFormat, convertedFormat, originalSize, convertedSize, savings, } } catch (error) { if (error instanceof tinify.AccountError) { throw new Error(`TinyPNG account error: ${error.message}`) } else if (error instanceof tinify.ClientError) { throw new Error(`TinyPNG client error: ${error.message}`) } else if (error instanceof tinify.ServerError) { throw new Error(`TinyPNG server error: ${error.message}`) } else if (error instanceof tinify.ConnectionError) { throw new Error(`TinyPNG connection error: ${error.message}`) } else { throw new Error(`Conversion failed: ${error instanceof Error ? error.message : 'Unknown error'}`) } } } /** * Convert multiple images in batch to the specified format(s). * * @param imagePaths Array of image file paths to convert. * @param options Conversion options. * @returns Promise resolving to array of conversion results. */ export async function convertImageBatch( imagePaths: string[], options: ConvertOptions, ): Promise<ConvertResult[]> { const results: ConvertResult[] = [] for (const imagePath of imagePaths) { try { const result = await convertImage(imagePath, options) results.push(result) } catch (error) { // For batch operations, we collect errors but continue processing const errorMessage = error instanceof Error ? error.message : 'Unknown error' throw new Error(`Failed to convert ${imagePath}: ${errorMessage}`) } } return results }

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