import sharp from "sharp"; const productDimensions = Object.freeze({ "3:4": Object.freeze({ pixelHeight: 1440, pixelWidth: 1080 }), "1:1": Object.freeze({ pixelHeight: 1080, pixelWidth: 1080 }), "4:3": Object.freeze({ pixelHeight: 1080, pixelWidth: 1440 }), "9:16": Object.freeze({ pixelHeight: 1920, pixelWidth: 1080 }), }); const gptImageRequestSizes = Object.freeze({ "3:4": "1056x1408", "1:1": "1088x1088", "4:3": "1408x1056", "9:16": "1008x1792", }); const allowedMimeTypes = new Set(["image/jpeg", "image/png", "image/webp"]); const maximumInputBytes = 20 * 1024 * 1024; function assertRatio(ratio) { if (!(ratio in productDimensions)) throw new Error("image_output_ratio_unsupported"); return ratio; } export function productDimensionsForRatio(ratio) { return { ...productDimensions[assertRatio(ratio)] }; } export function gptImageRequestSizeForRatio(ratio) { return gptImageRequestSizes[assertRatio(ratio)]; } export async function normalizeImageOutputToRatio(input) { const ratio = assertRatio(input?.ratio); if (!Buffer.isBuffer(input?.bytes) || input.bytes.length === 0 || input.bytes.length > maximumInputBytes || !allowedMimeTypes.has(input?.mimeType)) { throw new Error("image_output_media_invalid"); } const target = productDimensions[ratio]; if (input.pixelWidth === target.pixelWidth && input.pixelHeight === target.pixelHeight) { return { bytes: Buffer.from(input.bytes), mimeType: input.mimeType, normalized: false, ...target, upstreamPixelHeight: input.pixelHeight, upstreamPixelWidth: input.pixelWidth, }; } const image = sharp(input.bytes, { failOn: "error", limitInputPixels: 40_000_000 }); const metadata = await image.metadata(); if (!metadata.width || !metadata.height) throw new Error("image_output_dimensions_missing"); const requestedRatio = target.pixelWidth / target.pixelHeight; const upstreamRatio = metadata.width / metadata.height; if (Math.abs(upstreamRatio - requestedRatio) / requestedRatio > 0.02) { throw new Error("image_output_aspect_ratio_mismatch"); } const { data, info } = await image .resize(target.pixelWidth, target.pixelHeight, { fit: "fill", kernel: sharp.kernel.lanczos3 }) .png({ compressionLevel: 9 }) .toBuffer({ resolveWithObject: true }); if (info.width !== target.pixelWidth || info.height !== target.pixelHeight || info.format !== "png") { throw new Error("image_output_normalization_failed"); } return { bytes: data, mimeType: "image/png", normalized: true, ...target, upstreamPixelHeight: metadata.height, upstreamPixelWidth: metadata.width, }; }