mirror of
https://github.com/bpatrik/pigallery2.git
synced 2025-05-31 23:09:48 +02:00
implementing photo downscaling
project and config refactoring Warning: braking changes in the config
This commit is contained in:
parent
27a88c98eb
commit
cd06bc00ec
@ -1,13 +1,13 @@
|
||||
import {SQLConnection} from '../src/backend/model/sql/SQLConnection';
|
||||
import {SQLConnection} from '../src/backend/model/database/sql/SQLConnection';
|
||||
import {Config} from '../src/common/config/private/Config';
|
||||
import {ObjectManagers} from '../src/backend/model/ObjectManagers';
|
||||
import {DiskMangerWorker} from '../src/backend/model/threading/DiskMangerWorker';
|
||||
import {IndexingManager} from '../src/backend/model/sql/IndexingManager';
|
||||
import {SearchManager} from '../src/backend/model/sql/SearchManager';
|
||||
import {IndexingManager} from '../src/backend/model/database/sql/IndexingManager';
|
||||
import {SearchManager} from '../src/backend/model/database/sql/SearchManager';
|
||||
import * as fs from 'fs';
|
||||
import {SearchTypes} from '../src/common/entities/AutoCompleteItem';
|
||||
import {Utils} from '../src/common/Utils';
|
||||
import {GalleryManager} from '../src/backend/model/sql/GalleryManager';
|
||||
import {GalleryManager} from '../src/backend/model/database/sql/GalleryManager';
|
||||
import {DirectoryDTO} from '../src/common/entities/DirectoryDTO';
|
||||
import {ServerConfig} from '../src/common/config/private/IPrivateConfig';
|
||||
|
||||
|
@ -7,7 +7,7 @@ import {Utils} from '../src/common/Utils';
|
||||
import {DiskMangerWorker} from '../src/backend/model/threading/DiskMangerWorker';
|
||||
|
||||
const config: { path: string, system: string } = require(path.join(__dirname, 'config.json'));
|
||||
Config.Server.imagesFolder = config.path;
|
||||
Config.Server.Media.folder = config.path;
|
||||
const dbPath = path.join(__dirname, 'test.db');
|
||||
ProjectPath.reset();
|
||||
const RUNS = 50;
|
||||
|
6
package-lock.json
generated
6
package-lock.json
generated
@ -17675,9 +17675,9 @@
|
||||
}
|
||||
},
|
||||
"typeconfig": {
|
||||
"version": "1.0.8-c",
|
||||
"resolved": "https://registry.npmjs.org/typeconfig/-/typeconfig-1.0.8-c.tgz",
|
||||
"integrity": "sha512-4RrdrX0pJlC8zjGTkkZ95p6K3IMmd4Hk/TRTiTOmWhJU7ubu7diVEtIEh+sGk487E5LA6XSgNYP2fsQqp/+ELg==",
|
||||
"version": "1.0.8-d",
|
||||
"resolved": "https://registry.npmjs.org/typeconfig/-/typeconfig-1.0.8-d.tgz",
|
||||
"integrity": "sha512-WmuJEs/ZcSELWjRUZCN26wYZ5hZiT2jQ3CYrWjMyv+J7o245DEM/WJ/52ThNIdLaWpdvPW6uHMZMGe3AiHkZaA==",
|
||||
"requires": {
|
||||
"optimist": "0.6.1"
|
||||
}
|
||||
|
@ -135,6 +135,6 @@
|
||||
"sharp": "0.23.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6.9 <11.0"
|
||||
"node": ">=10 <13.0"
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ class ProjectPathClass {
|
||||
public Root: string;
|
||||
public ImageFolder: string;
|
||||
public ThumbnailFolder: string;
|
||||
public TranscendedFolder: string;
|
||||
public TranscodedFolder: string;
|
||||
public FrontendFolder: string;
|
||||
|
||||
constructor() {
|
||||
@ -27,10 +27,10 @@ class ProjectPathClass {
|
||||
|
||||
reset() {
|
||||
this.Root = path.join(__dirname, '/../../');
|
||||
this.ImageFolder = this.getAbsolutePath(Config.Server.imagesFolder);
|
||||
this.ThumbnailFolder = this.getAbsolutePath(Config.Server.Thumbnail.folder);
|
||||
this.TranscendedFolder = path.join(this.ThumbnailFolder, 'tc');
|
||||
this.FrontendFolder = path.join(this.Root, 'dist');
|
||||
this.ImageFolder = this.getAbsolutePath(Config.Server.Media.folder);
|
||||
this.ThumbnailFolder = this.getAbsolutePath(Config.Server.Media.tempFolder);
|
||||
this.TranscodedFolder = path.join(this.ThumbnailFolder, 'tc');
|
||||
|
||||
// create thumbnail folder if not exist
|
||||
if (!fs.existsSync(this.ThumbnailFolder)) {
|
||||
|
@ -10,12 +10,12 @@ import {PhotoDTO} from '../../common/entities/PhotoDTO';
|
||||
import {ProjectPath} from '../ProjectPath';
|
||||
import {Config} from '../../common/config/private/Config';
|
||||
import {UserDTO} from '../../common/entities/UserDTO';
|
||||
import {RandomQuery} from '../model/interfaces/IGalleryManager';
|
||||
import {RandomQuery} from '../model/database/interfaces/IGalleryManager';
|
||||
import {MediaDTO} from '../../common/entities/MediaDTO';
|
||||
import {VideoDTO} from '../../common/entities/VideoDTO';
|
||||
import {Utils} from '../../common/Utils';
|
||||
import {QueryParams} from '../../common/QueryParams';
|
||||
import {VideoConverterMWs} from './VideoConverterMWs';
|
||||
import {VideoProcessing} from '../model/fileprocessing/VideoProcessing';
|
||||
|
||||
|
||||
const LOG_TAG = '[GalleryMWs]';
|
||||
@ -94,7 +94,7 @@ export class GalleryMWs {
|
||||
}
|
||||
|
||||
|
||||
if (Config.Client.Video.enabled === false) {
|
||||
if (Config.Client.Media.Video.enabled === false) {
|
||||
if (cw.directory) {
|
||||
const removeVideos = (dir: DirectoryDTO) => {
|
||||
dir.media = dir.media.filter(m => !MediaDTO.isVideo(m));
|
||||
@ -180,22 +180,22 @@ export class GalleryMWs {
|
||||
}
|
||||
|
||||
public static loadBestFitVideo(req: Request, res: Response, next: NextFunction) {
|
||||
if (!(req.params.mediaPath)) {
|
||||
if (!(req.resultPipe)) {
|
||||
return next();
|
||||
}
|
||||
const fullMediaPath = path.join(ProjectPath.ImageFolder, req.params.mediaPath);
|
||||
const fullMediaPath = path.join(ProjectPath.ImageFolder, req.resultPipe);
|
||||
|
||||
if (fs.statSync(fullMediaPath).isDirectory()) {
|
||||
return next();
|
||||
}
|
||||
|
||||
// check if video exist
|
||||
// check if video does not exist
|
||||
if (fs.existsSync(fullMediaPath) === false) {
|
||||
return next(new ErrorDTO(ErrorCodes.GENERAL_ERROR, 'no such file:' + req.params.mediaPath, 'can\'t find file: ' + fullMediaPath));
|
||||
return next(new ErrorDTO(ErrorCodes.GENERAL_ERROR, 'no such file:' + req.resultPipe, 'can\'t find file: ' + fullMediaPath));
|
||||
}
|
||||
req.resultPipe = fullMediaPath;
|
||||
|
||||
const convertedVideo = VideoConverterMWs.generateConvertedFileName(fullMediaPath);
|
||||
const convertedVideo = VideoProcessing.generateConvertedFileName(fullMediaPath);
|
||||
|
||||
// check if transcoded video exist
|
||||
if (fs.existsSync(convertedVideo) === true) {
|
||||
|
@ -1,67 +0,0 @@
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
import * as util from 'util';
|
||||
import {ITaskExecuter, TaskExecuter} from '../model/threading/TaskExecuter';
|
||||
import {VideoConverterInput, VideoConverterWorker} from '../model/threading/VideoConverterWorker';
|
||||
import {MetadataLoader} from '../model/threading/MetadataLoader';
|
||||
import {Config} from '../../common/config/private/Config';
|
||||
import {ProjectPath} from '../ProjectPath';
|
||||
|
||||
const existPr = util.promisify(fs.exists);
|
||||
|
||||
export class VideoConverterMWs {
|
||||
private static taskQue: ITaskExecuter<VideoConverterInput, void> =
|
||||
new TaskExecuter(1, (input => VideoConverterWorker.convert(input)));
|
||||
|
||||
|
||||
public static generateConvertedFileName(videoPath: string): string {
|
||||
const extension = path.extname(videoPath);
|
||||
const file = path.basename(videoPath, extension);
|
||||
const postfix = Math.round(Config.Server.Video.transcoding.bitRate / 1024) + 'k' +
|
||||
Config.Server.Video.transcoding.codec.toString().toLowerCase() +
|
||||
Config.Server.Video.transcoding.resolution;
|
||||
return path.join(ProjectPath.TranscendedFolder,
|
||||
ProjectPath.getRelativePathToImages(path.dirname(videoPath)), file +
|
||||
'_' + postfix + '.' + Config.Server.Video.transcoding.format);
|
||||
}
|
||||
|
||||
public static async convertVideo(videoPath: string): Promise<void> {
|
||||
|
||||
|
||||
const outPath = this.generateConvertedFileName(videoPath);
|
||||
|
||||
if (await existPr(outPath)) {
|
||||
return;
|
||||
}
|
||||
const metaData = await MetadataLoader.loadVideoMetadata(videoPath);
|
||||
|
||||
const renderInput: VideoConverterInput = {
|
||||
videoPath: videoPath,
|
||||
output: {
|
||||
path: outPath,
|
||||
codec: Config.Server.Video.transcoding.codec,
|
||||
format: Config.Server.Video.transcoding.format
|
||||
}
|
||||
};
|
||||
|
||||
if (metaData.bitRate > Config.Server.Video.transcoding.bitRate) {
|
||||
renderInput.output.bitRate = Config.Server.Video.transcoding.bitRate;
|
||||
}
|
||||
if (metaData.fps > Config.Server.Video.transcoding.fps) {
|
||||
renderInput.output.fps = Config.Server.Video.transcoding.fps;
|
||||
}
|
||||
|
||||
if (Config.Server.Video.transcoding.resolution < metaData.size.height) {
|
||||
renderInput.output.resolution = Config.Server.Video.transcoding.resolution;
|
||||
}
|
||||
|
||||
const outDir = path.dirname(renderInput.output.path);
|
||||
if (!fs.existsSync(outDir)) {
|
||||
fs.mkdirSync(outDir, {recursive: true});
|
||||
}
|
||||
|
||||
await VideoConverterMWs.taskQue.execute(renderInput);
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ import {NextFunction, Request, Response} from 'express';
|
||||
import {ErrorCodes, ErrorDTO} from '../../../common/entities/Error';
|
||||
import {ObjectManagers} from '../../model/ObjectManagers';
|
||||
import {Config} from '../../../common/config/private/Config';
|
||||
import {ISQLGalleryManager} from '../../model/sql/IGalleryManager';
|
||||
import {ISQLGalleryManager} from '../../model/database/sql/IGalleryManager';
|
||||
import {ServerConfig} from '../../../common/config/private/IPrivateConfig';
|
||||
|
||||
const LOG_TAG = '[AdminMWs]';
|
||||
|
@ -3,7 +3,7 @@ import {NextFunction, Request, Response} from 'express';
|
||||
import {ErrorCodes, ErrorDTO} from '../../../common/entities/Error';
|
||||
import {ObjectManagers} from '../../model/ObjectManagers';
|
||||
import {Logger} from '../../Logger';
|
||||
import {SQLConnection} from '../../model/sql/SQLConnection';
|
||||
import {SQLConnection} from '../../model/database/sql/SQLConnection';
|
||||
import {Config} from '../../../common/config/private/Config';
|
||||
import {ConfigDiagnostics} from '../../model/diagnostics/ConfigDiagnostics';
|
||||
import {ClientConfig} from '../../../common/config/public/ConfigClass';
|
||||
@ -98,11 +98,11 @@ export class SettingsMWs {
|
||||
const original = Config.original();
|
||||
await ConfigDiagnostics.testClientVideoConfig(settings.client);
|
||||
await ConfigDiagnostics.testServerVideoConfig(settings.server, original);
|
||||
Config.Server.Video = settings.server;
|
||||
Config.Client.Video = settings.client;
|
||||
Config.Server.Media.Video = settings.server;
|
||||
Config.Client.Media.Video = settings.client;
|
||||
// only updating explicitly set config (not saving config set by the diagnostics)
|
||||
original.Server.Video = settings.server;
|
||||
original.Client.Video = settings.client;
|
||||
original.Server.Media.Video = settings.server;
|
||||
original.Client.Media.Video = settings.client;
|
||||
original.save();
|
||||
await ConfigDiagnostics.runDiagnostics();
|
||||
Logger.info(LOG_TAG, 'new config:');
|
||||
@ -281,12 +281,12 @@ export class SettingsMWs {
|
||||
|
||||
await ConfigDiagnostics.testServerThumbnailConfig(settings.server);
|
||||
await ConfigDiagnostics.testClientThumbnailConfig(settings.client);
|
||||
Config.Server.Thumbnail = settings.server;
|
||||
Config.Client.Thumbnail = settings.client;
|
||||
Config.Server.Media.Thumbnail = settings.server;
|
||||
Config.Client.Media.Thumbnail = settings.client;
|
||||
// only updating explicitly set config (not saving config set by the diagnostics)
|
||||
const original = Config.original();
|
||||
original.Server.Thumbnail = settings.server;
|
||||
original.Client.Thumbnail = settings.client;
|
||||
original.Server.Media.Thumbnail = settings.server;
|
||||
original.Client.Media.Thumbnail = settings.client;
|
||||
original.save();
|
||||
ProjectPath.reset();
|
||||
await ConfigDiagnostics.runDiagnostics();
|
||||
@ -311,7 +311,7 @@ export class SettingsMWs {
|
||||
await ConfigDiagnostics.testImageFolder(settings.imagesFolder);
|
||||
Config.Server.port = settings.port;
|
||||
Config.Server.host = settings.host;
|
||||
Config.Server.imagesFolder = settings.imagesFolder;
|
||||
Config.Server.Media.folder = settings.imagesFolder;
|
||||
Config.Client.publicUrl = settings.publicUrl;
|
||||
Config.Client.urlBase = settings.urlBase;
|
||||
Config.Client.applicationTitle = settings.applicationTitle;
|
||||
@ -319,7 +319,7 @@ export class SettingsMWs {
|
||||
const original = Config.original();
|
||||
original.Server.port = settings.port;
|
||||
original.Server.host = settings.host;
|
||||
original.Server.imagesFolder = settings.imagesFolder;
|
||||
original.Server.Media.folder = settings.imagesFolder;
|
||||
original.Client.publicUrl = settings.publicUrl;
|
||||
original.Client.urlBase = settings.urlBase;
|
||||
original.Client.applicationTitle = settings.applicationTitle;
|
||||
|
@ -1,5 +1,5 @@
|
||||
import {LoginCredential} from '../../../common/entities/LoginCredential';
|
||||
import {UserEntity} from '../../model/sql/enitites/UserEntity';
|
||||
import {UserEntity} from '../../model/database/sql/enitites/UserEntity';
|
||||
|
||||
|
||||
|
||||
|
30
src/backend/middlewares/thumbnail/PhotoConverterMWs.ts
Normal file
30
src/backend/middlewares/thumbnail/PhotoConverterMWs.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import {NextFunction, Request, Response} from 'express';
|
||||
import * as fs from 'fs';
|
||||
import {PhotoProcessing} from '../../model/fileprocessing/PhotoProcessing';
|
||||
import {Config} from '../../../common/config/private/Config';
|
||||
|
||||
export class PhotoConverterMWs {
|
||||
|
||||
public static async convertPhoto(req: Request, res: Response, next: NextFunction) {
|
||||
if (!(req.resultPipe || Config.Server.Media.Photo.converting.enabled === false)) {
|
||||
return next();
|
||||
}
|
||||
const fullMediaPath = req.resultPipe;
|
||||
|
||||
const convertedVideo = PhotoProcessing.generateConvertedFileName(fullMediaPath);
|
||||
|
||||
// check if transcoded video exist
|
||||
if (fs.existsSync(convertedVideo) === true) {
|
||||
req.resultPipe = convertedVideo;
|
||||
return next();
|
||||
}
|
||||
|
||||
if (Config.Server.Media.Photo.converting.onTheFly) {
|
||||
req.resultPipe = await PhotoProcessing.convertPhoto(fullMediaPath,
|
||||
Config.Server.Media.Photo.converting.resolution);
|
||||
}
|
||||
|
||||
return next();
|
||||
}
|
||||
}
|
||||
|
@ -1,53 +1,18 @@
|
||||
import * as path from 'path';
|
||||
import * as crypto from 'crypto';
|
||||
import * as fs from 'fs';
|
||||
import * as os from 'os';
|
||||
import {NextFunction, Request, Response} from 'express';
|
||||
import {ErrorCodes, ErrorDTO} from '../../../common/entities/Error';
|
||||
import {ContentWrapper} from '../../../common/entities/ConentWrapper';
|
||||
import {DirectoryDTO} from '../../../common/entities/DirectoryDTO';
|
||||
import {ProjectPath} from '../../ProjectPath';
|
||||
import {Config} from '../../../common/config/private/Config';
|
||||
import {ThumbnailTH} from '../../model/threading/ThreadPool';
|
||||
import {RendererInput, ThumbnailSourceType, ThumbnailWorker} from '../../model/threading/ThumbnailWorker';
|
||||
import {ThumbnailSourceType} from '../../model/threading/ThumbnailWorker';
|
||||
import {MediaDTO} from '../../../common/entities/MediaDTO';
|
||||
import {ITaskExecuter, TaskExecuter} from '../../model/threading/TaskExecuter';
|
||||
import {FaceRegion, PhotoDTO} from '../../../common/entities/PhotoDTO';
|
||||
import {PersonWithPhoto} from '../PersonMWs';
|
||||
import {ServerConfig} from '../../../common/config/private/IPrivateConfig';
|
||||
import {PhotoProcessing} from '../../model/fileprocessing/PhotoProcessing';
|
||||
|
||||
|
||||
export class ThumbnailGeneratorMWs {
|
||||
private static initDone = false;
|
||||
private static taskQue: ITaskExecuter<RendererInput, void> = null;
|
||||
|
||||
public static init() {
|
||||
if (this.initDone === true) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (Config.Server.Threading.enable === true) {
|
||||
if (Config.Server.Threading.thumbnailThreads > 0) {
|
||||
Config.Client.Thumbnail.concurrentThumbnailGenerations = Config.Server.Threading.thumbnailThreads;
|
||||
} else {
|
||||
Config.Client.Thumbnail.concurrentThumbnailGenerations = Math.max(1, os.cpus().length - 1);
|
||||
}
|
||||
} else {
|
||||
Config.Client.Thumbnail.concurrentThumbnailGenerations = 1;
|
||||
}
|
||||
|
||||
if (Config.Server.Threading.enable === true &&
|
||||
Config.Server.Thumbnail.processingLibrary === ServerConfig.ThumbnailProcessingLib.Jimp) {
|
||||
this.taskQue = new ThumbnailTH(Config.Client.Thumbnail.concurrentThumbnailGenerations);
|
||||
} else {
|
||||
this.taskQue = new TaskExecuter(Config.Client.Thumbnail.concurrentThumbnailGenerations,
|
||||
(input => ThumbnailWorker.render(input, Config.Server.Thumbnail.processingLibrary)));
|
||||
}
|
||||
|
||||
this.initDone = true;
|
||||
}
|
||||
|
||||
public static addThumbnailInformation(req: Request, res: Response, next: NextFunction) {
|
||||
if (!req.resultPipe) {
|
||||
return next();
|
||||
@ -81,7 +46,7 @@ export class ThumbnailGeneratorMWs {
|
||||
}
|
||||
|
||||
try {
|
||||
const size: number = Config.Client.Thumbnail.personThumbnailSize;
|
||||
const size: number = Config.Client.Media.Thumbnail.personThumbnailSize;
|
||||
|
||||
const persons: PersonWithPhoto[] = req.resultPipe;
|
||||
for (let i = 0; i < persons.length; i++) {
|
||||
@ -92,7 +57,7 @@ export class ThumbnailGeneratorMWs {
|
||||
|
||||
// generate thumbnail path
|
||||
const thPath = path.join(ProjectPath.ThumbnailFolder,
|
||||
ThumbnailGeneratorMWs.generatePersonThumbnailName(mediaPath, persons[i].samplePhoto.metadata.faces[0], size));
|
||||
PhotoProcessing.generatePersonThumbnailName(mediaPath, persons[i].samplePhoto.metadata.faces[0], size));
|
||||
|
||||
persons[i].readyThumbnail = fs.existsSync(thPath);
|
||||
}
|
||||
@ -106,106 +71,68 @@ export class ThumbnailGeneratorMWs {
|
||||
|
||||
}
|
||||
|
||||
|
||||
public static async generatePersonThumbnail(req: Request, res: Response, next: NextFunction) {
|
||||
if (!req.resultPipe) {
|
||||
return next();
|
||||
}
|
||||
// load parameters
|
||||
const photo: PhotoDTO = req.resultPipe;
|
||||
if (!photo.metadata.faces || photo.metadata.faces.length !== 1) {
|
||||
return next(new ErrorDTO(ErrorCodes.THUMBNAIL_GENERATION_ERROR, 'Photo does not contain a face'));
|
||||
}
|
||||
|
||||
// load parameters
|
||||
const mediaPath = path.join(ProjectPath.ImageFolder, photo.directory.path, photo.directory.name, photo.name);
|
||||
const size: number = Config.Client.Thumbnail.personThumbnailSize;
|
||||
// generate thumbnail path
|
||||
const thPath = path.join(ProjectPath.ThumbnailFolder,
|
||||
ThumbnailGeneratorMWs.generatePersonThumbnailName(mediaPath, photo.metadata.faces[0], size));
|
||||
|
||||
|
||||
req.resultPipe = thPath;
|
||||
|
||||
// check if thumbnail already exist
|
||||
if (fs.existsSync(thPath) === true) {
|
||||
return next();
|
||||
}
|
||||
|
||||
|
||||
const margin = {
|
||||
x: Math.round(photo.metadata.faces[0].box.width * (Config.Server.Thumbnail.personFaceMargin)),
|
||||
y: Math.round(photo.metadata.faces[0].box.height * (Config.Server.Thumbnail.personFaceMargin))
|
||||
};
|
||||
|
||||
|
||||
// run on other thread
|
||||
const input = <RendererInput>{
|
||||
type: ThumbnailSourceType.Image,
|
||||
mediaPath: mediaPath,
|
||||
size: size,
|
||||
thPath: thPath,
|
||||
makeSquare: false,
|
||||
cut: {
|
||||
left: Math.round(Math.max(0, photo.metadata.faces[0].box.left - margin.x / 2)),
|
||||
top: Math.round(Math.max(0, photo.metadata.faces[0].box.top - margin.y / 2)),
|
||||
width: photo.metadata.faces[0].box.width + margin.x,
|
||||
height: photo.metadata.faces[0].box.height + margin.y
|
||||
},
|
||||
qualityPriority: Config.Server.Thumbnail.qualityPriority
|
||||
};
|
||||
input.cut.width = Math.min(input.cut.width, photo.metadata.size.width - input.cut.left);
|
||||
input.cut.height = Math.min(input.cut.height, photo.metadata.size.height - input.cut.top);
|
||||
try {
|
||||
await ThumbnailGeneratorMWs.taskQue.execute(input);
|
||||
req.resultPipe = await PhotoProcessing.generatePersonThumbnail(req.resultPipe);
|
||||
return next();
|
||||
} catch (error) {
|
||||
return next(new ErrorDTO(ErrorCodes.THUMBNAIL_GENERATION_ERROR,
|
||||
'Error during generating face thumbnail: ' + input.mediaPath, error.toString()));
|
||||
'Error during generating face thumbnail: ' + req.resultPipe, error.toString()));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
public static generateThumbnailFactory(sourceType: ThumbnailSourceType) {
|
||||
return (req: Request, res: Response, next: NextFunction) => {
|
||||
return async (req: Request, res: Response, next: NextFunction) => {
|
||||
if (!req.resultPipe) {
|
||||
return next();
|
||||
}
|
||||
|
||||
// load parameters
|
||||
const mediaPath = req.resultPipe;
|
||||
let size: number = parseInt(req.params.size, 10) || Config.Client.Thumbnail.thumbnailSizes[0];
|
||||
let size: number = parseInt(req.params.size, 10) || Config.Client.Media.Thumbnail.thumbnailSizes[0];
|
||||
|
||||
// validate size
|
||||
if (Config.Client.Thumbnail.thumbnailSizes.indexOf(size) === -1) {
|
||||
size = Config.Client.Thumbnail.thumbnailSizes[0];
|
||||
if (Config.Client.Media.Thumbnail.thumbnailSizes.indexOf(size) === -1) {
|
||||
size = Config.Client.Media.Thumbnail.thumbnailSizes[0];
|
||||
}
|
||||
|
||||
ThumbnailGeneratorMWs.generateImage(mediaPath, size, sourceType, false, req, res, next);
|
||||
|
||||
try {
|
||||
req.resultPipe = await PhotoProcessing.generateThumbnail(mediaPath, size, sourceType, false);
|
||||
return next();
|
||||
} catch (error) {
|
||||
return next(new ErrorDTO(ErrorCodes.THUMBNAIL_GENERATION_ERROR,
|
||||
'Error during generating thumbnail: ' + mediaPath, error.toString()));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static generateIconFactory(sourceType: ThumbnailSourceType) {
|
||||
return (req: Request, res: Response, next: NextFunction) => {
|
||||
return async (req: Request, res: Response, next: NextFunction) => {
|
||||
if (!req.resultPipe) {
|
||||
return next();
|
||||
}
|
||||
|
||||
// load parameters
|
||||
const mediaPath = req.resultPipe;
|
||||
const size: number = Config.Client.Thumbnail.iconSize;
|
||||
ThumbnailGeneratorMWs.generateImage(mediaPath, size, sourceType, true, req, res, next);
|
||||
const size: number = Config.Client.Media.Thumbnail.iconSize;
|
||||
|
||||
try {
|
||||
req.resultPipe = await PhotoProcessing.generateThumbnail(mediaPath, size, sourceType, true);
|
||||
return next();
|
||||
} catch (error) {
|
||||
return next(new ErrorDTO(ErrorCodes.THUMBNAIL_GENERATION_ERROR,
|
||||
'Error during generating thumbnail: ' + mediaPath, error.toString()));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static generateThumbnailName(mediaPath: string, size: number): string {
|
||||
return crypto.createHash('md5').update(mediaPath).digest('hex') + '_' + size + '.jpg';
|
||||
}
|
||||
|
||||
public static generatePersonThumbnailName(mediaPath: string, faceRegion: FaceRegion, size: number): string {
|
||||
return crypto.createHash('md5').update(mediaPath + '_' + faceRegion.name + '_' + faceRegion.box.left + '_' + faceRegion.box.top)
|
||||
.digest('hex') + '_' + size + '.jpg';
|
||||
}
|
||||
|
||||
private static addThInfoTODir(directory: DirectoryDTO) {
|
||||
if (typeof directory.media !== 'undefined') {
|
||||
@ -222,9 +149,9 @@ export class ThumbnailGeneratorMWs {
|
||||
const thumbnailFolder = ProjectPath.ThumbnailFolder;
|
||||
for (let i = 0; i < photos.length; i++) {
|
||||
const fullMediaPath = path.join(ProjectPath.ImageFolder, photos[i].directory.path, photos[i].directory.name, photos[i].name);
|
||||
for (let j = 0; j < Config.Client.Thumbnail.thumbnailSizes.length; j++) {
|
||||
const size = Config.Client.Thumbnail.thumbnailSizes[j];
|
||||
const thPath = path.join(thumbnailFolder, ThumbnailGeneratorMWs.generateThumbnailName(fullMediaPath, size));
|
||||
for (let j = 0; j < Config.Client.Media.Thumbnail.thumbnailSizes.length; j++) {
|
||||
const size = Config.Client.Media.Thumbnail.thumbnailSizes[j];
|
||||
const thPath = path.join(thumbnailFolder, PhotoProcessing.generateThumbnailName(fullMediaPath, size));
|
||||
if (fs.existsSync(thPath) === true) {
|
||||
if (typeof photos[i].readyThumbnails === 'undefined') {
|
||||
photos[i].readyThumbnails = [];
|
||||
@ -233,46 +160,12 @@ export class ThumbnailGeneratorMWs {
|
||||
}
|
||||
}
|
||||
const iconPath = path.join(thumbnailFolder,
|
||||
ThumbnailGeneratorMWs.generateThumbnailName(fullMediaPath, Config.Client.Thumbnail.iconSize));
|
||||
PhotoProcessing.generateThumbnailName(fullMediaPath, Config.Client.Media.Thumbnail.iconSize));
|
||||
if (fs.existsSync(iconPath) === true) {
|
||||
photos[i].readyIcon = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static async generateImage(mediaPath: string,
|
||||
size: number,
|
||||
sourceType: ThumbnailSourceType,
|
||||
makeSquare: boolean,
|
||||
req: Request, res: Response, next: NextFunction) {
|
||||
// generate thumbnail path
|
||||
const thPath = path.join(ProjectPath.ThumbnailFolder, ThumbnailGeneratorMWs.generateThumbnailName(mediaPath, size));
|
||||
|
||||
|
||||
req.resultPipe = thPath;
|
||||
|
||||
// check if thumbnail already exist
|
||||
if (fs.existsSync(thPath) === true) {
|
||||
return next();
|
||||
}
|
||||
|
||||
|
||||
// run on other thread
|
||||
const input = <RendererInput>{
|
||||
type: sourceType,
|
||||
mediaPath: mediaPath,
|
||||
size: size,
|
||||
thPath: thPath,
|
||||
makeSquare: makeSquare,
|
||||
qualityPriority: Config.Server.Thumbnail.qualityPriority
|
||||
};
|
||||
try {
|
||||
await this.taskQue.execute(input);
|
||||
return next();
|
||||
} catch (error) {
|
||||
return next(new ErrorDTO(ErrorCodes.THUMBNAIL_GENERATION_ERROR,
|
||||
'Error during generating thumbnail: ' + input.mediaPath, error.toString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,13 +1,13 @@
|
||||
import {IUserManager} from './interfaces/IUserManager';
|
||||
import {IGalleryManager} from './interfaces/IGalleryManager';
|
||||
import {ISearchManager} from './interfaces/ISearchManager';
|
||||
import {SQLConnection} from './sql/SQLConnection';
|
||||
import {ISharingManager} from './interfaces/ISharingManager';
|
||||
import {IUserManager} from './database/interfaces/IUserManager';
|
||||
import {IGalleryManager} from './database/interfaces/IGalleryManager';
|
||||
import {ISearchManager} from './database/interfaces/ISearchManager';
|
||||
import {SQLConnection} from './database/sql/SQLConnection';
|
||||
import {ISharingManager} from './database/interfaces/ISharingManager';
|
||||
import {Logger} from '../Logger';
|
||||
import {IIndexingManager} from './interfaces/IIndexingManager';
|
||||
import {IPersonManager} from './interfaces/IPersonManager';
|
||||
import {IVersionManager} from './interfaces/IVersionManager';
|
||||
import {ITaskManager} from './interfaces/ITaskManager';
|
||||
import {IIndexingManager} from './database/interfaces/IIndexingManager';
|
||||
import {IPersonManager} from './database/interfaces/IPersonManager';
|
||||
import {IVersionManager} from './database/interfaces/IVersionManager';
|
||||
import {ITaskManager} from './database/interfaces/ITaskManager';
|
||||
|
||||
export class ObjectManagers {
|
||||
|
||||
@ -111,13 +111,13 @@ export class ObjectManagers {
|
||||
|
||||
public static async InitMemoryManagers() {
|
||||
await ObjectManagers.reset();
|
||||
const GalleryManager = require('./memory/GalleryManager').GalleryManager;
|
||||
const UserManager = require('./memory/UserManager').UserManager;
|
||||
const SearchManager = require('./memory/SearchManager').SearchManager;
|
||||
const SharingManager = require('./memory/SharingManager').SharingManager;
|
||||
const IndexingManager = require('./memory/IndexingManager').IndexingManager;
|
||||
const PersonManager = require('./memory/PersonManager').PersonManager;
|
||||
const VersionManager = require('./memory/VersionManager').VersionManager;
|
||||
const GalleryManager = require('./database/memory/GalleryManager').GalleryManager;
|
||||
const UserManager = require('./database/memory/UserManager').UserManager;
|
||||
const SearchManager = require('./database/memory/SearchManager').SearchManager;
|
||||
const SharingManager = require('./database/memory/SharingManager').SharingManager;
|
||||
const IndexingManager = require('./database/memory/IndexingManager').IndexingManager;
|
||||
const PersonManager = require('./database/memory/PersonManager').PersonManager;
|
||||
const VersionManager = require('./database/memory/VersionManager').VersionManager;
|
||||
ObjectManagers.getInstance().GalleryManager = new GalleryManager();
|
||||
ObjectManagers.getInstance().UserManager = new UserManager();
|
||||
ObjectManagers.getInstance().SearchManager = new SearchManager();
|
||||
@ -131,13 +131,13 @@ export class ObjectManagers {
|
||||
public static async InitSQLManagers() {
|
||||
await ObjectManagers.reset();
|
||||
await SQLConnection.init();
|
||||
const GalleryManager = require('./sql/GalleryManager').GalleryManager;
|
||||
const UserManager = require('./sql/UserManager').UserManager;
|
||||
const SearchManager = require('./sql/SearchManager').SearchManager;
|
||||
const SharingManager = require('./sql/SharingManager').SharingManager;
|
||||
const IndexingManager = require('./sql/IndexingManager').IndexingManager;
|
||||
const PersonManager = require('./sql/PersonManager').PersonManager;
|
||||
const VersionManager = require('./sql/VersionManager').VersionManager;
|
||||
const GalleryManager = require('./database/sql/GalleryManager').GalleryManager;
|
||||
const UserManager = require('./database/sql/UserManager').UserManager;
|
||||
const SearchManager = require('./database/sql/SearchManager').SearchManager;
|
||||
const SharingManager = require('./database/sql/SharingManager').SharingManager;
|
||||
const IndexingManager = require('./database/sql/IndexingManager').IndexingManager;
|
||||
const PersonManager = require('./database/sql/PersonManager').PersonManager;
|
||||
const VersionManager = require('./database/sql/VersionManager').VersionManager;
|
||||
ObjectManagers.getInstance().GalleryManager = new GalleryManager();
|
||||
ObjectManagers.getInstance().UserManager = new UserManager();
|
||||
ObjectManagers.getInstance().SearchManager = new SearchManager();
|
||||
|
@ -1,6 +1,6 @@
|
||||
import {DirectoryDTO} from '../../../common/entities/DirectoryDTO';
|
||||
import {PhotoDTO} from '../../../common/entities/PhotoDTO';
|
||||
import {OrientationType} from '../../../common/entities/RandomQueryDTO';
|
||||
import {DirectoryDTO} from '../../../../common/entities/DirectoryDTO';
|
||||
import {PhotoDTO} from '../../../../common/entities/PhotoDTO';
|
||||
import {OrientationType} from '../../../../common/entities/RandomQueryDTO';
|
||||
|
||||
export interface RandomQuery {
|
||||
directory?: string;
|
@ -1,4 +1,4 @@
|
||||
import {DirectoryDTO} from '../../../common/entities/DirectoryDTO';
|
||||
import {DirectoryDTO} from '../../../../common/entities/DirectoryDTO';
|
||||
|
||||
export interface IIndexingManager {
|
||||
indexDirectory(relativeDirectoryName: string): Promise<DirectoryDTO>;
|
@ -1,6 +1,6 @@
|
||||
import {PersonEntry} from '../sql/enitites/PersonEntry';
|
||||
import {PhotoDTO} from '../../../common/entities/PhotoDTO';
|
||||
import {PersonDTO} from '../../../common/entities/PersonDTO';
|
||||
import {PhotoDTO} from '../../../../common/entities/PhotoDTO';
|
||||
import {PersonDTO} from '../../../../common/entities/PersonDTO';
|
||||
|
||||
export interface IPersonManager {
|
||||
getAll(): Promise<PersonEntry[]>;
|
@ -1,5 +1,5 @@
|
||||
import {AutoCompleteItem, SearchTypes} from '../../../common/entities/AutoCompleteItem';
|
||||
import {SearchResultDTO} from '../../../common/entities/SearchResultDTO';
|
||||
import {AutoCompleteItem, SearchTypes} from '../../../../common/entities/AutoCompleteItem';
|
||||
import {SearchResultDTO} from '../../../../common/entities/SearchResultDTO';
|
||||
|
||||
export interface ISearchManager {
|
||||
autocomplete(text: string): Promise<AutoCompleteItem[]>;
|
@ -1,4 +1,4 @@
|
||||
import {SharingDTO} from '../../../common/entities/SharingDTO';
|
||||
import {SharingDTO} from '../../../../common/entities/SharingDTO';
|
||||
|
||||
export interface ISharingManager {
|
||||
findOne(filter: any): Promise<SharingDTO>;
|
@ -1,5 +1,5 @@
|
||||
import {TaskProgressDTO} from '../../../common/entities/settings/TaskProgressDTO';
|
||||
import {TaskDTO} from '../../../common/entities/task/TaskDTO';
|
||||
import {TaskProgressDTO} from '../../../../common/entities/settings/TaskProgressDTO';
|
||||
import {TaskDTO} from '../../../../common/entities/task/TaskDTO';
|
||||
|
||||
export interface ITaskManager {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import {UserDTO, UserRoles} from '../../../common/entities/UserDTO';
|
||||
import {UserDTO, UserRoles} from '../../../../common/entities/UserDTO';
|
||||
|
||||
export interface IUserManager {
|
||||
findOne(filter: any): Promise<UserDTO>;
|
@ -1,13 +1,13 @@
|
||||
import {DirectoryDTO} from '../../../common/entities/DirectoryDTO';
|
||||
import {DirectoryDTO} from '../../../../common/entities/DirectoryDTO';
|
||||
import {IGalleryManager, RandomQuery} from '../interfaces/IGalleryManager';
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
import {DiskManager} from '../DiskManger';
|
||||
import {ProjectPath} from '../../ProjectPath';
|
||||
import {Config} from '../../../common/config/private/Config';
|
||||
import {PhotoDTO} from '../../../common/entities/PhotoDTO';
|
||||
import {DiskMangerWorker} from '../threading/DiskMangerWorker';
|
||||
import {ServerConfig} from '../../../common/config/private/IPrivateConfig';
|
||||
import {DiskManager} from '../../DiskManger';
|
||||
import {ProjectPath} from '../../../ProjectPath';
|
||||
import {Config} from '../../../../common/config/private/Config';
|
||||
import {PhotoDTO} from '../../../../common/entities/PhotoDTO';
|
||||
import {DiskMangerWorker} from '../../threading/DiskMangerWorker';
|
||||
import {ServerConfig} from '../../../../common/config/private/IPrivateConfig';
|
||||
|
||||
export class GalleryManager implements IGalleryManager {
|
||||
|
@ -1,5 +1,5 @@
|
||||
import {IIndexingManager} from '../interfaces/IIndexingManager';
|
||||
import {DirectoryDTO} from '../../../common/entities/DirectoryDTO';
|
||||
import {DirectoryDTO} from '../../../../common/entities/DirectoryDTO';
|
||||
|
||||
export class IndexingManager implements IIndexingManager {
|
||||
|
@ -1,6 +1,6 @@
|
||||
import {IPersonManager} from '../interfaces/IPersonManager';
|
||||
import {PhotoDTO} from '../../../common/entities/PhotoDTO';
|
||||
import {PersonDTO} from '../../../common/entities/PersonDTO';
|
||||
import {PhotoDTO} from '../../../../common/entities/PhotoDTO';
|
||||
import {PersonDTO} from '../../../../common/entities/PersonDTO';
|
||||
|
||||
export class PersonManager implements IPersonManager {
|
||||
|
@ -1,6 +1,6 @@
|
||||
import {AutoCompleteItem, SearchTypes} from '../../../common/entities/AutoCompleteItem';
|
||||
import {AutoCompleteItem, SearchTypes} from '../../../../common/entities/AutoCompleteItem';
|
||||
import {ISearchManager} from '../interfaces/ISearchManager';
|
||||
import {SearchResultDTO} from '../../../common/entities/SearchResultDTO';
|
||||
import {SearchResultDTO} from '../../../../common/entities/SearchResultDTO';
|
||||
|
||||
export class SearchManager implements ISearchManager {
|
||||
autocomplete(text: string): Promise<AutoCompleteItem[]> {
|
@ -1,5 +1,5 @@
|
||||
import {ISharingManager} from '../interfaces/ISharingManager';
|
||||
import {SharingDTO} from '../../../common/entities/SharingDTO';
|
||||
import {SharingDTO} from '../../../../common/entities/SharingDTO';
|
||||
|
||||
export class SharingManager implements ISharingManager {
|
||||
|
@ -1,10 +1,10 @@
|
||||
import {UserDTO, UserRoles} from '../../../common/entities/UserDTO';
|
||||
import {UserDTO, UserRoles} from '../../../../common/entities/UserDTO';
|
||||
import {IUserManager} from '../interfaces/IUserManager';
|
||||
import {ProjectPath} from '../../ProjectPath';
|
||||
import {Utils} from '../../../common/Utils';
|
||||
import {ProjectPath} from '../../../ProjectPath';
|
||||
import {Utils} from '../../../../common/Utils';
|
||||
import * as fs from 'fs';
|
||||
import {PasswordHelper} from '../PasswordHelper';
|
||||
import {Config} from '../../../common/config/private/Config';
|
||||
import {PasswordHelper} from '../../PasswordHelper';
|
||||
import {Config} from '../../../../common/config/private/Config';
|
||||
|
||||
|
||||
export class UserManager implements IUserManager {
|
@ -1,5 +1,5 @@
|
||||
import {IVersionManager} from '../interfaces/IVersionManager';
|
||||
import {DataStructureVersion} from '../../../common/DataStructureVersion';
|
||||
import {DataStructureVersion} from '../../../../common/DataStructureVersion';
|
||||
|
||||
export class VersionManager implements IVersionManager {
|
||||
async getDataVersion(): Promise<string> {
|
@ -1,24 +1,24 @@
|
||||
import {IGalleryManager, RandomQuery} from '../interfaces/IGalleryManager';
|
||||
import {DirectoryDTO} from '../../../common/entities/DirectoryDTO';
|
||||
import {DirectoryDTO} from '../../../../common/entities/DirectoryDTO';
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
import {DirectoryEntity} from './enitites/DirectoryEntity';
|
||||
import {SQLConnection} from './SQLConnection';
|
||||
import {PhotoEntity} from './enitites/PhotoEntity';
|
||||
import {ProjectPath} from '../../ProjectPath';
|
||||
import {Config} from '../../../common/config/private/Config';
|
||||
import {ProjectPath} from '../../../ProjectPath';
|
||||
import {Config} from '../../../../common/config/private/Config';
|
||||
import {ISQLGalleryManager} from './IGalleryManager';
|
||||
import {PhotoDTO} from '../../../common/entities/PhotoDTO';
|
||||
import {OrientationType} from '../../../common/entities/RandomQueryDTO';
|
||||
import {PhotoDTO} from '../../../../common/entities/PhotoDTO';
|
||||
import {OrientationType} from '../../../../common/entities/RandomQueryDTO';
|
||||
import {Brackets, Connection, SelectQueryBuilder} from 'typeorm';
|
||||
import {MediaEntity} from './enitites/MediaEntity';
|
||||
import {VideoEntity} from './enitites/VideoEntity';
|
||||
import {DiskMangerWorker} from '../threading/DiskMangerWorker';
|
||||
import {Logger} from '../../Logger';
|
||||
import {DiskMangerWorker} from '../../threading/DiskMangerWorker';
|
||||
import {Logger} from '../../../Logger';
|
||||
import {FaceRegionEntry} from './enitites/FaceRegionEntry';
|
||||
import {ObjectManagers} from '../ObjectManagers';
|
||||
import {DuplicatesDTO} from '../../../common/entities/DuplicatesDTO';
|
||||
import {ServerConfig} from '../../../common/config/private/IPrivateConfig';
|
||||
import {ObjectManagers} from '../../ObjectManagers';
|
||||
import {DuplicatesDTO} from '../../../../common/entities/DuplicatesDTO';
|
||||
import {ServerConfig} from '../../../../common/config/private/IPrivateConfig';
|
||||
|
||||
const LOG_TAG = '[GalleryManager]';
|
||||
|
@ -1,6 +1,6 @@
|
||||
import {DirectoryDTO} from '../../../common/entities/DirectoryDTO';
|
||||
import {DirectoryDTO} from '../../../../common/entities/DirectoryDTO';
|
||||
import {IGalleryManager} from '../interfaces/IGalleryManager';
|
||||
import {DuplicatesDTO} from '../../../common/entities/DuplicatesDTO';
|
||||
import {DuplicatesDTO} from '../../../../common/entities/DuplicatesDTO';
|
||||
|
||||
export interface ISQLGalleryManager extends IGalleryManager {
|
||||
listDirectory(relativeDirectoryName: string,
|
@ -1,22 +1,22 @@
|
||||
import {DirectoryDTO} from '../../../common/entities/DirectoryDTO';
|
||||
import {DirectoryDTO} from '../../../../common/entities/DirectoryDTO';
|
||||
import {DirectoryEntity} from './enitites/DirectoryEntity';
|
||||
import {SQLConnection} from './SQLConnection';
|
||||
import {DiskManager} from '../DiskManger';
|
||||
import {DiskManager} from '../../DiskManger';
|
||||
import {PhotoEntity} from './enitites/PhotoEntity';
|
||||
import {Utils} from '../../../common/Utils';
|
||||
import {FaceRegion, PhotoMetadata} from '../../../common/entities/PhotoDTO';
|
||||
import {Utils} from '../../../../common/Utils';
|
||||
import {FaceRegion, PhotoMetadata} from '../../../../common/entities/PhotoDTO';
|
||||
import {Connection, Repository} from 'typeorm';
|
||||
import {MediaEntity} from './enitites/MediaEntity';
|
||||
import {MediaDTO} from '../../../common/entities/MediaDTO';
|
||||
import {MediaDTO} from '../../../../common/entities/MediaDTO';
|
||||
import {VideoEntity} from './enitites/VideoEntity';
|
||||
import {FileEntity} from './enitites/FileEntity';
|
||||
import {FileDTO} from '../../../common/entities/FileDTO';
|
||||
import {NotificationManager} from '../NotifocationManager';
|
||||
import {FileDTO} from '../../../../common/entities/FileDTO';
|
||||
import {NotificationManager} from '../../NotifocationManager';
|
||||
import {FaceRegionEntry} from './enitites/FaceRegionEntry';
|
||||
import {ObjectManagers} from '../ObjectManagers';
|
||||
import {ObjectManagers} from '../../ObjectManagers';
|
||||
import {IIndexingManager} from '../interfaces/IIndexingManager';
|
||||
import {DiskMangerWorker} from '../threading/DiskMangerWorker';
|
||||
import {Logger} from '../../Logger';
|
||||
import {DiskMangerWorker} from '../../threading/DiskMangerWorker';
|
||||
import {Logger} from '../../../Logger';
|
||||
|
||||
const LOG_TAG = '[IndexingManager]';
|
||||
|
@ -1,11 +1,11 @@
|
||||
import {IPersonManager} from '../interfaces/IPersonManager';
|
||||
import {SQLConnection} from './SQLConnection';
|
||||
import {PersonEntry} from './enitites/PersonEntry';
|
||||
import {PhotoDTO} from '../../../common/entities/PhotoDTO';
|
||||
import {PhotoDTO} from '../../../../common/entities/PhotoDTO';
|
||||
import {MediaEntity} from './enitites/MediaEntity';
|
||||
import {FaceRegionEntry} from './enitites/FaceRegionEntry';
|
||||
import {PersonDTO} from '../../../common/entities/PersonDTO';
|
||||
import {Utils} from '../../../common/Utils';
|
||||
import {PersonDTO} from '../../../../common/entities/PersonDTO';
|
||||
import {Utils} from '../../../../common/Utils';
|
||||
|
||||
const LOG_TAG = '[PersonManager]';
|
||||
|
@ -1,23 +1,23 @@
|
||||
import 'reflect-metadata';
|
||||
import {Connection, ConnectionOptions, createConnection, getConnection} from 'typeorm';
|
||||
import {UserEntity} from './enitites/UserEntity';
|
||||
import {UserRoles} from '../../../common/entities/UserDTO';
|
||||
import {UserRoles} from '../../../../common/entities/UserDTO';
|
||||
import {PhotoEntity} from './enitites/PhotoEntity';
|
||||
import {DirectoryEntity} from './enitites/DirectoryEntity';
|
||||
import {Config} from '../../../common/config/private/Config';
|
||||
import {Config} from '../../../../common/config/private/Config';
|
||||
import {SharingEntity} from './enitites/SharingEntity';
|
||||
import {PasswordHelper} from '../PasswordHelper';
|
||||
import {ProjectPath} from '../../ProjectPath';
|
||||
import {PasswordHelper} from '../../PasswordHelper';
|
||||
import {ProjectPath} from '../../../ProjectPath';
|
||||
import {VersionEntity} from './enitites/VersionEntity';
|
||||
import {Logger} from '../../Logger';
|
||||
import {Logger} from '../../../Logger';
|
||||
import {MediaEntity} from './enitites/MediaEntity';
|
||||
import {VideoEntity} from './enitites/VideoEntity';
|
||||
import {DataStructureVersion} from '../../../common/DataStructureVersion';
|
||||
import {DataStructureVersion} from '../../../../common/DataStructureVersion';
|
||||
import {FileEntity} from './enitites/FileEntity';
|
||||
import {FaceRegionEntry} from './enitites/FaceRegionEntry';
|
||||
import {PersonEntry} from './enitites/PersonEntry';
|
||||
import {Utils} from '../../../common/Utils';
|
||||
import {ServerConfig} from '../../../common/config/private/IPrivateConfig';
|
||||
import {Utils} from '../../../../common/Utils';
|
||||
import {ServerConfig} from '../../../../common/config/private/IPrivateConfig';
|
||||
|
||||
|
||||
export class SQLConnection {
|
@ -1,6 +1,6 @@
|
||||
import {AutoCompleteItem, SearchTypes} from '../../../common/entities/AutoCompleteItem';
|
||||
import {AutoCompleteItem, SearchTypes} from '../../../../common/entities/AutoCompleteItem';
|
||||
import {ISearchManager} from '../interfaces/ISearchManager';
|
||||
import {SearchResultDTO} from '../../../common/entities/SearchResultDTO';
|
||||
import {SearchResultDTO} from '../../../../common/entities/SearchResultDTO';
|
||||
import {SQLConnection} from './SQLConnection';
|
||||
import {PhotoEntity} from './enitites/PhotoEntity';
|
||||
import {DirectoryEntity} from './enitites/DirectoryEntity';
|
||||
@ -9,7 +9,7 @@ import {VideoEntity} from './enitites/VideoEntity';
|
||||
import {PersonEntry} from './enitites/PersonEntry';
|
||||
import {FaceRegionEntry} from './enitites/FaceRegionEntry';
|
||||
import {SelectQueryBuilder} from 'typeorm';
|
||||
import {Config} from '../../../common/config/private/Config';
|
||||
import {Config} from '../../../../common/config/private/Config';
|
||||
|
||||
export class SearchManager implements ISearchManager {
|
||||
|
@ -1,9 +1,9 @@
|
||||
import {ISharingManager} from '../interfaces/ISharingManager';
|
||||
import {SharingDTO} from '../../../common/entities/SharingDTO';
|
||||
import {SharingDTO} from '../../../../common/entities/SharingDTO';
|
||||
import {SQLConnection} from './SQLConnection';
|
||||
import {SharingEntity} from './enitites/SharingEntity';
|
||||
import {Config} from '../../../common/config/private/Config';
|
||||
import {PasswordHelper} from '../PasswordHelper';
|
||||
import {Config} from '../../../../common/config/private/Config';
|
||||
import {PasswordHelper} from '../../PasswordHelper';
|
||||
import {DeleteResult} from 'typeorm';
|
||||
|
||||
export class SharingManager implements ISharingManager {
|
@ -1,8 +1,8 @@
|
||||
import {UserDTO, UserRoles} from '../../../common/entities/UserDTO';
|
||||
import {UserDTO, UserRoles} from '../../../../common/entities/UserDTO';
|
||||
import {IUserManager} from '../interfaces/IUserManager';
|
||||
import {UserEntity} from './enitites/UserEntity';
|
||||
import {SQLConnection} from './SQLConnection';
|
||||
import {PasswordHelper} from '../PasswordHelper';
|
||||
import {PasswordHelper} from '../../PasswordHelper';
|
||||
|
||||
|
||||
export class UserManager implements IUserManager {
|
@ -1,6 +1,6 @@
|
||||
import * as crypto from 'crypto';
|
||||
import {IVersionManager} from '../interfaces/IVersionManager';
|
||||
import {DataStructureVersion} from '../../../common/DataStructureVersion';
|
||||
import {DataStructureVersion} from '../../../../common/DataStructureVersion';
|
||||
import {SQLConnection} from './SQLConnection';
|
||||
import {DirectoryEntity} from './enitites/DirectoryEntity';
|
||||
import {MediaEntity} from './enitites/MediaEntity';
|
@ -1,5 +1,5 @@
|
||||
import {Column, Entity, Index, ManyToOne, OneToMany, PrimaryGeneratedColumn, Unique} from 'typeorm';
|
||||
import {DirectoryDTO} from '../../../../common/entities/DirectoryDTO';
|
||||
import {DirectoryDTO} from '../../../../../common/entities/DirectoryDTO';
|
||||
import {MediaEntity} from './MediaEntity';
|
||||
import {FileEntity} from './FileEntity';
|
||||
import {columnCharsetCS} from './EntityUtils';
|
@ -1,5 +1,5 @@
|
||||
import {Config} from '../../../../common/config/private/Config';
|
||||
import {ServerConfig} from '../../../../common/config/private/IPrivateConfig';
|
||||
import {Config} from '../../../../../common/config/private/Config';
|
||||
import {ServerConfig} from '../../../../../common/config/private/IPrivateConfig';
|
||||
import {ColumnOptions} from 'typeorm/decorator/options/ColumnOptions';
|
||||
|
||||
export class ColumnCharsetCS implements ColumnOptions {
|
@ -1,4 +1,4 @@
|
||||
import {FaceRegion, FaceRegionBox} from '../../../../common/entities/PhotoDTO';
|
||||
import {FaceRegion, FaceRegionBox} from '../../../../../common/entities/PhotoDTO';
|
||||
import {Column, Entity, ManyToOne, PrimaryGeneratedColumn} from 'typeorm';
|
||||
import {PersonEntry} from './PersonEntry';
|
||||
import {MediaEntity} from './MediaEntity';
|
@ -1,6 +1,6 @@
|
||||
import {Column, Entity, ManyToOne, PrimaryGeneratedColumn, Index} from 'typeorm';
|
||||
import {DirectoryEntity} from './DirectoryEntity';
|
||||
import {FileDTO} from '../../../../common/entities/FileDTO';
|
||||
import {FileDTO} from '../../../../../common/entities/FileDTO';
|
||||
import {columnCharsetCS} from './EntityUtils';
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import {Column, Entity, Index, ManyToOne, OneToMany, PrimaryGeneratedColumn, TableInheritance, Unique} from 'typeorm';
|
||||
import {DirectoryEntity} from './DirectoryEntity';
|
||||
import {MediaDimension, MediaDTO, MediaMetadata} from '../../../../common/entities/MediaDTO';
|
||||
import {MediaDimension, MediaDTO, MediaMetadata} from '../../../../../common/entities/MediaDTO';
|
||||
import {OrientationTypes} from 'ts-exif-parser';
|
||||
import {CameraMetadataEntity, PositionMetaDataEntity} from './PhotoEntity';
|
||||
import {FaceRegionEntry} from './FaceRegionEntry';
|
@ -1,6 +1,6 @@
|
||||
import {Column, Entity, Index, OneToMany, PrimaryGeneratedColumn, Unique} from 'typeorm';
|
||||
import {FaceRegionEntry} from './FaceRegionEntry';
|
||||
import {PersonDTO} from '../../../../common/entities/PersonDTO';
|
||||
import {PersonDTO} from '../../../../../common/entities/PersonDTO';
|
||||
|
||||
|
||||
@Entity()
|
@ -1,5 +1,5 @@
|
||||
import {ChildEntity, Column} from 'typeorm';
|
||||
import {CameraMetadata, GPSMetadata, PhotoDTO, PhotoMetadata, PositionMetaData} from '../../../../common/entities/PhotoDTO';
|
||||
import {CameraMetadata, GPSMetadata, PhotoDTO, PhotoMetadata, PositionMetaData} from '../../../../../common/entities/PhotoDTO';
|
||||
import {MediaEntity, MediaMetadataEntity} from './MediaEntity';
|
||||
|
||||
export class CameraMetadataEntity implements CameraMetadata {
|
@ -1,7 +1,7 @@
|
||||
import {Column, Entity, ManyToOne, PrimaryGeneratedColumn} from 'typeorm';
|
||||
import {SharingDTO} from '../../../../common/entities/SharingDTO';
|
||||
import {SharingDTO} from '../../../../../common/entities/SharingDTO';
|
||||
import {UserEntity} from './UserEntity';
|
||||
import {UserDTO} from '../../../../common/entities/UserDTO';
|
||||
import {UserDTO} from '../../../../../common/entities/UserDTO';
|
||||
|
||||
@Entity()
|
||||
export class SharingEntity implements SharingDTO {
|
@ -1,4 +1,4 @@
|
||||
import {UserDTO, UserRoles} from '../../../../common/entities/UserDTO';
|
||||
import {UserDTO, UserRoles} from '../../../../../common/entities/UserDTO';
|
||||
import {Column, Entity, PrimaryGeneratedColumn, Unique} from 'typeorm';
|
||||
|
||||
@Entity()
|
@ -1,6 +1,6 @@
|
||||
import {ChildEntity, Column} from 'typeorm';
|
||||
import {MediaEntity, MediaMetadataEntity} from './MediaEntity';
|
||||
import {VideoDTO, VideoMetadata} from '../../../../common/entities/VideoDTO';
|
||||
import {VideoDTO, VideoMetadata} from '../../../../../common/entities/VideoDTO';
|
||||
|
||||
|
||||
export class VideoMetadataEntity extends MediaMetadataEntity implements VideoMetadata {
|
@ -2,7 +2,7 @@ import {Config} from '../../../common/config/private/Config';
|
||||
import {Logger} from '../../Logger';
|
||||
import {NotificationManager} from '../NotifocationManager';
|
||||
import {ProjectPath} from '../../ProjectPath';
|
||||
import {SQLConnection} from '../sql/SQLConnection';
|
||||
import {SQLConnection} from '../database/sql/SQLConnection';
|
||||
import * as fs from 'fs';
|
||||
import {ClientConfig} from '../../../common/config/public/ConfigClass';
|
||||
import {FFmpegFactory} from '../FFmpegFactory';
|
||||
@ -74,7 +74,7 @@ export class ConfigDiagnostics {
|
||||
}
|
||||
|
||||
static async testServerVideoConfig(videoConfig: ServerConfig.VideoConfig, config: IPrivateConfig) {
|
||||
if (config.Client.Video.enabled === true) {
|
||||
if (config.Client.Media.Video.enabled === true) {
|
||||
if (videoConfig.transcoding.fps <= 0) {
|
||||
throw new Error('fps should be grater than 0');
|
||||
}
|
||||
@ -101,15 +101,8 @@ export class ConfigDiagnostics {
|
||||
}
|
||||
}
|
||||
|
||||
static testThumbnailFolder(folder: string) {
|
||||
return new Promise((resolve, reject) => {
|
||||
fs.access(folder, fs.constants.W_OK, (err) => {
|
||||
if (err) {
|
||||
reject({message: 'Error during getting write access to temp folder', error: err.toString()});
|
||||
}
|
||||
});
|
||||
resolve();
|
||||
});
|
||||
static async testTempFolder(folder: string) {
|
||||
await this.checkReadWritePermission(folder);
|
||||
}
|
||||
|
||||
static testImageFolder(folder: string) {
|
||||
@ -129,7 +122,6 @@ export class ConfigDiagnostics {
|
||||
|
||||
static async testServerThumbnailConfig(thumbnailConfig: ServerConfig.ThumbnailConfig) {
|
||||
await ConfigDiagnostics.testThumbnailLib(thumbnailConfig.processingLibrary);
|
||||
await ConfigDiagnostics.testThumbnailFolder(thumbnailConfig.folder);
|
||||
}
|
||||
|
||||
static async testClientThumbnailConfig(thumbnailConfig: ClientConfig.ThumbnailConfig) {
|
||||
@ -226,24 +218,24 @@ export class ConfigDiagnostics {
|
||||
}
|
||||
}
|
||||
|
||||
if (Config.Server.Thumbnail.processingLibrary !== ServerConfig.ThumbnailProcessingLib.Jimp) {
|
||||
if (Config.Server.Media.Thumbnail.processingLibrary !== ServerConfig.ThumbnailProcessingLib.Jimp) {
|
||||
try {
|
||||
await ConfigDiagnostics.testThumbnailLib(Config.Server.Thumbnail.processingLibrary);
|
||||
await ConfigDiagnostics.testThumbnailLib(Config.Server.Media.Thumbnail.processingLibrary);
|
||||
} catch (ex) {
|
||||
const err: Error = ex;
|
||||
NotificationManager.warning('Thumbnail hardware acceleration is not possible.' +
|
||||
' \'' + ServerConfig.ThumbnailProcessingLib[Config.Server.Thumbnail.processingLibrary] + '\' node module is not found.' +
|
||||
' \'' + ServerConfig.ThumbnailProcessingLib[Config.Server.Media.Thumbnail.processingLibrary] + '\' node module is not found.' +
|
||||
' Falling back temporally to JS based thumbnail generation', err.toString());
|
||||
Logger.warn(LOG_TAG, '[Thumbnail hardware acceleration] module error: ', err.toString());
|
||||
Logger.warn(LOG_TAG, 'Thumbnail hardware acceleration is not possible.' +
|
||||
' \'' + ServerConfig.ThumbnailProcessingLib[Config.Server.Thumbnail.processingLibrary] + '\' node module is not found.' +
|
||||
' \'' + ServerConfig.ThumbnailProcessingLib[Config.Server.Media.Thumbnail.processingLibrary] + '\' node module is not found.' +
|
||||
' Falling back temporally to JS based thumbnail generation');
|
||||
Config.Server.Thumbnail.processingLibrary = ServerConfig.ThumbnailProcessingLib.Jimp;
|
||||
Config.Server.Media.Thumbnail.processingLibrary = ServerConfig.ThumbnailProcessingLib.Jimp;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
await ConfigDiagnostics.testThumbnailFolder(Config.Server.Thumbnail.folder);
|
||||
await ConfigDiagnostics.testTempFolder(Config.Server.Media.tempFolder);
|
||||
} catch (ex) {
|
||||
const err: Error = ex;
|
||||
NotificationManager.error('Thumbnail folder error', err.toString());
|
||||
@ -252,13 +244,13 @@ export class ConfigDiagnostics {
|
||||
|
||||
|
||||
try {
|
||||
await ConfigDiagnostics.testClientVideoConfig(Config.Client.Video);
|
||||
await ConfigDiagnostics.testServerVideoConfig(Config.Server.Video, Config);
|
||||
await ConfigDiagnostics.testClientVideoConfig(Config.Client.Media.Video);
|
||||
await ConfigDiagnostics.testServerVideoConfig(Config.Server.Media.Video, Config);
|
||||
} catch (ex) {
|
||||
const err: Error = ex;
|
||||
NotificationManager.warning('Video support error, switching off..', err.toString());
|
||||
Logger.warn(LOG_TAG, 'Video support error, switching off..', err.toString());
|
||||
Config.Client.Video.enabled = false;
|
||||
Config.Client.Media.Video.enabled = false;
|
||||
}
|
||||
|
||||
try {
|
||||
@ -272,14 +264,14 @@ export class ConfigDiagnostics {
|
||||
|
||||
|
||||
try {
|
||||
await ConfigDiagnostics.testImageFolder(Config.Server.imagesFolder);
|
||||
await ConfigDiagnostics.testImageFolder(Config.Server.Media.folder);
|
||||
} catch (ex) {
|
||||
const err: Error = ex;
|
||||
NotificationManager.error('Images folder error', err.toString());
|
||||
Logger.error(LOG_TAG, 'Images folder error', err.toString());
|
||||
}
|
||||
try {
|
||||
await ConfigDiagnostics.testClientThumbnailConfig(Config.Client.Thumbnail);
|
||||
await ConfigDiagnostics.testClientThumbnailConfig(Config.Client.Media.Thumbnail);
|
||||
} catch (ex) {
|
||||
const err: Error = ex;
|
||||
NotificationManager.error('Thumbnail settings error', err.toString());
|
||||
|
179
src/backend/model/fileprocessing/PhotoProcessing.ts
Normal file
179
src/backend/model/fileprocessing/PhotoProcessing.ts
Normal file
@ -0,0 +1,179 @@
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
import * as os from 'os';
|
||||
import * as crypto from 'crypto';
|
||||
import {ProjectPath} from '../../ProjectPath';
|
||||
import {Config} from '../../../common/config/private/Config';
|
||||
import {ThumbnailTH} from '../threading/ThreadPool';
|
||||
import {RendererInput, ThumbnailSourceType, ThumbnailWorker} from '../threading/ThumbnailWorker';
|
||||
import {ITaskExecuter, TaskExecuter} from '../threading/TaskExecuter';
|
||||
import {ServerConfig} from '../../../common/config/private/IPrivateConfig';
|
||||
import {FaceRegion, PhotoDTO} from '../../../common/entities/PhotoDTO';
|
||||
|
||||
|
||||
export class PhotoProcessing {
|
||||
|
||||
private static initDone = false;
|
||||
private static taskQue: ITaskExecuter<RendererInput, void> = null;
|
||||
|
||||
public static init() {
|
||||
if (this.initDone === true) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (Config.Server.Threading.enable === true) {
|
||||
if (Config.Server.Threading.thumbnailThreads > 0) {
|
||||
Config.Client.Media.Thumbnail.concurrentThumbnailGenerations = Config.Server.Threading.thumbnailThreads;
|
||||
} else {
|
||||
Config.Client.Media.Thumbnail.concurrentThumbnailGenerations = Math.max(1, os.cpus().length - 1);
|
||||
}
|
||||
} else {
|
||||
Config.Client.Media.Thumbnail.concurrentThumbnailGenerations = 1;
|
||||
}
|
||||
|
||||
if (Config.Server.Threading.enable === true &&
|
||||
Config.Server.Media.Thumbnail.processingLibrary === ServerConfig.ThumbnailProcessingLib.Jimp) {
|
||||
this.taskQue = new ThumbnailTH(Config.Client.Media.Thumbnail.concurrentThumbnailGenerations);
|
||||
} else {
|
||||
this.taskQue = new TaskExecuter(Config.Client.Media.Thumbnail.concurrentThumbnailGenerations,
|
||||
(input => ThumbnailWorker.render(input, Config.Server.Media.Thumbnail.processingLibrary)));
|
||||
}
|
||||
|
||||
this.initDone = true;
|
||||
}
|
||||
|
||||
|
||||
public static async generatePersonThumbnail(photo: PhotoDTO) {
|
||||
|
||||
// load parameters
|
||||
|
||||
if (!photo.metadata.faces || photo.metadata.faces.length !== 1) {
|
||||
throw new Error('Photo does not contain a face');
|
||||
}
|
||||
|
||||
// load parameters
|
||||
const mediaPath = path.join(ProjectPath.ImageFolder, photo.directory.path, photo.directory.name, photo.name);
|
||||
const size: number = Config.Client.Media.Thumbnail.personThumbnailSize;
|
||||
// generate thumbnail path
|
||||
const thPath = path.join(ProjectPath.ThumbnailFolder,
|
||||
PhotoProcessing.generatePersonThumbnailName(mediaPath, photo.metadata.faces[0], size));
|
||||
|
||||
|
||||
// check if thumbnail already exist
|
||||
if (fs.existsSync(thPath) === true) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
const margin = {
|
||||
x: Math.round(photo.metadata.faces[0].box.width * (Config.Server.Media.Thumbnail.personFaceMargin)),
|
||||
y: Math.round(photo.metadata.faces[0].box.height * (Config.Server.Media.Thumbnail.personFaceMargin))
|
||||
};
|
||||
|
||||
|
||||
// run on other thread
|
||||
const input = <RendererInput>{
|
||||
type: ThumbnailSourceType.Photo,
|
||||
mediaPath: mediaPath,
|
||||
size: size,
|
||||
outPath: thPath,
|
||||
makeSquare: false,
|
||||
cut: {
|
||||
left: Math.round(Math.max(0, photo.metadata.faces[0].box.left - margin.x / 2)),
|
||||
top: Math.round(Math.max(0, photo.metadata.faces[0].box.top - margin.y / 2)),
|
||||
width: photo.metadata.faces[0].box.width + margin.x,
|
||||
height: photo.metadata.faces[0].box.height + margin.y
|
||||
},
|
||||
qualityPriority: Config.Server.Media.Thumbnail.qualityPriority
|
||||
};
|
||||
input.cut.width = Math.min(input.cut.width, photo.metadata.size.width - input.cut.left);
|
||||
input.cut.height = Math.min(input.cut.height, photo.metadata.size.height - input.cut.top);
|
||||
|
||||
await PhotoProcessing.taskQue.execute(input);
|
||||
return thPath;
|
||||
}
|
||||
|
||||
|
||||
public static generateThumbnailName(mediaPath: string, size: number): string {
|
||||
return crypto.createHash('md5').update(mediaPath).digest('hex') + '_' + size + '.jpg';
|
||||
}
|
||||
|
||||
public static generatePersonThumbnailName(mediaPath: string, faceRegion: FaceRegion, size: number): string {
|
||||
return crypto.createHash('md5').update(mediaPath + '_' + faceRegion.name + '_' + faceRegion.box.left + '_' + faceRegion.box.top)
|
||||
.digest('hex') + '_' + size + '.jpg';
|
||||
}
|
||||
|
||||
|
||||
public static generateConvertedFileName(photoPath: string): string {
|
||||
const extension = path.extname(photoPath);
|
||||
const file = path.basename(photoPath, extension);
|
||||
const postfix = Config.Server.Media.Photo.converting.resolution;
|
||||
return path.join(ProjectPath.TranscodedFolder,
|
||||
ProjectPath.getRelativePathToImages(path.dirname(photoPath)), file +
|
||||
'_' + postfix + '.jpg');
|
||||
}
|
||||
|
||||
|
||||
public static async convertPhoto(mediaPath: string, size: number) {
|
||||
// generate thumbnail path
|
||||
const outPath = PhotoProcessing.generateConvertedFileName(mediaPath);
|
||||
|
||||
|
||||
// check if file already exist
|
||||
if (fs.existsSync(outPath) === true) {
|
||||
return outPath;
|
||||
}
|
||||
|
||||
|
||||
// run on other thread
|
||||
const input = <RendererInput>{
|
||||
type: ThumbnailSourceType.Photo,
|
||||
mediaPath: mediaPath,
|
||||
size: size,
|
||||
outPath: outPath,
|
||||
makeSquare: false,
|
||||
qualityPriority: Config.Server.Media.Thumbnail.qualityPriority
|
||||
};
|
||||
|
||||
const outDir = path.dirname(input.outPath);
|
||||
if (!fs.existsSync(outDir)) {
|
||||
fs.mkdirSync(outDir, {recursive: true});
|
||||
}
|
||||
await this.taskQue.execute(input);
|
||||
return outPath;
|
||||
}
|
||||
|
||||
public static async generateThumbnail(mediaPath: string,
|
||||
size: number,
|
||||
sourceType: ThumbnailSourceType,
|
||||
makeSquare: boolean) {
|
||||
// generate thumbnail path
|
||||
const outPath = path.join(ProjectPath.ThumbnailFolder, PhotoProcessing.generateThumbnailName(mediaPath, size));
|
||||
|
||||
|
||||
// check if thumbnail already exist
|
||||
if (fs.existsSync(outPath) === true) {
|
||||
return outPath;
|
||||
}
|
||||
|
||||
|
||||
// run on other thread
|
||||
const input = <RendererInput>{
|
||||
type: sourceType,
|
||||
mediaPath: mediaPath,
|
||||
size: size,
|
||||
outPath: outPath,
|
||||
makeSquare: makeSquare,
|
||||
qualityPriority: Config.Server.Media.Thumbnail.qualityPriority
|
||||
};
|
||||
|
||||
const outDir = path.dirname(input.outPath);
|
||||
if (!fs.existsSync(outDir)) {
|
||||
fs.mkdirSync(outDir, {recursive: true});
|
||||
}
|
||||
await this.taskQue.execute(input);
|
||||
return outPath;
|
||||
}
|
||||
}
|
||||
|
67
src/backend/model/fileprocessing/VideoProcessing.ts
Normal file
67
src/backend/model/fileprocessing/VideoProcessing.ts
Normal file
@ -0,0 +1,67 @@
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
import * as util from 'util';
|
||||
import {ITaskExecuter, TaskExecuter} from '../../model/threading/TaskExecuter';
|
||||
import {VideoConverterInput, VideoConverterWorker} from '../../model/threading/VideoConverterWorker';
|
||||
import {MetadataLoader} from '../../model/threading/MetadataLoader';
|
||||
import {Config} from '../../../common/config/private/Config';
|
||||
import {ProjectPath} from '../../ProjectPath';
|
||||
|
||||
const existPr = util.promisify(fs.exists);
|
||||
|
||||
export class VideoProcessing {
|
||||
private static taskQue: ITaskExecuter<VideoConverterInput, void> =
|
||||
new TaskExecuter(1, (input => VideoConverterWorker.convert(input)));
|
||||
|
||||
|
||||
public static generateConvertedFileName(videoPath: string): string {
|
||||
const extension = path.extname(videoPath);
|
||||
const file = path.basename(videoPath, extension);
|
||||
const postfix = Math.round(Config.Server.Media.Video.transcoding.bitRate / 1024) + 'k' +
|
||||
Config.Server.Media.Video.transcoding.codec.toString().toLowerCase() +
|
||||
Config.Server.Media.Video.transcoding.resolution;
|
||||
return path.join(ProjectPath.TranscodedFolder,
|
||||
ProjectPath.getRelativePathToImages(path.dirname(videoPath)), file +
|
||||
'_' + postfix + '.' + Config.Server.Media.Video.transcoding.format);
|
||||
}
|
||||
|
||||
public static async convertVideo(videoPath: string): Promise<void> {
|
||||
|
||||
|
||||
const outPath = this.generateConvertedFileName(videoPath);
|
||||
|
||||
if (await existPr(outPath)) {
|
||||
return;
|
||||
}
|
||||
const metaData = await MetadataLoader.loadVideoMetadata(videoPath);
|
||||
|
||||
const renderInput: VideoConverterInput = {
|
||||
videoPath: videoPath,
|
||||
output: {
|
||||
path: outPath,
|
||||
codec: Config.Server.Media.Video.transcoding.codec,
|
||||
format: Config.Server.Media.Video.transcoding.format
|
||||
}
|
||||
};
|
||||
|
||||
if (metaData.bitRate > Config.Server.Media.Video.transcoding.bitRate) {
|
||||
renderInput.output.bitRate = Config.Server.Media.Video.transcoding.bitRate;
|
||||
}
|
||||
if (metaData.fps > Config.Server.Media.Video.transcoding.fps) {
|
||||
renderInput.output.fps = Config.Server.Media.Video.transcoding.fps;
|
||||
}
|
||||
|
||||
if (Config.Server.Media.Video.transcoding.resolution < metaData.size.height) {
|
||||
renderInput.output.resolution = Config.Server.Media.Video.transcoding.resolution;
|
||||
}
|
||||
|
||||
const outDir = path.dirname(renderInput.output.path);
|
||||
if (!fs.existsSync(outDir)) {
|
||||
fs.mkdirSync(outDir, {recursive: true});
|
||||
}
|
||||
|
||||
await VideoProcessing.taskQue.execute(renderInput);
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import {ITaskManager} from '../interfaces/ITaskManager';
|
||||
import {ITaskManager} from '../database/interfaces/ITaskManager';
|
||||
import {TaskProgressDTO} from '../../../common/entities/settings/TaskProgressDTO';
|
||||
import {ITask} from './tasks/ITask';
|
||||
import {TaskRepository} from './TaskRepository';
|
||||
|
66
src/backend/model/tasks/tasks/FileTask.ts
Normal file
66
src/backend/model/tasks/tasks/FileTask.ts
Normal file
@ -0,0 +1,66 @@
|
||||
import {TaskProgressDTO, TaskState} from '../../../../common/entities/settings/TaskProgressDTO';
|
||||
import {ConfigTemplateEntry} from '../../../../common/entities/task/TaskDTO';
|
||||
import {Task} from './Task';
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
import * as util from 'util';
|
||||
import {DiskManager} from '../../DiskManger';
|
||||
import {DiskMangerWorker} from '../../threading/DiskMangerWorker';
|
||||
import {DirectoryDTO} from '../../../../common/entities/DirectoryDTO';
|
||||
|
||||
declare var global: NodeJS.Global;
|
||||
|
||||
|
||||
const LOG_TAG = '[FileTask]';
|
||||
const existsPr = util.promisify(fs.exists);
|
||||
|
||||
|
||||
export abstract class FileTask<T> extends Task {
|
||||
public readonly ConfigTemplate: ConfigTemplateEntry[] = null;
|
||||
directoryQueue: string[] = [];
|
||||
fileQueue: T[] = [];
|
||||
|
||||
|
||||
protected constructor(private scanFilter: DiskMangerWorker.DirectoryScanSettings) {
|
||||
super();
|
||||
}
|
||||
|
||||
protected async init() {
|
||||
this.directoryQueue = [];
|
||||
this.fileQueue = [];
|
||||
this.directoryQueue.push('/');
|
||||
}
|
||||
|
||||
protected abstract async processDirectory(directory: DirectoryDTO): Promise<T[]>;
|
||||
|
||||
protected abstract async processFile(file: T): Promise<void>;
|
||||
|
||||
protected async step(): Promise<TaskProgressDTO> {
|
||||
if ((this.directoryQueue.length === 0 && this.fileQueue.length === 0)
|
||||
|| this.state !== TaskState.running) {
|
||||
if (global.gc) {
|
||||
global.gc();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
this.progress.time.current = Date.now();
|
||||
if (this.directoryQueue.length > 0) {
|
||||
const directory = this.directoryQueue.shift();
|
||||
this.progress.comment = 'scanning directory: ' + directory;
|
||||
const scanned = await DiskManager.scanDirectory(directory, this.scanFilter);
|
||||
for (let i = 0; i < scanned.directories.length; i++) {
|
||||
this.directoryQueue.push(path.join(scanned.directories[i].path, scanned.directories[i].name));
|
||||
}
|
||||
this.fileQueue.push(...await this.processDirectory(scanned));
|
||||
} else if (this.fileQueue.length > 0) {
|
||||
const file = this.fileQueue.shift();
|
||||
this.progress.left = this.fileQueue.length;
|
||||
this.progress.progress++;
|
||||
this.progress.comment = 'processing: ' + file;
|
||||
await this.processFile(file);
|
||||
}
|
||||
return this.progress;
|
||||
}
|
||||
|
||||
}
|
@ -11,6 +11,7 @@ import {ThumbnailGeneratorMWs} from '../../../middlewares/thumbnail/ThumbnailGen
|
||||
import {Task} from './Task';
|
||||
import {ConfigTemplateEntry, DefaultsTasks} from '../../../../common/entities/task/TaskDTO';
|
||||
import {ServerConfig} from '../../../../common/config/private/IPrivateConfig';
|
||||
import {PhotoProcessing} from '../../fileprocessing/PhotoProcessing';
|
||||
|
||||
declare var global: NodeJS.Global;
|
||||
const LOG_TAG = '[IndexingTask]';
|
||||
@ -59,18 +60,18 @@ export class IndexingTask extends Task<{ createThumbnails: boolean }> {
|
||||
const media = scanned.media[i];
|
||||
const mPath = path.join(ProjectPath.ImageFolder, media.directory.path, media.directory.name, media.name);
|
||||
const thPath = path.join(ProjectPath.ThumbnailFolder,
|
||||
ThumbnailGeneratorMWs.generateThumbnailName(mPath, Config.Client.Thumbnail.thumbnailSizes[0]));
|
||||
PhotoProcessing.generateThumbnailName(mPath, Config.Client.Media.Thumbnail.thumbnailSizes[0]));
|
||||
if (fs.existsSync(thPath)) { // skip existing thumbnails
|
||||
continue;
|
||||
}
|
||||
await ThumbnailWorker.render(<RendererInput>{
|
||||
type: MediaDTO.isVideo(media) ? ThumbnailSourceType.Video : ThumbnailSourceType.Image,
|
||||
type: MediaDTO.isVideo(media) ? ThumbnailSourceType.Video : ThumbnailSourceType.Photo,
|
||||
mediaPath: mPath,
|
||||
size: Config.Client.Thumbnail.thumbnailSizes[0],
|
||||
thPath: thPath,
|
||||
size: Config.Client.Media.Thumbnail.thumbnailSizes[0],
|
||||
outPath: thPath,
|
||||
makeSquare: false,
|
||||
qualityPriority: Config.Server.Thumbnail.qualityPriority
|
||||
}, Config.Server.Thumbnail.processingLibrary);
|
||||
qualityPriority: Config.Server.Media.Thumbnail.qualityPriority
|
||||
}, Config.Server.Media.Thumbnail.processingLibrary);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
Logger.error(LOG_TAG, 'Error during indexing job: ' + e.toString());
|
||||
|
47
src/backend/model/tasks/tasks/PhotoConvertingTask.ts
Normal file
47
src/backend/model/tasks/tasks/PhotoConvertingTask.ts
Normal file
@ -0,0 +1,47 @@
|
||||
import {Config} from '../../../../common/config/private/Config';
|
||||
import {DefaultsTasks} from '../../../../common/entities/task/TaskDTO';
|
||||
import {ProjectPath} from '../../../ProjectPath';
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
import * as util from 'util';
|
||||
import {FileTask} from './FileTask';
|
||||
import {DirectoryDTO} from '../../../../common/entities/DirectoryDTO';
|
||||
import {PhotoProcessing} from '../../fileprocessing/PhotoProcessing';
|
||||
import {ThumbnailSourceType} from '../../threading/ThumbnailWorker';
|
||||
|
||||
const LOG_TAG = '[PhotoConvertingTask]';
|
||||
const existsPr = util.promisify(fs.exists);
|
||||
|
||||
|
||||
export class PhotoConvertingTask extends FileTask<string> {
|
||||
public readonly Name = DefaultsTasks[DefaultsTasks['Video Converting']];
|
||||
|
||||
constructor() {
|
||||
super({noVideo: true, noMetaFile: true});
|
||||
}
|
||||
|
||||
public get Supported(): boolean {
|
||||
return Config.Server.Media.Photo.converting.enabled === true;
|
||||
}
|
||||
|
||||
protected async processDirectory(directory: DirectoryDTO): Promise<string[]> {
|
||||
const ret = [];
|
||||
for (let i = 0; i < directory.media.length; ++i) {
|
||||
const photoPath = path.join(ProjectPath.ImageFolder,
|
||||
directory.media[i].directory.path,
|
||||
directory.media[i].directory.name,
|
||||
directory.media[i].name);
|
||||
|
||||
if (await existsPr(PhotoProcessing.generateConvertedFileName(photoPath)) === false) {
|
||||
ret.push(photoPath);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
protected async processFile(file: string): Promise<void> {
|
||||
await PhotoProcessing.generateThumbnail(file, Config.Server.Media.Photo.converting.resolution, ThumbnailSourceType.Photo, false);
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -1,83 +1,46 @@
|
||||
import {TaskProgressDTO, TaskState} from '../../../../common/entities/settings/TaskProgressDTO';
|
||||
import {Config} from '../../../../common/config/private/Config';
|
||||
import {ConfigTemplateEntry, DefaultsTasks} from '../../../../common/entities/task/TaskDTO';
|
||||
import {Task} from './Task';
|
||||
import {DefaultsTasks} from '../../../../common/entities/task/TaskDTO';
|
||||
import {ProjectPath} from '../../../ProjectPath';
|
||||
import {MediaDTO} from '../../../../common/entities/MediaDTO';
|
||||
import {Logger} from '../../../Logger';
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
import * as util from 'util';
|
||||
import {DiskManager} from '../../DiskManger';
|
||||
import {VideoConverterMWs} from '../../../middlewares/VideoConverterMWs';
|
||||
|
||||
declare var global: NodeJS.Global;
|
||||
|
||||
import {FileTask} from './FileTask';
|
||||
import {DirectoryDTO} from '../../../../common/entities/DirectoryDTO';
|
||||
import {VideoProcessing} from '../../fileprocessing/VideoProcessing';
|
||||
|
||||
const LOG_TAG = '[VideoConvertingTask]';
|
||||
const existsPr = util.promisify(fs.exists);
|
||||
|
||||
|
||||
export class VideoConvertingTask extends Task {
|
||||
export class VideoConvertingTask extends FileTask<string> {
|
||||
public readonly Name = DefaultsTasks[DefaultsTasks['Video Converting']];
|
||||
public readonly ConfigTemplate: ConfigTemplateEntry[] = null;
|
||||
directoryQueue: string[] = [];
|
||||
videoQueue: string[] = [];
|
||||
|
||||
constructor() {
|
||||
super({noPhoto: true, noMetaFile: true});
|
||||
}
|
||||
|
||||
public get Supported(): boolean {
|
||||
return Config.Client.Video.enabled === true;
|
||||
return Config.Client.Media.Video.enabled === true;
|
||||
}
|
||||
|
||||
protected async init() {
|
||||
this.directoryQueue = [];
|
||||
this.videoQueue = [];
|
||||
this.directoryQueue.push('/');
|
||||
}
|
||||
protected async processDirectory(directory: DirectoryDTO): Promise<string[]> {
|
||||
const ret = [];
|
||||
for (let i = 0; i < directory.media.length; ++i) {
|
||||
const videoPath = path.join(ProjectPath.ImageFolder,
|
||||
directory.media[i].directory.path,
|
||||
directory.media[i].directory.name,
|
||||
directory.media[i].name);
|
||||
|
||||
protected async step(): Promise<TaskProgressDTO> {
|
||||
if ((this.directoryQueue.length === 0 && this.videoQueue.length === 0)
|
||||
|| this.state !== TaskState.running) {
|
||||
if (global.gc) {
|
||||
global.gc();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
this.progress.left = this.videoQueue.length;
|
||||
this.progress.time.current = Date.now();
|
||||
if (this.directoryQueue.length > 0) {
|
||||
const directory = this.directoryQueue.shift();
|
||||
this.progress.comment = 'scanning directory: ' + directory;
|
||||
const scanned = await DiskManager.scanDirectory(directory, {noPhoto: true, noMetaFile: true});
|
||||
for (let i = 0; i < scanned.directories.length; i++) {
|
||||
this.directoryQueue.push(path.join(scanned.directories[i].path, scanned.directories[i].name));
|
||||
}
|
||||
|
||||
for (let i = 0; i < scanned.media.length; ++i) {
|
||||
if (!MediaDTO.isVideo(scanned.media[i])) {
|
||||
continue;
|
||||
}
|
||||
const videoPath = path.join(ProjectPath.ImageFolder,
|
||||
scanned.media[i].directory.path,
|
||||
scanned.media[i].directory.name,
|
||||
scanned.media[i].name);
|
||||
|
||||
if (await existsPr(VideoConverterMWs.generateConvertedFileName(videoPath)) === false) {
|
||||
this.videoQueue.push(videoPath);
|
||||
}
|
||||
}
|
||||
} else if (this.videoQueue.length > 0) {
|
||||
const videoPath = this.videoQueue.shift();
|
||||
this.progress.progress++;
|
||||
this.progress.comment = 'transcoding: ' + videoPath;
|
||||
try {
|
||||
await VideoConverterMWs.convertVideo(videoPath);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
Logger.error(LOG_TAG, 'Error during transcoding a video: ' + e.toString());
|
||||
if (await existsPr(VideoProcessing.generateConvertedFileName(videoPath)) === false) {
|
||||
ret.push(videoPath);
|
||||
}
|
||||
}
|
||||
return this.progress;
|
||||
return ret;
|
||||
}
|
||||
|
||||
protected async processFile(file: string): Promise<void> {
|
||||
await VideoProcessing.convertVideo(file);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -139,7 +139,7 @@ export class DiskMangerWorker {
|
||||
break;
|
||||
}
|
||||
} else if (DiskMangerWorker.isVideo(fullFilePath)) {
|
||||
if (Config.Client.Video.enabled === false || settings.noVideo) {
|
||||
if (Config.Client.Media.Video.enabled === false || settings.noVideo) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
|
@ -103,7 +103,7 @@ export class ThumbnailTH extends ThreadPool<void> implements ITaskExecuter<Rende
|
||||
return super.executeTask(<ThumbnailTask>{
|
||||
type: WorkerTaskTypes.thumbnail,
|
||||
input: input,
|
||||
renderer: Config.Server.Thumbnail.processingLibrary
|
||||
renderer: Config.Server.Media.Thumbnail.processingLibrary
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ export class ThumbnailWorker {
|
||||
private static rendererType: ServerConfig.ThumbnailProcessingLib = null;
|
||||
|
||||
public static render(input: RendererInput, renderer: ServerConfig.ThumbnailProcessingLib): Promise<void> {
|
||||
if (input.type === ThumbnailSourceType.Image) {
|
||||
if (input.type === ThumbnailSourceType.Photo) {
|
||||
return this.renderFromImage(input, renderer);
|
||||
}
|
||||
return this.renderFromVideo(input);
|
||||
@ -37,7 +37,7 @@ export class ThumbnailWorker {
|
||||
}
|
||||
|
||||
export enum ThumbnailSourceType {
|
||||
Image, Video
|
||||
Photo = 1, Video = 2
|
||||
}
|
||||
|
||||
export interface RendererInput {
|
||||
@ -45,7 +45,7 @@ export interface RendererInput {
|
||||
mediaPath: string;
|
||||
size: number;
|
||||
makeSquare: boolean;
|
||||
thPath: string;
|
||||
outPath: string;
|
||||
qualityPriority: boolean;
|
||||
cut?: {
|
||||
left: number,
|
||||
@ -83,8 +83,8 @@ export class VideoRendererFactory {
|
||||
}
|
||||
const ratio = height / width;
|
||||
const command: FfmpegCommand = ffmpeg(input.mediaPath);
|
||||
const fileName = path.basename(input.thPath);
|
||||
const folder = path.dirname(input.thPath);
|
||||
const fileName = path.basename(input.outPath);
|
||||
const folder = path.dirname(input.outPath);
|
||||
let executedCmd = '';
|
||||
command
|
||||
.on('start', (cmd) => {
|
||||
@ -98,9 +98,9 @@ export class VideoRendererFactory {
|
||||
})
|
||||
.outputOptions(['-qscale:v 4']);
|
||||
if (input.makeSquare === false) {
|
||||
const newWidth = Math.round(Math.sqrt((input.size * input.size) / ratio));
|
||||
const newSize = width < height ? Math.min(input.size, width) + 'x?' : '?x' + Math.min(input.size, height);
|
||||
command.takeScreenshots({
|
||||
timemarks: ['10%'], size: newWidth + 'x?', filename: fileName, folder: folder
|
||||
timemarks: ['10%'], size: newSize, filename: fileName, folder: folder
|
||||
});
|
||||
|
||||
|
||||
@ -156,9 +156,12 @@ export class ImageRendererFactory {
|
||||
);
|
||||
}
|
||||
if (input.makeSquare === false) {
|
||||
const newWidth = Math.sqrt((input.size * input.size) / ratio);
|
||||
if (image.bitmap.width < image.bitmap.height) {
|
||||
image.resize(Math.min(input.size, image.bitmap.width), Jimp.AUTO, algo);
|
||||
} else {
|
||||
image.resize(Jimp.AUTO, Math.min(image.size, image.bitmap.height), algo);
|
||||
}
|
||||
|
||||
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);
|
||||
@ -166,7 +169,7 @@ export class ImageRendererFactory {
|
||||
image.quality(60); // set JPEG quality
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
image.write(input.thPath, (err: Error | null) => { // save
|
||||
image.write(input.outPath, (err: Error | null) => { // save
|
||||
if (err) {
|
||||
return reject('[JimpThRenderer] ' + err.toString());
|
||||
}
|
||||
@ -202,10 +205,16 @@ export class ImageRendererFactory {
|
||||
image.extract(input.cut);
|
||||
}
|
||||
if (input.makeSquare === false) {
|
||||
const newWidth = Math.round(Math.sqrt((input.size * input.size) / ratio));
|
||||
image.resize(newWidth, null, {
|
||||
kernel: kernel
|
||||
});
|
||||
if (metadata.height > metadata.width) {
|
||||
image.resize(Math.min(input.size, metadata.width), null, {
|
||||
kernel: kernel
|
||||
});
|
||||
} else {
|
||||
image.resize(null, Math.min(input.size, metadata.height), {
|
||||
kernel: kernel
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
} else {
|
||||
image
|
||||
@ -215,7 +224,7 @@ export class ImageRendererFactory {
|
||||
fit: 'cover'
|
||||
});
|
||||
}
|
||||
await image.jpeg().toFile(input.thPath);
|
||||
await image.jpeg().toFile(input.outPath);
|
||||
};
|
||||
}
|
||||
|
||||
@ -252,7 +261,7 @@ export class ImageRendererFactory {
|
||||
image = image.resize(input.size, input.size)
|
||||
.crop(input.size, input.size);
|
||||
}
|
||||
image.write(input.thPath, (e) => {
|
||||
image.write(input.outPath, (e) => {
|
||||
if (e) {
|
||||
return reject('[GMThRenderer] ' + e.toString());
|
||||
}
|
||||
|
@ -7,17 +7,19 @@ import {UserRoles} from '../../common/entities/UserDTO';
|
||||
import {ThumbnailSourceType} from '../model/threading/ThumbnailWorker';
|
||||
import {VersionMWs} from '../middlewares/VersionMWs';
|
||||
import {SupportedFormats} from '../../common/SupportedFormats';
|
||||
import {PhotoConverterMWs} from '../middlewares/thumbnail/PhotoConverterMWs';
|
||||
|
||||
export class GalleryRouter {
|
||||
public static route(app: Express) {
|
||||
|
||||
this.addGetImageIcon(app);
|
||||
this.addGetVideoIcon(app);
|
||||
this.addGetImageThumbnail(app);
|
||||
this.addGetPhotoThumbnail(app);
|
||||
this.addGetVideoThumbnail(app);
|
||||
this.addGetBestFitImage(app);
|
||||
this.addGetImage(app);
|
||||
this.addGetVideo(app);
|
||||
this.addGetBestFitVideo(app);
|
||||
this.addGetVideo(app);
|
||||
this.addGetMetaFile(app);
|
||||
this.addRandom(app);
|
||||
this.addDirectoryList(app);
|
||||
@ -51,6 +53,17 @@ export class GalleryRouter {
|
||||
);
|
||||
}
|
||||
|
||||
private static addGetBestFitImage(app: Express) {
|
||||
app.get(['/api/gallery/content/:mediaPath(*\.(' + SupportedFormats.Photos.join('|') + '))/bestFit'],
|
||||
AuthenticationMWs.authenticate,
|
||||
AuthenticationMWs.normalizePathParam('mediaPath'),
|
||||
AuthenticationMWs.authorisePath('mediaPath', false),
|
||||
GalleryMWs.loadFile,
|
||||
PhotoConverterMWs.convertPhoto,
|
||||
RenderingMWs.renderFile
|
||||
);
|
||||
}
|
||||
|
||||
private static addGetVideo(app: Express) {
|
||||
app.get(['/api/gallery/content/:mediaPath(*\.(' + SupportedFormats.Videos.join('|') + '))'],
|
||||
AuthenticationMWs.authenticate,
|
||||
@ -66,6 +79,7 @@ export class GalleryRouter {
|
||||
AuthenticationMWs.authenticate,
|
||||
AuthenticationMWs.normalizePathParam('mediaPath'),
|
||||
AuthenticationMWs.authorisePath('mediaPath', false),
|
||||
GalleryMWs.loadFile,
|
||||
GalleryMWs.loadBestFitVideo,
|
||||
RenderingMWs.renderFile
|
||||
);
|
||||
@ -92,13 +106,13 @@ export class GalleryRouter {
|
||||
);
|
||||
}
|
||||
|
||||
private static addGetImageThumbnail(app: Express) {
|
||||
private static addGetPhotoThumbnail(app: Express) {
|
||||
app.get('/api/gallery/content/:mediaPath(*\.(' + SupportedFormats.Photos.join('|') + '))/thumbnail/:size?',
|
||||
AuthenticationMWs.authenticate,
|
||||
AuthenticationMWs.normalizePathParam('mediaPath'),
|
||||
AuthenticationMWs.authorisePath('mediaPath', false),
|
||||
GalleryMWs.loadFile,
|
||||
ThumbnailGeneratorMWs.generateThumbnailFactory(ThumbnailSourceType.Image),
|
||||
ThumbnailGeneratorMWs.generateThumbnailFactory(ThumbnailSourceType.Photo),
|
||||
RenderingMWs.renderFile
|
||||
);
|
||||
}
|
||||
@ -132,7 +146,7 @@ export class GalleryRouter {
|
||||
AuthenticationMWs.normalizePathParam('mediaPath'),
|
||||
AuthenticationMWs.authorisePath('mediaPath', false),
|
||||
GalleryMWs.loadFile,
|
||||
ThumbnailGeneratorMWs.generateIconFactory(ThumbnailSourceType.Image),
|
||||
ThumbnailGeneratorMWs.generateIconFactory(ThumbnailSourceType.Photo),
|
||||
RenderingMWs.renderFile
|
||||
);
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ import {Localizations} from './model/Localizations';
|
||||
import {CookieNames} from '../common/CookieNames';
|
||||
import {Router} from './routes/Router';
|
||||
import {ServerConfig} from '../common/config/private/IPrivateConfig';
|
||||
import {PhotoProcessing} from './model/fileprocessing/PhotoProcessing';
|
||||
|
||||
const _session = require('cookie-session');
|
||||
|
||||
@ -72,7 +73,7 @@ export class Server {
|
||||
this.app.use(cookieParser());
|
||||
|
||||
DiskManager.init();
|
||||
ThumbnailGeneratorMWs.init();
|
||||
PhotoProcessing.init();
|
||||
Localizations.init();
|
||||
|
||||
this.app.use(locale(Config.Client.languages, 'en'));
|
||||
|
@ -170,7 +170,7 @@ export class Utils {
|
||||
}
|
||||
|
||||
|
||||
public static findClosest(number: number, arr: Array<number>) {
|
||||
public static findClosest(number: number, arr: number[]): number {
|
||||
|
||||
let curr = arr[0];
|
||||
let diff = Math.abs(number - curr);
|
||||
@ -189,6 +189,25 @@ export class Utils {
|
||||
return curr;
|
||||
}
|
||||
|
||||
|
||||
public static findClosestinSorted(number: number, arr: number[]): number {
|
||||
|
||||
let curr = arr[0];
|
||||
let diff = Math.abs(number - curr);
|
||||
for (let i = 0; i < arr.length; ++i) {
|
||||
|
||||
const newDiff = Math.abs(number - arr[i]);
|
||||
if (newDiff > diff) {
|
||||
break;
|
||||
}
|
||||
diff = newDiff;
|
||||
curr = arr[i];
|
||||
}
|
||||
|
||||
|
||||
return curr;
|
||||
}
|
||||
|
||||
public static isUInt32(value: number, max: number = 4294967295) {
|
||||
return !isNaN(value) && value >= 0 && value <= max;
|
||||
}
|
||||
|
@ -43,7 +43,6 @@ export module ServerConfig {
|
||||
}
|
||||
|
||||
export interface ThumbnailConfig {
|
||||
folder: string;
|
||||
processingLibrary: ThumbnailProcessingLib;
|
||||
qualityPriority: boolean;
|
||||
personFaceMargin: number; // in ration [0-1]
|
||||
@ -98,21 +97,36 @@ export module ServerConfig {
|
||||
}
|
||||
|
||||
|
||||
export interface PhotoConfig {
|
||||
converting: {
|
||||
enabled: boolean;
|
||||
onTheFly: boolean;
|
||||
resolution: resolutionType
|
||||
};
|
||||
}
|
||||
|
||||
export interface MediaConfig {
|
||||
folder: string;
|
||||
tempFolder: string;
|
||||
Video: VideoConfig;
|
||||
Photo: PhotoConfig;
|
||||
Thumbnail: ThumbnailConfig;
|
||||
}
|
||||
|
||||
|
||||
export interface Config {
|
||||
port: number;
|
||||
host: string;
|
||||
imagesFolder: string;
|
||||
Thumbnail: ThumbnailConfig;
|
||||
Media: MediaConfig;
|
||||
Threading: ThreadingConfig;
|
||||
Database: DataBaseConfig;
|
||||
Sharing: SharingConfig;
|
||||
sessionTimeout: number;
|
||||
Indexing: IndexingConfig;
|
||||
photoMetadataSize: number;
|
||||
photoMetadataSize: number; // only this many bites will be loaded when scanning photo for metadata
|
||||
Duplicates: DuplicatesConfig;
|
||||
Log: LogConfig;
|
||||
Tasks: TaskConfig;
|
||||
Video: VideoConfig;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -11,12 +11,30 @@ export class PrivateConfigDefaultsClass extends PublicConfigClass implements IPr
|
||||
public Server: ServerConfig.Config = {
|
||||
port: 80,
|
||||
host: '0.0.0.0',
|
||||
imagesFolder: 'demo/images',
|
||||
Thumbnail: {
|
||||
folder: 'demo/tmp',
|
||||
processingLibrary: ServerConfig.ThumbnailProcessingLib.sharp,
|
||||
qualityPriority: true,
|
||||
personFaceMargin: 0.6
|
||||
Media: {
|
||||
folder: 'demo/images',
|
||||
tempFolder: 'demo/tmp',
|
||||
Thumbnail: {
|
||||
processingLibrary: ServerConfig.ThumbnailProcessingLib.sharp,
|
||||
qualityPriority: true,
|
||||
personFaceMargin: 0.6
|
||||
},
|
||||
Photo: {
|
||||
converting: {
|
||||
enabled: true,
|
||||
onTheFly: true,
|
||||
resolution: 1080
|
||||
}
|
||||
},
|
||||
Video: {
|
||||
transcoding: {
|
||||
bitRate: 5 * 1024 * 1024,
|
||||
codec: 'libx264',
|
||||
format: 'mp4',
|
||||
fps: 25,
|
||||
resolution: 720
|
||||
}
|
||||
}
|
||||
},
|
||||
Log: {
|
||||
level: ServerConfig.LogLevel.info,
|
||||
@ -71,15 +89,6 @@ export class PrivateConfigDefaultsClass extends PublicConfigClass implements IPr
|
||||
config: {},
|
||||
trigger: {type: TaskTriggerType.never}
|
||||
}]
|
||||
},
|
||||
Video: {
|
||||
transcoding: {
|
||||
bitRate: 5 * 1024 * 1024,
|
||||
codec: 'libx264',
|
||||
format: 'mp4',
|
||||
fps: 25,
|
||||
resolution: 720
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -68,6 +68,11 @@ export module ClientConfig {
|
||||
enabled: boolean;
|
||||
}
|
||||
|
||||
export interface MediaConfig {
|
||||
Thumbnail: ThumbnailConfig;
|
||||
Video: VideoConfig;
|
||||
}
|
||||
|
||||
export interface MetaFileConfig {
|
||||
enabled: boolean;
|
||||
}
|
||||
@ -83,7 +88,6 @@ export module ClientConfig {
|
||||
applicationTitle: string;
|
||||
publicUrl: string;
|
||||
urlBase: string;
|
||||
Thumbnail: ThumbnailConfig;
|
||||
Search: SearchConfig;
|
||||
Sharing: SharingConfig;
|
||||
Map: MapConfig;
|
||||
@ -92,7 +96,7 @@ export module ClientConfig {
|
||||
authenticationRequired: boolean;
|
||||
unAuthenticatedUserRole: UserRoles;
|
||||
languages: string[];
|
||||
Video: VideoConfig;
|
||||
Media: MediaConfig;
|
||||
MetaFile: MetaFileConfig;
|
||||
Faces: FacesConfig;
|
||||
}
|
||||
@ -107,11 +111,16 @@ export class PublicConfigClass {
|
||||
public Client: ClientConfig.Config = {
|
||||
applicationTitle: 'PiGallery 2',
|
||||
appVersion: '',
|
||||
Thumbnail: {
|
||||
concurrentThumbnailGenerations: 1,
|
||||
thumbnailSizes: [200, 400, 600],
|
||||
iconSize: 45,
|
||||
personThumbnailSize: 200
|
||||
Media: {
|
||||
Video: {
|
||||
enabled: true
|
||||
},
|
||||
Thumbnail: {
|
||||
concurrentThumbnailGenerations: 1,
|
||||
thumbnailSizes: [160, 240, 480],
|
||||
iconSize: 45,
|
||||
personThumbnailSize: 200
|
||||
}
|
||||
},
|
||||
Search: {
|
||||
enabled: true,
|
||||
@ -139,9 +148,6 @@ export class PublicConfigClass {
|
||||
RandomPhoto: {
|
||||
enabled: true
|
||||
},
|
||||
Video: {
|
||||
enabled: true
|
||||
},
|
||||
MetaFile: {
|
||||
enabled: true
|
||||
},
|
||||
|
@ -5,6 +5,8 @@ import {MediaDTO} from '../../../../common/entities/MediaDTO';
|
||||
|
||||
export class Media extends MediaIcon {
|
||||
|
||||
static readonly sortedThumbnailSizes = Config.Client.Media.Thumbnail.thumbnailSizes
|
||||
.sort((a, b) => a - b);
|
||||
|
||||
constructor(media: MediaDTO, public renderWidth: number, public renderHeight: number) {
|
||||
super(media);
|
||||
@ -19,8 +21,8 @@ export class Media extends MediaIcon {
|
||||
}
|
||||
|
||||
getThumbnailSize() {
|
||||
const renderSize = Math.sqrt(this.renderWidth * this.renderHeight);
|
||||
return Utils.findClosest(renderSize, Config.Client.Thumbnail.thumbnailSizes);
|
||||
const longerEdge = Math.max(this.renderWidth * this.renderHeight);
|
||||
return Utils.findClosestinSorted(longerEdge, Media.sortedThumbnailSizes);
|
||||
}
|
||||
|
||||
getReplacementThumbnailSize(): number {
|
||||
|
@ -27,6 +27,7 @@ export class GalleryLightboxMediaComponent implements OnChanges {
|
||||
public imageLoadFinished = false;
|
||||
thumbnailSrc: string = null;
|
||||
photoSrc: string = null;
|
||||
isPhotoSrcBestFit = true;
|
||||
public transcodeNeedVideos = SupportedFormats.TranscodeNeed.Videos;
|
||||
private mediaLoaded = false;
|
||||
private videoProgress = 0;
|
||||
@ -84,9 +85,6 @@ export class GalleryLightboxMediaComponent implements OnChanges {
|
||||
return this.video.nativeElement.paused;
|
||||
}
|
||||
|
||||
public get PhotoSrc(): string {
|
||||
return this.gridMedia.getMediaPath();
|
||||
}
|
||||
|
||||
private get ThumbnailUrl(): string {
|
||||
if (this.gridMedia.isThumbnailAvailable() === true) {
|
||||
@ -113,10 +111,25 @@ export class GalleryLightboxMediaComponent implements OnChanges {
|
||||
.then((src) => this.thumbnailSrc = src);
|
||||
}
|
||||
|
||||
if (this.photoSrc == null && this.gridMedia && this.loadMedia) {
|
||||
if (this.zoom === 1) {
|
||||
if (this.photoSrc == null && this.gridMedia && this.loadMedia) {
|
||||
FixOrientationPipe.transform(this.gridMedia.getBestFitMediaPath(), this.gridMedia.Orientation)
|
||||
.then((src) => {
|
||||
this.photoSrc = src;
|
||||
this.isPhotoSrcBestFit = true;
|
||||
});
|
||||
}
|
||||
// on zoom load high res photo
|
||||
} else if ((this.isPhotoSrcBestFit === true ||
|
||||
this.photoSrc == null) && this.gridMedia && this.loadMedia) {
|
||||
FixOrientationPipe.transform(this.gridMedia.getMediaPath(), this.gridMedia.Orientation)
|
||||
.then((src) => this.photoSrc = src);
|
||||
.then((src) => {
|
||||
this.photoSrc = src;
|
||||
this.isPhotoSrcBestFit = false;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
public mute() {
|
||||
@ -141,7 +154,7 @@ export class GalleryLightboxMediaComponent implements OnChanges {
|
||||
onImageError() {
|
||||
// TODO:handle error
|
||||
this.imageLoadFinished = true;
|
||||
console.error('Error: cannot load media for lightbox url: ' + this.gridMedia.getMediaPath());
|
||||
console.error('Error: cannot load media for lightbox url: ' + this.gridMedia.getBestFitMediaPath());
|
||||
}
|
||||
|
||||
onImageLoad() {
|
||||
|
@ -35,8 +35,8 @@ export class GalleryMapLightboxComponent implements OnChanges, AfterViewInit {
|
||||
paths: LatLng[][] = [];
|
||||
@ViewChild('root', {static: true}) elementRef: ElementRef;
|
||||
@ViewChild('yagaMap', {static: true}) yagaMap: MapComponent;
|
||||
public smallIconSize = new Point(Config.Client.Thumbnail.iconSize * 0.75, Config.Client.Thumbnail.iconSize * 0.75);
|
||||
public iconSize = new Point(Config.Client.Thumbnail.iconSize, Config.Client.Thumbnail.iconSize);
|
||||
public smallIconSize = new Point(Config.Client.Media.Thumbnail.iconSize * 0.75, Config.Client.Media.Thumbnail.iconSize * 0.75);
|
||||
public iconSize = new Point(Config.Client.Media.Thumbnail.iconSize, Config.Client.Media.Thumbnail.iconSize);
|
||||
private startPosition: Dimension = null;
|
||||
|
||||
constructor(public fullScreenService: FullScreenService,
|
||||
|
@ -19,7 +19,7 @@ export class ThumbnailLoaderService {
|
||||
}
|
||||
|
||||
run = () => {
|
||||
if (this.que.length === 0 || this.runningRequests >= Config.Client.Thumbnail.concurrentThumbnailGenerations) {
|
||||
if (this.que.length === 0 || this.runningRequests >= Config.Client.Media.Thumbnail.concurrentThumbnailGenerations) {
|
||||
return;
|
||||
}
|
||||
const task = this.getNextTask();
|
||||
|
@ -28,7 +28,7 @@ export class BasicSettingsComponent extends SettingsComponent<BasicConfigDTO> {
|
||||
super(i18n('Basic'), _authService, _navigation, _settingsService, notification, i18n, s => ({
|
||||
port: s.Server.port,
|
||||
host: s.Server.host,
|
||||
imagesFolder: s.Server.imagesFolder,
|
||||
imagesFolder: s.Server.Media.folder,
|
||||
applicationTitle: s.Client.applicationTitle,
|
||||
publicUrl: s.Client.publicUrl,
|
||||
urlBase: s.Client.urlBase
|
||||
|
@ -11,8 +11,8 @@
|
||||
|
||||
|
||||
<div class="form-group row progress-row ">
|
||||
<div class="col-md-1 text-right" title="time elapsed" i18n-title>{{TimeElapsed| duration}}</div>
|
||||
<div class="progress col-md-10 ">
|
||||
<div class="col-1 text-right" title="time elapsed" i18n-title>{{TimeElapsed| duration}}</div>
|
||||
<div class="progress col-10 ">
|
||||
<div
|
||||
class="progress-bar d-inline-block progress-bar-success {{progress.state === TaskState.stopping ? 'bg-secondary' : ''}}"
|
||||
role="progressbar"
|
||||
@ -25,6 +25,6 @@
|
||||
/{{progress.progress + progress.left}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-1" title="time left" i18n-title>{{TimeAll| duration}}</div>
|
||||
<div class="col-1" title="time left" i18n-title>{{TimeAll| duration}}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -34,18 +34,18 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label class="col-md-2 control-label" for="th_folder" i18n>Thumbnail folder</label>
|
||||
<div class="col-md-10">
|
||||
<input type="text" class="form-control" placeholder="path"
|
||||
id="th_folder"
|
||||
[(ngModel)]="settings.server.folder"
|
||||
name="path" required>
|
||||
<small class="form-text text-muted" i18n>Thumbnails will be saved in this folder. Write access is required
|
||||
</small>
|
||||
<!-- <div class="form-group row">-->
|
||||
<!-- <label class="col-md-2 control-label" for="th_folder" i18n>Thumbnail folder</label>-->
|
||||
<!-- <div class="col-md-10">-->
|
||||
<!-- <input type="text" class="form-control" placeholder="path"-->
|
||||
<!-- id="th_folder"-->
|
||||
<!-- [(ngModel)]="settings.server.folder"-->
|
||||
<!-- name="path" required>-->
|
||||
<!-- <small class="form-text text-muted" i18n>Thumbnails will be saved in this folder. Write access is required-->
|
||||
<!-- </small>-->
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<!-- </div>-->
|
||||
<!-- </div>-->
|
||||
<div class="form-group row" [hidden]="simplifiedMode">
|
||||
<label class="col-md-2 control-label" for="quality" i18n>Thumbnail Quality</label>
|
||||
<div class="col-md-10">
|
||||
|
@ -28,8 +28,8 @@ export class ThumbnailSettingsComponent
|
||||
notification: NotificationService,
|
||||
i18n: I18n) {
|
||||
super(i18n('Thumbnail'), _authService, _navigation, _settingsService, notification, i18n, s => ({
|
||||
client: s.Client.Thumbnail,
|
||||
server: s.Server.Thumbnail
|
||||
client: s.Client.Media.Thumbnail,
|
||||
server: s.Server.Media.Thumbnail
|
||||
}));
|
||||
}
|
||||
|
||||
|
@ -36,8 +36,8 @@ export class VideoSettingsComponent extends SettingsComponent<{ server: ServerCo
|
||||
notification: NotificationService,
|
||||
i18n: I18n) {
|
||||
super(i18n('Video'), _authService, _navigation, <any>_settingsService, notification, i18n, s => ({
|
||||
client: s.Client.Video,
|
||||
server: s.Server.Video
|
||||
client: s.Client.Media.Video,
|
||||
server: s.Server.Media.Video
|
||||
}));
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import {Config} from '../../src/common/config/private/Config';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import {SQLConnection} from '../../src/backend/model/sql/SQLConnection';
|
||||
import {SQLConnection} from '../../src/backend/model/database/sql/SQLConnection';
|
||||
import {ServerConfig} from '../../src/common/config/private/IPrivateConfig';
|
||||
|
||||
declare let describe: any;
|
||||
|
@ -2,20 +2,20 @@ import {expect} from 'chai';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import {Config} from '../../../../../src/common/config/private/Config';
|
||||
import {SQLConnection} from '../../../../../src/backend/model/sql/SQLConnection';
|
||||
import {UserEntity} from '../../../../../src/backend/model/sql/enitites/UserEntity';
|
||||
import {SQLConnection} from '../../../../../src/backend/model/database/sql/SQLConnection';
|
||||
import {UserEntity} from '../../../../../src/backend/model/database/sql/enitites/UserEntity';
|
||||
import {UserRoles} from '../../../../../src/common/entities/UserDTO';
|
||||
import {PasswordHelper} from '../../../../../src/backend/model/PasswordHelper';
|
||||
import {DirectoryEntity} from '../../../../../src/backend/model/sql/enitites/DirectoryEntity';
|
||||
import {DirectoryEntity} from '../../../../../src/backend/model/database/sql/enitites/DirectoryEntity';
|
||||
import {
|
||||
CameraMetadataEntity,
|
||||
GPSMetadataEntity,
|
||||
PhotoEntity,
|
||||
PhotoMetadataEntity,
|
||||
PositionMetaDataEntity
|
||||
} from '../../../../../src/backend/model/sql/enitites/PhotoEntity';
|
||||
import {MediaDimensionEntity} from '../../../../../src/backend/model/sql/enitites/MediaEntity';
|
||||
import {VersionEntity} from '../../../../../src/backend/model/sql/enitites/VersionEntity';
|
||||
} from '../../../../../src/backend/model/database/sql/enitites/PhotoEntity';
|
||||
import {MediaDimensionEntity} from '../../../../../src/backend/model/database/sql/enitites/MediaEntity';
|
||||
import {VersionEntity} from '../../../../../src/backend/model/database/sql/enitites/VersionEntity';
|
||||
import {ServerConfig} from '../../../../../src/common/config/private/IPrivateConfig';
|
||||
|
||||
describe('Typeorm integration', () => {
|
||||
|
@ -3,9 +3,9 @@ import {AuthenticationMWs} from '../../../../../src/backend/middlewares/user/Aut
|
||||
import {ErrorCodes, ErrorDTO} from '../../../../../src/common/entities/Error';
|
||||
import {UserDTO, UserRoles} from '../../../../../src/common/entities/UserDTO';
|
||||
import {ObjectManagers} from '../../../../../src/backend/model/ObjectManagers';
|
||||
import {UserManager} from '../../../../../src/backend/model/memory/UserManager';
|
||||
import {UserManager} from '../../../../../src/backend/model/database/memory/UserManager';
|
||||
import {Config} from '../../../../../src/common/config/private/Config';
|
||||
import {IUserManager} from '../../../../../src/backend/model/interfaces/IUserManager';
|
||||
import {IUserManager} from '../../../../../src/backend/model/database/interfaces/IUserManager';
|
||||
import * as path from 'path';
|
||||
|
||||
|
||||
|
@ -1,14 +1,14 @@
|
||||
import {expect} from 'chai';
|
||||
import {TestHelper} from './TestHelper';
|
||||
import {SQLTestHelper} from '../../../SQLTestHelper';
|
||||
import {GalleryManager} from '../../../../../src/backend/model/sql/GalleryManager';
|
||||
import {IndexingManager} from '../../../../../src/backend/model/sql/IndexingManager';
|
||||
import {GalleryManager} from '../../../../../src/backend/model/database/sql/GalleryManager';
|
||||
import {IndexingManager} from '../../../../../src/backend/model/database/sql/IndexingManager';
|
||||
import {DirectoryDTO} from '../../../../../src/common/entities/DirectoryDTO';
|
||||
import {Utils} from '../../../../../src/common/Utils';
|
||||
import {ObjectManagers} from '../../../../../src/backend/model/ObjectManagers';
|
||||
import {PersonManager} from '../../../../../src/backend/model/sql/PersonManager';
|
||||
import {MediaEntity} from '../../../../../src/backend/model/sql/enitites/MediaEntity';
|
||||
import {VersionManager} from '../../../../../src/backend/model/sql/VersionManager';
|
||||
import {PersonManager} from '../../../../../src/backend/model/database/sql/PersonManager';
|
||||
import {MediaEntity} from '../../../../../src/backend/model/database/sql/enitites/MediaEntity';
|
||||
import {VersionManager} from '../../../../../src/backend/model/database/sql/VersionManager';
|
||||
|
||||
class IndexingManagerTest extends IndexingManager {
|
||||
|
||||
|
@ -1,20 +1,20 @@
|
||||
import {expect} from 'chai';
|
||||
import * as fs from 'fs';
|
||||
import {Config} from '../../../../../src/common/config/private/Config';
|
||||
import {SQLConnection} from '../../../../../src/backend/model/sql/SQLConnection';
|
||||
import {GalleryManager} from '../../../../../src/backend/model/sql/GalleryManager';
|
||||
import {SQLConnection} from '../../../../../src/backend/model/database/sql/SQLConnection';
|
||||
import {GalleryManager} from '../../../../../src/backend/model/database/sql/GalleryManager';
|
||||
import {DirectoryDTO} from '../../../../../src/common/entities/DirectoryDTO';
|
||||
import {TestHelper} from './TestHelper';
|
||||
import {Connection} from 'typeorm';
|
||||
import {DirectoryEntity} from '../../../../../src/backend/model/sql/enitites/DirectoryEntity';
|
||||
import {DirectoryEntity} from '../../../../../src/backend/model/database/sql/enitites/DirectoryEntity';
|
||||
import {Utils} from '../../../../../src/common/Utils';
|
||||
import {MediaDTO} from '../../../../../src/common/entities/MediaDTO';
|
||||
import {FileDTO} from '../../../../../src/common/entities/FileDTO';
|
||||
import {IndexingManager} from '../../../../../src/backend/model/sql/IndexingManager';
|
||||
import {IndexingManager} from '../../../../../src/backend/model/database/sql/IndexingManager';
|
||||
import {ObjectManagers} from '../../../../../src/backend/model/ObjectManagers';
|
||||
import {PersonManager} from '../../../../../src/backend/model/sql/PersonManager';
|
||||
import {PersonManager} from '../../../../../src/backend/model/database/sql/PersonManager';
|
||||
import {SQLTestHelper} from '../../../SQLTestHelper';
|
||||
import {VersionManager} from '../../../../../src/backend/model/sql/VersionManager';
|
||||
import {VersionManager} from '../../../../../src/backend/model/database/sql/VersionManager';
|
||||
import {DiskMangerWorker} from '../../../../../src/backend/model/threading/DiskMangerWorker';
|
||||
import {ServerConfig} from '../../../../../src/common/config/private/IPrivateConfig';
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import {PersonManager} from '../../../../../src/backend/model/sql/PersonManager';
|
||||
import {PersonManager} from '../../../../../src/backend/model/database/sql/PersonManager';
|
||||
|
||||
|
||||
// to help WebStorm to handle the test cases
|
||||
|
@ -1,15 +1,15 @@
|
||||
import {expect} from 'chai';
|
||||
import {SQLConnection} from '../../../../../src/backend/model/sql/SQLConnection';
|
||||
import {PhotoEntity} from '../../../../../src/backend/model/sql/enitites/PhotoEntity';
|
||||
import {SearchManager} from '../../../../../src/backend/model/sql/SearchManager';
|
||||
import {SQLConnection} from '../../../../../src/backend/model/database/sql/SQLConnection';
|
||||
import {PhotoEntity} from '../../../../../src/backend/model/database/sql/enitites/PhotoEntity';
|
||||
import {SearchManager} from '../../../../../src/backend/model/database/sql/SearchManager';
|
||||
import {AutoCompleteItem, SearchTypes} from '../../../../../src/common/entities/AutoCompleteItem';
|
||||
import {SearchResultDTO} from '../../../../../src/common/entities/SearchResultDTO';
|
||||
import {DirectoryEntity} from '../../../../../src/backend/model/sql/enitites/DirectoryEntity';
|
||||
import {DirectoryEntity} from '../../../../../src/backend/model/database/sql/enitites/DirectoryEntity';
|
||||
import {Utils} from '../../../../../src/common/Utils';
|
||||
import {TestHelper} from './TestHelper';
|
||||
import {VideoEntity} from '../../../../../src/backend/model/sql/enitites/VideoEntity';
|
||||
import {PersonEntry} from '../../../../../src/backend/model/sql/enitites/PersonEntry';
|
||||
import {FaceRegionEntry} from '../../../../../src/backend/model/sql/enitites/FaceRegionEntry';
|
||||
import {VideoEntity} from '../../../../../src/backend/model/database/sql/enitites/VideoEntity';
|
||||
import {PersonEntry} from '../../../../../src/backend/model/database/sql/enitites/PersonEntry';
|
||||
import {FaceRegionEntry} from '../../../../../src/backend/model/database/sql/enitites/FaceRegionEntry';
|
||||
import {PhotoDTO} from '../../../../../src/common/entities/PhotoDTO';
|
||||
import {SQLTestHelper} from '../../../SQLTestHelper';
|
||||
import {Config} from '../../../../../src/common/config/private/Config';
|
||||
|
@ -1,8 +1,8 @@
|
||||
import {expect} from 'chai';
|
||||
import {SQLConnection} from '../../../../../src/backend/model/sql/SQLConnection';
|
||||
import {SharingManager} from '../../../../../src/backend/model/sql/SharingManager';
|
||||
import {SQLConnection} from '../../../../../src/backend/model/database/sql/SQLConnection';
|
||||
import {SharingManager} from '../../../../../src/backend/model/database/sql/SharingManager';
|
||||
import {SharingDTO} from '../../../../../src/common/entities/SharingDTO';
|
||||
import {UserEntity} from '../../../../../src/backend/model/sql/enitites/UserEntity';
|
||||
import {UserEntity} from '../../../../../src/backend/model/database/sql/enitites/UserEntity';
|
||||
import {UserDTO, UserRoles} from '../../../../../src/common/entities/UserDTO';
|
||||
import {SQLTestHelper} from '../../../SQLTestHelper';
|
||||
|
||||
|
@ -1,14 +1,14 @@
|
||||
import {MediaDimensionEntity} from '../../../../../src/backend/model/sql/enitites/MediaEntity';
|
||||
import {MediaDimensionEntity} from '../../../../../src/backend/model/database/sql/enitites/MediaEntity';
|
||||
import {
|
||||
CameraMetadataEntity,
|
||||
GPSMetadataEntity,
|
||||
PhotoEntity,
|
||||
PhotoMetadataEntity,
|
||||
PositionMetaDataEntity
|
||||
} from '../../../../../src/backend/model/sql/enitites/PhotoEntity';
|
||||
} from '../../../../../src/backend/model/database/sql/enitites/PhotoEntity';
|
||||
import {OrientationTypes} from 'ts-exif-parser';
|
||||
import {DirectoryEntity} from '../../../../../src/backend/model/sql/enitites/DirectoryEntity';
|
||||
import {VideoEntity, VideoMetadataEntity} from '../../../../../src/backend/model/sql/enitites/VideoEntity';
|
||||
import {DirectoryEntity} from '../../../../../src/backend/model/database/sql/enitites/DirectoryEntity';
|
||||
import {VideoEntity, VideoMetadataEntity} from '../../../../../src/backend/model/database/sql/enitites/VideoEntity';
|
||||
import {MediaDimension} from '../../../../../src/common/entities/MediaDTO';
|
||||
import {CameraMetadata, FaceRegion, GPSMetadata, PhotoDTO, PhotoMetadata, PositionMetaData} from '../../../../../src/common/entities/PhotoDTO';
|
||||
import {DirectoryDTO} from '../../../../../src/common/entities/DirectoryDTO';
|
||||
|
@ -8,7 +8,7 @@ import {Utils} from '../../../../../src/common/Utils';
|
||||
describe('DiskMangerWorker', () => {
|
||||
|
||||
it('should parse metadata', async () => {
|
||||
Config.Server.imagesFolder = path.join(__dirname, '/../../assets');
|
||||
Config.Server.Media.folder = path.join(__dirname, '/../../assets');
|
||||
ProjectPath.ImageFolder = path.join(__dirname, '/../../assets');
|
||||
const dir = await DiskMangerWorker.scanDirectory('/');
|
||||
expect(dir.media.length).to.be.equals(4);
|
||||
|
@ -15,4 +15,21 @@ describe('Utils', () => {
|
||||
expect(Utils.concatUrls('abc\\/', '/cde/')).to.be.equal('abc/cde');
|
||||
expect(Utils.concatUrls('abc\\/', '/cde/', 'fgh')).to.be.equal('abc/cde/fgh');
|
||||
});
|
||||
|
||||
it('should find closest number', () => {
|
||||
|
||||
expect(Utils.findClosest(10, [10, 13, 4, 20])).to.be.equal(10);
|
||||
expect(Utils.findClosest(10, [13, 4, 20])).to.be.equal(13);
|
||||
expect(Utils.findClosest(10, [4, 20])).to.be.equal(4);
|
||||
expect(Utils.findClosest(10, [20])).to.be.equal(20);
|
||||
});
|
||||
it('should find closest number in sorted array', () => {
|
||||
|
||||
expect(Utils.findClosestinSorted(10, [3, 5, 8, 10, 15, 20])).to.be.equal(10);
|
||||
expect(Utils.findClosestinSorted(10, [3, 5, 8, 15, 20])).to.be.equal(8);
|
||||
expect(Utils.findClosestinSorted(10, [3, 5, 15, 20])).to.be.equal(15);
|
||||
expect(Utils.findClosestinSorted(10, [3, 5, 20])).to.be.equal(5);
|
||||
expect(Utils.findClosestinSorted(10, [3, 20])).to.be.equal(3);
|
||||
expect(Utils.findClosestinSorted(10, [20])).to.be.equal(20);
|
||||
});
|
||||
});
|
||||
|
Loading…
x
Reference in New Issue
Block a user