92 lines
2.3 KiB
TypeScript
92 lines
2.3 KiB
TypeScript
|
|
import sharp from 'sharp';
|
||
|
|
import fs from 'fs';
|
||
|
|
import { absolutePath, ensureDir } from './storage.js';
|
||
|
|
|
||
|
|
export interface ImageMeta {
|
||
|
|
width: number;
|
||
|
|
height: number;
|
||
|
|
mimeType: string;
|
||
|
|
size: number;
|
||
|
|
}
|
||
|
|
|
||
|
|
export async function extractMeta(filePath: string): Promise<ImageMeta> {
|
||
|
|
const abs = absolutePath(filePath);
|
||
|
|
const meta = await sharp(abs).metadata();
|
||
|
|
const stat = fs.statSync(abs);
|
||
|
|
|
||
|
|
const mimeMap: Record<string, string> = {
|
||
|
|
jpeg: 'image/jpeg',
|
||
|
|
png: 'image/png',
|
||
|
|
gif: 'image/gif',
|
||
|
|
webp: 'image/webp',
|
||
|
|
};
|
||
|
|
|
||
|
|
return {
|
||
|
|
width: meta.width ?? 0,
|
||
|
|
height: meta.height ?? 0,
|
||
|
|
mimeType: mimeMap[meta.format ?? ''] ?? 'image/jpeg',
|
||
|
|
size: stat.size,
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
export async function saveBuffer(buffer: Buffer, destRelPath: string): Promise<void> {
|
||
|
|
ensureDir(destRelPath);
|
||
|
|
const abs = absolutePath(destRelPath);
|
||
|
|
fs.writeFileSync(abs, buffer);
|
||
|
|
}
|
||
|
|
|
||
|
|
export interface ResizeOptions {
|
||
|
|
width?: number;
|
||
|
|
height?: number;
|
||
|
|
quality?: number;
|
||
|
|
}
|
||
|
|
|
||
|
|
export async function resizeImage(
|
||
|
|
srcRelPath: string,
|
||
|
|
destRelPath: string,
|
||
|
|
options: ResizeOptions
|
||
|
|
): Promise<ImageMeta> {
|
||
|
|
const srcAbs = absolutePath(srcRelPath);
|
||
|
|
ensureDir(destRelPath);
|
||
|
|
const destAbs = absolutePath(destRelPath);
|
||
|
|
|
||
|
|
const src = await sharp(srcAbs).metadata();
|
||
|
|
const isGif = src.format === 'gif';
|
||
|
|
|
||
|
|
let pipeline = sharp(srcAbs, { animated: isGif });
|
||
|
|
|
||
|
|
if (options.width || options.height) {
|
||
|
|
pipeline = pipeline.resize({
|
||
|
|
width: options.width,
|
||
|
|
height: options.height,
|
||
|
|
fit: 'inside',
|
||
|
|
withoutEnlargement: true,
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!isGif && options.quality) {
|
||
|
|
if (src.format === 'jpeg') pipeline = pipeline.jpeg({ quality: options.quality });
|
||
|
|
else if (src.format === 'png') pipeline = pipeline.png({ quality: options.quality });
|
||
|
|
else if (src.format === 'webp') pipeline = pipeline.webp({ quality: options.quality });
|
||
|
|
}
|
||
|
|
|
||
|
|
await pipeline.toFile(destAbs);
|
||
|
|
|
||
|
|
const resultMeta = await sharp(destAbs).metadata();
|
||
|
|
const stat = fs.statSync(destAbs);
|
||
|
|
|
||
|
|
const mimeMap: Record<string, string> = {
|
||
|
|
jpeg: 'image/jpeg',
|
||
|
|
png: 'image/png',
|
||
|
|
gif: 'image/gif',
|
||
|
|
webp: 'image/webp',
|
||
|
|
};
|
||
|
|
|
||
|
|
return {
|
||
|
|
width: resultMeta.width ?? 0,
|
||
|
|
height: resultMeta.height ?? 0,
|
||
|
|
mimeType: mimeMap[resultMeta.format ?? ''] ?? 'image/jpeg',
|
||
|
|
size: stat.size,
|
||
|
|
};
|
||
|
|
}
|