npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@rytass/file-converter-adapter-image-transcoder

v0.1.14

Published

Rytass Utils Storages Images Converter

Readme

Rytass Utils - File Converter Image Transcoder Adapter

A powerful image format conversion adapter for the Rytass file converter framework. Built on top of Sharp, this adapter provides high-performance image format transcoding with support for modern formats including WebP, AVIF, and HEIF.

Features

  • [x] Modern image format support (WebP, AVIF, HEIF)
  • [x] Traditional format conversion (JPEG, PNG, GIF, TIFF)
  • [x] High-performance Sharp integration
  • [x] Configurable quality settings per format
  • [x] Progressive JPEG support
  • [x] Lossless and lossy compression options
  • [x] SVG input support
  • [x] Buffer and Stream processing
  • [x] Concurrency control
  • [x] TypeScript type safety

Supported Formats

Input Formats

  • JPEG (jpg, jpeg)
  • PNG
  • WebP
  • GIF
  • AVIF
  • TIFF (tif, tiff)
  • SVG

Output Formats

  • JPEG - Universal compatibility with quality control
  • PNG - Lossless compression with transparency
  • WebP - Modern format with superior compression
  • AVIF - Next-generation format with excellent compression
  • HEIF - Apple's preferred format for photos
  • GIF - Animated image support
  • TIFF - Professional photography and print

Installation

npm install @rytass/file-converter-adapter-image-transcoder
# or
yarn add @rytass/file-converter-adapter-image-transcoder

Peer Dependencies:

npm install @rytass/file-converter sharp

Basic Usage

JPEG to WebP Conversion

import { ImageTranscoder } from '@rytass/file-converter-adapter-image-transcoder';
import { readFileSync, writeFileSync } from 'fs';

// Create transcoder for WebP output
const transcoder = new ImageTranscoder({
  targetFormat: 'webp',
  quality: 85,
  effort: 4, // WebP compression effort (0-6)
});

// Convert image
const jpegBuffer = readFileSync('photo.jpg');
const webpBuffer = await transcoder.convert<Buffer>(jpegBuffer);
writeFileSync('photo.webp', webpBuffer);

PNG to AVIF with Quality Control

import { ImageTranscoder } from '@rytass/file-converter-adapter-image-transcoder';

const transcoder = new ImageTranscoder({
  targetFormat: 'avif',
  quality: 80,
  effort: 4,
  chromaSubsampling: '4:4:4', // Preserve color quality
});

const pngBuffer = readFileSync('image.png');
const avifBuffer = await transcoder.convert<Buffer>(pngBuffer);

Batch Format Conversion

import { ImageTranscoder } from '@rytass/file-converter-adapter-image-transcoder';
import { ConverterManager } from '@rytass/file-converter';

// Create transcoders for different formats
const webpTranscoder = new ImageTranscoder({
  targetFormat: 'webp',
  quality: 85,
});

const avifTranscoder = new ImageTranscoder({
  targetFormat: 'avif',
  quality: 80,
});

const jpegTranscoder = new ImageTranscoder({
  targetFormat: 'jpeg',
  quality: 90,
  progressive: true,
});

// Convert to multiple formats
async function convertToMultipleFormats(inputBuffer: Buffer) {
  const [webp, avif, jpeg] = await Promise.all([
    webpTranscoder.convert<Buffer>(inputBuffer),
    avifTranscoder.convert<Buffer>(inputBuffer),
    jpegTranscoder.convert<Buffer>(inputBuffer),
  ]);

  return { webp, avif, jpeg };
}

Format-Specific Configuration

WebP Options

const webpTranscoder = new ImageTranscoder({
  targetFormat: 'webp',
  quality: 85, // Quality 0-100
  alphaQuality: 90, // Alpha channel quality
  lossless: false, // Use lossless compression
  nearLossless: false, // Use near-lossless compression
  smartSubsample: true, // Use smart subsampling
  effort: 4, // Compression effort 0-6 (higher = better compression)
});

AVIF Options

const avifTranscoder = new ImageTranscoder({
  targetFormat: 'avif',
  quality: 80, // Quality 1-100
  lossless: false, // Use lossless compression
  effort: 4, // Compression effort 0-9
  chromaSubsampling: '4:4:4', // Color subsampling
  bitdepth: 8, // Bit depth 8, 10, or 12
});

JPEG Options

const jpegTranscoder = new ImageTranscoder({
  targetFormat: 'jpeg',
  quality: 90, // Quality 1-100
  progressive: true, // Progressive JPEG
  mozjpeg: false, // Use mozjpeg encoder
  trellisQuantisation: false, // Enable trellis quantisation
  overshootDeringing: false, // Enable overshoot deringing
  optimiseScans: false, // Optimize progressive scans
});

PNG Options

const pngTranscoder = new ImageTranscoder({
  targetFormat: 'png',
  progressive: false, // Progressive PNG
  compressionLevel: 9, // Compression level 0-9
  adaptiveFiltering: false, // Adaptive filtering
  palette: false, // Use palette
  quality: 100, // Quality 0-100 (for palette)
  effort: 7, // Compression effort 1-10
});

HEIF Options

const heifTranscoder = new ImageTranscoder({
  targetFormat: 'heif',
  quality: 85, // Quality 1-100
  compression: 'av1', // Compression: 'av1' or 'hevc'
  effort: 4, // Compression effort 0-9
  lossless: false, // Use lossless compression
});

GIF Options

const gifTranscoder = new ImageTranscoder({
  targetFormat: 'gif',
  colours: 256, // Number of colors in palette
  effort: 7, // Quantization effort 1-10
  dither: 1.0, // Dithering level 0-1
});

TIFF Options

const tiffTranscoder = new ImageTranscoder({
  targetFormat: 'tiff',
  quality: 90, // Quality for JPEG compression
  compression: 'jpeg', // Compression: 'lzw', 'deflate', 'jpeg', 'ccittfax4'
  predictor: 'horizontal', // Predictor for lzw/deflate
  tile: false, // Use tiled format
  tileWidth: 256, // Tile width
  tileHeight: 256, // Tile height
  xres: 300, // Horizontal resolution
  yres: 300, // Vertical resolution
});

Advanced Usage

Pipeline Integration

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 processing pipeline
const pipeline = new ConverterManager([
  // Step 1: Resize image
  new ImageResizer({
    maxWidth: 1920,
    maxHeight: 1080,
    keepAspectRatio: true,
  }),

  // Step 2: Convert to modern format
  new ImageTranscoder({
    targetFormat: 'avif',
    quality: 80,
    effort: 6,
  }),
]);

// Process image through pipeline
const originalImage = readFileSync('large-photo.jpg');
const optimizedImage = await pipeline.convert<Buffer>(originalImage);
writeFileSync('optimized.avif', optimizedImage);

Stream Processing for Large Files

import { createReadStream, createWriteStream } from 'fs';
import { pipeline } from 'stream/promises';

const transcoder = new ImageTranscoder({
  targetFormat: 'webp',
  quality: 85,
});

// Process large files with streams
async function transcodeStream(inputPath: string, outputPath: string) {
  const inputStream = createReadStream(inputPath);
  const outputStream = createWriteStream(outputPath);

  const processedStream = await transcoder.convert<Readable>(inputStream);

  await pipeline(processedStream, outputStream);
  console.log('Stream transcoding completed');
}

await transcodeStream('large-image.tiff', 'compressed.webp');

Performance Optimization

import { ImageTranscoder } from '@rytass/file-converter-adapter-image-transcoder';

// Configure for high-performance processing
const transcoder = new ImageTranscoder({
  targetFormat: 'webp',
  quality: 85,
  effort: 2, // Lower effort for faster processing
  concurrency: 4, // Process multiple images simultaneously
});

// Batch processing with concurrency control
class BatchTranscoder {
  constructor(private transcoder: ImageTranscoder) {}

  async processBatch(files: Buffer[], maxConcurrent: number = 4): Promise<Buffer[]> {
    const results: Buffer[] = [];

    for (let i = 0; i < files.length; i += maxConcurrent) {
      const batch = files.slice(i, i + maxConcurrent);

      const batchResults = await Promise.all(batch.map(file => this.transcoder.convert<Buffer>(file)));

      results.push(...batchResults);
    }

    return results;
  }
}

Format Detection and Auto-Conversion

import { fromBuffer } from 'file-type';

async function smartTranscode(inputBuffer: Buffer, preferredFormat: 'webp' | 'avif' | 'jpeg'): Promise<Buffer> {
  const fileType = await fromBuffer(inputBuffer);

  if (!fileType) {
    throw new Error('Unsupported file format');
  }

  // Skip conversion if already in preferred format
  if (fileType.ext === preferredFormat) {
    return inputBuffer;
  }

  const qualityMap = {
    webp: 85,
    avif: 80,
    jpeg: 90,
  };

  const transcoder = new ImageTranscoder({
    targetFormat: preferredFormat,
    quality: qualityMap[preferredFormat],
  });

  return transcoder.convert<Buffer>(inputBuffer);
}

// Usage
const result = await smartTranscode(imageBuffer, 'avif');

Error Handling and Validation

import { UnsupportedSource } from '@rytass/file-converter-adapter-image-transcoder';

async function safeTranscode(inputBuffer: Buffer, options: ImageTranscoderOptions): Promise<Buffer | null> {
  try {
    const transcoder = new ImageTranscoder(options);
    return await transcoder.convert<Buffer>(inputBuffer);
  } catch (error) {
    if (error instanceof UnsupportedSource) {
      console.warn('Unsupported image format');
      return null;
    }

    console.error('Transcoding failed:', error.message);
    throw error;
  }
}

// Fallback strategy
async function transcodeWithFallback(inputBuffer: Buffer): Promise<Buffer> {
  // Try modern formats first
  const formats = ['avif', 'webp', 'jpeg'] as const;

  for (const format of formats) {
    try {
      const transcoder = new ImageTranscoder({
        targetFormat: format,
        quality: 85,
      });

      return await transcoder.convert<Buffer>(inputBuffer);
    } catch (error) {
      console.warn(`Failed to convert to ${format}:`, error.message);
    }
  }

  throw new Error('All transcoding attempts failed');
}

Integration Examples

Express.js Image API

import express from 'express';
import multer from 'multer';
import { ImageTranscoder } from '@rytass/file-converter-adapter-image-transcoder';

const app = express();
const upload = multer({ storage: multer.memoryStorage() });

// Format conversion endpoint
app.post('/convert/:format', upload.single('image'), async (req, res) => {
  try {
    const { format } = req.params;
    const quality = parseInt(req.query.quality as string) || 85;

    if (!req.file) {
      return res.status(400).json({ error: 'No image uploaded' });
    }

    if (!['webp', 'avif', 'jpeg', 'png'].includes(format)) {
      return res.status(400).json({ error: 'Unsupported format' });
    }

    const transcoder = new ImageTranscoder({
      targetFormat: format as any,
      quality,
    });

    const convertedImage = await transcoder.convert<Buffer>(req.file.buffer);

    res.set('Content-Type', `image/${format}`);
    res.send(convertedImage);
  } catch (error) {
    res.status(500).json({ error: 'Conversion failed' });
  }
});

// Modern format serving with fallback
app.get('/image/:id', async (req, res) => {
  const { id } = req.params;
  const accepts = req.headers.accept || '';

  // Determine best format based on browser support
  let targetFormat: 'avif' | 'webp' | 'jpeg' = 'jpeg';
  if (accepts.includes('image/avif')) {
    targetFormat = 'avif';
  } else if (accepts.includes('image/webp')) {
    targetFormat = 'webp';
  }

  try {
    const originalImage = await getImageById(id); // Your image loading logic

    const transcoder = new ImageTranscoder({
      targetFormat,
      quality: 85,
    });

    const optimizedImage = await transcoder.convert<Buffer>(originalImage);

    res.set('Content-Type', `image/${targetFormat}`);
    res.set('Cache-Control', 'public, max-age=31536000');
    res.send(optimizedImage);
  } catch (error) {
    res.status(404).json({ error: 'Image not found' });
  }
});

NestJS Image Service

import { Injectable } from '@nestjs/common';
import { ImageTranscoder } from '@rytass/file-converter-adapter-image-transcoder';

@Injectable()
export class ImageTranscodingService {
  private readonly webpTranscoder: ImageTranscoder;
  private readonly avifTranscoder: ImageTranscoder;
  private readonly jpegTranscoder: ImageTranscoder;

  constructor() {
    this.webpTranscoder = new ImageTranscoder({
      targetFormat: 'webp',
      quality: 85,
      effort: 4,
    });

    this.avifTranscoder = new ImageTranscoder({
      targetFormat: 'avif',
      quality: 80,
      effort: 6,
    });

    this.jpegTranscoder = new ImageTranscoder({
      targetFormat: 'jpeg',
      quality: 90,
      progressive: true,
    });
  }

  async createImageVariants(originalBuffer: Buffer): Promise<{
    webp: Buffer;
    avif: Buffer;
    jpeg: Buffer;
  }> {
    const [webp, avif, jpeg] = await Promise.all([
      this.webpTranscoder.convert<Buffer>(originalBuffer),
      this.avifTranscoder.convert<Buffer>(originalBuffer),
      this.jpegTranscoder.convert<Buffer>(originalBuffer),
    ]);

    return { webp, avif, jpeg };
  }

  async getBestFormat(
    originalBuffer: Buffer,
    acceptedFormats: string[],
  ): Promise<{ buffer: Buffer; mimeType: string }> {
    if (acceptedFormats.includes('image/avif')) {
      const buffer = await this.avifTranscoder.convert<Buffer>(originalBuffer);
      return { buffer, mimeType: 'image/avif' };
    }

    if (acceptedFormats.includes('image/webp')) {
      const buffer = await this.webpTranscoder.convert<Buffer>(originalBuffer);
      return { buffer, mimeType: 'image/webp' };
    }

    const buffer = await this.jpegTranscoder.convert<Buffer>(originalBuffer);
    return { buffer, mimeType: 'image/jpeg' };
  }
}

CDN Image Optimization

import { ImageTranscoder } from '@rytass/file-converter-adapter-image-transcoder';

class CDNImageOptimizer {
  private transcoders = new Map<string, ImageTranscoder>();

  constructor() {
    // Pre-configure transcoders for different quality levels
    this.transcoders.set(
      'high-webp',
      new ImageTranscoder({
        targetFormat: 'webp',
        quality: 95,
        effort: 6,
      }),
    );

    this.transcoders.set(
      'medium-webp',
      new ImageTranscoder({
        targetFormat: 'webp',
        quality: 85,
        effort: 4,
      }),
    );

    this.transcoders.set(
      'low-webp',
      new ImageTranscoder({
        targetFormat: 'webp',
        quality: 70,
        effort: 2,
      }),
    );

    this.transcoders.set(
      'high-avif',
      new ImageTranscoder({
        targetFormat: 'avif',
        quality: 90,
        effort: 8,
      }),
    );

    this.transcoders.set(
      'medium-avif',
      new ImageTranscoder({
        targetFormat: 'avif',
        quality: 80,
        effort: 6,
      }),
    );
  }

  async optimize(
    imageBuffer: Buffer,
    options: {
      format: 'webp' | 'avif';
      quality: 'high' | 'medium' | 'low';
    },
  ): Promise<Buffer> {
    const transcoderKey = `${options.quality}-${options.format}`;
    const transcoder = this.transcoders.get(transcoderKey);

    if (!transcoder) {
      throw new Error(`No transcoder configured for ${transcoderKey}`);
    }

    return transcoder.convert<Buffer>(imageBuffer);
  }

  async generateResponsiveVariants(imageBuffer: Buffer): Promise<{
    webp: { high: Buffer; medium: Buffer; low: Buffer };
    avif: { high: Buffer; medium: Buffer };
  }> {
    const [webpHigh, webpMedium, webpLow, avifHigh, avifMedium] = await Promise.all([
      this.optimize(imageBuffer, { format: 'webp', quality: 'high' }),
      this.optimize(imageBuffer, { format: 'webp', quality: 'medium' }),
      this.optimize(imageBuffer, { format: 'webp', quality: 'low' }),
      this.optimize(imageBuffer, { format: 'avif', quality: 'high' }),
      this.optimize(imageBuffer, { format: 'avif', quality: 'medium' }),
    ]);

    return {
      webp: { high: webpHigh, medium: webpMedium, low: webpLow },
      avif: { high: avifHigh, medium: avifMedium },
    };
  }
}

Performance Guidelines

Memory Management

  • Use streams for files larger than 50MB
  • Set appropriate concurrency limits (2-4 for CPU-intensive formats like AVIF)
  • Monitor memory usage in production environments

Format Selection

  • AVIF: Best compression, slower encoding, limited browser support
  • WebP: Good compression, fast encoding, wide browser support
  • JPEG: Universal compatibility, fast encoding

Quality Settings

  • High quality: 90-95 for archival/professional use
  • Standard quality: 80-85 for web content
  • Low quality: 60-75 for thumbnails/previews

Optimization Tips

  • Lower effort settings for real-time processing
  • Higher effort settings for batch processing
  • Use progressive formats for better perceived performance
  • Cache converted images to avoid repeated processing

Error Handling

The adapter throws specific errors for different failure scenarios:

import { UnsupportedSource } from '@rytass/file-converter-adapter-image-transcoder';

try {
  const result = await transcoder.convert<Buffer>(imageBuffer);
} catch (error) {
  if (error instanceof UnsupportedSource) {
    // Handle unsupported input format
    console.error('Input format not supported');
  } else {
    // Handle other transcoding errors
    console.error('Transcoding failed:', error.message);
  }
}

Testing

import { ImageTranscoder } from '@rytass/file-converter-adapter-image-transcoder';
import { readFileSync } from 'fs';
import sharp from 'sharp';

describe('ImageTranscoder', () => {
  it('should convert JPEG to WebP', async () => {
    const transcoder = new ImageTranscoder({
      targetFormat: 'webp',
      quality: 85,
    });

    const jpegBuffer = createTestJpeg();
    const webpBuffer = await transcoder.convert<Buffer>(jpegBuffer);

    const metadata = await sharp(webpBuffer).metadata();
    expect(metadata.format).toBe('webp');
  });

  it('should maintain quality in lossless conversion', async () => {
    const transcoder = new ImageTranscoder({
      targetFormat: 'png',
    });

    const originalBuffer = createTestImage();
    const convertedBuffer = await transcoder.convert<Buffer>(originalBuffer);

    const [originalMeta, convertedMeta] = await Promise.all([
      sharp(originalBuffer).metadata(),
      sharp(convertedBuffer).metadata(),
    ]);

    expect(convertedMeta.width).toBe(originalMeta.width);
    expect(convertedMeta.height).toBe(originalMeta.height);
  });
});

Best Practices

Format Strategy

  • Use AVIF for maximum compression when encoding time isn't critical
  • Use WebP for balanced compression and compatibility
  • Provide JPEG fallbacks for maximum browser support

Quality Configuration

  • Test different quality settings with your typical image content
  • Consider using different quality settings for different image types
  • Monitor file size vs quality trade-offs

Performance

  • Use appropriate effort settings based on your use case
  • Implement caching to avoid repeated conversions
  • Consider using worker threads for CPU-intensive batch processing

Error Handling

  • Always handle UnsupportedSource errors gracefully
  • Implement fallback strategies for critical conversions
  • Log conversion failures for monitoring and debugging

License

MIT