feat(browser): clamp screenshots under 5MB
This commit is contained in:
69
src/browser/screenshot.ts
Normal file
69
src/browser/screenshot.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
import sharp from "sharp";
|
||||
|
||||
export const DEFAULT_BROWSER_SCREENSHOT_MAX_SIDE = 2000;
|
||||
export const DEFAULT_BROWSER_SCREENSHOT_MAX_BYTES = 5 * 1024 * 1024;
|
||||
|
||||
export async function normalizeBrowserScreenshot(
|
||||
buffer: Buffer,
|
||||
opts?: {
|
||||
maxSide?: number;
|
||||
maxBytes?: number;
|
||||
},
|
||||
): Promise<{ buffer: Buffer; contentType?: "image/jpeg" }> {
|
||||
const maxSide = Math.max(
|
||||
1,
|
||||
Math.round(opts?.maxSide ?? DEFAULT_BROWSER_SCREENSHOT_MAX_SIDE),
|
||||
);
|
||||
const maxBytes = Math.max(
|
||||
1,
|
||||
Math.round(opts?.maxBytes ?? DEFAULT_BROWSER_SCREENSHOT_MAX_BYTES),
|
||||
);
|
||||
|
||||
const meta = await sharp(buffer, { failOnError: false }).metadata();
|
||||
const width = Number(meta.width ?? 0);
|
||||
const height = Number(meta.height ?? 0);
|
||||
const maxDim = Math.max(width, height);
|
||||
|
||||
if (
|
||||
buffer.byteLength <= maxBytes &&
|
||||
(maxDim === 0 || (width <= maxSide && height <= maxSide))
|
||||
) {
|
||||
return { buffer };
|
||||
}
|
||||
|
||||
const qualities = [85, 75, 65, 55, 45, 35];
|
||||
const sideStart = maxDim > 0 ? Math.min(maxSide, maxDim) : maxSide;
|
||||
const sideGrid = [sideStart, 1800, 1600, 1400, 1200, 1000, 800]
|
||||
.map((v) => Math.min(maxSide, v))
|
||||
.filter((v, i, arr) => v > 0 && arr.indexOf(v) === i)
|
||||
.sort((a, b) => b - a);
|
||||
|
||||
let smallest: { buffer: Buffer; size: number } | null = null;
|
||||
|
||||
for (const side of sideGrid) {
|
||||
for (const quality of qualities) {
|
||||
const out = await sharp(buffer, { failOnError: false })
|
||||
.resize({
|
||||
width: side,
|
||||
height: side,
|
||||
fit: "inside",
|
||||
withoutEnlargement: true,
|
||||
})
|
||||
.jpeg({ quality, mozjpeg: true })
|
||||
.toBuffer();
|
||||
|
||||
if (!smallest || out.byteLength < smallest.size) {
|
||||
smallest = { buffer: out, size: out.byteLength };
|
||||
}
|
||||
|
||||
if (out.byteLength <= maxBytes) {
|
||||
return { buffer: out, contentType: "image/jpeg" };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const best = smallest?.buffer ?? buffer;
|
||||
throw new Error(
|
||||
`Browser screenshot could not be reduced below ${(maxBytes / (1024 * 1024)).toFixed(0)}MB (got ${(best.byteLength / (1024 * 1024)).toFixed(2)}MB)`,
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user