File Converter Adapters
This skill provides comprehensive guidance for using @rytass/file-converter packages to process images with resizing, format conversion, watermarking, and pipeline processing.
Overview
All adapters implement the FileConverter interface from @rytass/file-converter , providing a unified API for image processing:
Package Function Based On
@rytass/file-converter
Core interfaces and pipeline manager (核心介面與管道管理器) TypeScript interfaces
@rytass/file-converter-adapter-image-resizer
Image resizing (圖像縮放) Sharp ^0.34.5
@rytass/file-converter-adapter-image-transcoder
Format conversion (格式轉換) Sharp ^0.34.5, file-type ^21.1.1
@rytass/file-converter-adapter-image-watermark
Watermark overlay (浮水印疊加) Sharp ^0.34.5
Base Interface (@rytass/file-converter)
All adapters share these core concepts:
FileConverter - Main interface for file conversion
interface FileConverter<O = Record<string, unknown>> { convert<Buffer>(file: ConvertableFile): Promise<Buffer>; convert<Readable>(file: ConvertableFile): Promise<Readable>; }
ConvertableFile - Supported input types
type ConvertableFile = Readable | Buffer;
ConverterManager - Pipeline processor for chaining conversions
class ConverterManager { constructor(converters: FileConverter[]); convert<Output>(file: ConvertableFile): Promise<Output>; }
Installation
Install base package
npm install @rytass/file-converter
Choose the adapters you need
npm install @rytass/file-converter-adapter-image-resizer npm install @rytass/file-converter-adapter-image-transcoder npm install @rytass/file-converter-adapter-image-watermark
Quick Start
ImageResizer (圖像縮放)
Resize images while maintaining or controlling aspect ratio:
import { ImageResizer } from '@rytass/file-converter-adapter-image-resizer'; import { readFileSync, writeFileSync } from 'fs';
// Resize to max 800x600, maintaining aspect ratio const resizer = new ImageResizer({ maxWidth: 800, maxHeight: 600, keepAspectRatio: true, concurrency: 4, // Optional: parallel processing });
const originalImage = readFileSync('photo.jpg'); const resizedImage = await resizer.convert<Buffer>(originalImage); writeFileSync('photo-resized.jpg', resizedImage);
ImageTranscoder (格式轉換)
Convert images between formats (JPEG, PNG, WebP, AVIF, etc.):
import { ImageTranscoder } from '@rytass/file-converter-adapter-image-transcoder';
// Convert to WebP format const transcoder = new ImageTranscoder({ targetFormat: 'webp', quality: 85, effort: 4, });
const jpegBuffer = readFileSync('photo.jpg'); const webpBuffer = await transcoder.convert<Buffer>(jpegBuffer); writeFileSync('photo.webp', webpBuffer);
ImageWatermark (浮水印)
Add watermarks to images:
import { ImageWatermark } from '@rytass/file-converter-adapter-image-watermark'; import { gravity } from 'sharp';
// Add watermark at bottom-right corner const watermarker = new ImageWatermark({ watermarks: [ { image: readFileSync('logo.png'), // Can be Buffer or file path gravity: gravity.southeast, // Position: bottom-right }, ], });
const originalImage = readFileSync('photo.jpg'); const watermarkedImage = await watermarker.convert<Buffer>(originalImage); writeFileSync('photo-watermarked.jpg', watermarkedImage);
ConverterManager (管道處理)
Chain multiple converters in a pipeline:
import { ConverterManager } from '@rytass/file-converter'; import { ImageResizer } from '@rytass/file-converter-adapter-image-resizer'; import { ImageWatermark } from '@rytass/file-converter-adapter-image-watermark'; import { ImageTranscoder } from '@rytass/file-converter-adapter-image-transcoder'; import { gravity } from 'sharp';
// Create processing pipeline: Resize → Watermark → Convert to WebP const pipeline = new ConverterManager([ new ImageResizer({ maxWidth: 1200, maxHeight: 800, keepAspectRatio: true, }), new ImageWatermark({ watermarks: [ { image: './logo.png', gravity: gravity.southeast, }, ], }), new ImageTranscoder({ targetFormat: 'webp', quality: 85, effort: 4, }), ]);
const originalImage = readFileSync('photo.jpg'); const processedImage = await pipeline.convert<Buffer>(originalImage); writeFileSync('processed.webp', processedImage);
Common Patterns
Buffer vs Stream Processing
Buffer - Use for small files (小檔案), faster, uses more memory:
const buffer = readFileSync('image.jpg'); const result = await converter.convert<Buffer>(buffer); writeFileSync('output.jpg', result);
Stream (Readable) - Use for large files (大檔案), memory efficient:
import { createReadStream, createWriteStream } from 'fs'; import { Readable } from 'stream';
const stream = createReadStream('large-image.jpg'); const resultStream = await converter.convert<Readable>(stream);
// Pipe to output resultStream.pipe(createWriteStream('output.jpg'));
Single Conversion
Process one image with one converter:
// Resize only const resizer = new ImageResizer({ maxWidth: 800 }); const resized = await resizer.convert<Buffer>(imageBuffer);
// Convert format only const transcoder = new ImageTranscoder({ targetFormat: 'webp', quality: 90 }); const webp = await transcoder.convert<Buffer>(jpegBuffer);
Pipeline Conversion
Chain multiple operations:
// Three-step processing const pipeline = new ConverterManager([ new ImageResizer({ maxWidth: 1000, maxHeight: 750 }), new ImageWatermark({ watermarks: [{ image: 'logo.png' }] }), new ImageTranscoder({ targetFormat: 'webp', quality: 85 }), ]);
const result = await pipeline.convert<Buffer>(originalImage);
Batch Processing
Process multiple images:
import { glob } from 'glob'; import { basename } from 'path';
const pipeline = new ConverterManager([ new ImageResizer({ maxWidth: 800 }), new ImageTranscoder({ targetFormat: 'webp', quality: 85 }), ]);
// Find all JPEG files const images = await glob('photos/*.jpg');
// Process all images
await Promise.all(
images.map(async (imagePath) => {
const buffer = readFileSync(imagePath);
const result = await pipeline.convert<Buffer>(buffer);
const outputName = basename(imagePath, '.jpg') + '.webp';
writeFileSync(output/${outputName}, result);
})
);
Feature Comparison
Feature ImageResizer ImageTranscoder ImageWatermark ConverterManager
Resize Images (縮放圖片) Yes No No Via pipeline
Convert Formats (轉換格式) No Yes No Via pipeline
Add Watermarks (添加浮水印) No No Yes Via pipeline
Maintain Aspect Ratio (保持縱橫比) Yes N/A N/A N/A
Crop to Fit (裁剪適配) Yes No No N/A
Buffer Support (緩衝支援) Yes Yes Yes Yes
Stream Support (串流支援) Yes Yes Yes Yes
Concurrency Control (並發控制) Yes Yes Yes N/A
Multiple Watermarks (多浮水印) No No Yes N/A
Pipeline Chaining (管道串聯) No No No Yes
Supported Formats (ImageTranscoder)
Input Formats Output Formats
jpg, png, webp, gif, avif, tif, svg jpg/jpeg, png, webp, avif, heif, gif, tif/tiff
Note: Input formats are detected by file-type library. JPEG uses 'jpg' internally.
內部常數(未導出):
-
SupportSources
-
支援的輸入格式陣列:['jpg', 'png', 'webp', 'gif', 'avif', 'tif', 'svg']
-
UnsupportedSource
-
當輸入格式不支援時拋出的錯誤類別
ImageTranscoderOptions Types
每種輸出格式都有對應的 Sharp options:
import type { AvifOptions, GifOptions, HeifOptions, JpegOptions, PngOptions, TiffOptions, WebpOptions } from 'sharp';
type ImageTranscoderOptions = | { targetFormat: 'avif' } & AvifOptions | { targetFormat: 'heif' } & HeifOptions | { targetFormat: 'gif' } & GifOptions | { targetFormat: 'tif' | 'tiff' } & TiffOptions | { targetFormat: 'png' } & PngOptions | { targetFormat: 'webp' } & WebpOptions | { targetFormat: 'jpg' | 'jpeg' } & JpegOptions;
// Constructor 額外支援 concurrency 選項 new ImageTranscoder(options: ImageTranscoderOptions & { concurrency?: number });
ImageResizerOptions
interface ImageResizerOptions { maxWidth?: number; // 最大寬度(至少需設定 maxWidth 或 maxHeight 其一) maxHeight?: number; // 最大高度 keepAspectRatio?: boolean; // 保持縱橫比(預設 true,使用 'inside' fit) concurrency?: number; // 並發數(預設 1) }
Note: 使用 withoutEnlargement: true 確保不會放大小於目標尺寸的圖片。當 keepAspectRatio=false 時使用 fit: 'cover' 裁剪適配。
ImageWatermarkOptions
interface Watermark { image: string | Buffer; // 浮水印圖片(檔案路徑或 Buffer) gravity?: Gravity; // 位置(預設 southeast) }
interface ImageWatermarkOptions { watermarks: Watermark[]; // 浮水印陣列(支援多個浮水印) concurrency?: number; // 並發數(預設 1) }
Watermark Positions (ImageWatermark)
Position Gravity Constant Description
Top-Left (左上) gravity.northwest
Northwest corner
Top-Center (上中) gravity.north
Top center
Top-Right (右上) gravity.northeast
Northeast corner
Middle-Left (中左) gravity.west
Middle left
Center (中央) gravity.center
Center
Middle-Right (中右) gravity.east
Middle right
Bottom-Left (左下) gravity.southwest
Southwest corner
Bottom-Center (下中) gravity.south
Bottom center
Bottom-Right (右下) gravity.southeast
Southeast corner (default)
Detailed Documentation
For complete API reference and advanced usage:
-
ImageResizer Complete Reference - All options, methods, and use cases
-
ImageTranscoder Complete Reference - Format-specific options, error handling
-
ImageWatermark Complete Reference - Watermark configuration, positioning
Complete Examples
E-commerce Product Images
Process product photos: resize for web, add branding watermark, convert to modern format:
import { ConverterManager } from '@rytass/file-converter'; import { ImageResizer } from '@rytass/file-converter-adapter-image-resizer'; import { ImageWatermark } from '@rytass/file-converter-adapter-image-watermark'; import { ImageTranscoder } from '@rytass/file-converter-adapter-image-transcoder'; import { readFileSync, writeFileSync } from 'fs'; import { gravity } from 'sharp';
// Create product image processor const productImageProcessor = new ConverterManager([ // Step 1: Resize to 1200x1200 max new ImageResizer({ maxWidth: 1200, maxHeight: 1200, keepAspectRatio: true, concurrency: 4, }),
// Step 2: Add brand watermark at bottom-right new ImageWatermark({ watermarks: [ { image: readFileSync('brand-logo.png'), gravity: gravity.southeast, }, ], }),
// Step 3: Convert to WebP for smaller file size new ImageTranscoder({ targetFormat: 'webp', quality: 85, effort: 4, // Higher effort = better compression }), ]);
// Process a product image const originalPhoto = readFileSync('product-001-raw.jpg'); const processedPhoto = await productImageProcessor.convert<Buffer>(originalPhoto); writeFileSync('product-001.webp', processedPhoto);
Large File Stream Processing
Handle large files efficiently with streams to avoid memory issues:
import { createReadStream, createWriteStream } from 'fs'; import { ImageResizer } from '@rytass/file-converter-adapter-image-resizer'; import { Readable } from 'stream';
// Create resizer const resizer = new ImageResizer({ maxWidth: 1920, maxHeight: 1080, keepAspectRatio: true, });
// Process large file using streams const inputStream = createReadStream('large-photo.jpg'); const outputStream = await resizer.convert<Readable>(inputStream);
// Pipe to output file outputStream.pipe(createWriteStream('large-photo-resized.jpg'));
// Wait for completion await new Promise((resolve, reject) => { outputStream.on('finish', resolve); outputStream.on('error', reject); });
Batch Processing with Progress Tracking
Process multiple images with progress updates:
import { glob } from 'glob'; import { basename } from 'path'; import { ConverterManager } from '@rytass/file-converter'; import { ImageResizer } from '@rytass/file-converter-adapter-image-resizer'; import { ImageTranscoder } from '@rytass/file-converter-adapter-image-transcoder';
// Create pipeline const pipeline = new ConverterManager([ new ImageResizer({ maxWidth: 800, maxHeight: 600, keepAspectRatio: true }), new ImageTranscoder({ targetFormat: 'webp', quality: 80 }), ]);
// Find all images const images = await glob('input-photos/*.{jpg,png,jpeg}'); const total = images.length; let processed = 0;
console.log(Found ${total} images to process);
// Process with progress tracking for (const imagePath of images) { const buffer = readFileSync(imagePath); const result = await pipeline.convert<Buffer>(buffer);
const outputName = basename(imagePath).replace(/.(jpg|png|jpeg)$/, '.webp');
writeFileSync(output/${outputName}, result);
processed++;
console.log(Progress: ${processed}/${total} (${Math.round((processed / total) * 100)}%));
}
console.log('All images processed successfully!');
Conditional Processing
Apply different processing based on image properties:
import sharp from 'sharp'; import { ImageResizer } from '@rytass/file-converter-adapter-image-resizer'; import { ImageTranscoder } from '@rytass/file-converter-adapter-image-transcoder'; import { ConverterManager } from '@rytass/file-converter';
async function processImage(inputBuffer: Buffer): Promise<Buffer> { // Get image metadata const metadata = await sharp(inputBuffer).metadata();
if (!metadata.width || !metadata.height) { throw new Error('Unable to read image dimensions'); }
// For large images: resize first if (metadata.width > 2000 || metadata.height > 2000) { console.log('Large image detected, resizing...'); const pipeline = new ConverterManager([ new ImageResizer({ maxWidth: 1600, maxHeight: 1600, keepAspectRatio: true }), new ImageTranscoder({ targetFormat: 'webp', quality: 85 }), ]); return pipeline.convert<Buffer>(inputBuffer); }
// For small images: just convert format console.log('Small image detected, converting format only...'); const transcoder = new ImageTranscoder({ targetFormat: 'webp', quality: 90 }); return transcoder.convert<Buffer>(inputBuffer); }
// Usage const imageBuffer = readFileSync('photo.jpg'); const result = await processImage(imageBuffer); writeFileSync('output.webp', result);
Troubleshooting
Common Issues
Memory Errors with Large Images
- Solution: Use Stream processing instead of Buffer
// Instead of Buffer const buffer = readFileSync('huge-image.jpg'); const result = await converter.convert<Buffer>(buffer);
// Use Stream const stream = createReadStream('huge-image.jpg'); const resultStream = await converter.convert<Readable>(stream);
Sharp Cache Issues
-
Sharp automatically disables cache in all adapters with sharp.cache(false)
-
No action needed
Concurrency Performance
-
Adjust concurrency option based on your CPU cores
-
Default is 1 (single-threaded)
-
Try 4-8 for multi-core systems
const resizer = new ImageResizer({ maxWidth: 800, concurrency: 8, // Adjust based on CPU cores });
Unsupported Format Errors (ImageTranscoder)
-
Check input format is supported: JPEG, PNG, WebP, GIF, AVIF, TIFF, SVG
-
Error will throw UnsupportedSource exception (internal class, catch using instanceof Error and check message === 'UnsupportedSource' )
Output Quality Issues
-
Increase quality parameter (0-100)
-
For WebP/AVIF, increase effort (0-9) for better compression
const transcoder = new ImageTranscoder({ targetFormat: 'webp', quality: 95, // Higher quality effort: 6, // More compression effort });