mirror of
https://github.com/xuthus83/pigallery2.git
synced 2025-01-14 14:43:17 +08:00
adding graphicsmagick support for thumbnail
This commit is contained in:
parent
b7c8570249
commit
b336a91fe0
@ -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
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
};
|
||||
}
|
||||
|
@ -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<PhotoDTO>) {
|
||||
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<PhotoDTO>) {
|
||||
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 = <RendererInput>{
|
||||
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 = <RendererInput>{
|
||||
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";
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -7,6 +7,5 @@ export let Config = new PrivateConfigClass();
|
||||
|
||||
|
||||
ConfigLoader.loadBackendConfig(Config,
|
||||
path.join(__dirname, './../../../config.json'),
|
||||
[["PORT", "Server-port"]]);
|
||||
|
||||
path.join(__dirname, './../../../config.json'),
|
||||
[["PORT", "Server-port"]]);
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -1,19 +1,19 @@
|
||||
interface SearchConfig {
|
||||
searchEnabled: boolean
|
||||
instantSearchEnabled: boolean
|
||||
autocompleteEnabled: boolean
|
||||
searchEnabled: boolean
|
||||
instantSearchEnabled: boolean
|
||||
autocompleteEnabled: boolean
|
||||
}
|
||||
|
||||
interface ClientConfig {
|
||||
iconSize: number;
|
||||
thumbnailSizes: Array<number>;
|
||||
Search: SearchConfig;
|
||||
concurrentThumbnailGenerations: number;
|
||||
enableCache: boolean;
|
||||
enableOnScrollRendering: boolean;
|
||||
enableOnScrollThumbnailPrioritising: boolean;
|
||||
authenticationRequired: boolean;
|
||||
googleApiKey: string;
|
||||
iconSize: number;
|
||||
thumbnailSizes: Array<number>;
|
||||
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: ""
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
|
@ -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));
|
||||
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user