|
@@ -29,6 +29,34 @@ interface SharpAssetPreviewConfig {
|
|
|
* @default 1600
|
|
* @default 1600
|
|
|
*/
|
|
*/
|
|
|
maxWidth?: number;
|
|
maxWidth?: number;
|
|
|
|
|
+ /**
|
|
|
|
|
+ * @description
|
|
|
|
|
+ * Set Sharp's options for encoding jpeg files: https://sharp.pixelplumbing.com/api-output#jpeg
|
|
|
|
|
+ *
|
|
|
|
|
+ * @since 1.7.0
|
|
|
|
|
+ */
|
|
|
|
|
+ jpegOptions?: sharp.JpegOptions;
|
|
|
|
|
+ /**
|
|
|
|
|
+ * @description
|
|
|
|
|
+ * Set Sharp's options for encoding png files: https://sharp.pixelplumbing.com/api-output#png
|
|
|
|
|
+ *
|
|
|
|
|
+ * @since 1.7.0
|
|
|
|
|
+ */
|
|
|
|
|
+ pngOptions?: sharp.PngOptions;
|
|
|
|
|
+ /**
|
|
|
|
|
+ * @description
|
|
|
|
|
+ * Set Sharp's options for encoding webp files: https://sharp.pixelplumbing.com/api-output#webp
|
|
|
|
|
+ *
|
|
|
|
|
+ * @since 1.7.0
|
|
|
|
|
+ */
|
|
|
|
|
+ webpOptions?: sharp.WebpOptions;
|
|
|
|
|
+ /**
|
|
|
|
|
+ * @description
|
|
|
|
|
+ * Set Sharp's options for encoding gif files: https://sharp.pixelplumbing.com/api-output#gif
|
|
|
|
|
+ *
|
|
|
|
|
+ * @since 1.7.0
|
|
|
|
|
+ */
|
|
|
|
|
+ gifOptions?: sharp.GifOptions;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
@@ -37,39 +65,74 @@ interface SharpAssetPreviewConfig {
|
|
|
* preview images of uploaded binary files. For non-image binaries, a generic "file" icon with the mime type
|
|
* preview images of uploaded binary files. For non-image binaries, a generic "file" icon with the mime type
|
|
|
* overlay will be generated.
|
|
* overlay will be generated.
|
|
|
*
|
|
*
|
|
|
|
|
+ * By default, this strategy will produce previews up to maximum dimensions of 1600 x 1600 pixels. The created
|
|
|
|
|
+ * preview images will match the input format - so a source file in jpeg format will output a jpeg preview,
|
|
|
|
|
+ * a webp source file will output a webp preview, and so on.
|
|
|
|
|
+ *
|
|
|
|
|
+ * The settings for the outputs will default to Sharp's defaults (https://sharp.pixelplumbing.com/api-output).
|
|
|
|
|
+ * However, it is possible to pass your own configurations to control the output of each format:
|
|
|
|
|
+ *
|
|
|
|
|
+ * ```TypeScript
|
|
|
|
|
+ * AssetServerPlugin.init({
|
|
|
|
|
+ * previewStrategy: new SharpAssetPreviewStrategy({
|
|
|
|
|
+ * jpegOptions: { quality: 95 },
|
|
|
|
|
+ * webpOptions: { quality: 95 },
|
|
|
|
|
+ * }),
|
|
|
|
|
+ * }),
|
|
|
|
|
+ * ```
|
|
|
|
|
+ *
|
|
|
* @docsCategory AssetServerPlugin
|
|
* @docsCategory AssetServerPlugin
|
|
|
* @docsPage SharpAssetPreviewStrategy
|
|
* @docsPage SharpAssetPreviewStrategy
|
|
|
* @docsWeight 0
|
|
* @docsWeight 0
|
|
|
*/
|
|
*/
|
|
|
export class SharpAssetPreviewStrategy implements AssetPreviewStrategy {
|
|
export class SharpAssetPreviewStrategy implements AssetPreviewStrategy {
|
|
|
- constructor(private config: SharpAssetPreviewConfig) {}
|
|
|
|
|
private readonly defaultConfig: Required<SharpAssetPreviewConfig> = {
|
|
private readonly defaultConfig: Required<SharpAssetPreviewConfig> = {
|
|
|
maxHeight: 1600,
|
|
maxHeight: 1600,
|
|
|
maxWidth: 1600,
|
|
maxWidth: 1600,
|
|
|
|
|
+ jpegOptions: {},
|
|
|
|
|
+ pngOptions: {},
|
|
|
|
|
+ webpOptions: {},
|
|
|
|
|
+ gifOptions: {},
|
|
|
};
|
|
};
|
|
|
|
|
+ private readonly config: Required<SharpAssetPreviewConfig>;
|
|
|
|
|
|
|
|
- async generatePreviewImage(ctx: RequestContext, mimeType: string, data: Buffer): Promise<Buffer> {
|
|
|
|
|
- const assetType = getAssetType(mimeType);
|
|
|
|
|
- const config = {
|
|
|
|
|
|
|
+ constructor(config?: SharpAssetPreviewConfig) {
|
|
|
|
|
+ this.config = {
|
|
|
...this.defaultConfig,
|
|
...this.defaultConfig,
|
|
|
- ...this.config,
|
|
|
|
|
|
|
+ ...(config ?? {}),
|
|
|
};
|
|
};
|
|
|
- const { maxWidth, maxHeight } = config;
|
|
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ async generatePreviewImage(ctx: RequestContext, mimeType: string, data: Buffer): Promise<Buffer> {
|
|
|
|
|
+ const assetType = getAssetType(mimeType);
|
|
|
|
|
+
|
|
|
|
|
+ const { maxWidth, maxHeight } = this.config;
|
|
|
|
|
|
|
|
if (assetType === AssetType.IMAGE) {
|
|
if (assetType === AssetType.IMAGE) {
|
|
|
try {
|
|
try {
|
|
|
- const image = sharp(data);
|
|
|
|
|
|
|
+ const image = sharp(data).rotate();
|
|
|
const metadata = await image.metadata();
|
|
const metadata = await image.metadata();
|
|
|
const width = metadata.width || 0;
|
|
const width = metadata.width || 0;
|
|
|
const height = metadata.height || 0;
|
|
const height = metadata.height || 0;
|
|
|
if (maxWidth < width || maxHeight < height) {
|
|
if (maxWidth < width || maxHeight < height) {
|
|
|
- return image.rotate().resize(maxWidth, maxHeight, { fit: 'inside' }).toBuffer();
|
|
|
|
|
|
|
+ image.resize(maxWidth, maxHeight, { fit: 'inside' });
|
|
|
|
|
+ }
|
|
|
|
|
+ if (mimeType === 'image/svg+xml') {
|
|
|
|
|
+ // Convert the SVG to a raster for the preview
|
|
|
|
|
+ return image.toBuffer();
|
|
|
} else {
|
|
} else {
|
|
|
- if (mimeType === 'image/svg+xml') {
|
|
|
|
|
- // Convert the SVG to a raster for the preview
|
|
|
|
|
- return image.toBuffer();
|
|
|
|
|
- } else {
|
|
|
|
|
- return image.rotate().toBuffer();
|
|
|
|
|
|
|
+ switch (metadata.format) {
|
|
|
|
|
+ case 'jpeg':
|
|
|
|
|
+ case 'jpg':
|
|
|
|
|
+ return image.jpeg(this.config.jpegOptions).toBuffer();
|
|
|
|
|
+ case 'png':
|
|
|
|
|
+ return image.png(this.config.pngOptions).toBuffer();
|
|
|
|
|
+ case 'webp':
|
|
|
|
|
+ return image.webp(this.config.webpOptions).toBuffer();
|
|
|
|
|
+ case 'gif':
|
|
|
|
|
+ return image.gif(this.config.jpegOptions).toBuffer();
|
|
|
|
|
+ default:
|
|
|
|
|
+ return image.toBuffer();
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
} catch (err: any) {
|
|
} catch (err: any) {
|