1
0
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:
Patrik J. Braun 2019-12-14 17:27:01 +01:00
parent 27a88c98eb
commit cd06bc00ec
85 changed files with 837 additions and 562 deletions

View File

@ -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';

View File

@ -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
View File

@ -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"
}

View File

@ -135,6 +135,6 @@
"sharp": "0.23.4"
},
"engines": {
"node": ">= 6.9 <11.0"
"node": ">=10 <13.0"
}
}

View File

@ -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)) {

View File

@ -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) {

View File

@ -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);
}
}

View File

@ -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]';

View File

@ -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;

View File

@ -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';

View 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();
}
}

View File

@ -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()));
}
}
}

View File

@ -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();

View File

@ -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;

View File

@ -1,4 +1,4 @@
import {DirectoryDTO} from '../../../common/entities/DirectoryDTO';
import {DirectoryDTO} from '../../../../common/entities/DirectoryDTO';
export interface IIndexingManager {
indexDirectory(relativeDirectoryName: string): Promise<DirectoryDTO>;

View File

@ -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[]>;

View File

@ -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[]>;

View File

@ -1,4 +1,4 @@
import {SharingDTO} from '../../../common/entities/SharingDTO';
import {SharingDTO} from '../../../../common/entities/SharingDTO';
export interface ISharingManager {
findOne(filter: any): Promise<SharingDTO>;

View File

@ -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 {

View File

@ -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>;

View File

@ -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 {

View File

@ -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 {

View File

@ -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 {

View File

@ -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[]> {

View File

@ -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 {

View File

@ -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 {

View File

@ -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> {

View File

@ -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]';

View File

@ -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,

View File

@ -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]';

View File

@ -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]';

View File

@ -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 {

View File

@ -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 {

View File

@ -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 {

View File

@ -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 {

View File

@ -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';

View File

@ -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';

View File

@ -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 {

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -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()

View File

@ -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 {

View File

@ -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 {

View File

@ -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()

View File

@ -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 {

View File

@ -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());

View 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;
}
}

View 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);
}
}

View File

@ -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';

View 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;
}
}

View File

@ -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());

View 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);
}
}

View File

@ -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);
}
}

View 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 {

View File

@ -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
});
}
}

View File

@ -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());
}

View File

@ -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
);
}

View File

@ -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'));

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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
}
}
};
}

View File

@ -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
},

View File

@ -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 {

View File

@ -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() {

View File

@ -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,

View File

@ -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();

View File

@ -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

View File

@ -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>

View File

@ -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">

View File

@ -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
}));
}

View File

@ -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
}));
}

View File

@ -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;

View File

@ -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', () => {

View File

@ -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';

View File

@ -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 {

View File

@ -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';

View File

@ -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

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -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);

View File

@ -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);
});
});