2026-04-06 12:53:53 +08:00
|
|
|
import sharp from "sharp";
|
|
|
|
|
|
|
|
|
|
export interface OutputFormat {
|
|
|
|
|
format: keyof sharp.FormatEnum;
|
|
|
|
|
extension: string;
|
|
|
|
|
contentType: string;
|
|
|
|
|
quality: number;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const FORMAT_MAP: Record<
|
|
|
|
|
string,
|
|
|
|
|
{ format: keyof sharp.FormatEnum; extension: string; contentType: string }
|
|
|
|
|
> = {
|
|
|
|
|
jpeg: { format: "jpeg", extension: "jpg", contentType: "image/jpeg" },
|
|
|
|
|
png: { format: "png", extension: "png", contentType: "image/png" },
|
|
|
|
|
webp: { format: "webp", extension: "webp", contentType: "image/webp" },
|
|
|
|
|
gif: { format: "gif", extension: "gif", contentType: "image/gif" },
|
|
|
|
|
tiff: { format: "tiff", extension: "tiff", contentType: "image/tiff" },
|
|
|
|
|
avif: { format: "avif", extension: "avif", contentType: "image/avif" },
|
2026-04-20 22:13:53 +08:00
|
|
|
heif: { format: "avif", extension: "avif", contentType: "image/avif" },
|
2026-04-21 09:59:57 +08:00
|
|
|
jxl: { format: "png", extension: "png", contentType: "image/png" },
|
2026-04-06 12:53:53 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const DEFAULT_QUALITY = 95;
|
|
|
|
|
const PNG_FALLBACK = FORMAT_MAP.png;
|
|
|
|
|
|
2026-04-21 09:59:57 +08:00
|
|
|
/** Formats that have no Sharp output encoder — fall back to PNG. */
|
|
|
|
|
const PNG_FALLBACK_FORMATS = new Set(["svg", "bmp", "raw", "tga", "psd", "exr", "hdr", "ico"]);
|
|
|
|
|
|
2026-04-06 12:53:53 +08:00
|
|
|
/**
|
|
|
|
|
* Detect the input image format and return matching output config.
|
|
|
|
|
* Falls back to PNG for undetectable or unsupported output formats
|
2026-04-21 09:59:57 +08:00
|
|
|
* (SVG, BMP, Camera RAW, TGA, PSD, EXR, HDR, ICO).
|
2026-04-06 12:53:53 +08:00
|
|
|
*/
|
|
|
|
|
export async function resolveOutputFormat(
|
|
|
|
|
inputBuffer: Buffer,
|
|
|
|
|
_filename: string,
|
|
|
|
|
qualityOverride?: number,
|
|
|
|
|
): Promise<OutputFormat> {
|
|
|
|
|
let detected: string | undefined;
|
|
|
|
|
try {
|
|
|
|
|
const meta = await sharp(inputBuffer).metadata();
|
|
|
|
|
detected = meta.format;
|
|
|
|
|
} catch {
|
|
|
|
|
// format detection failed
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-21 09:59:57 +08:00
|
|
|
// Force PNG fallback for formats without a Sharp output encoder
|
|
|
|
|
if (detected && PNG_FALLBACK_FORMATS.has(detected)) {
|
|
|
|
|
detected = undefined;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-06 12:53:53 +08:00
|
|
|
const mapped = detected ? FORMAT_MAP[detected] : undefined;
|
|
|
|
|
const config = mapped ?? PNG_FALLBACK;
|
|
|
|
|
const quality = qualityOverride ?? DEFAULT_QUALITY;
|
|
|
|
|
|
|
|
|
|
return { ...config, quality };
|
|
|
|
|
}
|