Node.js에서 sharp 이미지 처리 에러 해결
sharp 라이브러리를 사용한 이미지 처리 시 발생하는 다양한 에러와 그 해결 방법을 다룹니다.
Introduction
sharp는 Node.js에서 가장 많이 사용되는 이미지 처리 라이브러리입니다. 하지만 libvips 의존성 문제로 인해 various 에러가 발생할 수 있습니다. 이번 포스트에서는 sharp 관련 에러를 해결하는 방법을 살펴보겠습니다.
Environment
node --version
# v20.11.0
npm list sharp
# sharp@0.33.2
# 시스템 의존성 확인
ldconfig -p | grep vips
# libvips.so.42 => /usr/lib/x86_64-linux-gnu/libvips.so.42Problem
sharp를 설치하거나 사용할 때 다음과 같은 에러가 발생했습니다:
# 설치 에러
npm install sharp
# sharp: Installation error: sharp requires libvips >= 8.14.0
# 런타임 에러
Error: Could not load the "sharp" module using the linux-x64 runtime
libvips.so.42: cannot open shared object file: No such file or directory추가 에러들
# Windows 환경
sharp: Installation error: The sharp library requires the "node-gyp" build tools
# 메모리 에러
Error: vips_jpegload: unable to read image data
# 포맷 지원 에러
Error: Input buffer contains unsupported image formatSolution
1. Windows에서 sharp 설치
# 방법 1: windows-build-tools 설치
npm install --global windows-build-tools
# 방법 2: Visual Studio Build Tools 설치 후
npm install sharp --build-from-source
# 방법 3: 바이너리 버전 사용
npm install sharp --arch=x64 --platform=win322. 기본 이미지 처리
const sharp = require('sharp');
// 이미지 리사이즈
async function resizeImage(inputPath, outputPath, width, height) {
try {
await sharp(inputPath)
.resize(width, height, {
fit: 'cover',
position: 'centre',
})
.toFile(outputPath);
console.log('Image resized successfully');
} catch (error) {
console.error('Resize error:', error.message);
throw error;
}
}
// 이미지 포맷 변환
async function convertFormat(inputPath, outputPath, format) {
const options = {
jpeg: { quality: 80, progressive: true },
png: { compressionLevel: 6, adaptiveFiltering: true },
webp: { quality: 80, lossless: false },
avif: { quality: 80, speed: 4 },
};
await sharp(inputPath)
.toFormat(format, options[format])
.toFile(outputPath);
}3. 에러 핸들링 강화
const sharp = require('sharp');
class ImageProcessor {
constructor() {
this.maxSize = 10 * 1024 * 1024; // 10MB
this.allowedFormats = ['jpeg', 'jpg', 'png', 'webp', 'gif', 'avif'];
}
async processImage(inputPath, outputPath, options = {}) {
try {
// 메타데이터 확인
const metadata = await sharp(inputPath).metadata();
if (!this.allowedFormats.includes(metadata.format)) {
throw new Error(`Unsupported format: ${metadata.format}`);
}
if (metadata.size > this.maxSize) {
throw new Error('Image too large');
}
// 처리 파이프라인
let pipeline = sharp(inputPath);
// 리사이즈 옵션
if (options.width || options.height) {
pipeline = pipeline.resize({
width: options.width,
height: options.height,
fit: options.fit || 'cover',
withoutEnlargement: true,
});
}
// 포맷 변환
if (options.format) {
pipeline = pipeline.toFormat(options.format, {
quality: options.quality || 80,
});
}
// 메타데이터 유지
if (options.keepMetadata) {
pipeline = pipeline.withMetadata();
}
// 출력
await pipeline.toFile(outputPath);
return {
success: true,
metadata: await sharp(outputPath).metadata(),
};
} catch (error) {
return {
success: false,
error: error.message,
code: error.code,
};
}
}
}
// 사용 예시
const processor = new ImageProcessor();
const result = await processor.processImage(
'input.jpg',
'output.webp',
{ width: 800, height: 600, format: 'webp', quality: 80 }
);4. 스트리밍 처리
const sharp = require('sharp');
const { pipeline } = require('stream');
const fs = require('fs');
async function processImageStream(inputPath, outputPath) {
return new Promise((resolve, reject) => {
const readStream = fs.createReadStream(inputPath);
const transform = sharp()
.resize(800, 600)
.webp({ quality: 80 });
const writeStream = fs.createWriteStream(outputPath);
pipeline(readStream, transform, writeStream, (err) => {
if (err) {
reject(err);
} else {
resolve();
}
});
});
}5. 배치 처리
const sharp = require('sharp');
const path = require('path');
const fs = require('fs').promises;
class BatchProcessor {
constructor(inputDir, outputDir) {
this.inputDir = inputDir;
this.outputDir = outputDir;
this.concurrency = 4;
}
async processAll() {
const files = await fs.readdir(this.inputDir);
const imageFiles = files.filter(f =>
/\.(jpg|jpeg|png|webp|gif)$/i.test(f)
);
const chunks = this.chunkArray(imageFiles, this.concurrency);
const results = [];
for (const chunk of chunks) {
const chunkResults = await Promise.all(
chunk.map(file => this.processFile(file))
);
results.push(...chunkResults);
}
return results;
}
async processFile(filename) {
const inputPath = path.join(this.inputDir, filename);
const outputPath = path.join(this.outputDir, `processed_${filename}`);
try {
await sharp(inputPath)
.resize(1200, 1200, { fit: 'inside', withoutEnlargement: true })
.jpeg({ quality: 85, progressive: true })
.toFile(outputPath);
return { file: filename, success: true };
} catch (error) {
return { file: filename, success: false, error: error.message };
}
}
chunkArray(array, size) {
const chunks = [];
for (let i = 0; i < array.length; i += size) {
chunks.push(array.slice(i, i + size));
}
return chunks;
}
}
// 사용 예시
const processor = new BatchProcessor('./uploads', './processed');
const results = await processor.processAll();
console.log(`Processed ${results.filter(r => r.success).length}/${results.length} files`);Lessons Learned
- 시스템 의존성 확인: sharp 설치 전 libvips 등 시스템 의존성을 확인하세요
- 에러 핸들링: 모든 이미지 처리 작업에 적절한 에러 핸들링을 추가하세요
- 메타데이터 검증: 처리 전 이미지 메타데이터를 확인하여 유효성을 검증하세요
- 배치 처리 최적화: 대량 이미지 처리 시 동시성을 제한하여 리소스를 관리하세요
- 포맷 지원 확인: 처리할 이미지 포맷이 sharp에서 지원되는지 확인하세요
This blog does not accept any external sponsorships, affiliate marketing, or ad revenue.