diff --git a/README.md b/README.md index 0a6abf78..2bfcb97f 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,7 @@ To configure it. Run `PiGallery2` first to create `config.json` file, then edit * prioritizes thumbnail generation (generating thumbnail first for the visible photos) * saving generated thumbnails to TEMP folder for reuse * supporting several core CPU - * supporting hardware acceleration ([sharp](https://github.com/lovell/sharp) as optional and JS-based [Jimp](https://github.com/oliver-moran/jimp) as fallback) + * supporting hardware acceleration ([sharp](https://github.com/lovell/sharp) and [gm](https://github.com/aheckmann/gm) as optional and JS-based [Jimp](https://github.com/oliver-moran/jimp) as fallback) * Custom lightbox for full screen photo viewing * keyboard support for navigation - `In progress` * showing low-res thumbnail while full image loads diff --git a/backend/middlewares/thumbnail/THRenderers.ts b/backend/middlewares/thumbnail/THRenderers.ts index 3ca1c279..d2e317b9 100644 --- a/backend/middlewares/thumbnail/THRenderers.ts +++ b/backend/middlewares/thumbnail/THRenderers.ts @@ -1,22 +1,67 @@ import {Metadata, SharpInstance} from "@types/sharp"; +import {Dimensions, State} from "@types/gm"; -export interface RendererInput { +export module ThumbnailRenderers { + + export interface RendererInput { imagePath: string; size: number; makeSquare: boolean; thPath: string; qualityPriority: boolean, __dirname: string; -} + } -export const softwareRenderer = (input: RendererInput, done) => { + export const jimp = (input: RendererInput, done) => { //generate thumbnail const Jimp = require("jimp"); Jimp.read(input.imagePath).then((image) => { + const Logger = require(input.__dirname + "/../../Logger").Logger; + Logger.silly("[JimpThRenderer] rendering thumbnail:", input.imagePath); + /** + * newWidth * newHeight = size*size + * newHeight/newWidth = height/width + * + * newHeight = (height/width)*newWidth + * newWidth * newWidth = (size*size) / (height/width) + * + * @type {number} + */ + const ratio = image.bitmap.height / image.bitmap.width; + const algo = input.qualityPriority == true ? Jimp.RESIZE_BEZIER : Jimp.RESIZE_NEAREST_NEIGHBOR; + if (input.makeSquare == false) { + let newWidth = Math.sqrt((input.size * input.size) / ratio); + + image.resize(newWidth, Jimp.AUTO, algo); + } else { + image.resize(input.size / Math.min(ratio, 1), Jimp.AUTO, algo); + image.crop(0, 0, input.size, input.size); + } + image.quality(60); // set JPEG quality + image.write(input.thPath, () => { // save + return done(); + }); + }).catch(function (err) { + const Error = require(input.__dirname + "/../../../common/entities/Error").Error; + const ErrorCodes = require(input.__dirname + "/../../../common/entities/Error").ErrorCodes; + return done(new Error(ErrorCodes.GENERAL_ERROR, err)); + }); + }; + + export const sharp = (input: RendererInput, done) => { + + //generate thumbnail + const sharp = require("sharp"); + + const image: SharpInstance = sharp(input.imagePath); + image + .metadata() + .then((metadata: Metadata) => { + const Logger = require(input.__dirname + "/../../Logger").Logger; - Logger.silly("[SoftwareThRenderer] rendering thumbnail:", input.imagePath); + Logger.silly("[SharpThRenderer] rendering thumbnail:", input.imagePath); /** * newWidth * newHeight = size*size * newHeight/newWidth = height/width @@ -26,81 +71,99 @@ export const softwareRenderer = (input: RendererInput, done) => { * * @type {number} */ - const ratio = image.bitmap.height / image.bitmap.width; - const algo = input.qualityPriority == true ? Jimp.RESIZE_BEZIER : Jimp.RESIZE_NEAREST_NEIGHBOR; - if (input.makeSquare == false) { - let newWidth = Math.sqrt((input.size * input.size) / ratio); + try { + const ratio = metadata.height / metadata.width; + const kernel = input.qualityPriority == true ? sharp.kernel.lanczos3 : sharp.kernel.nearest; + const interpolator = input.qualityPriority == true ? sharp.interpolator.bicubic : sharp.interpolator.nearest; + if (input.makeSquare == false) { + const newWidth = Math.round(Math.sqrt((input.size * input.size) / ratio)); + image.resize(newWidth, null, { + kernel: kernel, + interpolator: interpolator + }); - image.resize(newWidth, Jimp.AUTO, algo); - } else { - image.resize(input.size / Math.min(ratio, 1), Jimp.AUTO, algo); - image.crop(0, 0, input.size, input.size); - } - image.quality(60); // set JPEG quality - image.write(input.thPath, () => { // save + } else { + image + .resize(input.size, input.size, { + kernel: kernel, + interpolator: interpolator + }) + .crop(sharp.strategy.center); + } + image + .jpeg() + .toFile(input.thPath).then(() => { return done(); - }); - }).catch(function (err) { - const Error = require(input.__dirname + "/../../../common/entities/Error").Error; - const ErrorCodes = require(input.__dirname + "/../../../common/entities/Error").ErrorCodes; - return done(new Error(ErrorCodes.GENERAL_ERROR, err)); - }); -}; + }).catch(function (err) { + const Error = require(input.__dirname + "/../../../common/entities/Error").Error; + const ErrorCodes = require(input.__dirname + "/../../../common/entities/Error").ErrorCodes; + return done(new Error(ErrorCodes.GENERAL_ERROR, err)); + }); + } catch (err) { + const Error = require(input.__dirname + "/../../../common/entities/Error").Error; + const ErrorCodes = require(input.__dirname + "/../../../common/entities/Error").ErrorCodes; + return done(new Error(ErrorCodes.GENERAL_ERROR, err)); + } + }); -export const hardwareRenderer = (input: RendererInput, done) => { + }; + + + export const gm = (input: RendererInput, done) => { //generate thumbnail - const sharp = require("sharp"); + const gm = require("gm"); - const image: SharpInstance = sharp(input.imagePath); + let image: State = gm(input.imagePath); image - .metadata() - .then((metadata: Metadata) => { + .size((err, value: Dimensions) => { + if (err) { + const Error = require(input.__dirname + "/../../../common/entities/Error").Error; + const ErrorCodes = require(input.__dirname + "/../../../common/entities/Error").ErrorCodes; + return done(new Error(ErrorCodes.GENERAL_ERROR, err)); + } + const Logger = require(input.__dirname + "/../../Logger").Logger; + Logger.silly("[GMThRenderer] rendering thumbnail:", input.imagePath); - const Logger = require(input.__dirname + "/../../Logger").Logger; - Logger.silly("[SoftwareThRenderer] rendering thumbnail:", input.imagePath); - /** - * newWidth * newHeight = size*size - * newHeight/newWidth = height/width - * - * newHeight = (height/width)*newWidth - * newWidth * newWidth = (size*size) / (height/width) - * - * @type {number} - */ - try { - const ratio = metadata.height / metadata.width; - const kernel = input.qualityPriority == true ? sharp.kernel.lanczos3 : sharp.kernel.nearest; - const interpolator = input.qualityPriority == true ? sharp.interpolator.bicubic : sharp.interpolator.nearest; - if (input.makeSquare == false) { - const newWidth = Math.round(Math.sqrt((input.size * input.size) / ratio)); - image.resize(newWidth, null, { - kernel: kernel, - interpolator: interpolator - }); - } else { - image - .resize(input.size, input.size, { - kernel: kernel, - interpolator: interpolator - }) - .crop(sharp.strategy.center); - } - image - .jpeg() - .toFile(input.thPath).then(() => { - return done(); - }).catch(function (err) { - const Error = require(input.__dirname + "/../../../common/entities/Error").Error; - const ErrorCodes = require(input.__dirname + "/../../../common/entities/Error").ErrorCodes; - return done(new Error(ErrorCodes.GENERAL_ERROR, err)); - }); - } catch (err) { - const Error = require(input.__dirname + "/../../../common/entities/Error").Error; - const ErrorCodes = require(input.__dirname + "/../../../common/entities/Error").ErrorCodes; - return done(new Error(ErrorCodes.GENERAL_ERROR, err)); + /** + * newWidth * newHeight = size*size + * newHeight/newWidth = height/width + * + * newHeight = (height/width)*newWidth + * newWidth * newWidth = (size*size) / (height/width) + * + * @type {number} + */ + try { + const ratio = value.height / value.width; + const filter = input.qualityPriority == true ? 'Lanczos' : 'Point'; + image.filter(filter); + + if (input.makeSquare == false) { + const newWidth = Math.round(Math.sqrt((input.size * input.size) / ratio)); + image = image.resize(newWidth); + } else { + image = image.resize(input.size, input.size) + .crop(input.size, input.size); + } + image.write(input.thPath, (err) => { + if (err) { + + const Error = require(input.__dirname + "/../../../common/entities/Error").Error; + const ErrorCodes = require(input.__dirname + "/../../../common/entities/Error").ErrorCodes; + return done(new Error(ErrorCodes.GENERAL_ERROR, err)); } - }); + return done(); + }); + } catch (err) { + const Error = require(input.__dirname + "/../../../common/entities/Error").Error; + const ErrorCodes = require(input.__dirname + "/../../../common/entities/Error").ErrorCodes; + return done(new Error(ErrorCodes.GENERAL_ERROR, err)); + } -}; \ No newline at end of file + }); + + + }; +} diff --git a/backend/middlewares/thumbnail/ThumbnailGeneratorMWs.ts b/backend/middlewares/thumbnail/ThumbnailGeneratorMWs.ts index 1b384fde..7c2245fd 100644 --- a/backend/middlewares/thumbnail/ThumbnailGeneratorMWs.ts +++ b/backend/middlewares/thumbnail/ThumbnailGeneratorMWs.ts @@ -9,8 +9,10 @@ import {ContentWrapper} from "../../../common/entities/ConentWrapper"; import {DirectoryDTO} from "../../../common/entities/DirectoryDTO"; import {ProjectPath} from "../../ProjectPath"; import {PhotoDTO} from "../../../common/entities/PhotoDTO"; -import {hardwareRenderer, RendererInput, softwareRenderer} from "./THRenderers"; +import {ThumbnailRenderers} from "./THRenderers"; import {Config} from "../../../common/config/private/Config"; +import {ThumbnailProcessingLib} from "../../../common/config/private/IPrivateConfig"; +import RendererInput = ThumbnailRenderers.RendererInput; Config.Client.concurrentThumbnailGenerations = Math.max(1, os.cpus().length - 1); @@ -20,157 +22,165 @@ const pool = new Pool(Config.Client.concurrentThumbnailGenerations); export class ThumbnailGeneratorMWs { - private static poolsInited = false; + private static poolsInited = false; + private static ThumbnailFunction = null - private static initPools() { - if (this.poolsInited == true) { - return - } - if (Config.Server.thumbnail.hardwareAcceleration == true) { - pool.run(hardwareRenderer); - } else { - pool.run(softwareRenderer); - } - this.poolsInited = true; + private static initPools() { + if (this.poolsInited == true) { + return + } + switch (Config.Server.thumbnail.processingLibrary) { + case ThumbnailProcessingLib.Jimp: + this.ThumbnailFunction = ThumbnailRenderers.jimp; + break; + case ThumbnailProcessingLib.gm: + this.ThumbnailFunction = ThumbnailRenderers.gm; + break; + case ThumbnailProcessingLib.sharp: + this.ThumbnailFunction = ThumbnailRenderers.sharp; + + break; + default: + throw "Unknown thumbnail processing lib"; + } + pool.run(this.ThumbnailFunction); + + this.poolsInited = true; + } + + private static addThInfoTODir(directory: DirectoryDTO) { + if (typeof directory.photos == "undefined") { + directory.photos = []; + } + if (typeof directory.directories == "undefined") { + directory.directories = []; + } + ThumbnailGeneratorMWs.addThInfoToPhotos(directory.photos); + + for (let i = 0; i < directory.directories.length; i++) { + ThumbnailGeneratorMWs.addThInfoTODir(directory.directories[i]); } - private static addThInfoTODir(directory: DirectoryDTO) { - if (typeof directory.photos == "undefined") { - directory.photos = []; - } - if (typeof directory.directories == "undefined") { - directory.directories = []; - } - ThumbnailGeneratorMWs.addThInfoToPhotos(directory.photos); + } - for (let i = 0; i < directory.directories.length; i++) { - ThumbnailGeneratorMWs.addThInfoTODir(directory.directories[i]); - } - - } - - private static addThInfoToPhotos(photos: Array) { - let thumbnailFolder = ProjectPath.ThumbnailFolder; - for (let i = 0; i < photos.length; i++) { - let fullImagePath = path.join(ProjectPath.ImageFolder, photos[i].directory.path, photos[i].directory.name, photos[i].name); - for (let j = 0; j < Config.Client.thumbnailSizes.length; j++) { - let size = Config.Client.thumbnailSizes[j]; - let thPath = path.join(thumbnailFolder, ThumbnailGeneratorMWs.generateThumbnailName(fullImagePath, size)); - if (fs.existsSync(thPath) === true) { - if (typeof photos[i].readyThumbnails == "undefined") { - photos[i].readyThumbnails = []; - } - photos[i].readyThumbnails.push(size); - } - } - let iconPath = path.join(thumbnailFolder, ThumbnailGeneratorMWs.generateThumbnailName(fullImagePath, Config.Client.iconSize)); - if (fs.existsSync(iconPath) === true) { - photos[i].readyIcon = true; - } - - } - } - - public static addThumbnailInformation(req: Request, res: Response, next: NextFunction) { - if (!req.resultPipe) - return next(); - - let cw: ContentWrapper = req.resultPipe; - if (cw.directory) { - ThumbnailGeneratorMWs.addThInfoTODir(cw.directory); - } - if (cw.searchResult) { - ThumbnailGeneratorMWs.addThInfoToPhotos(cw.searchResult.photos); - } - - - return next(); - - } - - public static generateThumbnail(req: Request, res: Response, next: NextFunction) { - if (!req.resultPipe) - return next(); - - //load parameters - let imagePath = req.resultPipe; - let size: number = parseInt(req.params.size) || Config.Client.thumbnailSizes[0]; - - //validate size - if (Config.Client.thumbnailSizes.indexOf(size) === -1) { - size = Config.Client.thumbnailSizes[0]; - } - - ThumbnailGeneratorMWs.generateImage(imagePath, size, false, req, res, next); - - - } - - public static generateIcon(req: Request, res: Response, next: NextFunction) { - if (!req.resultPipe) - return next(); - - //load parameters - let imagePath = req.resultPipe; - let size: number = Config.Client.iconSize; - ThumbnailGeneratorMWs.generateImage(imagePath, size, true, req, res, next); - - - } - - - private static generateImage(imagePath: string, size: number, makeSquare: boolean, req: Request, res: Response, next: NextFunction) { - - //generate thumbnail path - let thPath = path.join(ProjectPath.ThumbnailFolder, ThumbnailGeneratorMWs.generateThumbnailName(imagePath, size)); - - - req.resultPipe = thPath; - - //check if thumbnail already exist + private static addThInfoToPhotos(photos: Array) { + let thumbnailFolder = ProjectPath.ThumbnailFolder; + for (let i = 0; i < photos.length; i++) { + let fullImagePath = path.join(ProjectPath.ImageFolder, photos[i].directory.path, photos[i].directory.name, photos[i].name); + for (let j = 0; j < Config.Client.thumbnailSizes.length; j++) { + let size = Config.Client.thumbnailSizes[j]; + let thPath = path.join(thumbnailFolder, ThumbnailGeneratorMWs.generateThumbnailName(fullImagePath, size)); if (fs.existsSync(thPath) === true) { - return next(); + if (typeof photos[i].readyThumbnails == "undefined") { + photos[i].readyThumbnails = []; + } + photos[i].readyThumbnails.push(size); } + } + let iconPath = path.join(thumbnailFolder, ThumbnailGeneratorMWs.generateThumbnailName(fullImagePath, Config.Client.iconSize)); + if (fs.existsSync(iconPath) === true) { + photos[i].readyIcon = true; + } - //create thumbnail folder if not exist - if (!fs.existsSync(ProjectPath.ThumbnailFolder)) { - fs.mkdirSync(ProjectPath.ThumbnailFolder); - } + } + } - this.initPools(); - //run on other thread + public static addThumbnailInformation(req: Request, res: Response, next: NextFunction) { + if (!req.resultPipe) + return next(); - let input = { - imagePath: imagePath, - size: size, - thPath: thPath, - makeSquare: makeSquare, - qualityPriority: Config.Server.thumbnail.qualityPriority, - __dirname: __dirname, - }; - if (Config.Server.enableThreading == true) { - pool.send(input) - .on('done', (out) => { - return next(out); - }).on('error', (error) => { - console.log(error); - return next(new Error(ErrorCodes.THUMBNAIL_GENERATION_ERROR, error)); - }); - } else { - try { - if (Config.Server.thumbnail.hardwareAcceleration == true) { - hardwareRenderer(input, out => next(out)); - } else { - softwareRenderer(input, out => next(out)); - } - }catch (error){ - console.log(error); - return next(new Error(ErrorCodes.THUMBNAIL_GENERATION_ERROR, error)); - } - } + let cw: ContentWrapper = req.resultPipe; + if (cw.directory) { + ThumbnailGeneratorMWs.addThInfoTODir(cw.directory); + } + if (cw.searchResult) { + ThumbnailGeneratorMWs.addThInfoToPhotos(cw.searchResult.photos); } - private static generateThumbnailName(imagePath: string, size: number): string { - return crypto.createHash('md5').update(imagePath).digest('hex') + "_" + size + ".jpg"; + + return next(); + + } + + public static generateThumbnail(req: Request, res: Response, next: NextFunction) { + if (!req.resultPipe) + return next(); + + //load parameters + let imagePath = req.resultPipe; + let size: number = parseInt(req.params.size) || Config.Client.thumbnailSizes[0]; + + //validate size + if (Config.Client.thumbnailSizes.indexOf(size) === -1) { + size = Config.Client.thumbnailSizes[0]; } + + ThumbnailGeneratorMWs.generateImage(imagePath, size, false, req, res, next); + + + } + + public static generateIcon(req: Request, res: Response, next: NextFunction) { + if (!req.resultPipe) + return next(); + + //load parameters + let imagePath = req.resultPipe; + let size: number = Config.Client.iconSize; + ThumbnailGeneratorMWs.generateImage(imagePath, size, true, req, res, next); + + + } + + + private static generateImage(imagePath: string, size: number, makeSquare: boolean, req: Request, res: Response, next: NextFunction) { + + //generate thumbnail path + let thPath = path.join(ProjectPath.ThumbnailFolder, ThumbnailGeneratorMWs.generateThumbnailName(imagePath, size)); + + + req.resultPipe = thPath; + + //check if thumbnail already exist + if (fs.existsSync(thPath) === true) { + return next(); + } + + //create thumbnail folder if not exist + if (!fs.existsSync(ProjectPath.ThumbnailFolder)) { + fs.mkdirSync(ProjectPath.ThumbnailFolder); + } + + this.initPools(); + //run on other thread + + let input = { + imagePath: imagePath, + size: size, + thPath: thPath, + makeSquare: makeSquare, + qualityPriority: Config.Server.thumbnail.qualityPriority, + __dirname: __dirname, + }; + if (Config.Server.enableThreading == true) { + pool.send(input) + .on('done', (out) => { + return next(out); + }).on('error', (error) => { + console.log(error); + return next(new Error(ErrorCodes.THUMBNAIL_GENERATION_ERROR, error)); + }); + } else { + try { + ThumbnailGeneratorMWs.ThumbnailFunction(input, out => next(out)); + } catch (error) { + console.log(error); + return next(new Error(ErrorCodes.THUMBNAIL_GENERATION_ERROR, error)); + } + } + } + + private static generateThumbnailName(imagePath: string, size: number): string { + return crypto.createHash('md5').update(imagePath).digest('hex') + "_" + size + ".jpg"; + } } diff --git a/backend/server.ts b/backend/server.ts index be8a5f2f..6ae31326 100644 --- a/backend/server.ts +++ b/backend/server.ts @@ -13,7 +13,7 @@ import {SharingRouter} from "./routes/SharingRouter"; import {ObjectManagerRepository} from "./model/ObjectManagerRepository"; import {Logger} from "./Logger"; import {Config} from "../common/config/private/Config"; -import {DatabaseType} from "../common/config/private/IPrivateConfig"; +import {DatabaseType, ThumbnailProcessingLib} from "../common/config/private/IPrivateConfig"; const LOG_TAG = "[server]"; export class Server { @@ -98,7 +98,7 @@ export class Server { await ObjectManagerRepository.InitMemoryManagers(); } - if (Config.Server.thumbnail.hardwareAcceleration == true) { + if (Config.Server.thumbnail.processingLibrary == ThumbnailProcessingLib.sharp) { try { const sharp = require("sharp"); sharp(); @@ -109,7 +109,29 @@ export class Server { Logger.warn(LOG_TAG, "Thumbnail hardware acceleration is not possible." + " 'Sharp' node module is not found." + " Falling back to JS based thumbnail generation"); - Config.Server.thumbnail.hardwareAcceleration = false; + Config.Server.thumbnail.processingLibrary = ThumbnailProcessingLib.Jimp; + } + } + + + if (Config.Server.thumbnail.processingLibrary == ThumbnailProcessingLib.gm) { + try { + const gm = require("gm"); + gm(1, 1).stream((err) => { + Logger.warn(LOG_TAG, "[Thumbnail hardware acceleration] gm module error: ", err); + Logger.warn(LOG_TAG, "Thumbnail hardware acceleration is not possible." + + " 'gm' node module is not found." + + " Falling back to JS based thumbnail generation"); + Config.Server.thumbnail.processingLibrary = ThumbnailProcessingLib.Jimp; + }); + + } catch (err) { + Logger.warn(LOG_TAG, "[Thumbnail hardware acceleration] gm module error: ", err); + + Logger.warn(LOG_TAG, "Thumbnail hardware acceleration is not possible." + + " 'gm' node module is not found." + + " Falling back to JS based thumbnail generation"); + Config.Server.thumbnail.processingLibrary = ThumbnailProcessingLib.Jimp; } } diff --git a/common/config/private/Config.ts b/common/config/private/Config.ts index 9bed34c1..a8ff87e3 100644 --- a/common/config/private/Config.ts +++ b/common/config/private/Config.ts @@ -7,6 +7,5 @@ export let Config = new PrivateConfigClass(); ConfigLoader.loadBackendConfig(Config, - path.join(__dirname, './../../../config.json'), - [["PORT", "Server-port"]]); - \ No newline at end of file + path.join(__dirname, './../../../config.json'), + [["PORT", "Server-port"]]); diff --git a/common/config/private/IPrivateConfig.ts b/common/config/private/IPrivateConfig.ts index c9d12fa4..c1034ca2 100644 --- a/common/config/private/IPrivateConfig.ts +++ b/common/config/private/IPrivateConfig.ts @@ -1,30 +1,36 @@ export enum DatabaseType{ - memory = 0, mysql = 1 + memory = 0, mysql = 1 } export enum LogLevel { - error, warn, info, debug, verbose + error, warn, info, debug, verbose +} + +export enum ThumbnailProcessingLib{ + Jimp = 0, + gm = 1, + sharp = 2 } export interface MySQLConfig { - host: string; - database: string; - username: string; - password: string; + host: string; + database: string; + username: string; + password: string; } export interface DataBaseConfig { - type: DatabaseType; - mysql?: MySQLConfig; + type: DatabaseType; + mysql?: MySQLConfig; } export interface ThumbnailConfig { - folder: string; - hardwareAcceleration: boolean; - qualityPriority: boolean; + folder: string; + processingLibrary: ThumbnailProcessingLib; + qualityPriority: boolean; } export interface ServerConfig { - port: number; - imagesFolder: string; - thumbnail: ThumbnailConfig; - database: DataBaseConfig; - enableThreading:boolean; + port: number; + imagesFolder: string; + thumbnail: ThumbnailConfig; + database: DataBaseConfig; + enableThreading: boolean; } diff --git a/common/config/private/PrivateConfigClass.ts b/common/config/private/PrivateConfigClass.ts index 84fd30ac..c53af05d 100644 --- a/common/config/private/PrivateConfigClass.ts +++ b/common/config/private/PrivateConfigClass.ts @@ -1,5 +1,5 @@ import {PublicConfigClass} from "../public/ConfigClass"; -import {DatabaseType, ServerConfig} from "./IPrivateConfig"; +import {DatabaseType, ServerConfig, ThumbnailProcessingLib} from "./IPrivateConfig"; /** @@ -7,35 +7,35 @@ import {DatabaseType, ServerConfig} from "./IPrivateConfig"; */ export class PrivateConfigClass extends PublicConfigClass { - public Server: ServerConfig = { - port: 80, - imagesFolder: "demo/images", - thumbnail: { - folder: "demo/TEMP", - hardwareAcceleration: true, - qualityPriority: true - }, - database: { - type: DatabaseType.mysql, - mysql: { - host: "localhost", - username: "root", - password: "root", - database: "pigallery2" + public Server: ServerConfig = { + port: 80, + imagesFolder: "demo/images", + thumbnail: { + folder: "demo/TEMP", + processingLibrary: ThumbnailProcessingLib.sharp, + qualityPriority: true + }, + database: { + type: DatabaseType.mysql, + mysql: { + host: "localhost", + username: "root", + password: "root", + database: "pigallery2" - } - }, - enableThreading: true - }; + } + }, + enableThreading: true + }; - public setDatabaseType(type: DatabaseType) { - this.Server.database.type = type; - if (type === DatabaseType.memory) { - this.Client.Search.searchEnabled = false; - this.Client.Search.instantSearchEnabled = false; - this.Client.Search.autocompleteEnabled = false; - } + public setDatabaseType(type: DatabaseType) { + this.Server.database.type = type; + if (type === DatabaseType.memory) { + this.Client.Search.searchEnabled = false; + this.Client.Search.instantSearchEnabled = false; + this.Client.Search.autocompleteEnabled = false; } + } } diff --git a/common/config/public/Config.ts b/common/config/public/Config.ts index 6e7a2486..453b5d4e 100644 --- a/common/config/public/Config.ts +++ b/common/config/public/Config.ts @@ -3,13 +3,12 @@ import {WebConfigLoader} from "typeconfig/src/WebConfigLoader"; declare module ServerInject { - export const ConfigInject; + export const ConfigInject; } export let Config = new PublicConfigClass(); if (typeof ServerInject !== "undefined" && typeof ServerInject.ConfigInject !== "undefined") { - WebConfigLoader.loadFrontendConfig(Config.Client, ServerInject.ConfigInject); + WebConfigLoader.loadFrontendConfig(Config.Client, ServerInject.ConfigInject); } - \ No newline at end of file diff --git a/common/config/public/ConfigClass.ts b/common/config/public/ConfigClass.ts index fee5d07c..aaae53e8 100644 --- a/common/config/public/ConfigClass.ts +++ b/common/config/public/ConfigClass.ts @@ -1,19 +1,19 @@ interface SearchConfig { - searchEnabled: boolean - instantSearchEnabled: boolean - autocompleteEnabled: boolean + searchEnabled: boolean + instantSearchEnabled: boolean + autocompleteEnabled: boolean } interface ClientConfig { - iconSize: number; - thumbnailSizes: Array; - Search: SearchConfig; - concurrentThumbnailGenerations: number; - enableCache: boolean; - enableOnScrollRendering: boolean; - enableOnScrollThumbnailPrioritising: boolean; - authenticationRequired: boolean; - googleApiKey: string; + iconSize: number; + thumbnailSizes: Array; + Search: SearchConfig; + concurrentThumbnailGenerations: number; + enableCache: boolean; + enableOnScrollRendering: boolean; + enableOnScrollThumbnailPrioritising: boolean; + authenticationRequired: boolean; + googleApiKey: string; } /** @@ -21,21 +21,21 @@ interface ClientConfig { */ export class PublicConfigClass { - public Client: ClientConfig = { - thumbnailSizes: [200, 400, 600], - iconSize: 30, - Search: { - searchEnabled: true, - instantSearchEnabled: true, - autocompleteEnabled: true - }, - concurrentThumbnailGenerations: 1, - enableCache: false, - enableOnScrollRendering: true, - enableOnScrollThumbnailPrioritising: true, - authenticationRequired: true, - googleApiKey: "" - }; + public Client: ClientConfig = { + thumbnailSizes: [200, 400, 600], + iconSize: 30, + Search: { + searchEnabled: true, + instantSearchEnabled: true, + autocompleteEnabled: true + }, + concurrentThumbnailGenerations: 1, + enableCache: false, + enableOnScrollRendering: true, + enableOnScrollThumbnailPrioritising: true, + authenticationRequired: true, + googleApiKey: "" + }; } diff --git a/frontend/main.ts b/frontend/main.ts index 055aa32c..1a175d9e 100644 --- a/frontend/main.ts +++ b/frontend/main.ts @@ -1,5 +1,11 @@ import {platformBrowserDynamic} from "@angular/platform-browser-dynamic"; +import {enableProdMode} from "@angular/core"; +import {environment} from "./environments/environment"; import {AppModule} from "./app/app.module"; +if (environment.production) { + enableProdMode(); +} + platformBrowserDynamic().bootstrapModule(AppModule).catch(err => console.error(err)); diff --git a/package.json b/package.json index dd8c5045..90803470 100644 --- a/package.json +++ b/package.json @@ -63,6 +63,7 @@ "@angular/language-service": "^4.0.0", "@types/express": "^4.0.35", "@types/express-session": "1.15.0", + "@types/gm": "^1.17.31", "@types/jasmine": "^2.5.51", "@types/node": "^7.0.27", "@types/optimist": "0.0.29", @@ -97,6 +98,7 @@ "typescript": "^2.3.4" }, "optionalDependencies": { + "gm": "^1.23.0", "sharp": "^0.18.1" } }