feat: 完善视频任务与 KOC 资源库
This commit is contained in:
83
lib/workbook-image.ts
Normal file
83
lib/workbook-image.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
import sharp from "sharp";
|
||||
|
||||
export type WorkbookSourceImage = {
|
||||
bytes: Uint8Array;
|
||||
contentType: string;
|
||||
width?: number | null;
|
||||
height?: number | null;
|
||||
description: string;
|
||||
};
|
||||
|
||||
export type WorkbookImageNormalizationOptions = {
|
||||
maxDimension?: number;
|
||||
outputFormat?: "png" | "jpeg";
|
||||
jpegQuality?: number;
|
||||
};
|
||||
|
||||
const NORMALIZABLE_IMAGE = /^image\/(?:jpe?g|png|webp|gif|tiff?|avif|heic|heif)$/i;
|
||||
|
||||
function hasImageSignature(bytes: Uint8Array) {
|
||||
if (bytes.length < 4) return false;
|
||||
if (bytes[0] === 0xff && bytes[1] === 0xd8) return true;
|
||||
if (bytes[0] === 0x89 && bytes[1] === 0x50 && bytes[2] === 0x4e && bytes[3] === 0x47) {
|
||||
return true;
|
||||
}
|
||||
if (bytes[0] === 0x47 && bytes[1] === 0x49 && bytes[2] === 0x46) return true;
|
||||
if (
|
||||
bytes.length >= 12 &&
|
||||
String.fromCharCode(...bytes.slice(0, 4)) === "RIFF" &&
|
||||
String.fromCharCode(...bytes.slice(8, 12)) === "WEBP"
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return (
|
||||
(bytes[0] === 0x49 && bytes[1] === 0x49 && bytes[2] === 0x2a && bytes[3] === 0x00) ||
|
||||
(bytes[0] === 0x4d && bytes[1] === 0x4d && bytes[2] === 0x00 && bytes[3] === 0x2a)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Excel viewers disagree on whether JPEG EXIF orientation should be applied.
|
||||
* Bake that orientation into the pixels before the image enters the workbook.
|
||||
* Callers may also resize and encode large source images as JPEG to keep the
|
||||
* generated workbook within the upload limit.
|
||||
*/
|
||||
export async function normalizeWorkbookImage(
|
||||
image: WorkbookSourceImage,
|
||||
options: WorkbookImageNormalizationOptions = {},
|
||||
): Promise<WorkbookSourceImage> {
|
||||
if (!NORMALIZABLE_IMAGE.test(image.contentType) && !hasImageSignature(image.bytes)) {
|
||||
return image;
|
||||
}
|
||||
try {
|
||||
let normalized = sharp(image.bytes, { animated: false }).rotate();
|
||||
if (options.maxDimension && options.maxDimension > 0) {
|
||||
normalized = normalized.resize({
|
||||
width: Math.floor(options.maxDimension),
|
||||
height: Math.floor(options.maxDimension),
|
||||
fit: "inside",
|
||||
withoutEnlargement: true,
|
||||
});
|
||||
}
|
||||
normalized =
|
||||
options.outputFormat === "jpeg"
|
||||
? normalized
|
||||
.flatten({ background: "#ffffff" })
|
||||
.jpeg({
|
||||
quality: Math.min(95, Math.max(50, options.jpegQuality ?? 82)),
|
||||
mozjpeg: true,
|
||||
})
|
||||
: normalized.png({ compressionLevel: 6 });
|
||||
const { data, info } = await normalized.toBuffer({ resolveWithObject: true });
|
||||
return {
|
||||
...image,
|
||||
bytes: new Uint8Array(data),
|
||||
contentType:
|
||||
options.outputFormat === "jpeg" ? "image/jpeg" : "image/png",
|
||||
width: info.width,
|
||||
height: info.height,
|
||||
};
|
||||
} catch {
|
||||
return image;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user