Browse Source

feat(server): Generate preview images for non-image assets

Michael Bromley 7 years ago
parent
commit
61e57650ed

+ 2 - 1
admin-ui/src/app/data/definitions/product-definitions.ts

@@ -85,7 +85,8 @@ export const ASSET_FRAGMENT = gql`
     fragment Asset on Asset {
         id
         name
-        mimetype
+        fileSize
+        mimeType
         type
         name
         preview

File diff suppressed because it is too large
+ 0 - 0
schema.json


+ 16 - 0
server/src/common/utils.ts

@@ -1,3 +1,4 @@
+import { AssetType } from 'shared/generated-types';
 import { ID } from 'shared/shared-types';
 
 /**
@@ -35,3 +36,18 @@ export function idsAreEqual(id1?: ID, id2?: ID): boolean {
     }
     return id1.toString() === id2.toString();
 }
+
+/**
+ * Returns the AssetType based on the mime type.
+ */
+export function getAssetType(mimeType: string): AssetType {
+    const type = mimeType.split('/')[0];
+    switch (type) {
+        case 'image':
+            return AssetType.IMAGE;
+        case 'video':
+            return AssetType.VIDEO;
+        default:
+            return AssetType.BINARY;
+    }
+}

+ 1 - 1
server/src/config/asset-preview-strategy/asset-preview-strategy.ts

@@ -9,5 +9,5 @@ import { Stream } from 'stream';
  * - watermarks added to preview images
  */
 export interface AssetPreviewStrategy {
-    generatePreviewImage(mimetype: string, data: Buffer): Promise<Buffer>;
+    generatePreviewImage(mimeType: string, data: Buffer): Promise<Buffer>;
 }

+ 1 - 1
server/src/config/asset-preview-strategy/no-asset-preview-strategy.ts

@@ -6,7 +6,7 @@ import { AssetPreviewStrategy } from './asset-preview-strategy';
  * A placeholder strategy which will simply throw an error when used.
  */
 export class NoAssetPreviewStrategy implements AssetPreviewStrategy {
-    generatePreviewImage(mimetype: string, data: Buffer): Promise<Buffer> {
+    generatePreviewImage(mimeType: string, data: Buffer): Promise<Buffer> {
         throw new I18nError('error.no-asset-preview-strategy-configured');
     }
 }

+ 3 - 1
server/src/entity/asset/asset.entity.ts

@@ -21,7 +21,9 @@ export class Asset extends VendureEntity {
 
     @Column('varchar') type: AssetType;
 
-    @Column() mimetype: string;
+    @Column() mimeType: string;
+
+    @Column() fileSize: number;
 
     @Column() source: string;
 

+ 2 - 1
server/src/entity/asset/asset.graphql

@@ -3,7 +3,8 @@ type Asset implements Node {
     name: String!
     description: String
     type: AssetType!
-    mimetype: String!
+    fileSize: Int!
+    mimeType: String!
     source: String!
     preview: String!
 }

+ 41 - 11
server/src/plugin/default-asset-server/default-asset-preview-strategy.ts

@@ -1,6 +1,8 @@
+import * as path from 'path';
+import { AssetType } from 'shared/generated-types';
 import * as sharp from 'sharp';
-import { Stream } from 'stream';
 
+import { getAssetType } from '../../common/utils';
 import { AssetPreviewStrategy } from '../../config/asset-preview-strategy/asset-preview-strategy';
 
 export class DefaultAssetPreviewStrategy implements AssetPreviewStrategy {
@@ -11,18 +13,46 @@ export class DefaultAssetPreviewStrategy implements AssetPreviewStrategy {
         },
     ) {}
 
-    async generatePreviewImage(mimetype: string, data: Buffer): Promise<Buffer> {
-        const image = sharp(data);
-        const metadata = await image.metadata();
-        const width = metadata.width || 0;
-        const height = metadata.height || 0;
+    async generatePreviewImage(mimeType: string, data: Buffer): Promise<Buffer> {
+        const assetType = getAssetType(mimeType);
         const { maxWidth, maxHeight } = this.config;
-        if (maxWidth < width || maxHeight < height) {
-            return image
-                .resize(maxWidth, maxHeight)
-                .max()
+
+        if (assetType === AssetType.IMAGE) {
+            const image = sharp(data);
+            const metadata = await image.metadata();
+            const width = metadata.width || 0;
+            const height = metadata.height || 0;
+            if (maxWidth < width || maxHeight < height) {
+                return image
+                    .resize(maxWidth, maxHeight)
+                    .max()
+                    .toBuffer();
+            } else {
+                return data;
+            }
+        } else {
+            return sharp(path.join(__dirname, 'file-icon.png'))
+                .resize(800, 800)
+                .overlayWith(this.generateMimeTypeOverlay(mimeType), {
+                    gravity: sharp.gravity.center,
+                })
+                .min()
                 .toBuffer();
         }
-        return data;
+    }
+
+    private generateMimeTypeOverlay(mimeType: string): Buffer {
+        return Buffer.from(`
+            <svg xmlns="http://www.w3.org/2000/svg" height="150" width="800">
+            <style>
+                text {
+                   font-size: 64px;
+                   font-family: Arial, sans-serif;
+                   fill: #666;
+                }
+              </style>
+
+              <text x="400" y="110"  text-anchor="middle" width="800">${mimeType}</text>
+            </svg>`);
     }
 }

BIN
server/src/plugin/default-asset-server/file-icon.png


BIN
server/src/plugin/default-asset-server/file-icon.psd


+ 17 - 5
server/src/service/asset.service.ts

@@ -7,6 +7,7 @@ import { ID, PaginatedList } from 'shared/shared-types';
 import { Connection } from 'typeorm';
 
 import { ListQueryOptions } from '../common/types/common-types';
+import { getAssetType } from '../common/utils';
 import { ConfigService } from '../config/config.service';
 import { Asset } from '../entity/asset/asset.entity';
 
@@ -35,17 +36,18 @@ export class AssetService {
         const normalizedFileName = this.normalizeFileName(filename);
 
         const sourceFileName = await assetStorageStrategy.writeFileFromStream(normalizedFileName, stream);
-        const image = await assetStorageStrategy.readFileToBuffer(sourceFileName);
-        const preview = await assetPreviewStrategy.generatePreviewImage(mimetype, image);
+        const sourceFile = await assetStorageStrategy.readFileToBuffer(sourceFileName);
+        const preview = await assetPreviewStrategy.generatePreviewImage(mimetype, sourceFile);
         const previewFileName = await assetStorageStrategy.writeFileFromBuffer(
-            this.addSuffix(normalizedFileName, '__preview'),
+            this.getPreviewFileName(mimetype, normalizedFileName),
             preview,
         );
 
         const asset = new Asset({
-            type: AssetType.IMAGE,
+            type: getAssetType(mimetype),
             name: filename,
-            mimetype,
+            fileSize: sourceFile.byteLength,
+            mimeType: mimetype,
             source: sourceFileName,
             preview: previewFileName,
         });
@@ -60,6 +62,16 @@ export class AssetService {
         return this.addSuffix(normalized, `-${randomPart}`);
     }
 
+    private getPreviewFileName(mimeType: string, sourceFileName: string): string {
+        const previewSuffix = '__preview';
+        switch (getAssetType(mimeType)) {
+            case AssetType.IMAGE:
+                return this.addSuffix(sourceFileName, previewSuffix);
+            default:
+                return this.addSuffix(`${sourceFileName}.png`, previewSuffix);
+        }
+    }
+
     private addSuffix(fileName: string, suffix: string): string {
         const ext = path.extname(fileName);
         const baseName = path.basename(fileName, ext);

+ 6 - 3
shared/generated-types.ts

@@ -1465,7 +1465,8 @@ export interface GetAssetList_assets_items {
   __typename: "Asset";
   id: string;
   name: string;
-  mimetype: string;
+  fileSize: number;
+  mimeType: string;
   type: AssetType;
   preview: string;
   source: string;
@@ -1496,7 +1497,8 @@ export interface CreateAsset_createAsset {
   __typename: "Asset";
   id: string;
   name: string;
-  mimetype: string;
+  fileSize: number;
+  mimeType: string;
   type: AssetType;
   preview: string;
   source: string;
@@ -1814,7 +1816,8 @@ export interface Asset {
   __typename: "Asset";
   id: string;
   name: string;
-  mimetype: string;
+  fileSize: number;
+  mimeType: string;
   type: AssetType;
   preview: string;
   source: string;

Some files were not shown because too many files changed in this diff